This section discusses process management in Linux, including processes, tasks, kernel threads, scheduling, and context switching.
Processes, Tasks, and Kernel Threads
A task is simply a generic "description of work that needs to be done," whether it is a lightweight thread or a full process.
A thread is the most lightweight instance of a task. Creating a thread in the kernel can be expensive or relatively cheap, depending on the characteristics the thread needs to possess. In the simplest case, a thread shares everything with its parent including text, data, and many internal data structures, possessing only the minimum differences necessary to distinguish the thread from another thread.
A process is a "heavier" data structure in Linux. Several threads can operate within (and share some resources of) a single process, if desired. In Linux, a process is simply a thread with all of its heavyweight characteristics. Threads and processes are scheduled identically by the scheduler.
A kernel thread is a thread that always operates in kernel mode and has no user context. Kernel threads are usually present for a specific function and are most easily handled from within the kernel. They often have the desirable side effect of being schedulable like any other process and of giving other processes a target (by sending a signal) when they need that function to take effect.
Scheduling and Context Switching
Process scheduling is the science (some would say art) of making sure that each process gets a fair share of the CPU. There is always an element of disagreement over the definition of "fair" because the choices the scheduler must make often depend on information that is not apparent.
Process scheduling is covered more thoroughly in later chapters in this book, but it is important to note that it is deemed by many Linux users to be more important to have a scheduler that gets it mostly right all of the time than completely right most of the time—that is, slow-running processes are better than processes that stop dead in their tracks either due to deliberate choices in scheduling policies or outright bugs. The current Linux scheduler code adheres to this principle.
When one process stops running and another replaces it, this is known as a context switch. Generally, the overhead for this is high, and kernel programmers and application programmers try to minimize the number of context switches performed by the system. Processes can stop running voluntarily because they are waiting for some event or resource, or involuntarily if the system decides it is time to give the CPU to another process. In the first case, the CPU may actually become idle if no other process is waiting to run. In the second case, either the process is replaced with another that has been waiting, or the process is given a new timeslice, or period of time in which to run, and is allowed to continue.
Even when processes are being scheduled and run in an orderly fashion, they can be interrupted for other, higher-priority tasks. If a disk has data ready from a disk read, it signals the CPU and expects to have the information taken from it. The kernel must handle this situation in a timely fashion, or it will slow down the disk’s transfer rates. Signals, interrupts, and exceptions are asynchronous events that are distinct but similar in many ways, and all must be dealt with quickly, even when the CPU is already busy.
For instance, a disk with data ready causes an interrupt. The kernel calls the interrupt handler for that particular device, interrupting the process that is currently running, and utilizing many of its resources. When the interrupt handler is done, the currently running process resumes. This in effect steals time from the currently running process, because current versions of the kernel measure only the time that has passed since the process was placed on the CPU, ignoring the fact that interrupts can use up precious milliseconds for that process.
Interrupt handlers are usually very fast and compact and thereby handle and clear interrupts quickly so that the next bit of data can come in. At times, however, an interrupt can require more work than is prudent in the short time desired in an interrupt handler. An interrupt can also require a well-defined environment to complete its work (remember, an interrupt utilizes a random process’s resources). In this case, enough information is collected to defer the work to what is called a bottom half handler. The bottom half handler is scheduled to run every so often. Although the use of bottom halves was common in earlier versions of Linux, their use is discouraged in current versions of Linux.