>I don’t understand. The necessity to protect against context switch is
because of preemptive multitasking. I don’t see the relation to IO
reactivity. Well, the probability you’re preempted in a bad moment changes
but it has to be handled the same way even if probability is very low. Am I
missing something?
There are two different mechanisms in action here: preemption and
interruption.
A CPU is running, an interrupt hits: the interrupt handler gets control, it
interrupts the current code, does its job, and exits. At this point the OS
has the choice of preempting or not preempting the interrupted task.
Typically, an interrupt will hit when it’s got higher interrupt priority (do
not confuse it with dispatching priority ! ) than the code that’s currently
running. In many systems, this is implemented purely at hardware level: the
equivalent to WinNT IRQLs are programmed in the interrupt controller and
only higher priority interrupts will ever reach the processor.
The easiest way to think of it is by looking at how the Protected Mode
architecture handles it. You’re running under a task, your Task Register
points to some entry in your GDT or LDT. You get an interrupt, and if you
have an interrupt gate or a task gate in the IDT, you do not get a
multitasking context switch: you’re running this new interrupt routine, but
you didn’t get “preempted”, you get “interrupted” ! However, if you have a
Task Gate in your IDT, your interrupt will run on that task and hence your
running task will be context-switched. So, your average interrupt doesn’t
preempt, what it does is, well, it interrupts. At the end of an interrupt
handling the OS may decide to preempt, but that’s a different matter.
Now, why would we want to allow a nested interrupt to happen ? We’re not
talking about preemptive multitasking here, we’re talking about nesting
interruptions. The one reason to allow an interrupt to interrupt an
interrupt is to increase reactivity of the system: that is, to try to
minimize the possibility of losing an interrupt because we missed it while
handling another interrupt handler with interrupts disabled. So, in WinNT, a
program may be running at passive or dispatch level, that is, not inside an
interrupt handler: an interrupt hits, and it’s at some higher IRQL level,
and the rules of the OS mimic the time-honored hardware implementation that
only lets higher priority interrupts to hit. When that happens, the
time-honored technique is, handle the interrupt, quiet down your peripheral,
and get back to the interrupted code as fast as you can !
At this point, it may be someone’s idea of improvement to allow the current
task to be preempted, but I don’t think that’s necessarily a good idea.
Because, as we’ve been discussing, the CPU may be in the middle of a
hardlock loop, and the result of preemption may be deadlock. So, no, the
point of allowing nested interrupts isn’t preemptive multitasking, it’s
rather to increase the reactivity of the system in terms of a potentially
faster interrupt handling. So, if I’m inside a spinlock loop, it’s ok to be
interrupted - I don’t call it “preemption” to make a difference between this
and the time-slice-driven mechanism - but whoever interrupts must make sure
deadlocks cannot occur. Now, if we’re allowing interrupt routines to wait on
spinlocks, deadlocks are always a very present danger, so, there’s only one
solution: interrupts must be either partially or totally disabled while an
interrupt handler or a spinlock is going on. If we have good hardware
support, this is all done in hardware and it’s transparent to the
programmer, but if not, it’s got to be done in software. Total interrupt
disabling is done via manipulating the flags, while partial interrupt
disabling is done by raising the IRQL.
If we don’t want to try to get the I/O to be more reactive, why IRQLs ?
Might as well run interrupt handlers with interrupts disabled and we’d be
better off. It may be the case that a timer tick may have to break a
spinlock loop, but that’s going to be a timeout and an error condition ! I
do not see what’s the possible benefit of interrupting a CPU in the middle
of a spinlock loop and getting it to handle another task that can reach for
the same spinlock and somehow prevent the original holder from releasing it,
the only possible reason I see is I/O reactivity.
Normally, you shouldn’t need to know processor number. Code running at
PASSIVE_LEVEL is preemptive and I see it as an advantage (kernel threads).
What is an alternative? Cooperative multitasking in kernel? You can always
raise IRQL to DISPATCH_LEVEL to avoid preemption.
But the issue is not preemption, the issue’s interruption. Apart from the
potential undesirability of letting a time slice context switch to take
place inside a spinlock loop - that’s about preemption allright - we also
have the potential undesirability of allowing interrupts within a spinlocked
critical section. The “raise the IRQL” trick sits on the assumption that
well, if my interrupt routine is running at IRQL level umpteen and
busywaits, that no other routine at a higher IRQL will busywait on the same
piece of memory, or a deadlock may occur. So, I don’t know if I’m at passive
level or at dispatch level, but in either case a spinlock should be
protected from preemption, although not necessarily from interruption.
Disable interrupts just because some code waits for a lock? It affects
whole
system because of local conditions. Do you really think it is a good idea?
Disabling interrupts only affects that one CPU anyway, and spinlocks are
supposed to be very fine grained ! Note well, raising the IRQL is a form of
disabling interrupts, it’s a partial interrupt disable. The difference
between raising the IRQL and issuing a CLI here is basically how much I/O
interruption we’re willing to allow that one CPU to be subjected to, and
again, the issue becomes one of reactivity: what do I really gain by
allowing that to happen ? If we were running on a marvellously fast bus with
an immense i/o bandwidth, well, maybe, but we’re running on a 133Mhz bus.
Take a 3Ghz machine, and that one CPU can run over 20 instructions during
the time it takes for the PCI bus to perform one interrupt cycle. I’d be
curious to figure out how many instructions a 3Ghz Xeon CPU can execute
during the average PCI bus interrupt latency, that’s going to be a measure
of how many interrupts we’d possibly lose by allowing a 50-instruction
critical section, say, to run on a CPU with interrupts disabled. So, I’m not
sure I’m worried about affecting the whole system, unless of course one
abuses of the concept of a spinlocked critical section.
It doesn’t mean it must be used in cases where it causes unnecessary
complications. The goal of OOP is to simplify programming and make code
more
readable and not to make developers headache because of purity.
OO gives us new programming paradigms. I shouldn’t be programming C++ in C
style any more than I should be programming C in assembler style ! I daresay
that C++ programming should simplify programming to OO programmers, and it
should make code more readable to OO programmers !
Yes, it is this case. OS enforces some rules and class design have to
fulfil
them even if you don’t like it.
I’m not complaining about the rules, I’m complaining about the loose
semantics. For example, the hw architecture doesn’t allow an interrupt
between a move to esp and a move to ss. Maybe the OS should not allow a task
to be rescheduled to another processor between the time one reads the
processor number and the time one uses it ?
This depends on personal preferences. Automatic locks are widely used and
shouldn’t confuse experienced C++ developers. On the contrary, the
necessity
to manually unlock can be confusing to somebody who routinely uses
autolocks.
You know, we used to have automatic typing in Fortran, and that was turned
down in favor of explicit typing. And it was done for a reason !
Synchronization code, likewise, will benefit from explicitly acquiring and
freeing those synchronization constructs. But you may be right, it could be
a question of personal preference.
I’d agree. With these requirements it makes sense to use stack frame,
doesn’t it?
Not necessarily: these things don’t need to be in the same thread.
Hmm, guess why queued spinlock acquire/release routines have
…InStack…in the name. Doesn’t it imply usage?
It certainly does, but again, I find it restrictive. And here again, even
within the same stack, I still find it a good idea to explicitly free it !
Alberto.
The contents of this e-mail are intended for the named addressee only. It
contains information that may be confidential. Unless you are the named
addressee or an authorized designee, you may not copy or use it, or disclose
it to anyone else. If you received it in error please notify us immediately
and then destroy it.