[mod.computers.vax] Critical Regions in VAX/VMS

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...]