I will not talk about the concept of processes and threads. In short, you remember: the kernel scheduling object is a thread, not a process. In Linux, threads are very special. They do not distinguish between threads and processes. Another process is called a task. Like the author, I am used to running the user spaceProgramIt is called a process, and the program running in the kernel is called a task.
The kernel stores processes in a two-way cyclic linked list called a task list. Each item in the chain list is a structure of the task_struct type and named process descriptor, this structure is defined in include/Linux/sched. the H file contains all information about a specific process.
In Linux, The task_struct structure is allocated through the slab distributor to achieve Object reuse and cache coloring. In kernels earlier than 2.6, task_struct of each process is stored at the end of their Kernel stack. Because the slab distributor is used to dynamically generate task_struct, you only need to create a new structure (struct thread_info) at the bottom of the stack or at the top of the stack, which is defined in ASM/thread_info.h, for more information, see. The thread_info structure in each task is allocated at the end of its kernel stack. In the structure, the task domain stores the actual task_struct pointer to the task.
In the kernel, the access task usually needs to obtain the task_struct pointer to it. In fact, most of the processes in the kernelCodeAll are performed through task_struct. It is particularly important to find the process descriptor of the currently executing process through the current macro. On the x86 system, current shields the last 13 valid bits of the stack pointer to calculate the offset of thread_info. This operation is completed using the current_thread_info function. The Assembly Code is as follows:
Movl $-8192, % eax Andl % ESP, % eax |
Finally, current extracts data from the task domain of thread_info and returns the value of task_struct: current_thread_info ()-> task;
The state field in the process descriptor describes the current state of the process. Every process in the system must be in one of the five processes. What are the running states, blocking states, and the conditions for conversion between them, why? In any operating system book, I speak better than me. If I want to talk about it, I need to talk about it badly, right? Now I am concerned about the problem: What should I do when the kernel needs to adjust the status of a process? In this case, it is best to use the set_task_state (task, state) function, which sets the specified process to the specified state. When necessary, it sets the memory blocking to force other processors to re-sort. (This is generally necessary only in SMP systems) otherwise, it is equivalent to: task-> state = State; in addition, set_current_state (State) and set_task_state (current, State) are equivalent.
The general program is executed in the user space. When a program executes a system call or triggers an exception, it falls into the kernel space. System calls and exception handling programs are clearly defined interfaces for the kernel. Only these interfaces can be used by a process to run in the kernel-All accesses to the kernel must pass through these interfaces.
There is a clear inheritance relationship between Linux processes. All processes are descendants of the INIT process with PID 1, and the kernel starts the INIT process at the final stage of system startup. This process reads the system initialization script and executes other related programs to complete the entire system startup process.
Each process in the system must have a parent process, and each process can have one or more child processes. Since the process has the name of a father and a son, of course it has the meaning of a brother. Each task_struct contains a pointer pointing to its parent process task_struct and is called parent. It also contains a subprocess linked list called children. Therefore, access the parent process: struct task_struct * task = Current-> parent; access the child process as follows:
Struct task_struct * task; Struct list_head * List; List_for_each (list, & Current-> Children ){ Task = list_entry (list, struct task_struct, sibling ); } |
The INIT process descriptor is statically allocated as init_task. Through the INIT process, parent-child process relationship, sibling process relationship, and process descriptor structure above, we can get an amazing fact: you can use this relationship to find any specified process from any process in the system. In addition, there are quite a lot of ways to read the book, and I will not talk about it if there is a lot of content, but what I should point out at the end is, it is time-consuming to traverse all processes in a system with a large number of processes. Therefore, if there is no good reason, never do this.Love requires 10 thousand reasons. I can't see this.
Many operating systems provide process Generation Mechanisms, and Linux is no exception. UNIX is simple: fork () creates a sub-process by copying the current process. The difference between a sub-parent process is PID, ppid, and some resources and statistics. Then the exec () function reads the executable file, loads it into the address space, and executes it. From the above analysis, we can see that the traditional fork () system call directly copies all the resources to the process created by the heart. This method is too simple but efficient. In LinuxCopy-on-write page. This technology principle is: the memory does not copy the whole process address space, but allows the parent process and the child process to share the same copy. Data is copied only when the write is required. Don't you understand? Simply put, the replication of resources only takes place when the data needs to be written. Before that, all resources are shared in read-only mode.
in Linux, fork () is implemented by calling the clone () system (), the parameter flag indicates the resources shared by the parent and child processes. Whether fork () or vfork () ,__ clone (), clone () is called Based on the parameter flag required by each other (). then clone () to call do_fork (). in this case, I want everyone to understand what I mean. The key to the problem lies in do_fork (), which is defined in kernel/fork. c, completed most of the work, the function calls the copy_process () function, and then let the city start to run, copy_precess () function to complete the work is very interesting:
1. call dup_task_struct () to create a new kernel stack for the new process, which is defined in kernel/fork. c file. This function calls copy_process () . Then let the process start running. The name of the function DUP indicates that the sub-process and the parent process descriptor are identical. 2. After checking the newly created sub-process, the number of processes owned by the current user does not exceed the resource limit allocated to him. 3. Now, sub-processes start to differentiate themselves from parent processes. Many members in the process descriptor must be cleared by 0 or set as the initial value. 4. Next, the sub-process status is set to task_uninterruptible to ensure that it will not be put into operation. 5. Call copy_flags () to update the flags member of task_struct. The pf_superpriv flag indicating whether the process has superuser permissions is cleared. Table the process has not yet called the pf_forknoexec flag of the exec function. 6. call get_pid () to obtain a valid PID for the new process. 7. copy or share open files, file system information, and signal processing functions based on the parameter flag passed to clone. Process address space and namespace. generally, these resources are shared by all threads of the given process. Otherwise, these resources are different for each process and therefore are copied here. 8. let the parent process and child process divide the remaining time slices equally 9. finally, run the scan and return a pointer to the sub-process. |
After the above operation, return to the do_fork () function. If the copy_process () function is successful, return. The newly created sub-process is awakened and put into operation. The kernel intentionally selects a sub-process to run first. Generally, sub-processes call the exec () function immediately to avoid additional overhead of copy during write. If the parent process is executed first, it may start to write data to the address space.
After fork is finished, let's talk about his brother-vfork (). The brother is the brother. This is like! The two functions are the same. The difference is that vfork () does not copy the page table items of the parent process. A child process runs in its address space as a separate thread of the parent process. The parent process is blocked until the child process exits or executes exec (), the sub-process cannot write data to the address space. According to the preceding method, we will analyze vfork (), which is implemented by passing a special sign to the clone () system call. The process is as follows:
1. When copy_process is called, The vfor_done member of task_struct is set to null. 2. When do_fork () is executed, if a special flag is given, vfork_done points to a special address. 3. After the sub-process starts to execute, the parent process does not resume execution immediately, but waits until the sub-process sends a signal to it through the vfork_done pointer. 4. When mm_release () is called, this function is used to exit the memory address space of the process. If vfork_done is not empty, a signal is sent to the parent process. 5. Return to do_fork (), the parent process wakes up and returns. |
The successful completion of the above steps means that the parent and child processes will run in their respective address spaces. To be honest, I found that such overhead is reduced, but it is not technically superior.
If the process is the rising sun on the morning of 1980s, the thread is the hot sun at noon. The thread Mechanism provides a set of threads that run in the same program's shared memory address space. The thread mechanism supports concurrent programming technology to share opened files and other resources. If your system is multi-core, the multi-thread technology can ensure real parallel processing of the system. However, there is a strange thing. in Linux, there is no thread concept. All threads in Linux are processed as processes, in other words, there is no special structure orAlgorithmTo indicate the thread. In Linux, the thread is just a process that uses shared resources. Each thread has its own task_struct. Therefore, the thread is essentially a process, but the process can share some resource information with other processes.
In this case, we will understand that the two are in the same category, and the Creation method is the same, but there are always differences. How can this difference be reflected, when we call clone (), we pass some parameter flags to specify the resources to be shared: Clone (clone_vm | clone_fs | clone_files | clone_sighand, 0 ); the result of this Code is similar to that of Fork (), but the parent and child share the address space, file system resources, file descriptors, and signal processing programs. In other words, both the parent process and child process are called threads. That is to say, the clone () parameter determines the clone behavior. What are the specific parameters? I am a lazy and don't want to talk about them anymore.
The front side is mainly about user-level threads. Now let's talk about kernel-level threads. The difference between a kernel thread and a user-level thread is that the kernel thread does not have an independent address space (in fact, its mm pointer is set to null). It can also be scheduled or preemptible. The kernel thread can only be created by other kernel threads. The method is as follows: int kernel_thread (INT (* fN) (void *), void * Arg, unsigned long flags ). A new task is created by passing specific flags parameters like a normal clone () System Call. When the above function returns, the parent process exits and returns the pointer of a sub-thread task_struct. The sub-process starts to run the function pointed to by FN. Arg is a required parameter for running. A special clone flag clone_kernel defines the flags commonly used by kernel threads: clone_fs, clone_files, clone_sighand. Most kernel threads pass this flag to their flags parameters.
Although I have talent, it is better to say it in the book. If I talk about so many creation, birth, and the end of the story, it's a little sentimental. However, the process will end after being sentimental.When a process ends, the resources it occupies must be released and the message must be sent to its parent process. There are many methods to terminate a process. The analysis structure of a process occurs after it calls exit (), which may explicitly call this system call, it may also be implicitly returned from the main function of a program. When a process receives a signal or exception that cannot be processed or ignored, it may also end passively. However, no matter how the process ends, most of the tasks must be completed by do_exit (), which is defined in kernel/exit. C. The specific work is as follows:
1. Set the flag Member in tast_struct to pf_exiting. 2. If the BSD process accounting function is enabled, call acct_process to output accounting information. 3. Call the _ exit_mm () function to discard the mm_struct occupied by processes. If no other processes use them, they will be completely released if they are not shared. 4. Call the sem_exit () function. If the process queues for the IPC signal, it leaves the queue. 5. Call _ exit_files (), _ exit_fs (), _ exit_namespace (), and exit_sighand () to decrease the file descriptor, file system data, process Reference count of namespace and signal processing functions. When the reference count value is 0, it indicates that no process is using these resources, and then the resources are released. 6. Set the task exit code in the exit_code member of task_struct to the Code provided by exit (), or complete any other kernel mechanism. The exit action. 7. Call exit_notify () to send a signal to the parent process, reset the parent process of the child process to another thread or INIT process in the thread group, and set the process status Set to task_zombie. 8. Finally, call schedule () to switch to another process. |
After the above steps, all the resources related to the process are released, so it cannot run again and is in the task_zomble state. All the resources it occupies are the slab that saves the kernel stack of threadk_info and the tast_struct structure. At this time, the only purpose of a process is to provide information to its parent process.
A zombie process cannot run any more. However, the system still keeps its process descriptor so that it can still obtain its information at the end of the Child process. After the parent process obtains information about the terminated child process, the task_struct structure of the child process is released.
We all know that there are a series of wait () functions in Linux that are implemented based on the system call wait4. Its action is to suspend the process that calls it until one of the sub-processes exits. At this time, the function returns the PID of the sub-process that exits. the pointer provided when the function is called contains the exit code when the sub-function exits. When the process descriptor is finally released, release_task () is called. The work is as follows:
1. Call free_uid () to reduce the process usage count of the process owner. 2. Call unhash_process () to delete the process from pidhash, and also delete the process from task_list. 3. If the process is being tracked by ptrace, reset the parent process of the tracing process to its original parent process and delete it from ptrace_list. 4. Finally, call put_task_struct to release the pages occupied by the kernel stack and thread_info structure of the process, and release the slab cache occupied by task_struct. |
At this point, the process Descriptor and the resources exclusive to all processes are all released.
Finally, let's discuss the last question related to the process: everything on the front looks perfect, beautiful, and scary, isn't it? If something goes wrong, the parent process creates a child process, and then the child process exits to release the occupied resources and tells the parent process its own PID and exit status.The problem lies here. Can a child process always exit before the parent process, therefore, you must have an organic mechanism to ensure that the child process can find a new parent process in this case. Otherwise, the process that becomes an orphan will always be frozen when it exits, and the white will consume memory. The solution to this problem is to find a thread in the current thread group as the parent for the sub-process. If this is the case, it will not work (luck is too good, no ).In do_exit (), notify_present () will be called. This function will execute the parent searching process through forget_original_parent. I will not talk about it. I will not go into the details here, I cannot.
Once the system successfully finds and sets a new parent process for the process, there will be no danger of resident dead processes. The INIT process will routinely call wait () to wait for the child process, clear all dead processes related to the instance.