G27
Self-checkedTier 1 12 min reados.semaphores

Semaphores

The semaphore: an integer with atomic wait and signal operations; busy-waiting and blocking implementations, the value arithmetic, binary and counting semaphores, the three uses (mutual exclusion, ordering, resource counting), the rendezvous, deadlock from the wrong order, and finding the possible outputs of programs with semaphores.

Why it exists

Peterson's algorithm needs two processes, careful ordering and busy waiting. The operating system can do better and offer one simple tool that works for any number of processes and does not waste CPU time while waiting: the semaphore, introduced by Dijkstra. It is an integer variable that can only be changed by two atomic operations, wait (also called P, down) and signal (also called V, up, post). Semaphores solve mutual exclusion, force an order between actions of different processes, and count available resources. GATE asks you to trace programs that use them (values, who blocks, what can be printed), to say whether a given use deadlocks, and to complete a solution to a synchronization problem.

Prerequisites

You need the requirements of mutual exclusion from critical section and race conditions.

Precise definitions

Semaphore. A variable SS accessed only through two atomic operations (no other process can interleave in the middle of either):

  • wait(S) (P): if S>0S > 0 then decrement SS and continue; otherwise block until SS becomes positive, then decrement.
  • signal(S) (V): increment SS, which may allow a blocked process to continue.

Busy-waiting definition (the simplest, a spinlock):

wait(S):   while (S <= 0) ;   S = S - 1;
signal(S): S = S + 1;

Blocking implementation (no busy waiting). The semaphore holds an integer value and a queue of waiting processes:

wait(S):   S.value = S.value - 1;
           if (S.value < 0) { add this process to S.queue; block; }
signal(S): S.value = S.value + 1;
           if (S.value <= 0) { remove a process P from S.queue; wakeup(P); }

Here the value may become negative, and then −S.value-S.value is the number of processes waiting. The two definitions behave the same; they differ only in whether the value is allowed to go below 0. The operations themselves must be atomic: on a single processor by disabling interrupts for these few instructions, on a multiprocessor by a spinlock or atomic instruction.

Value arithmetic. If a semaphore starts at nn and kk wait operations and mm signal operations have been executed, then the logical value is

n−k+m.n - k + m.

If this is non-negative, it is the value of the semaphore and nobody is blocked. If it is negative, its magnitude is the number of blocked processes. Each blocked process corresponds to a wait that has not yet been matched by a signal.

Kinds.

  • Counting semaphore: any non-negative initial value; used to count units of a resource (initial value = number of units).
  • Binary semaphore (mutex lock): value 0 or 1; used for mutual exclusion.
  • A strong semaphore wakes blocked processes in FIFO order (no starvation); a weak one wakes in an unspecified order.

The three uses.

  1. Mutual exclusion: initial value 1; each process does wait(mutex) before its critical section and signal(mutex) after.
  2. Ordering (synchronization): to force action A of process 1 to happen before action B of process 2, use a semaphore initially 0: process 1 does A; signal(S) and process 2 does wait(S); B.
  3. Resource counting: initial value = the number of identical resources; wait to acquire one, signal to release.

Rendezvous (barrier of two). To make two processes wait for each other at a point: P1 does a1; signal(S1); wait(S2); a2 and P2 does b1; signal(S2); wait(S1); b2, with both semaphores initially 0. Then a2 and b2 both come after both a1 and b1.

Pitfalls.

  • Wrong order of operations can deadlock. If two processes take two semaphores in opposite orders (P1: wait(A); wait(B), P2: wait(B); wait(A)), both can end up holding one and waiting for the other.
  • Forgetting a signal (a semaphore is never released) blocks everyone else; an extra signal breaks mutual exclusion (the value rises above 1).
  • Using signal before wait in a mutual exclusion section just fails to protect it.
  • Priority inversion: a low-priority process holding a semaphore that a high-priority process needs can be preempted by a medium-priority process, so the high-priority process waits for the medium one. (Remedy: priority inheritance.)
  • Semaphores put the burden on the programmer: one wrong use anywhere breaks the whole system.

How it works

