Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Please explain how the mutual exclusion is achieved in the XV6 operating system

ID: 3832217 • Letter: P

Question

Please explain how the mutual exclusion is achieved in the XV6 operating system according to the source code below:

spinlock.c

/ Mutual exclusion spin locks.

#include "types.h"

#include "defs.h"

#include "param.h"

#include "x86.h"

#include "memlayout.h"

#include "mmu.h"

#include "proc.h"

#include "spinlock.h"

void

initlock(struct spinlock *lk, char *name)

{

lk->name = name;

lk->locked = 0;

lk->cpu = 0;

}

// Acquire the lock.

// Loops (spins) until the lock is acquired.

// Holding a lock for a long time may cause

// other CPUs to waste time spinning to acquire it.

void

acquire(struct spinlock *lk)

{

pushcli(); // disable interrupts to avoid deadlock.

if(holding(lk))

panic("acquire");

// The xchg is atomic.

while(xchg(&lk->locked, 1) != 0)

;

// Tell the C compiler and the processor to not move loads or stores

// past this point, to ensure that the critical section's memory

// references happen after the lock is acquired.

__sync_synchronize();

// Record info about lock acquisition for debugging.

lk->cpu = cpu;

getcallerpcs(&lk, lk->pcs);

}

// Release the lock.

void

release(struct spinlock *lk)

{

if(!holding(lk))

panic("release");

lk->pcs[0] = 0;

lk->cpu = 0;

// Tell the C compiler and the processor to not move loads or stores

// past this point, to ensure that all the stores in the critical

// section are visible to other cores before the lock is released.

// Both the C compiler and the hardware may re-order loads and

// stores; __sync_synchronize() tells them both to not re-order.

__sync_synchronize();

// Release the lock.

lk->locked = 0;

popcli();

}

// Record the current call stack in pcs[] by following the %ebp chain.

void

getcallerpcs(void *v, uint pcs[])

{

uint *ebp;

int i;

ebp = (uint*)v - 2;

for(i = 0; i < 10; i++){

if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff)

break;

pcs[i] = ebp[1]; // saved %eip

ebp = (uint*)ebp[0]; // saved %ebp

}

for(; i < 10; i++)

pcs[i] = 0;

}

// Check whether this cpu is holding the lock.

int

holding(struct spinlock *lock)

{

return lock->locked && lock->cpu == cpu;

}

// Pushcli/popcli are like cli/sti except that they are matched:

// it takes two popcli to undo two pushcli. Also, if interrupts

// are off, then pushcli, popcli leaves them off.

void

pushcli(void)

{

int eflags;

eflags = readeflags();

cli();

if(cpu->ncli == 0)

cpu->intena = eflags & FL_IF;

cpu->ncli += 1;

}

void

popcli(void)

{

if(readeflags()&FL_IF)

panic("popcli - interruptible");

if(--cpu->ncli < 0)

panic("popcli");

if(cpu->ncli == 0 && cpu->intena)

sti();

}

Explanation / Answer

The mutual exclusion problem is to devise a pre-protocol (or entry protocol) and a post-protocol (or exist protocol) to keep two or more threads from being in their critical sections at the same time. Tanenbaum examine proposals for critical-section problem or mutual exclusion problem.

Problem

When one process is updating shared modifiable data in its critical section, no other process should allowed to enter in its critical section.

Proposal 1 -Disabling Interrupts (Hardware Solution)

Each process disables all interrupts just after entering in its critical section and re-enable all interrupts just before leaving critical section. With interrupts turned off the CPU could not be switched to other process. Hence, no other process will enter its critical and mutual exclusion achieved.

Conclusion
Disabling interrupts is sometimes a useful interrupts is sometimes a useful technique within the kernel of an operating system, but it is not appropriate as a general mutual exclusion mechanism for users process. The reason is that it is unwise to give user process the power to turn off interrupts.

Proposal 2 - Lock Variable (Software Solution)

In this solution, we consider a single, shared, (lock) variable, initially 0. When a process wants to enter in its critical section, it first test the lock. If lock is 0, the process first sets it to 1 and then enters the critical section. If the lock is already 1, the process just waits until (lock) variable becomes 0. Thus, a 0 means that no process in its critical section, and 1 means hold your horses - some process is in its critical section.

Conclusion
The flaw in this proposal can be best explained by example. Suppose process A sees that the lock is 0. Before it can set the lock to 1 another process B is scheduled, runs, and sets the lock to 1. When the process A runs again, it will also set the lock to 1, and two processes will be in their critical section simultaneously.

Proposal 3 - Strict Alteration

In this proposed solution, the integer variable 'turn' keeps track of whose turn is to enter the critical section. Initially, process A inspect turn, finds it to be 0, and enters in its critical section. Process B also finds it to be 0 and sits in a loop continually testing 'turn' to see when it becomes 1.Continuously testing a variable waiting for some value to appear is called the Busy-Waiting.

Conclusion
Taking turns is not a good idea when one of the processes is much slower than the other. Suppose process 0 finishes its critical section quickly, so both processes are now in their noncritical section. This situation violates above mentioned condition 3.

Using Systems calls 'sleep' and 'wakeup'

Basically, what above mentioned solution do is this: when a processes wants to enter in its critical section , it checks to see if then entry is allowed. If it is not, the process goes into tight loop and waits (i.e., start busy waiting) until it is allowed to enter. This approach waste CPU-time.

Now look at some interprocess communication primitives is the pair of steep-wakeup.

Both 'sleep' and 'wakeup' system calls have one parameter that represents a memory address used to match up 'sleeps' and 'wakeups' .

The Bounded Buffer Producers and Consumers

The bounded buffer producers and consumers assumes that there is a fixed buffer size i.e., a finite numbers of slots are available.

Statement
To suspend the producers when the buffer is full, to suspend the consumers when the buffer is empty, and to make sure that only one process at a time manipulates a buffer so there are no race conditions or lost updates.

As an example how sleep-wakeup system calls are used, consider the producer-consumer problem also known as bounded buffer problem.

Two processes share a common, fixed-size (bounded) buffer. The producer puts information into the buffer and the consumer takes information out.

Trouble arises when

Conclusion
This approaches also leads to same race conditions we have seen in earlier approaches. Race condition can occur due to the fact that access to 'count' is unconstrained. The essence of the problem is that a wakeup call, sent to a process that is not sleeping, is lost.