0% found this document useful (0 votes)
61 views11 pages

Chapter 6 Solutions

The document states that training data is current only up to October 2023. No additional information is provided. The context or subject matter is not specified.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
61 views11 pages

Chapter 6 Solutions

The document states that training data is current only up to October 2023. No additional information is provided. The context or subject matter is not specified.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd

6.1. In Section 6.4, we mentioned that disabling interrupts frequently can affect the system’s clock.

Explain why this can occur and how such effects can be minimized.

• System Clock Basics: Most operating systems maintain time using timer interrupts that fire at
regular intervals (e.g., every millisecond). These interrupts update internal counters used to
track time, schedule tasks, and manage delays.
• Interrupt Disabling: When interrupts are disabled, these timer interrupts are also blocked. If
they are delayed or missed, the OS can't update the system clock accurately.
• Result: The system "misses time", causing:
• Clock drift (system time becomes inaccurate)
• Skipped or delayed task scheduling
• Performance or synchronization issues in time-sensitive programs

How to Minimize the Effects


1. Minimize Duration of Disabled Interrupts
• Keep the critical sections short.
• Only disable interrupts when absolutely necessary (e.g., very low-level kernel code).
2. Use Fine-Grained Synchronization
• Instead of disabling interrupts, use mutex locks, spinlocks, or monitors that allow
interrupts to remain enabled.
3. Re-enable Interrupts ASAP
• Design code to re-enable interrupts quickly after the critical operation is complete.
4. Use Priority-Based Interrupt Handling
• Some systems allow nested or prioritized interrupts, so the clock interrupt can still be
processed even if others are disabled.
5. Compensate for Lost Time
• Advanced OS kernels may track lost ticks and adjust the system clock once interrupts
are re-enabled.

📌 Summary
Disabling interrupts can delay timer ticks, which causes the system to lose track of real
time. Keeping interrupt-disabled periods short and using proper synchronization
mechanisms helps minimize these issues.
6.2 What is the meaning of the term busy waiting? What other kinds of waiting are there in an
Operating System? Can busy waiting be avoided altogether? Explain your answer.

Ans: 🌀 What is Busy Waiting?


Busy waiting refers to a situation where a process continuously checks for a condition to become true
(like access to a critical section or availability of a resource), without releasing the CPU.
📍 Example:
c
CopyEdit
while (lock == 1); // spin here doing nothing until the lock becomes 0

• This loop spins in place, consuming CPU cycles unnecessarily.

Other Types of Waiting in an Operating System


Besides busy waiting, there are more efficient forms of waiting:
1. Blocking (or Sleeping)
• The process is suspended and put into a waiting queue.
• It doesn’t consume CPU while waiting.
• It’s resumed only when the awaited condition or resource becomes available.
2. Polling
• Similar to busy waiting, but often used with timeouts or periodic checking, possibly
with CPU yielding in between checks.
• Slightly better than busy waiting but still suboptimal.
3. Event-Driven (Interrupt-based) Waiting
• The process or OS waits for an event or interrupt to signal that an action can proceed
(e.g., I/O completion).
• This method is very efficient, allowing CPU to do other work in the meantime.

❌ Can Busy Waiting Be Avoided Altogether?


In most cases, yes — and it should be avoided. However:
• In user-space applications and general OS scheduling, busy waiting is highly inefficient and
can be replaced with blocking synchronization mechanisms (like semaphores, condition
variables, or events).
• In low-level kernel programming or multiprocessor systems, some amount of busy waiting
is necessary and even optimal — especially for:
• Very short waits (e.g., spinlocks)
• Critical sections where the cost of blocking/unblocking outweighs the CPU cycles
wasted
🟡 Spinlocks are a common example where busy waiting is acceptable — but even then, they’re usually
used with care and only when contention is expected to be minimal.
6.4 Explain why spinlocks are not appropriate for single-processor systems yet are often used in
multiprocessor systems.
Answer: Spinlocks are not appropriate for single-processor systems because the condition that would
break a process out of the spinlock could be obtained only by executing a different process. If the
process is not relinquishing the processor, other processes do not get the opportunity to set the program
condition required for the first process to make progress. In a multiprocessor system, other processes
execute on other processors and thereby modify the program state in order to release the first process
from the spinlock.

6.4 Show that, if the wait() and signal() semaphore operations are not executed atomically, then
mutual exclusion may be violated.
Answer: A wait operation atomically decrements the value associated with a semaphore. If two wait
operations are executed on a semaphore when its value is 1, if the two operations are not performed
atomically, then it is possible that both operations might proceed to decrement the semaphore value
thereby violating mutual exclusion.

6.6 Race conditions are possible in many computer systems. Consider a banking system that maintains
an account balance with two functions: deposit(amount) and withdraw(amount). These two functions
are passed the amount that is to be deposited or withdrawn from the bank account balance. Assume that
a husband a nd wife share a bank account. Concurrently, the husband calls the withdraw() function, and
the wife calls deposit(). Describe how a race condition is possible and what might be done to prevent
the race condition from occurring.