The animation shows the semaphore doing mutual exclusion for the update that raced on the previous page. mutex starts at 1. T1 does wait(mutex) and starts its update; when T2 does wait(mutex) the value is 0, so T2 blocks (it joins the semaphore's queue and takes no more steps). T1 finishes the update, and signal(mutex) wakes T2 instead of raising the value. T2 then does its update. Whatever the interleaving, x ends at 2.

A semaphore protects the update: the second thread blocks and is woken: start

Interleaving found by search, not typed
T1
▶wait(mutex)
r1 = x
r1 = r1 + 1
x = r1
signal(mutex)
T2
▶wait(mutex)
r2 = x
r2 = r2 + 1
x = r2
signal(mutex)
x
0
r1
0
r2
0
mutex
1

Step 0

mutex = 1, x = 0. The same load-add-store update as in the race, now between wait(mutex) and signal(mutex). Whatever the interleaving, the final value of x is 2.

0 / 10
computed by script: exhaustive search of the interleavings of a small concurrent program. python3 scripts/gen-os-sync-traces.py

Worked examples

EX 1Value arithmetic

Easy

A counting semaphore starts at 10. After 6 completed waits and 4 signals its value is 10−6+4=810 - 6 + 4 = 8. A semaphore starts at 3; after 5 waits and 2 signals, 3−5+2=03 - 5 + 2 = 0 and nobody is blocked (the 5 waits found enough units: the first 3 passed, the next 2 only passed after signals). A semaphore starts at 2; after 5 waits and 1 signal, 2−5+1=−22 - 5 + 1 = -2: two processes are blocked. (The first two waits pass; the next three block; the signal wakes one of them; two remain blocked.) If the question asks the "value" and the implementation allows negative values, give −2-2; if it asks how many are blocked, give 2.

EX 2How many different outputs?

Medium

Process 1 prints A then B. Process 2 prints C then D. With no synchronization the outputs are all the interleavings. Add a semaphore SS initially 0: process 1 prints A, does wait(S), prints B; process 2 prints C, does signal(S), prints D. A program tries every schedule:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
/* All the outputs two threads can print, found by trying every interleaving. A step is a print, wait(S) or signal(S) on one semaphore S. */
struct step { char kind; char ch; };            /* 'p' print ch, 'w' wait(S), 's' signal(S) */
static const struct step t0[3] = {{'p', 'A'}, {'w', 0}, {'p', 'B'}};
static const struct step t1[3] = {{'p', 'C'}, {'s', 0}, {'p', 'D'}};
static char seen[16][8];
static int nseen;
 
static void record(const char *out) {
    for (int i = 0; i < nseen; i++) if (strcmp(seen[i], out) == 0) return;
    strcpy(seen[nseen++], out);
}
 
static void dfs(int p0, int p1, int s, char *out, int len, int use_sem) {
    if (p0 == 3 && p1 == 3) { out[len] = 0; record(out); return; }
    if (p0 < 3) {
        struct step st = t0[p0];
        if (st.kind == 'p') { out[len] = st.ch; dfs(p0 + 1, p1, s, out, len + 1, use_sem); }
        else if (!use_sem) dfs(p0 + 1, p1, s, out, len, use_sem);
        else if (s > 0) dfs(p0 + 1, p1, s - 1, out, len, use_sem);      /* wait: only if the value is positive */
    }
    if (p1 < 3) {
        struct step st = t1[p1];
        if (st.kind == 'p') { out[len] = st.ch; dfs(p0, p1 + 1, s, out, len + 1, use_sem); }
        else if (!use_sem) dfs(p0, p1 + 1, s, out, len, use_sem);
        else dfs(p0, p1 + 1, s + 1, out, len, use_sem);                 /* signal */
    }
}
 
static int cmp(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); }
 
static void run(int use_sem, const char *what) {
    char out[8];
    nseen = 0;
    dfs(0, 0, 0, out, 0, use_sem);
    qsort(seen, (size_t)nseen, sizeof seen[0], cmp);
    printf("%s: %d outputs:", what, nseen);
    for (int i = 0; i < nseen; i++) printf(" %s", seen[i]);
    printf("\n");
}
 
