2

I am reading this article about preemptive multitasking and I didn't understand few things in the article:

The explanation says

Figure 1 shows the execution timeline for two tasks and an ISR. First, the ISR preempts the lower-priority task. But that ISR's execution makes the higher-priority task ready to use the processor again. So the scheduler selects that task to run after the ISR, further delaying the return to the preempted task. Note that the processor considers the lowest-priority interrupt in a system more important than its highest-priority task.

Figure 1:

In this Image,

My questions are :

  1. Shouldn't the exit from high-priority task be done by returning back to the ISR and then returning back to the low-priority task?

  2. How is the lowest-priority interrupt in a system more important than its highest-priority task?

uint128_t
  • 8,724
  • 6
  • 27
  • 28
MaNyYaCk
  • 1,468
  • 1
  • 15
  • 30
  • 1
    The ISR does not "call" the high priority task so the high priority task does not need to return to the ISR. Likewise, the low priority task is not "calling" the ISR. Read more about interrupts and what a context switch is. – kkrambo Feb 19 '17 at 20:10
  • @kkrambo thanks for reminding me that. It totally skipped out of my mind that preemption is done on the timing basis and it doesn't flow sequentially – MaNyYaCk Feb 20 '17 at 05:36

4 Answers4

2

1) the diagram show the execution of 'user' (task + interrupt) code, not necesarily how the swithing is done. In some systems all switches (except maybe the interrupt activation) involve a switch to the tasking supervisor, and only then a switch to the next thread.

2) all interrupts have a higher priority than all threads. Interrupts are supposed to handle things that can't wait, and take very little time to handle. Taks are supposed to handle things that take longer and can wait (a little).

Wouter van Ooijen
  • 48,572
  • 1
  • 63
  • 136
2

Shouldn't the exit from high-priority task be done by returning back to the ISR and then returning back to the low-priority task?

