I am trying to understand how the code below works. This is straight out of my profs lecture slides. This P() and V() function is the part of semaphore implementation in the OS that we use in class (OS161). I think you might need understanding of the OS161 to answer my question, since its widely used, hopefully some one can answer this questions.
My understanding of this code with lecture notes:
X:Flow of the P() function
1. When a thread call P(), we disable interrupt
2. check if we have any resources available on sem->count
3.a) if count is 0 then we go to sleep
3.b) if count != 0 then we decrements count and allow the calling thread to continue to critical section
4. Enable Interrupt
Y:Flow of the V() function
1. When a thread call V(), we disable interrupt
2. Increment the counter, implying that now there is 1 more resource available to grab
3. Now we go ahead and wake up all the thread that we sent to sleep in P(), because there was not enough resources available at the time the thread tried to grab a lock to a critical section
4. Enable Interrupt
My Problems:
1. Does "disable interrupt" section disable interrupt on a particular thread or does it disable all the interrupts?
2. On V() function when we wake up all the threads, the thread slept inside the while loop in P() function start to execute the while loop. In the lecture it says one thread grab the lock and rest go back to sleep. My question is why "sem->count == 0" condition doesn't evaluate to false to other threads but only one.
I really want to know how does the interrupt disable part works. which is my first question. Does it stops thread scheduler?, does it stop context switch in the system?
Why does the thread goes to sleep with interrupt disable? isn't that dangerous, since it can miss I/O finished signals and other things?
P(sem) {
Disable interrupts;
while (sem->count == 0) {
thread_sleep(sem); /* current thread
will sleep on this sem */
}
sem->count--;
Enable interrupts;
}
V(sem) {
Disable interrupts;
sem->count++;
thread_wakeup (sem); /* this will wake
up all the threads waiting on this
sem. Why wake up all threads? */
Enable interrupts;
}
Thank you.
The CPU knows nothing about threads, they are just a logical/abstract concept implemented in software. But the CPU does know about interrupts, they are real, and whenever one comes in from some device, the CPU stops executing whatever it has been executing and starts executing the routine dedicated to handling this particular interrupt. Once done, the routine signals completion of interrupt handling and the CPU resumes execution of whatever was preempted by the interrupt handling routine.
If the preempted code belonged to a thread, so be it. If it was another interrupt handling routine, fine too.
Just before the interrupt handling routine starts, the CPU saves some of the execution context (a few general-purpose and maybe a few control/system registers) either on the stack or somewhere else, so the routine can use them for its own purposes, and then at the end of the routine the CPU restores those registers from wherever they were stored, as if nothing ever happened from the point of view of the interrupted code. If the routine alters those registers, the CPU will resume execution somewhere else, not where it was executing the last time before the interrupt.
So, there, you can use interrupts to switch execution between the various pieces of code, threads or what have you. In fact, this is exactly how many schedulers work. They receive periodic interrupts from a timer and in the interrupt handler they save the context of the preempted code (e.g. thread A) in memory and load the context of another preempted code (e.g. thread B) from memory and return thereby continuing execution in another thread.
If you disable those timer interrupts, periodic thread scheduling/switching will be disabled as well. Interrupts affect the entire CPU and the currently executing thread (or whatever it is) and by induction they affect all threads.
Got it?
Now, if there are threads in the system, there's always at least one thread that can execute. That's because the CPU needs to execute something, it can't just stop and wait for a thread to arrive from nowhere (after all, it's the CPU who creates threads and makes them runnable and runs them). For this purpose there's a dummy (or not so dummy) thread in the system that has a low priority and does pretty much nothing, looping forever and perhaps telling the CPU that it may switch to a lower power state or halt until an interrupt comes in. An interrupt would end the low-power mode and cause the code to continue execution.
So, when a thread blocks on a semaphore or some other synchronization primitive, the scheduler simply picks another thread to execute. If all threads are blocked, the dummy thread is selected.
In your code, interrupts get disabled for a short period of time, while the kernel code is manipulating the various global variables (e.g. the lists of blocked/sleeping and ready threads). That's normal. You don't want race conditions here. Interrupts are re-enabled when the scheduler picks another thread to execute and proceeds to execute it.
Observe that when an at-some-point-current thread finishes sleeping (e.g. when some other thread wakes it up), it always enables interrupts:
spl = splhigh(); // disable interrupts
while (sem->count==0) {
thread_sleep(sem); // context switch (to another thread and then back) occurs here
}
sem->count--;
splx(spl); // <-- re-enable interrupts here
Every thread blocked in this manner will cause interrupts to be enabled again when it's woken up and chosen by the scheduler to run.
Just think about it. You have 2 (or more) instances of the above or similar code in 2 (or more) threads. When one enters thread_sleep()
or a similar function, some other thread comes out of its thread_sleep()
or a similar function and re-enables interrupts.
Follow the code and comments along this path:
P()
thread_sleep()
mi_switch()
md_switch()
mips_switch()
As for the semaphore count, I'm not willing to do more code analysis now. You should try to figure it out yourself, unless someone else chimes in and covers it.