SEMAPHORE OPERATIONS Let’s assume that it is necessary to allow to only one of two processes to execute its critical section of code, i.e. section of code in which it must get access to critical resource which must not be used simultaneously by more than one process. Р1 Р2 resource FLAG=1/0 W1: TEST FLAG, 1 JZ W1 MOV FLAG, 0 Critical section MOV FLAG, 1 W2: TEST FLAG, 1 JZ W2 MOV FLAG, 0 Critical section MOV FLAG, 1 Simultaneous entrance in critical section may occur on the following scenario: instruction TEST is executed in the 1st process, and before execution of MOV FLAG, 0 , control is yielded to the 2nd process. Instruction TEST is executed in the 2nd process. To escape such a situation we may use two flag variables, each of which by zero value indicates willing of the respective process to enter critical section: P1: MOV FLAG1, 0 CHECK1: TEST FLAG2, 1 JZ CHECK1 Critical section MOV FLAG1,1 P2: MOV FLAG2, 0 CHECK2: TEST FLAG1, 1 JZ CHECK2 Critical section MOV FLAG2,1 This solution guarantees mutual exclusion, but originates other difficulty – deadlock of processes, or mutual blocking. If switching from the 1st process to the 2nd occurs between commands MOV and TEST, then 2nd process also can throw its flag and both flags will become in zero state. To provide mutual exclusion and to avoid deadlock it is necessary to introduce 3rd flag, which shows what process has higher priority if collision arises. This idea is implemented in Dijkstra’s algorithm, which we shall consider farther for N processes, but it will be seen that it is not a simple one. More simple solution can be represented by the following code: Pi: MOV AL, 0 TRY: XCHG AL, FLAG TEST AL, AL JZ TRY Critical section MOV FLAG,1 In this code it is used special instruction XCHG which in one instruction swaps both its operands. This solution is not a best one because waiting process will be in the state of active waiting (it rounds in the loop of checking register al) and wastes useless processor’s time. Other solution follows: Pi: AGAIN: MOV AL, 0 XCHG AL, SEMA TEST AL, AL JNZ NEXT Call Dispatcher (process is frozen, passive waiting) JMP SHORT AGAIN NEXT: Critical section Such operation of testing semaphore value in the result of which process can be suspended is called P-operation on SEMA. Inverse operation, called V, is use to awake sleeping process; it may be realized as follows MOV SEMA, 1 Call Dispatcher Dispatcher analyses queue of waiting SEMA processes and awakes one of them. Semaphores can be used not only for mutual exclusion, but also in other synchronization tasks, for example: Process А Process В РА1: activate В РВ1: wait signal from А … … РА2: wait signal from В РВ2: activate А Such scheme of interaction can be implemented with the help of 2 semaphores: WAKEA, WAKEB=0 P1 P2 РА1: V(WAKEB) РВ1: P(WAKEB) … … РА2: P(WAKEB) РB2: V(WAKEA) Standard synchronization tasks Process is a calculation, application of a finite set of operations to the finite set of data. Two processes are considered to be parallel, if execution of the first operation of one process begins before the termination of the other process. Resources used by several processes are called shared resources. Critical resource is a shared resource which simultaneously can be used no more than by one process. Interconnected processes are processes using common shared resource or exchanging by information. To synchronize processes means to formulate restrictions superimposed on the order of process performing. These restrictions are assigned by means of rules of synchronizing, which are described by means of mechanisms of synchronizing (primitives). Standard synchronization tasks are the following: mutual exclusion; dining philosophers; producers - consumers; readers - writers. “Mutual exclusion” task is the following: there are several processes, program of which contain regions, where processes retrieve to the shared resource. It’s required to exclude a simultaneous working of the processes in this critical interval. It is necessary to require also that delay of any process outside of its critical interval must not affect on development of the other processes. Solution must be symmetrical for all processes (all processes are equal). Solution mustn’t lead to global or local deadlocks. Global deadlock means locking of the whole system of processes, and local means locking of some subset of processes. “Dining philosophers” task is the following: there are k plates with meal on the round table, between them lay k forks, k=4,..7. There are k philosophers in the room, interleaving philosophical thinking with taking a food. For each philosopher is fixed its plate; to eat philosopher needs in two forks, moreover he can use only forks, adjacent to his plate. It’s required to synchronize philosophers so that each of them could get for finite time access to his plate. It is assumed that duration of eating and thinking of the philosopher are finite, but are not known beforehand. “Producers – consumers” task is the following: there is a limited buffer on m places (m portions of information). It is a critical resource for processes of two types: processes-producers which gaining access to the resource place there a portion of information on the free place; processes-consumers which gaining access to the resource read portion of information from it. It’s required to exclude simultaneous access of processes to the resource. Under the full emptying of the buffer there are must be delayed processes-consumers, under full filling of a buffer there are must be delayed processesproducers. “Readers-writers” task is the following: there is a shared resource - a region of the memory, to which it is required access for processes of two types: processes-readers which may gain access to the resource simultaneously, they read information (notdamaging reading); processes-writers mutually exclude each other and readers. Two variants of this task are known: 1) reader requested access to the resource, must get it as quickly as possible; 2) reader requested access to the resource, must get it as quickly as possible, if there are no requests from writers. Writer, requiring access to the resource, must get it as quickly as possible, but after servicing the readers which have come to resource before the first writer. Semaphores mechanism Let S be a semaphore - a variable of a special type with integer values, on which there are defined two operations: P and V (P - a closing, V - an opening). It’s considered that S1. Let’s define these operations. P(S): if S1 then the process continues to be executed, but S is decreased by 1; if S=0, the process is suspended, and its name is transferred to the queue of processes, waiting access to this resource (usually semaphores correspond to some resources). Operations P and V are indivisible, i.e. with one semaphore simultaneously can work only one process (process, started to work with the semaphore, must finish it before switching a processor on other process in the case of quasi parallel processes or in the case of switching other processes must not get access to this semaphore). V(S): if there are processes in the queue to the semaphore S, one of them is chosen and activated (is transferred to the readiness state: it is pushed in the queue of processes, pretending on a processor time); if queue is empty then it is executed S=S+1(at the term of result being no greater than maximal possible semaphore value; for the binary semaphore 0S1). Operation V(S) is usually executed by the process, which comes out of critical sections. Semaphores mechanism allows to solve a task of the mutual exclusion: S=1; Process-i: P(S); Critical section V(S); It is necessary to point attention to the fact that binary semaphore variable S was initiated by the value 1; under other variant of initialization processes may have got in the deadlock or it will not be a solution for the mutual exclusion task. Not careful usage of semaphore variables may lead to dead-locks conditions, for instance in presented below two processes under simultaneous their coming to the second operator P: S1=1 S2=1 P(S1) P(S2) P(S2) P(S1) … … V(S2) V(S1) V(S1) V(S2) Meanwhile, it must be noted, that given above processes can work infinitely long time not falling into the dead-lock. Semaphore solution of the tasks “dining philosophers” Let’s denote processes-philosophers by Р1, Р2, Р3, Р4 and forks by f1, f2, f3, f4. For eating philosopher needs in two nearby forks simultaneously: Р1 f4 Р1, P2, P3, P4 – philosophers f1 f1, f2, f3, f4 – forks Р4 Р2 Two forks are needed to eat f2 f3 Р3 Algorithm of working of each philosopher assumes using of 5 semaphores (Si, i 1,4 , S), one of which (S) is intended for the mutual exclusion of processes when getting access to the array b, responsible for forks, and semaphore Si is intended for suspending of i-th philosopher in the case if a result of checking the forks will find, that necessary to him fork are absent, i=1,..,4. Let’s consider algorithm of working of the first philosopher; the rest work similarly. P1: P(S[1]); P(S); if (f1>0)&(f4>0) then begin f1:=0; f4:=0; V(S); {eating} P(S); f1:=1; f4:=1; V(S); for i:=1 to 4 do V(S[i]); {thinking} end else V(S); goto P1; Sharing common procedures (reentrant procedures) When common procedures are used sequentially, there local variables are to be repeatedly initialized. Common procedures allowing time multiplexing are called reentrant. Such procedures can be viewed as common but not critical resource for several processes. Such procedures must contain pure not modifiable code. For this sake data which are to be modified must be kept in local memories of respective processes, common solution is to use for local procedure variables stack of the process. Process 1 Process 2 CS CS DS Reentrant procedure DS ES ES SS SS Data segment Data segment Extended data segment Extended data segment Stack segment Stack segment Let’s consider reentrant procedure, which multiplies two signed32-bit integers and returns 64-bit result. Six words are allocated in stack for temporary results. To allocate this space, 12 is subtracted from SP and result is loaded in BP, other references are made via BP. X senior Х Y junior Х senior Y junior Y х = sen х216 + jun х y = sen y216 + jun y We need in 4 words of result: 3 2 1 0 248 232 216 20 х у=sen х sen у 232 + sen у 216 jun х + sen х 216 jun у + jun х jun у = = (sen (sen х sen у)216 + jun (sen х sen у)) 232 + (sen (sen у jun х) 216 + jun (sen y jun x)) 216 + (sen (sen х jun у) 216 + jun (sen х jun y)) 216 + sen (jun х jun у) 216 + jun (jun х jun у) = (sen (sen х sen у) 248 + 232(jun (sen х sen у) + sen (sen у jun х) + sen (sen х jun у)) + + 216(sen (jun х jun у) + jun (jun х sen у) + jun (sen х jun у)) + 20(jun (jun х jun у)) where: (sen (sen х sen у)) is a 3rd word of a result; (jun (jun х jun у)) is a 0th word of a result. These calculations may be represented as follows: sen Х 3 jun Х sen Y + + 2 1 Layout of the program stack is following: ADDR_X ADDR_Y ADDR_Z CS IP BP jun Y 0 TEMPX TEMPY TEMPZ А программа: FLAME STRUC TEMPZ DW ?,? TEMPY DW ?,? TEMPX DW ?,? SUBEBP DW ? SAVE_CS_IP DW ? Z_ADDR DW ? Y_ADDR DW ? X_ADDR DW ? FLAME ENDS MULTIPLLY PROC FAR PUSH BP SUB SP, 12 MOV BP, SP PUSH AX PUSH CX PUSH DX PUSH SI PUSH DI MOV SI, 0 MOV BX, [BP].X_ADDR MOV AX, [BP] MOV [BP].TEMPX, AX MOV [BP].TEMPX+2, AX MOV BX, [BP].Y_ADDR MOV AX, [BP] MOV [BP].Y_ADDR, AX MOV AX, [BX+2] MOV [BP].Y_ADDR+2, AX CMP [BP].TEMPX+2, 0 JGE CHECK_Y NOT [BP].TEMPX NOT [BP].TEMPX+2 ADD [BP].TEMPX, 1 ADC [BP].TEMPX+2, 0 MOV SI, 1 CHECK_Y: CMP [BP].TEMPX+2,0 JGE START NOT [BP].TEMPY NOT [BP].TEMPY+2 ADD [BP].TEMPY, 1 ADC [BP].TEMPY+2, 0 XOR SI, 1 START: MOV AX, [BP].TEMPX MOL [BP].TEMPY MOV [BP].TEMPZ, AX MOV CX, DX MOV AX, [BP].TEMPX+2 MOL [BP].TEMPY MOV BX, DX ADD CX, AX ADC BX, 0 MOV DI, 0 ADC DI, 0 MOV AX, [BP].TEMPX MOL [BP].TEMPY ADD CX, AX ADC BX, DX MOV [BP].TEMPZ+2, CX MOV CX, 0 ADC SX, DI MOV AX, [BP].TEMPX+2 MOL [BP].TEMPY+2 ADD AX, BX ADD DX, CX MOV CX, [BP].TEMPZ+2 CMP SI, 0 JE STOREZ NOT [BP].TEMPZ NOT CX NOT AX NOT DX ADD [BP].TEMPZ, 1 ADC CX, 0 ADC AX, 0 ADC DX, 0 STOREZ: MOV BX, [BP].ADDR_Z MOV [BX+6], DX MOV [BX+4], AX MOV [BX+2], CDX MOV AX, [BP].TEMPZ MOV [BX], AX POP DI POPSI POP DX, CX, BX, AX ADD SP, 12 POP BP RET 6 END Synchronization mechanisms for parallel processes They may be classified as Centralized (are used in sharing memory systems – busy waiting, semaphores, events, critical regions and conditional critical regions, monitors, path expressions ); Decentralized (are used in distributed systems – ADA language rendezvous, OCCAM channels, message passing interface). The simplest representative of former is active waiting. Let’s consider mutual exclusion task for N processes. Array C(N) contains N elements; if C(I)=0, then i-th process wills to enter critical section; if C(I)=1 then doesn’t want. Condition for entering critical interval: L: C(I)=0 FOR J=1 TO N DO BEGIN IF (I<>J) AND (C(J)=0) GOTO L END; Such solution may lead to the deadlock. To escape deadlock we need in additional array: B(N+1), which is used to fix fact of activity of respective process. Variable queue determines priority process (initially, 0), all elements of B, corresponding to real processes, are 1’s initially (not active processes). //initialization queue=0; B(I):=0,i=1,..,N B(0):=1 Pi: //i-th process, i>0 L1: IF (queue=i) GOTO M L: C(i):=1 IF (B(queue)=1) * queue:=i GOTO L1 M: C[i]:=0 FOR J=1 TO N DO IF (i<>J) AND (C(J)=0) GOTO L Critical Section C(i):=1 B(i):=1 queue:=0 If in the place marked by * 1st process will be interrupted, then – the 2nd, and son on, then they will come to M simultaneously, and if one of them has passed to critical section, other return to L. If nobody has entered critical section, then all return to L and check whether B(queue)=1, meanwhile queue is equal to the number of the last process went to M, so all processes will go to L1, and the last will go again to M and then to critical section.