A lot depends on the exact details of the O/S design. But assuming the diagram is accurate for the case under discussion, then interrupts by definition are more important than the highest priority task (or process, or thread -- each of which are distinct but not necessarily distinct in exactly the same way in all operating systems -- I'll just use 'task' in the rest below.) That's the answer to your second question. But I'll get to that, shortly.

So the interrupt takes place, the O/S preempts the task and saves important state in a task state structure (used to restore the task's state before re-starting it later), and then the interrupt handler executes for a time. Let's say that in doing so it places some data into a network receive buffer and that a higher priority task has been waiting for a network receive buffer to become available.

For the interrupt routine to truly complete, it "signals" any tasks that have been waiting on a network receive buffer. (It calls a function for that purpose.) The function it calls finds that there is, in fact, a task waiting on it. The task had previously been moved OFF of the ready queue and onto the semaphore queue, while waiting. However, now this function moves the task from the semaphore queue and back onto the ready queue. It does this by calling an "insert" function designed to do that job. The insert function now inserts the high priority task back into the ready queue. But, of course, it does so with the higher priority in mind. And this makes it the "next task to run."

Now, the interrupt exits. But in doing so, the O/S now restores the state of the top (highest priority) task that exists in its ready queue. This NOW, it turns out, happens to be that higher-priority task (which was just moved to the ready queue.) The task state that the O/S restores next, is the task state from this highest priority task. And so it is re-started now, with the prior task still sitting around in the ready queue, waiting.

Most operating systems will work like that. So it's not hypothetical, it's very practical. But one could ALSO design an operating system to behave differently, too. It's just that it is actually very easy to do it the way I just described and most users of such operating systems want and expect that behavior. Since it is expected, natural, and also easy to do, it is usually the way it is done.

That is... if preemption is supported. Some embedded systems make that rather difficult -- particularly when non-preemptive, proprietary libraries are being used and there is no possible way to actually save the preempted state, correctly, since there is no access to the internals of the library state (due to lack of source code to read and/or tie into) and therefore no possible way to be sure you can return to the task safely. In cases like this, one might consider a voluntary release of control through a "switch()" call executed periodically by each task. Or by tapping into the compiler's generation of function call prologues and epilogues and inserting a special call at those points (since it is usually safe to switch between completed calls.)

How is the lowest-priority interrupt in a system more important than its highest-priority task?

If we are talking about hardware interrupts (software generated ones are a different matter, perhaps), then it is usually the case that there is a very limited time to respond before a hardware buffer is over-written (for receive) and/or that sending out information is very important be kept in a relatively continuous form.

For example, suppose the device is taking in measurements from an ADC, doing some processing on those values, and then sending out a measurement on a regular basis to a DAC. (Let's say that this is a temperature measurement that may be used as part of a closed loop control system of a FAB making ICs.) It may be VERY IMPORTANT that the ADC is read on a very repeatable timing cycle and that the DAC is also updated on a very repeatable timing cycle and that the delay between the ADC input and the DAC output must ALSO be highly precise and invariant. (For example, a fourier transform might be performed on the values read from the DAC output by the next device in the chain or else a closed loop control algorithm's design may depend upon accurate estimates of the time delay as well as the measurement frequency.)

In cases like the above, it's vital that the interrupt events be serviced well above the priority of some task with unknown duration.

That said, one could just as easily suggest cases where it's not as important. For example, a user-pressed key might either generate an interrupt or also be polled. It could easily be the case that even if an interrupt is permitted (and polling isn't used), that the priority of responding to that key press event should be lower than some tasks that are running. But then, it's usually better to just poll it, instead. There would be no good reason to let a key-press event generate interrupts in that case, so I think polling would be selected. And that gets us back to the remaining interrupts, which again are probably important.

So, in general, you design things such that interrupting events actually are more important than all of your tasks. You would generally NOT design things such that some interrupting events are less important. You'd poll those, instead.

jonk
  • 77,876
  • 6
  • 77
  • 188
1

The usual trick to this is that the scheduler runs at the end of the interrupt handler, and changes the stack pointer to point to the stack corresponding to the low priority task, it then just does a return from interrupt... The return from interrupt restores the registers from the stack frame of the low priority task (because that is what the stack pointer points to) then pops the return address off the low priority tasks stack and returns to that address.

There is also usually a need to adjust the page tables and flush any TLB or such on a modern system, but that is a detail.

Interrupts are (usually) hardware events that will cause a trap into the kernel,they need to be able to interrupt high priority tasks as they are sometimes time sensitive and sometimes change what is runnable. Consider for example if the high priority task is blocking on IO, the IO device ready interrupt will cause the kernel to move the task from blocked to runnable and then the scheduler may decide to run it, this has to happen at a higher priority then any task.

Dan Mills
  • 17,516
  • 1
  • 20
  • 38
0

How is the lowest-priority interrupt in a system more important than its highest-priority task?

Basically, an interrupt is the hardware's way of making sure the OS knows that a hardware event has occurred. It is up to the OS as to what action it takes and when. If the OS decides to immediately return to some other task, then nothing is lost except maybe a few microseconds.

If interrupts were lower priority than even one task, then it would be possible for an event to occur without the OS being notified for a while. That might mean important events are not handled properly.

For example, on some systems the "clock tick" interrupt happens many times per second, and if it is ever missed, the system time will become incorrect. Or, a serial device will signal the arrival of a character and if it is not collected quickly it will be overwritten with the next one.

That is not to say that these actions must be taken within an ISR. Depending on the system design, the ISR may simply ask the OS to schedule an appropriate high-priority task to deal with the event very soon. Indeed, long-running ISRs are normally discouraged because they delay all other system tasks.

Of course it is possible to create hardware where certain high-priority tasks take precedence over certain low-priority interrupts. But what benefits does this actually bring? Hardly any in practice. If there are certain high-priority tasks that would have their timing disturbed by an interrupt, the OS can simply disable interrupts during the critical bits.

Shouldn't the exit from high-priority task be done by returning back to the ISR and then returning back to the low-priority task?

No. Multitasking is not about tasks waiting for some particular task to complete; it is about tasks waiting for a turn on the CPU (unless they have nothing to do).

Consider a "high priority" task which reads from a keyboard and sends characters down a serial line. 99.9% of the time, it will be asleep, waiting for input from the keyboard. Its code looks something like:

#kb-to-serial task
while True:
    data = kb.read()
    serial.write(data)

Note that there is no return—this code is never "finished". But it will enter kb.read() and will have nothing to do until more data arrives.

Here is how things play out:

  1. Low-priority task is running
  2. User presses a key
  3. ISR starts
  4. The keycode is obtained and any tasks that are waiting for keyboard data are marked as awake
  5. The OS scheduler runs, chooses the kb-to-serial task, and prepares to restore that task's context
  6. ISR ends
  7. kb-to-serial starts and receives a character
  8. kb-to-serial tells the OS to write to the serial device
  9. The OS buffers the data for writing
  10. kb-to-serial resumes
  11. kb-to-serial requests more keyboard data
  12. The OS marks kb-to-serial as "asleep" and runs the scheduler
  13. The scheduler chooses a runnable task and prepares to restore its context
  14. The original low-priority task resumes
Artelius
  • 296
  • 1
  • 4