int main(void) {
    run(0, "no synchronization       ");
    run(1, "S = 0, B waits for C's signal");
    return 0;
}
no synchronization       : 6 outputs: ABCD ACBD ACDB CABD CADB CDAB
S = 0, B waits for C's signal: 5 outputs: ACBD ACDB CABD CADB CDAB

Reading it. Without synchronization there are (42)=6\binom{4}{2} = 6 interleavings of the two pairs. The semaphore forbids the one in which B comes before C (ABCD): B cannot print before C has done signal(S). The other five outputs remain, since the semaphore only orders B after C.

EX 3Forcing an alternation

Medium

To make two processes print a and b alternately, abab, use S1=1S_1 = 1 and S2=0S_2 = 0. Process 1 does (twice) wait(S1); print a; signal(S2); process 2 does (twice) wait(S2); print b; signal(S1). Process 1 goes first because S1=1S_1 = 1; its signal lets process 2 run; and so on. Searching every schedule gives exactly one output, abab. If both semaphores were initially 1, the processes are no longer forced to alternate: an exhaustive search gives abab, abba, baab and baba (aabb is still impossible, since the second wait(S1) needs a signal that only comes after a b). The initial values encode who goes first.

EX 4Two semaphores, one deadlock

GATE level

P1: wait(A); wait(B); critical; signal(B); signal(A). P2: wait(B); wait(A); critical; signal(A); signal(B), with A=B=1A = B = 1. Schedule: P1 does wait(A), then P2 does wait(B). Now P1 needs B (held by P2) and P2 needs A (held by P1): deadlock. If P2 also took A first and then B, both would queue for A, the winner would take B freely, and no deadlock can happen. An exhaustive search of both versions confirms it: opposite orders can deadlock, the same order cannot. The rule (a consequence of the circular-wait condition in the deadlock page): acquire shared resources in one global order.

EX 5The rendezvous and the resource count

GATE level

Rendezvous. With S1=S2=0S_1 = S_2 = 0, P1: print a; signal(S1); wait(S2); print A, P2: print b; signal(S2); wait(S1); print B. Neither capital letter can be printed before both small ones: the possible outputs are abAB, abBA, baAB, baBA (found by trying every schedule). Resource count. A pool of 5 identical printers is guarded by a counting semaphore initialised to 5. Each job does wait(printers) ... signal(printers). At most 5 jobs hold printers at once; a sixth blocks until one is released; with 8 jobs requesting at the same time, the logical value after all 8 waits is 5−8=−35 - 8 = -3, so 3 jobs are blocked.

Cheat box

Cheat box

  • wait(S) (P): if S>0S > 0, SS-- else block. signal(S) (V): SS++ and wake one blocked process if any. Both atomic.
  • Value =n−k+m= n - k + m (nn initial, kk waits, mm signals). Negative: its magnitude is the number blocked.
  • Mutex: initial 1, wait before and signal after the critical section.
  • Ordering A before B: S=0S = 0; first does A; signal(S), second does wait(S); B.
  • Counting semaphore: initial value == number of units.
  • Opposite order of two waits can deadlock; take resources in one order.
  • Blocking semaphores use no CPU while waiting; spinlocks do. Strong: FIFO wake-up.
  • Rendezvous: two semaphores, each starts at 0, each side signals then waits.

Traps and GATE patterns

Connections

A semaphore provides what Peterson's algorithm cannot easily generalize to nn processes and without spinning, and it repairs the race. The blocked process is in the Waiting state of the process life cycle. Using semaphores in the wrong order leads to deadlock, and the standard patterns (bounded buffer, readers and writers, dining philosophers) are on the classic problems page.

PYQs

