Chapter 8 multitasking
By Allen B. Downey
Original text: Chapter 8 multitasking
Translator: Flying Dragon
Protocol: CC by-nc-sa 4.0
On many current systems, CPU contains multiple cores, which means it can run multiple processes at the same time. Moreover, each core has the ability of “multitasking”, that is, it can quickly switch from one process to another, creating the illusion of running many processes at the same time.
The part of the operating system that implements multitasking is called the kernel. In nuts or seeds, the inner core is the innermost part, surrounded by the outer shell. In various operating systems, the kernel is the bottom layer of software, surrounded by some other layers, including the interface called “shell”. Computer scientists like quotations.
In essence, the kernel’s job is to handle interrupts. Interrupt is an event that stops the normal instruction cycle and causes the execution flow to jump into a special code area called the interrupt processor.
When a device sends a signal to the CPU, a hardware interrupt occurs. For example, a network device may have an interrupt when a packet arrives, or a disk drive may have an interrupt when the data transfer is complete. Most systems also have timers that generate interrupts on a fixed cycle.
Software interrupts are generated by running programs. For example, if an instruction is not completed for some reason, an interrupt may be triggered so that the situation can be handled by the operating system. Some floating-point errors, such as division by zero, are handled by interrupts.
When the program needs to access the hardware device, it will make a “system call”, which is like a function call. In addition to not jumping to the start of the function, it will execute a special instruction to trigger the interrupt and make the execution flow jump to the kernel. The kernel reads the parameters of the system call, performs the requested operation, and then resumes the interrupted process.
8.1 hardware status
Interrupt processing needs the cooperation of hardware and software. When an interrupt occurs, multiple instructions may be running on the CPU, and data is also stored in the registers.
Usually the hardware is responsible for setting the CPU to a consistent state. For example, each instruction should be executed completely or not at all, and there should be no instructions that are executed to half. Moreover, the hardware is also responsible for saving program counters (PCS) so that the kernel can understand where to resume execution.
After that, the interrupt processor is usually responsible for saving the context of the register. To do this, the kernel needs to execute instructions, which modify the data register and bit register. Therefore, this “hardware state” needs to be saved before modification and restored after the interrupted process resumes operation.
Here is the general process of this series of events:
When an interrupt occurs, the hardware saves the program counter in a special register and jumps to the appropriate interrupt processor.
The interrupt processor stores the contents of program counters and bit registers, as well as any intended data registers, in memory.
Interrupt processor runs the code needed to handle the interrupt.
It then restores the contents of the saved registers. Finally, recover the program counter of the interrupted process, which will jump back to the interrupted process.
If this mechanism works properly, the interrupted process usually has no way to know it is an interrupt unless it detects a small change between instructions.
8.2 context switching
Interrupt processors are very fast because they do not need to save the entire hardware state. They only need to hold the registers they intend to use.
However, when an interrupt occurs, the kernel does not always resume the interrupted process. It can choose to switch to other processes, a mechanism called context switching.
Generally, the kernel does not know which register a process will use, so it needs to save all. Also, when it switches to a new process, it may need to clear the data stored in the memory management unit (MMU). And after context switching, it may take some time to load data into the cache for the new process. For these reasons, context switching is relatively slow, with a few thousand cycles or milliseconds.
In a multitasking system, each process is allowed to run for a short period of time, called a “time slice” or “quantum”. During context switching, the kernel will set some hardware counters, which will break at the end of the time slice. When an interrupt occurs, the kernel can switch to another process or allow the interrupted process to continue. This part of the operating system that makes decisions is called a scheduler.
8.3 process life cycle
When a process is created, the operating system assigns the data structure containing the process information to the process, which is called “process control block” (PCB). In other aspects, the PCB tracks the status of the process, including:
Running, if the process is running on a core.
Ready, if a process can but is not running, usually because the number of ready processes is greater than the number of cores.
Blocked, if a process cannot run because it is waiting for future events, such as network communications or disk reads.
Done: if the process has finished running, but with exit status information that has not been read.
Here are some events that can cause process state transitions:
The execution of a running program in a process is similar to
forkSystem call to. At the end of the system call, the new process is usually ready. The scheduler may then restore the original process (the “parent process”) or start the new process (“child process”).
When a process is started or resumed by the scheduler, its state changes from ready to running.
When a process is interrupted and the scheduler has no choice to restore it, its state changes from run to ready.
If a process performs a system call that cannot be completed immediately, such as a disk request, it becomes blocked and the scheduler selects another process.
When an operation similar to a disk request is completed, an interrupt occurs. The interrupt processor knows which process is waiting for a request and changes its state from blocked to ready.
When a process calls
exitThe interrupt processor stores the exit code in the PCB and changes the status of the process to terminate.
As we saw in Section 2.3, there may be hundreds of processes running on a computer, but most of them are usually blocked. In most cases, only a small number of processes are ready or running. When an interrupt occurs, the scheduler determines which process should start or resume.
On workstations or laptops, the primary goal of a scheduler is to minimize response time, that is, computers should respond quickly to user actions. Response time is also important on the server, but the scheduler may also try to maximize throughput, which is the request completed in unit time.
The scheduler usually doesn’t need a lot of information about what the process does, so it makes decisions based on some inspiration:
Processes may be limited by different resources. A process that performs a lot of computation is computationally intensive, that is, its running time depends on how much CPU time it gets. The process of reading data from the network or disk is Io intensive, that is, if the data input and output are faster, it will be faster, but it will not run faster in more CPU time. Finally, programs that interact with users may be blocked most of the time, waiting for user actions. Operating systems can sometimes classify processes based on their past behavior and schedule them accordingly. For example, when an interactive process is no longer blocked, it should run immediately because the user may be waiting for a response. On the other hand, CPU intensive processes that have been running for a long time may not be time sensitive.
If a process is likely to run for a short time, and then a blocking request is issued, it should run immediately for two reasons: (1) if the request takes some time to complete, we should start it as soon as possible; (2) a long-running process should wait for a short-running process, rather than vice versa. As an analogy, suppose you’re making apple pie. The crust takes five minutes to prepare, but then it takes half an hour to cool. The filling takes 20 minutes to prepare. If you prepare the crust first, you can prepare the filling as it cools, and it can be done in 35 minutes. If you prepare the filling first, it will take 55 minutes.
Most schedulers use some form of priority based scheduling, in which each process has a priority that can be adjusted up or down. When the scheduler runs, it selects the highest priority ready process.
Here are some factors that determine process priority:
Processes with higher priority usually run faster.
If a process makes a request and is blocked before the end of the timeslice, it may be IO intensive or interactive, and the priority should be increased.
If a process runs in the whole time slice, it may be a long-running computing intensive program, and the priority should be reduced.
If a task is blocked for a long time and then becomes ready, it should be raised to the highest priority to respond to what is waiting.
If process a is blocked while waiting for process B, for example, if they are connected by pipes, the priority of process B should be raised.
niceAllows processes to reduce (but not increase) their priority and allows programmers to pass explicit information to the scheduler.
For most systems running normal workload, scheduling algorithm has no significant impact on performance. A simple scheduling strategy is good enough.
8.5 real time scheduling
However, scheduling is very important for programs that interact with the real world. For example, programs that read data from sensors and control motors may need to perform repetitive tasks with the minimum frequency and respond to external events with the maximum response time. These requirements are usually expressed as “tasks” that must be completed before the “deadline”.
Scheduling tasks that meet deadlines is called “real time scheduling.”. For some applications, a general operating system similar to Linux can be modified to handle real-time scheduling. These changes may include:
Provides a richer API for controlling the priority of tasks.
Modify the scheduler to ensure that the highest priority process runs for a fixed time.
Reorganize interrupt processors to ensure maximum completion time.
Modify locks and other synchronization mechanisms (discussed in the next chapter) to allow high priority tasks to preempt low priority tasks.
Select a dynamic memory allocation implementation that guarantees the maximum completion time.
For more demanding applications, especially in areas where real-time response is vital, real-time operating systems provide specialized capabilities, often with simpler designs than general-purpose operating systems.