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!waters
DCATHEY%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...]