16 previous year questions from your book. I re-solved each one myself (11 by running or simulating, 5 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2026 · MSQQ1 · page 316 of the book
Consider three processes P1, P2, and P3 running
identical code, as shown in the pseudocode below.
A and B are two binary semaphores initialized to 1
and 0, respectively. X is a shared variable
initialized to 0. Each line in the pseudocode is
executed atomically.

Pseudocode of P1, P2, and P3:

wait(A);
print(*);
X=X+1;

if(X==2)
{
    print($);
    signal(B);
}

signal(A);

wait(B);
print(#);
signal(B);

Assume that any of the three processes can start to
execute first and context switching can happen
between these processes at any arbitrary time and
in any arbitrary order.

Which of the following patterns is/are possible to
be generated as an outcome of the execution of
these three processes?
  • (a)**$*###
  • (b)**$#*##
  • (c)**$##*#
  • (d)***$###
GATE 2024 · MSQQ2 · page 316 of the book
Consider a multi-threaded program with two threads
T1 and T2. The threads share two semaphores: s1
(initialized to 1) and s2 (initialized to 0). The threads
also share a global variable x (initialized to 0). The
threads execute the code shown below.

T1:

wait(s1);

x = x + 1;

 print(x);

 wait(s2);

 signal(s1);

T2:

wait(s1);

 x = x + 1;

 print(x);

 signal(s2);

 signal(s1);
Which of the following outcomes is/are possible
when threads T1 and T2 execute concurrently?
  • (a)T1 runs first and prints 1, T2 runs next and prints 2
  • (b)T2 runs first and prints 1, T1 runs next and prints 2
  • (c)T1 runs first and prints 1, T2 does not print anything (deadlock)
  • (d)T2 runs first and prints 1, T1 does not print anything (deadlock) OPERATING SYSTEMS
GATE 2023 · MCQQ3 · page 317 of the book
Consider the two functions incr and decr shown
below.

          incr(){
                     wait(s);

                     X = X+1;
                      signal(s);
         }
         decr(){
                     wait(s);

                     X = X-1;
                      signal(s);
         }

There are 5 threads each invoking incr once, and 3
threads each invoking decor once, on the same
shared variable X. The initial value of X is 10.

Suppose there are two implementations of the
semaphore s, as follows:

I-1: s is a binary semaphore initialized to 1.
I-2: s is a counting semaphore initialized to 2.

Let V1, and V2 be the values of X at the end of the
execution of all the threads with implementations I-
1, and I-2, respectively.

Which one of the following choices corresponds to
the minimum possible values of V1, V2,
respectively?
  • (a)15,7
  • (b)7,7
  • (c)12,7
  • (d)12,8
GATE 2022 · MCQ · 1 markQ4 · page 317 of the book
Consider the following threads T1, T2, and T3

executing on a single processor, synchronized using
three binary semaphore variables S1, S2, and
S3,operated upon using standard wait() and signal().
The  threads can be context switched in any order
and at any time.
T1:
 while(true) {
 wait(S3);
 print("C");
 signal(S2);
 }
T2:
 while(true) {
 wait(S1);
 print("B");
 signal(S3);
 }
T3:
 while(true) {
 wait(S2);
 print("A");
 signal(S1);
 }
Which initialization of the semaphores would print
the sequence BCABCABCA...?
  • (a)S1 = 1; S2 = 1; S3 = 1
  • (b)S1 = 1; S2 = 1; S3 = 0
  • (c)S1 = 1; S2 = 0; S3 = 0
  • (d)S1 = 0; S2 = 1; S3 = 1 OPERATING SYSTEMS
GATE 2021 · MSQQ5 · page 318 of the book
Consider the following pseudocode, where S is a
semaphore initialized to 5 in line #2 and counter is
a shared variable initialized to 0 in line#1. Assume
that the increment operation in line#7 is not atomic.

1. int counter = 0

2. Semaphore S = init(5);

3. void parop(void)

4. {

5.

wait(S);

6.

 wait(S);

7.

  counter++;

8.
        signal(S);

9.                signal(S);

10.}

If five threads execute the function parop
concurrently, which of the following program
behavior(s) is/are possible?
  • (a)The value of counter is 5 after all the threads successfully complete the execution of parop.
  • (b)The value of counter is 1 after all the threads successfully complete execution of parop.
  • (c)The value of counter is 0 after all the threads successfully complete the execution of parop.
  • (d)There is a deadlock involving all the threads.
GATE 2020 · MCQQ7 · page 318 of the book
Each of a set of n processes executes the following
code using two semaphores a and b initialized to 1
and 0, respectively. Assume that count is a shared
variable initialized to 0 and not used in CODE
SECTION P.

CODE SECTION P

wait (a);
           count = count+1;

if (count==n) signal (b);

signal (a); wait (b); signal (b);

CODE SECTION Q

What does the code achieve?
  • (a)It ensures that all processes execute CODE SECTION P mutually exclusively.
  • (b)It ensures that at most two processes are in CODE SECTION Q at any time.
  • (c)It ensures that no process executes CODE SECTION Q before every process has finished CODE SECTION P.
  • (d)It ensures that at most n-1 processes are in CODE SECTION P at any time. OPERATING SYSTEMS
GATE 2018 · MCQQ9 · page 319 of the book
Consider the following solution to the producer-
consumer synchronization problem. The shared
buffer size is N. Three semaphores empty,
full and mutex are defined with respective initial
values of 0, N and 1. Semaphore empty denotes the
number of available slots in the buffer, for the
consumer to read from. Semaphore full denotes the
number of available slots in the buffer, for the
producer to write to. The placeholder variables,
denoted by P, Q, R and S, in the code below can be
assigned either empty or full. The valid semaphore
operations are: wait () and signal ().
Producer
Consumer
do{
do {
wait (P);
wait (R);
wait (mutex);
wait mutex);
//Add item to buffer
//Consume item from
buffer
Signal (mutex);
Signal (mutex);
Signal (Q);
Signal (S);
} while (1);
} while (1);

Which one of the following assignments to P, Q, R
and S will yield the correct solution?
  • (a)P: full, Q: full, R: empty, S: empty
  • (b)P: empty, Q: empty, R: full, S: full
  • (c)P: full, Q: empty, R: empty, S: full
  • (d)P: empty, Q: full, R: full, S: empty
GATE 2016 · NATQ12 · page 320 of the book
Consider a non-negative counting semaphore S.
The operation P(S) decrements S, and V(S)
increments S. During an execution, 20 P(S)
operations and 12 V(S) operations are issued in
some order. The largest initial value of S for which
at least one P(S) operation will remain blocked is
_________.
GATE 2014 · MCQQ15 · page 320 of the book
Consider the procedure below for the Producer-
Consumer problem which uses semaphores:

semaphore n = 0;
semaphore s = 1;
void producer() void consumer()
{
    while(true) while(true)
    {
        produce();
        semWait(s) ;
        addToBuffer();
        semSignal(s);
        semSignal(n);
    }
}
void consumer()
{
    while(true)
    {
        semWait(s);
        semWait(n);
        removeFromBuffer();
        semSignal(s);
        consume();
    }
}

OPERATING SYSTEMS

Which one of the following is TRUE?
  • (a)The producer will be able to add an item to the buffer, but the consumer can never consume it.
  • (b)The consumer will remove no more than one item from the buffer.
  • (c)Deadlock occurs if the consumer succeeds in acquiring semaphore s when the buffer is empty.
  • (d)The starting value for the semaphore n must be 1 and not 0 for deadlock-free operation.
GATE 2013 · MCQQ16 · page 321 of the book
A certain computation generates two arrays a and b
such that a[i]=f(i) for 0 ≤ i< n and b[i]=g(a[i]) for 0
≤ i< n. Suppose this computation is decomposed
into two concurrent processes X and Y such that X
computes the array a and Y computes the array b.
The processes employ two binary semaphores R
and S, both initialized to zero. The array a is shared
by the two processes. The structures of the
processes are shown below.

Process X:
Process Y:
private i;
private i;
for (i= 0; i< n; i + +){
for (i= 0; i< n; i + +){

a[i] = f(i);
EntryY (R, S);
ExitX (R, S);
b[i] = g(a[i]);
}
}

Which one of the following represents the
CORRECT implementations of Exit X and Entry
Y?
  • (a)ExitX(R, S) { P(R); V(S); } EntryY(R, S) { P(S); V(R); }
  • (b)ExitX(R, S) { V(R); V(S); } EntryY(R, S) { P(R); P(S); }
  • (c)ExitX(R, S) { P(S); V(R); } EntryY(R, S) { V(S); P(R); }
  • (d)ExitX(R, S) { V(R); P(S); } EntryY(R,S) { V(S); P(R); }
GATE 2013 · MCQQ17 · page 321 of the book
A shared variable x, initialized to zero, is operated on
by four concurrent processes W, X, Y, Z as follows.
Each of the processes W and X reads x from memory,
increments by one, stores it to memory, and then
terminates. Each of the processes Y and Z reads x
from memory, decrements by two, stores it to
memory, and then terminates. Each process before
reading x invokes the P operation (i.e. wait) on a
counting semaphore S and invokes the V operation
(i.e., signal) on the semaphore S after storing x to

OPERATING SYSTEMS
memory. Semaphore S is initialized to two. What is
the maximum possible value of x after all processes
complete execution?
  • (a)-2
  • (b)-1
  • (c)1
  • (d)2
GATE 2010 · MCQQ20 · page 322 of the book
The following program consists of 3 concurrent
processes and 3 binary semaphores. The semaphores
are initialized as S0 = 1, S1 = 0, S2=0.

Process P0
Process P1
Process P2
while (true) {
wait (S1);
wait (S2);
wait (S0);
Release (S0);
Release (S0);

print '0'

release (S1);

release (S2);

}

How many times will the process P0 print '0'?
  • (a)At least twice
  • (b)exactly twice
  • (c)exactly thrice
  • (d)exactly one
GATE 2008 · MCQQ22 · page 323 of the book
The P and V operations on counting semaphores,
where s is a counting semaphore, are defined as
follows:
P(s):
 s = s - 1;
 if s < 0 then wait;
V(s):
 s = s + 1;
 if s ≤ 0 then wake up process waiting on s;

Assume that Pb and Vb, the wait and signal
operations on binary semaphores, are provided.
Two binary semaphores xb and yb are used to
implement the semaphore operations P(s) and V(s)
as follows:
P(s):
 Pb(xb);
 s = s - 1;
 if (s < 0)
 {
  Vb(xb);
  Pb(yb);
 }
 else Vb(xb);
V(s):
 Pb(xb);
 s = s + 1;
 if (s ≤ 0) Vb(yb);
 Vb(xb);

The initial values of xb and yb are respectively
  • (a)0 and 0
  • (b)0 and 1
  • (c)1 and 0
  • (d)1 and 1
GATE 2006 · MCQ · 2 marksQ25 · page 324 of the book
Barrier is a synchronization construct where a set of
processes synchronize globally. Let the number of
processes be three and S be a binary semaphore.
Consider the following implementation:
void barrier(void) {
1:  P(S);
2:  process_arrived++;
3:  V(S);
4:  while (process_arrived != 3);
5:  P(S);
6:  process_left++;
7:  if (process_left == 3) {
8:      process_arrived = 0;
9:      process_left = 0;
10: }
11: V(S);
}
Variables process_arrived and process_left are
shared and initialized to 0.
Which one of the following is true?
  • (a)The barrier implementation is wrong due to the use of binary semaphore S
  • (b)The barrier implementation may lead to a deadlock if two barrier invocations are used in immediate succession
  • (c)Lines 6 to 10 need not be inside a critical section
  • (d)The barrier implementation is correct if there are only two processes instead of three
GATE 2006 · MCQQ26 · page 324 of the book
Barrier is a synchronization construct where a set of
processes synchronize globally. Let the number of
processes be three and S be a binary semaphore.
Consider the following implementation:

void barrier(void) {

1:  P(S);

2:  process_arrived++;

3:  V(S);

4:  while (process_arrived != 3);

5:  P(S);

6:  process_left++;

7:  if (process_left == 3) {

8:  process_arrived = 0;

9: process_left = 0;

10: }

11: V(S);

}

The variables process_arrived and process_left are
shared and initialized to zero.

Which one of the following rectifies the problem in
the implementation?
  • (a)Lines 6 to 10 are simply replaced by process_arrived--
  • (b)At the beginning of the barrier the first process to enter waits until process_arrived becomes zero before proceeding to execute P(S)
  • (c)Context switch is disabled at the beginning of the barrier and re-enabled at the end
  • (d)The variable process_left is made private instead of shared OPERATING SYSTEMS
GATE 2013 · MCQ · 1 markQ14 · page 333 of the book
Three concurrent processes X, Y, and Z execute
three different code segments that access and
update certain shared variables. Process X executes
the P operation (i.e., wait) on semaphores a, b, and
c; process Y executes the P operation on
semaphores b, c, and d; process Z executes the P
operation on semaphores c, d, and a before entering
the respective code segments. After completing the
execution of its code segment, each process
invokes the V operation (i.e., signal) on its three
semaphores.
All
semaphores
are
binary
semaphores initialized to one. Which one of the
following represents a deadlock-free order of
invoking the P operations by the processes?
  • (a)X: P(a) P(b) P(c)Y: P(b) P(c) P(d) Z: P(c) P(d) P(a)
  • (b)X: P(b) P(a) P(c) Y: P(b) P(c) P(d) Z: P(a) P(c) P(d)
  • (c)X: P(b) P(a) P(c) Y: P(c) P(b) P(d) Z: P(a) P(c) P(d)
  • (d)X: P(a) P(b) P(c) Y: P(c) P(b) P(d) Z: P(c) P(d) P(a)

Self-check

Question 1

A semaphore starts at 1. P1 does wait, P2 does wait, P3 does wait, then P1 does signal. What is the logical value and who is blocked?

Show answer

1−3+1=−11 - 3 + 1 = -1: one process is blocked. P1 passed its wait; P2 and P3 blocked; P1's signal woke P2; P3 remains blocked.

Question 2

How do you force process 2's statement B to run only after process 1's statement A?

Show answer

Use a semaphore initialised to 0: process 1 does A; signal(S), process 2 does wait(S); B.

Question 3

A mutex semaphore is accidentally initialised to 2. What goes wrong?

Show answer

Two processes can pass wait together, so two can be in the critical section: mutual exclusion is lost.

Question 4

Why must wait and signal themselves be atomic?

Show answer

They read and change the semaphore's value (and queue) in several steps. If two processes interleaved inside them, both could see a positive value and both decrement it, so the semaphore itself would have a race condition.

Revision card

One-screen revision

  • wait: decrement or block; signal: increment or wake. Atomic.
  • Value =n−k+m= n - k + m; negative means that many blocked.
  • Uses: mutex (1), ordering (0), counting (units), rendezvous (two semaphores at 0).
  • Opposite order of waits can deadlock. Initial values decide who goes first.

References

  • ·Silberschatz, Galvin, Gagne, Operating System Concepts, Chapter 6 Synchronization Tools (semaphores) and Chapter 7 Synchronization Examples
  • ·Tanenbaum and Bos, Modern Operating Systems, Chapter 2 Processes and Threads (semaphores, mutexes)
  • ·Stallings, Operating Systems: Internals and Design Principles, Chapter 5 Concurrency (semaphores)

Verification

The value arithmetic (value minus number blocked equals initial minus waits plus signals) is checked on 2000 random operation sequences in scripts/enum-checks/os_sync.py. The lists of possible outputs of the example programs are found by exploring every interleaving in three independent ways: the C program on this page (npm run verify:c), a depth-first search in Python, and the generic model checker in scripts/gen-os-sync-traces.py, and they must agree; so must the claim that two semaphores taken in opposite orders can deadlock and in the same order cannot, and the rendezvous result. The mutex animation is the real schedule found by the search and is rechecked by tests/os-sync-traces.test.ts. The model uses STRONG semaphores (blocked processes are woken in FIFO order); with weak semaphores the order of waking is unspecified, which changes some outputs but never the safety results. Textbook chapter titles are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand. Two defects fixed: the page said that with both semaphores at 1 the alternation example could print aabb, which an exhaustive search shows is impossible (the outputs are abab, abba, baab, baba); and the signal trap now distinguishes the two conventions of the semaphore value. Status raised to self-checked; verified needs a textbook or human check.

last verified: never