Chapter 3rd Process Management
3.1 Process
- A process is a program that is in execution (the target code is stored on a storage medium), but the process is not confined to an executable code. Typically processes also include other resources, such as open files, pending signals, kernel internal data, processor state, one or more memory-mapped address spaces, and one or more execution threads. Of course, the data segments used to hold the global variables are, in fact, the real-time results of the executing program code, and the kernel needs to manage all the details efficiently and transparently.
- An executing thread, called a thread, is an object that is active in the process, each with a separate program counter, a process stack, and a set of process registers, the kernel dispatches the thread, not the process, and in a traditional Linux system, a process contains only one thread, but now the system, multithreaded programs that contain multiple threads are commonplace. The thread implementation of a Linux system is very special: it does not differentiate between threads and processes, and for Linux, threads are just a special process.
- In modern operating systems, processes provide two virtual mechanisms: virtual processors and virtual memory. Interestingly, note that virtual memory can be shared between threads, but each has its own virtual processor.
- In the modern Linux kernel, fork () is actually implemented by the clone () system call.
3.2 Process descriptor and task structure
- The kernel stores the list of processes in a two-way circular chain table called the task queue.
- Each item in the list is a structure of type task_struct, called the process descriptor, which is defined in the <linux/sched.h> file.
- The process descriptor contains all the information for a specific process.
- The task_struct is relatively large, and it is approximately 1.7KB on 32-bit machines. But if you consider that the structure contains all the information that the kernel needs to manage a process, it is quite small in size. The data contained in the process descriptor can fully describe an executing program: It opens the file, the process's address space, the pending signal, the status of the process, and more information.
3.2.1 Assigning process descriptors
- Linux allocates the TASK_STRUCT structure through the slab allocator, which achieves object reuse and cache coloring. This is done so that the hardware architecture that has fewer registers like x86 can be used to estimate his position as long as the stack pointer is passed.
- The THREAD_INFO structure of each task is allocated at the end of his kernel stack.
3.2.2 Process Descriptor Storage
- The kernel identifies each process through a unique process identity value or PID, and the kernel stores the PID of each process in their respective process descriptors.
- This maximum value is important because it is actually the maximum number of simultaneous processes allowed in the system, although 32768 is sufficient for a typical desktop system, but a larger server may require more processes, the smaller the value, the faster the loop, the larger the value of the process is running late than the smaller process. This, however, undermines this principle, and if it does, it can be done without consideration for compatibility with older systems, with the system administrator raising the cap by modifying it.
- In the kernel, access tasks often require pointers to their task_struct, and in fact, most of the speed of processing process descriptors in the kernel is particularly important. The hardware architecture is different, the implementation of this macro is different, it must be for the specific hardware architecture to do processing, and some hardware architecture can come up with a special register to hold a pointer to the current process task_struct, to speed up access.
3.2.3 Process Status
- The state field in the process descriptor describes the current status of the process.
- Five process states: Running, interruptible, non-disruptive, process tracked by other processes, stop.
3.2.4 Setting the current process state
- The kernel needs to adjust the state of a process often: set_task_state (task,state) function
3.2.5 Process Context
- Executable code is an important part of the process. The code is executed from an executable file loaded into the address space of the process. The general program executes in user space. When a program invokes a system call (see Chapter 5th) or triggers an exception, it falls into kernel space. At this point, we call the kernel "execute on behalf of the process" and in the context of the process. The current macro is valid in this context. Unless a process with a higher priority in this gap needs to be executed and adjusted by the Scheduler, program recovery will continue in user space when the kernel exits.
- System calls and exception handlers are explicitly defined interfaces to the kernel. The process only passes through these interfaces to get into kernel execution-all access to the kernel must pass through these interfaces.
3.2.6 Process Family tree
- There is an obvious inheritance between the processes of the Unⅸ system, as well as in Linux systems. All processes are descendants of the init process with PID 1. The kernel initiates the INIT process at the last stage of system startup. The process reads the initialization script of the system and executes other related programs, and finally completes the system startup process.
3.3.1 Write-time copy
- Traditional fork () system calls replicate all resources directly to the newly created process, which is too simple and inefficient because the data it copies may not be shared and, worse, if the new process intends to execute a new image immediately, all copies will be wasted. Linux fork () is implemented using a write-time copy page, which is a technique that can postpone or even eliminate copy data. The kernel does not replicate the entire process address space at this time, but instead lets the parent process and child processes share the same copy.
- Data is copied only when it needs to be written, so that each process has its own copy, that is, the replication of the resource only occurs when it needs to be written, and before that, it is only shared in read-only mode. This technique allows copies of pages on the address space to be deferred until the actual write occurs, and they do not have to be duplicated if the page is not written at all.
- The actual cost of fork () is to copy the page table of the parent process and create a unique process descriptor for the child process. In general, the process is created immediately after the execution of an executable file, this optimization can avoid copying a large number of data that is not used at all (often contains dozens of trillion of data in the address space) due to the Unix emphasis on the ability of the process to execute quickly, so this optimization is very important.
3.3.2 Fork ()Work done by Copy_process ():
- 1) Call Dup_task_struct () to create a kernel stack, thread_info structure, and task_struct for the new process with the same values as the current process, at which point the child process and the parent process descriptor are identical.
- 2) Check to make sure that the number of processes currently owned by the current user does not exceed the limit of resources allocated to it after the new child process is created
- 3) The subprocess begins to differentiate itself from the parent process, and many of the members within the process descriptor are cleared 0 or set as the initial value, and the process descriptor members that are not inherited are mainly statistics, and most of the data in the task_struct remains unmodified
- 4) The status of the child process is set to task_uninterruptible to ensure that it is not put into operation
- 5) copy_process () call Copy_flags to update the flags member of the TASK_STRUCT
- 6) Call Alloc_pid () to assign a valid PID to the new process
- 7) copy_process () copies or shares open files, file system information, signal processing functions, process address spaces, and namespaces according to the parameter flags passed to clone (), and in general, these resources are shared by all threads of a given process; These resources are different for each process and are therefore copied here.
3.3.3 vfork ()
3.4 Implementation of threads in Linux
- Threading mechanism is an abstract concept commonly used in modern programming technology, which provides a group thread that runs in the same-program shared memory address space, which can also share open files and other resources, the threading mechanism supports concurrent programming techniques, and it also guarantees true parallel processing on multiprocessor systems.
- Linux implements threading mechanism is very unique, from the kernel point of view, it does not have the concept of threading, Linux has all the threads as a process to implement, the kernel does not prepare a special scheduling algorithm or define a special data structure to characterize the thread, instead, the thread is only considered- A process that shares some resources with other processes, each with its own unique task_struct, so in the kernel it looks like a normal process (only threads and some other processes share certain resources, such as address space).
3.4.1 Creating Threads
- The creation of a process is similar to the creation of a normal process, except that it is necessary to pass some parameter flags when invoking clone () to indicate the resources that need to be shared.
3.4.2 Kernel thread
- The kernel often needs to perform some operations in the background, which can be done through kernel threads-a standard process that runs independently of the kernel space. The difference between a kernel thread and a normal process is that the kernel thread does not have a separate address space. They run only in kernel space and never switch user space, and kernel processes, like normal processes, can be scheduled or preempted.
- Linux does take some tasks to the kernel threads, such as flush and ksofirqd are obvious examples, running the PS-EF command on the machine with the Linux system, you can see the kernel thread, there are many! These threads are created by other kernel threads at system startup, and in fact, kernel threads can only be created by other kernel threads, which are handled automatically by the kernel thread from the Kthreadd kernel process, and the interface is declared in
3.5 Process End
- When a process is terminated, the kernel must release the resources it occupies and inform its parent process of this misfortune.
3.5.1 Deleting a process descriptor
- Call the Release_task function.
3.5.2 The dilemma caused by the orphan process
- If the parent process exits before the child process, there must be a mechanism to ensure that the child process can find a new father otherwise these orphaned processes will always be in a zombie state at the time of exiting, consuming memory for nothing. The previous section has already hinted at this problem, and the workaround is to have the child process find a thread within the current thread group as the father, and if not, let Init do their parent process.
3.6 Summary
- In this chapter, we examine the core concepts in the operating system-the process, why we are so important, and the relationship between processes and threads, and then discuss the general nature of the process, and then discuss how Linux stores and represents processes, how to create processes, and how to load new execution images into address spaces. How to represent the hierarchical relationship of a process, how the parent process collects information about its descendants, and how the process ultimately dies.
- Process is a very basic, very critical abstraction, located at the heart of every modern operating system, and the ultimate reason we have an operating system (to run programs).
"Linux kernel Design and implementation" Chapter III reading notes