JMS@ARIZMIS.BITNET (Joel M Snyder) (08/09/86)
Critical regions in VAX/VMS can be achieved by use of the lock
system. Locks are actually an implementation of mutex that
runs across a single processor or a whole VAX cluster. In fact,
locks are what Digital uses to keep the cluster hanging together.
Locks are named resources that may be requested ($ENQ system service),
released ($DEQ system service) or inquired about ($GETLKI system
service). A process requesting a lock, and having it granted
at the access mode requested, is assured that the textbook rules
regarding mutex were followed. For example, if you $ENQ a lock
named LOCK_A at the 'exclusive access' mode, and if the system
grants it to you, you can be assured that you are the only process in
your current group to have that lock granted. All other requests
($ENQs) will wait until you release ($DEQ) that lock.
Locks can also be used system wide, but that requires the SYSLCK
(system lock) privilege.
Locks can also have 16-byte value blocks associated with them, and
thus can be used as a crude (although effective) method of interprocess
communication. This could represent your variable x in the program
you submitted.
For more information on locks, look in the system services manual.
Like timing services, they have their own chapter in the introduction,
as well as the definitions of each service.
Here is an example set of subroutines for locks:
.TITLE MUTEX - enqueues and dequeues locks for ED1 program
.IDENT /01A/
; Externals
.EXTERNAL ED1_INUSE
; Macro library calls
$SSDEF
$LCKDEF
.macro errorck,?l1
blbs r0,l1
$exit_s code=r0
l1:
.endm
; Read-Write data program section
.PSECT MUTEX_RWDATA,RD,WRT,NOEXE
LKSB: .BLKQ 1
RESLEN: .BLKL 1
RESPTR: .BLKL 1
.PSECT MUTEX_CODE,EXE,RD,NOWRT
.entry ed1$acquire_mutex,^m<R2,R3,R4,R5,R6>
movl 4(AP),reslen ; get descriptor block
movl reslen,resptr ; length and address
movl @reslen,reslen ; get the length
addl #4,resptr ; get buffer pointer
movl @resptr,resptr ; and address of buffer
cmpw reslen,#31 ; see if a problem with length
blequ length_ok ; ... if no problem, skip
movw #31,reslen ; ... if too long, shorten
length_ok:
$enq_s -
efn=#1,- ; use event flag 0
lkmode=#LCK$K_EXMODE,- ; lock at exclusive access
lksb=LKSB,- ; address of lock status block
flags=#LCK$M_NOQUEUE,-
resnam=RESLEN ; name of resource to lock
cmpl R0,#SS$_NOTQUEUED ; did we get it?
beql no_lock ; no, sorry
errorck ; check for other errors
ret ; yes, return
no_lock:
pushl #ED1_INUSE ; file is in-use
calls #1,G^LIB$SIGNAL ; signal, and end
ret
.entry ed1$release_mutex,^m<R2,R3,R4>
$deq_s -
lkid=LKSB+4 ; the lock id is in lock status block
movl #SS$_NORMAL,R0
ret
.end
jms
+-------------------------------+
| Joel M Snyder | BITNET: jms@arizmis.BITNET
| Univ of Arizona Dep't of MIS | ArizoNET: MRSVAX::JMS
| Tucson, Arizona 85721 | Pseudo-PhoneNET: (602) 621-2748
+-------------------------------+
(std. disclaimer in re: nobody taking anything I say seriously)
--*> "Wherever you go ... there you are." -- Buckaroo Bonzai <*--waters@MISTAH.DEC.COM (Greg Waters, 225-4986, HLO2-1/J12) (08/11/86)
The original requestor asked how to increment a shared variable at some point
in a program, but be able to have an exit handler know whether or not the
increment instruction was performed yet. One way to do this is to have the
exit handler check where the program was interrupted (compare the saved PC
to the critical region). Someone suggested using VMS Locks, which are used
to synchronize critical regions of several processes. The poster forgot to
mention that the lock would have to be applied between multiple threads of
a single process in this application, as in
clear go_to_exit_handler
lock the counter
increment the counter
unlock the counter
if go_to_exit_handler then goto exit handler
and in the exception handler
test the counter lock
if locked, then dismiss the exception and continue (don't know
whether increment is done or not) after setting
go_to_exit_handler (so that we regain control later)
go to exit handler
exit handler
if counter has been incremented then decrement it
A much more efficient way to create an atomic increment-and-remember-that-I-did
operation is to use IPL synchronization. In the main program, replace the
increment with
status = call sys$cmkrnl( increment_and_remember, %REF(counter),
%REF(flag) )
<critical region...>
status = call sys$cmkrnl( decrement_and_forget, %REF(counter...))
increment_and_remember:
save IPL
elevate to IPL$_ASTDEL
increment counter
set flag
restore IPL
Now, an asynchronous thread such as the condition handler can accurately
determine if the increment has been performed yet or not. IPL$_ASTDEL
prevents any asynchronous happenings in the process, such as ^Y or even
process deletion requested by some other privileged process. To support
multiprocessor VAXes, use ADAWI for the increment, or access the counter
through an inter-processor synchronization mechanism (such as an interlocked
queue) before altering it.
Greg W., DEC Hudson
waters%oracle.DEC@decwrl.DEC.COM
...decwrl!dec-rhea!dec-oracle!watersDCATHEY%1175%ti-eg.CSNET@CSNET-RELAY.ARPA ("David L. Cathey, DEIS/DCS VA (08/16/86)
>The original requestor asked how to increment a shared variable at some point >in a program, but be able to have an exit handler know whether or not the >increment instruction was performed yet. ... > ... Someone suggested using VMS Locks, which are used >to synchronize critical regions of several processes. The poster forgot to >mention that the lock would have to be applied between multiple threads of >a single process in this application, as in > > clear go_to_exit_handler > lock the counter > increment the counter > unlock the counter > if go_to_exit_handler then goto exit handler > >and in the exception handler > > test the counter lock > if locked, then dismiss the exception and continue (don't know > whether increment is done or not) after setting > go_to_exit_handler (so that we regain control later) > go to exit handler > > exit handler > if counter has been incremented then decrement it > You missed my point. Use the lock itself as the counter. The $GETLKI system service will tell you the number of locks against the resource, which is the value of your "counter". The method you show of using the locks still may not work correctly if the process is deleted. If the lock itself is the counter, when all user-mode locks are $DEQueued, the counter is automatically "decremented" without needing exception/exit handlers. I.E.: increment(){ SYS$ENQW(...) ; } decrement(){ SYS$DEQ(...) ; } value_of_counter(){ SYS$GETLKI(... using the LKI$_LCKCOUNT item code ...) return(...LKI$_LCKCOUNT result ...) ; } By the original poster's phrasing of his application, you don't even need to have an exlusive lock. He just wants to make sure that if he increments the variable, he decrements the variable. The lock services are more useful in process communication than just restricting access to resources. >A much more efficient way to create an atomic increment-and-remember-that-I-did >operation is to use IPL synchronization. In the main program, replace the >increment with > > status = call sys$cmkrnl( increment_and_remember, %REF(counter), > %REF(flag) ) > <critical region...> > status = call sys$cmkrnl( decrement_and_forget, %REF(counter...)) > > increment_and_remember: > save IPL > elevate to IPL$_ASTDEL > increment counter > set flag > restore IPL > True, but they seemed to want to avoid privileged code. And KERNEL code is very privileged. Since he is raising IPL to ASTDEL or greater, they would also have to lock key pages in memory to prevent page faults (Locking pages in working set might do, but the process would have to disable process swap mode...). David L. Cathey VAX System Support Texas Instruments Incorporated [Not only are these views not those of my employer, I had to tie him up so I could type this in...]