Soln: A race condition occurs when multiple processes or threads access and manipulate shared data
concurrently, and the final result depends on the timing of their execution. Let’s break down how this
can happen in your banking system scenario:

🏦 Scenario: Shared Bank Account


Functions:
• deposit(amount)
• withdraw(amount)
Shared variable:
• account_balance
Let’s assume:
• Current account_balance = 1000

• Husband calls withdraw(200)

• Wife calls deposit(300)

• These happen concurrently (at the same time).

⚠️How a Race Condition Can Happen:


Both functions read, modify, and write to the shared account_balance. Suppose the operations are
broken down like this:

Husband’s withdraw(200):
1. Read account_balance (gets 1000)

2. Subtract 200 → 800


3. Write back 800

Wife’s deposit(300):
1. Read account_balance (gets 1000)

2. Add 300 → 1300


3. Write back 1300

If both read account_balance before either writes back, both use the same starting value
(1000). This leads to overwriting each other's result, and the final balance could be:

• 800 (if husband's write is last)


• 1300 (if wife's write is last)

➡️Correct value should be: 1000 - 200 + 300 = 1100

✅ Solution: Prevent Race Condition with Mutual Exclusion


To prevent this, we need to protect the critical section — the part where shared data
(account_balance) is accessed and modified.

Use a mutex (mutual exclusion lock):


In pseudocode:
c
CopyEdit
mutex_lock(mutex);
account_balance = account_balance - 200;
mutex_unlock(mutex);

c
CopyEdit
mutex_lock(mutex);
account_balance = account_balance + 300;
mutex_unlock(mutex);

This ensures:
• Only one function accesses/modifies account_balance at a time.

• No interference from another process or thread during that time.

push(item) {
if (top < SIZE) {
stack[top] = item;
top++;}
else
ERROR}

pop() {
if (!is empty()) {
top--;
return stack[top];}
else
ERROR}

is_empty() {
if (top == 0)
return true;
else
return false;}

Figure 6.15 Array-based stack for Exercise 6.12.

6.7 The pseudocode of Figure 6.15 illustrates the basic push() and pop() operations of an array-based
stack. Assuming that this algorithm could be used in a concurrent environment, answer the following
questions:
a. What data have a race condition and how race condition can occur?
b. How could the race condition be fixed?
Shared Data Involved:
• stack[]: the shared array storing stack elements.

• top: the index of the top element in the stack.


Potential Race Conditions:
A race condition occurs when two or more concurrent operations try to read-modify-write shared
data without proper synchronization.

🧨 Examples of Race Conditions:


1. Two push() operations run concurrently:

• Both check if (top < SIZE) and see it’s valid (e.g., top == 5)

• Both write their item to stack[5]

• Both increment top to 6 → Incorrectly skips index 6 or overwrites → Lost data or


inconsistency
2. Two pop() operations run concurrently:

• Both check is_empty() and see top > 0

• Both decrement top (e.g., from 5 to 4)

• Both return stack[4] → Same item returned twice

3. One push() and one pop() run concurrently:

• Timing mismatch can cause the top index to point to wrong position or skip valid
elements.

b. How could the race condition be fixed?


✅ Use Mutual Exclusion (Mutex) to Protect Critical Sections
Wrap all modifications and reads of top and stack[] in a mutex lock to prevent concurrent access.

🔐 Example in Pseudocode (With Mutex):


mutex m; // shared mutex

push(item) {
lock(m);
if (top < SIZE) {
stack[top] = item;
top++;
} else {
ERROR;
}
unlock(m);
}
pop() {
lock(m);
if (!is_empty()) {
top--;
item = stack[top];
} else {
ERROR;
}
unlock(m);
return item;
}

is_empty() {
return (top == 0);
}

Why This Works:


• Only one thread/process can access or modify top and stack[] at a time.

• This prevents interleaving that causes incorrect behavior.

6.8 Race conditions are possible in many computer systems. Consider an online auction system where
the current highest bid for each item must be maintained. A person who wishes to bid on an item calls
the bid(amount) function, which compares the amount being bid to the current highest bid. If the
amount exceeds the current highest bid, the highest bid is set to the new amount. This is illustrated
below:
void bid(double amount) {
If (amount > highestBid)
highestBid = amount;}

Describe how a race condition is possible in this situation and what might be done to prevent the race
condition from occurring.

In the given bid(amount) function:


void bid(double amount) {
if (amount > highestBid)
highestBid = amount;}
This seems fine in single-threaded environments, but in concurrent systems, it can cause a race
condition.

What Is the Race Condition Here?


Let’s say two bidders, A and B, call bid() at the same time:

• Current highestBid = 100

• Bidder A wants to bid 120


• Bidder B wants to bid 130
👇 Timeline of What Might Happen (Interleaving):
1. A reads highestBid = 100 → sees 120 > 100 → proceeds

2. B reads highestBid = 100 → sees 130 > 100 → proceeds

3. A sets highestBid = 120

4. B sets highestBid = 130

✅ In this case, no problem — 130 wins.


But imagine this interleaving instead:
1. B sets highestBid = 130

2. A sets highestBid = 120

❌ Now the final highestBid = 120 which is incorrect — a lower bid overwrote a higher one.

How to Prevent the Race Condition?


🔐 Use Mutual Exclusion (Mutex Lock)
You need to protect the entire if check and update block as a critical section.

🔧 Corrected Version with Mutex (Pseudocode):


mutex m;

void bid(double amount) {


lock(m);
if (amount > highestBid)
highestBid = amount;
unlock(m);}
Only one bidder can compare and update at a time — no overwriting of higher bids.

Explain why implementing synchronization primitives by disabling interrupts is not appropriate


in a single-processor system if the synchronization primitives are to be used in user-level
programs.

Disabling interrupts to implement synchronization primitives is not appropriate in a single-


processor system when used in user-level programs for the following reasons:

🔒 1. User Programs Cannot Disable Interrupts


• Only the operating system (kernel) has the privilege to disable and enable hardware
interrupts.
• User-level programs run in user mode, which does not allow access to privileged CPU
instructions such as those needed to disable interrupts.
✅ Conclusion: User programs simply cannot execute the necessary instructions to disable interrupts.

⚠️2. Disabling Interrupts Affects System Responsiveness


• Disabling interrupts blocks all interrupt handling, including from I/O devices and timers.
• If a user-level program could disable interrupts (hypothetically), it might:
• Freeze keyboard, mouse, or disk operations.
• Prevent the OS scheduler from working correctly.
✅ Conclusion: It would compromise the stability and responsiveness of the entire system.

🧠 3. Not Needed in Single-Processor User Space


• In single-processor systems, race conditions in user-space programs typically arise due to
preemption (context switches), not true parallel execution.
• User-space threads (without kernel support) cannot be interrupted in a way that causes
concurrent execution on a single processor.
✅ Conclusion: Instead of disabling interrupts, user-level synchronization should rely on:
• Atomic operations (e.g., compare-and-swap).

• OS-provided primitives like mutexes or semaphores that block/suspend threads.

🔁 4. Disabling Interrupts Only Prevents Kernel-Level Interruption


• Even if a thread disables interrupts (in a privileged mode), it only prevents interrupt-based
preemption.
• It cannot prevent scheduling or concurrency if the OS is managing multiple threads or
processes.
✅ Conclusion: It's an unreliable method for thread-level synchronization even in kernel mode.

✅ Summary
Reason Explanation
User programs can't execute privileged instructions like disabling
Privilege Violation
interrupts.
Reason Explanation
Interrupts are critical for system operations like I/O and
System Instability
scheduling.
Mutexes, semaphores, and atomic operations are safer and more
Better Alternatives Exist
effective.
Doesn’t Handle Concurrency
Doesn’t prevent preemption by other threads/processes effectively.
Properly

Q. Explain why interrupts are not appropriate for implementing synchronization primitives in
multiprocessor systems?
Interrupts are not appropriate for implementing synchronization primitives in multiprocessor
systems for several important technical reasons:

🚫 1. Interrupt Disabling is Local to a CPU


• On a multiprocessor system, disabling interrupts on one CPU only affects that CPU.
• Other processors continue running and accessing shared resources, potentially leading to
race conditions.
✅ Example:
CPU 0 disables interrupts and enters a critical section.
At the same time, CPU 1 (with interrupts still enabled) accesses the same shared data.
➡️Data corruption can occur despite interrupts being disabled on CPU 0.

🔄 2. No Coordination Between CPUs


• Disabling interrupts does not provide any inter-processor coordination.
• Synchronization primitives need to ensure mutual exclusion across all processors.
• Interrupt disabling only stops the local CPU from being interrupted — not other CPUs.
✅ Conclusion: There's no way to guarantee exclusive access to shared data system-wide with just
interrupt disabling.

3. Spinlocks and Atomic Instructions are Better Alternatives


• Spinlocks and atomic instructions like compare-and-swap or test-and-set are
hardware-supported and work across all CPUs.
• They ensure correct mutual exclusion by coordinating access at the memory level, not just CPU
control level.
✅ Example:
A shared lock variable in memory can be tested and set atomically by any processor.
This ensures only one CPU enters the critical section at a time.

⚠️4. Performance and Complexity


• Managing interrupts on multiple processors is complex and inefficient.
• It can:
• Introduce bugs and hard-to-detect race conditions.
• Increase latency in handling interrupts (bad for real-time systems).
• Affect all interrupt-driven subsystems, including timers and I/O.
✅ Conclusion: Disabling interrupts can severely degrade system performance and responsiveness.

🧠 Summary Table
Reason Why Interrupts Are Inappropriate
Local Scope Only Disabling interrupts affects only the local CPU.
No Inter-CPU Coordination Other CPUs can still access shared data.
Better Alternatives Exist Spinlocks and atomic operations ensure safe, global synchronization.
Inefficient and Complex Hard to implement, affects system latency and responsiveness.

✅ Conclusion
In multiprocessor systems, synchronization must be system-wide, not CPU-local. That’s why
interrupt disabling is inadequate. Instead, hardware-supported atomic operations and locks (like
mutexes, semaphores, and spinlocks) are used for correct and efficient synchronization.

You might also like