Hi,
iansjack wrote:
I think it may just be a case of detail. To my mind we have
1. A task is running (in user mode).
2. At some stage the task decides to relinquish control.
3. Some procedure now happens which ipso facto involves a switch to supervisor mode. That procedure carries to the details of a task switch, and eventually returns to a user task (normally a different one).
Alternatively 2 and 3 may be replaced by a time-slice switch, but that's probably the exception rather than the rule.
4. A task is running (in user mode).
I think, as far as most people are concerned, this means that a task switch involves a change of privilege level. 1 and 4 are the salient stages, with 2 and 3 being a black-box that can be implemented in different ways.
No, there's too many cases where either there isn't any privilege level switch (e.g. IRQ interrupts kernel code) or there isn't any interrupt (e.g. system calls) or there's too much other work that happens before or after the task switch to consider them part of the same thing. Your way of thinking only works for one special case where there is both an IRQ and an privilege level switch, which (unfortunately, when combined with "round-robin") is often the first case that a poorly guided (or unguided) beginner sees. This leads them into believing in "wrong" and then getting horribly confused later when they can't get anything else to work properly.
iansjack wrote:
But all of those ways will involve a switch to supervisor mode. So to say that "privilege level changes (including IRET changing from CPL=0 to CPL=3) have nothing to do with task switches", whilst strictly true, is perhaps a little misleading and is liable to confuse beginners. Of course the processor is in supervisor mode immediately before the task switch, and immediately after, but that is too atomic a viewpoint.
For a true beginner (someone with no prior knowledge of task switching); I normally teach them to implement a "switch_to_task()" (like the example I wrote
here) and then test it by having two kernel tasks that explicitly switch to each other; and then build on that (add code to select a task before calling "switch_to_task()", add generic "block_task(reason)" and an "unblock_task(task)", etc). This is not confusing at all - it's a relatively natural progression (that has the added benefit of making it easy to test each step along the way).
Where there's confusion, it's always because someone has had their mind poisoned before they learn how to do things right, because doing it right conflicts with their pre-existing misconceptions (and "task switching always involves interrupts" is the single biggest misconception).
Cheers,
Brendan