Date |
Kernel version |
Architecture |
author |
GitHub |
CSDN |
2016-06-02 |
Linux-4.5 |
X86 & Arm |
Gatieme |
Linuxdevicedrivers |
Linux process management and scheduling--a description of the process |
Why kernel threads need kernel threads
The Linux kernel can be viewed as a service process (managing hardware and software resources, responding to the various reasonable and unreasonable requests of user processes).
The kernel requires multiple execution streams to be parallel, and supporting multithreading is necessary to prevent possible blocking.
A kernel thread is a clone of the kernel, and a single clone can handle a specific thing. Kernel thread Scheduling is the responsibility of the kernel, while one kernel thread is in a blocking state without affecting other kernel threads because it is the basic unit of dispatch.
This is not the same as the user thread. Because kernel threads only run in the kernel state
Therefore, it can only use address space greater than page_offset (which is 3G on the traditional x86_32).
Kernel Threading Overview
Kernel threads are processes that are initiated directly by the kernel itself. The kernel thread is actually delegating the kernel function to a separate process, which is executed "in parallel" with other processes in the kernel. Kernel threads are often referred to as kernel daemons .
They perform the following tasks
Periodically synchronizing a modified memory page with a page source block device
Write to swap if memory pages are seldom used
Manage delay actions, such as process 2nd to take over the creation of kernel processes
Implementing the file System transaction log
There are two main types of kernel threads
The thread waits until it is started, until the kernel requests the thread to perform a specific operation.
Threads start and run at periodic intervals, detecting the use of specific resources and taking action when the usage exceeds or falls below the preset limit.
Kernel threads are generated by the kernel itself, which is characterized by
They are executed in the CPU's pipe state, not the user state.
They can only access the kernel portion of the virtual address space (all addresses above task_size), but cannot access the user space
Kernel thread's Process descriptor task_struct
The Task_struct process descriptor contains two fields related to the process address space mm, active_mm,
struct task_struct{ // ... struct mm_struct *mm; struct mm_struct *avtive_mm; //...};
The total virtual address space of the system on most computers is divided into two parts: the virtual address space for the user program Access and the kernel space for kernel access. Each time the kernel performs a context switch, the user layer portion of the virtual address space is toggled so that the currently running process matches, and the kernel space does not release the switch.
For normal user processes, mm points to the user space portion of the virtual address space, and MM is NULL for kernel threads.
This optimization provides some leeway to follow the so-called lazy tlb processing (lazy tlb handing). ACTIVE_MM is primarily used for optimization, because kernel threads are not associated with any particular user-level process, and the kernel does not need to be switched to the user layer portion of the virtual address space, preserving the old settings. Because the kernel thread may be any user layer process in the execution, so the content of the user space is essentially random, kernel threads must not modify its contents, so the MM is set to NULL, and if the switch out of the user process, the kernel of the original process of the mm stored in the new kernel thread active_mm, Because at some point the kernel must know what the user space currently contains.
Why is a process without a mm pointer called an inert TLB process?
If the process running after the kernel thread is the same as before, in this case the kernel does not need to modify the user-space Address table. The information in the address translation fallback buffer (that is, TLB) is still valid. It is only necessary to switch (and to purge TLB data) only after the kernel thread is executing a process that is different from the previous user-level process.
The difference between a kernel thread and a normal process is that the kernel thread does not have a separate address space, the MM pointer is set to NULL, it only runs in kernel space, never switches to user space, and, like normal processes, can be dispatched or preempted.
The creation of kernel threads creates the evolution of the kernel threading interface
Kernel threads can be implemented in two ways:
Ancient interfaces Kernel_create and Daemonize
Passing a function to Kernel_thread creates and initializes a task, which is then responsible for helping the kernel call Daemonize has been converted to the kernel daemon, daemonize then completes some column operations, such as the function frees all the resources of its parent process. Otherwise these resources will remain locked until the thread ends. The reception of the blocking signal, using INIT as the parent process of the daemon
The more current methods of kthead_create and Kthread_run
The more common method of creating a kernel is the auxiliary function kthread_create, which creates a new kernel thread. The initial thread is stopped and needs to be started with wake_up_process.
With Kthread_run, unlike Kthread_create, which wakes up a new thread immediately after it is created, it is essentially creating a kernel thread with kthread_create and then waking it up through wake_up_process
The birth of the 2nd process Kthreadd
Early Kernel_create and Daemonize interfaces
In the early kernel, kernel_create and daemonize interfaces were provided, but the mechanism was complex and gave all tasks to the kernel to complete.
But this mechanism is inefficient and cumbersome to plug all the operations to the kernel, we create kernel threads is not originally meant to share the work of the kernel, reduce the cost of the kernel
Workqueue mechanism
As a result, after linux-2.6, more convenient interfaces kthead_create and Kthread_run are provided, while the creation of kernel threads is deferred and presented to a task queue workqueue, see http://lxr.linux.no/ LINUX+V2.6.13/KERNEL/KTHREAD.C#L21,
The workqueue mechanism in Linux is designed to simplify the creation of kernel threads. Instead of actually creating a kernel thread through kthread_create, you insert the creation work create task into the work queue helper_wq, and then call Workqueue's interface to create the kernel thread. and the number of threads can be created based on the number of current system CPUs, so that threading transactions can be parallelized. Workqueue is a simple and effective mechanism in the kernel, and he clearly simplifies the creation of kernel daemon and facilitates user programming.
The Work queue (Workqueue) is another form of delaying work. The work queue can be pushed back into a kernel thread to execute, that is, the lower part can be executed in the context of the process. The most important thing is that the work queue is allowed to be re-dispatched or even asleep.
For specific information, see
How Linux Workqueue Works
Process Kthreadd No. 2nd
But this approach still doesn't look graceful enough, why don't we just hand over the work of creating kernel threads to a special kernel thread?
So linux-2.6.22 introduced the kthreadd process and then evolved to process 2nd, which was created with process 1th during system initialization (certainly through Kernel_thread), see rest_init function, And then evolve into a real builder to create kernel threads, see KTHREADD and Kthreadd functions, which loop through the query work list static list_head (kthread_create_list), whether there are kernel threads that need to be created, What we do with kthread_create is simply to add a create task to the kernel thread task queue kthread_create_list and then wake up the Kthreadd process to perform a true create operation
The kernel thread appears in the list of system processes, but the process name command is surrounded by square brackets in the output of PS to differentiate it from normal processes.
As shown, we can see that all kernel threads in the system are identified with [] and that the parent process ID of these processes is 2, and that the parent process of process Kthreadd 2nd is process number No. 0
Using Ps-eo Pid,ppid,command
Kernel_thread
Kernel_thread is the most basic interface for creating kernel threads, which creates a process by passing a function directly to the kernel, creates a process that runs in kernel space, and shares the kernel virtual address space with other process threads
Kernel_thread's implementation has undergone many changes.
Earlier Kernel_thread performed a lower-level operation, directly creating the task_struct and initializing it,
After the introduction of the Kthread_create and Kthreadd 2nd processes, the implementation of Kernel_thread is also implemented by a unified _do_fork (or earlier do_fork) hosting
Early implementations
In the early kernel, Kernel_thread was not implemented using a unified do_fork or _do_fork-encapsulated interface, but with more underlying details
See
http://lxr.free-electrons.com/source/kernel/fork.c?v=2.4.37#L613
We can see that it internally calls the more underlying arch_kernel_thread to create a thread
Arch_kernel_thread
For specific implementations, see the
Http://lxr.free-electrons.com/ident?v=2.4.37;i=arch_kernel_thread
However, threads created in this way are not suitable for running, so the kernel provides the Daemonize function, which is declared in Include/linux/sched.h
// http://lxr.free-electrons.com/source/include/linux/sched.h?v=2.4.37#L800externvoid daemonize(void);
Defined in KERNEL/SCHED.C
http://lxr.free-electrons.com/source/kernel/sched.c?v=2.4.37#L1326
Perform the following operations primarily
The function frees all the resources of its parent process, otherwise the resources are locked until the thread ends.
Reception of blocking signals
Using Init as the parent process for daemons
We can see that this interface is used in many parts of the early kernel, such as
You can see
Http://lxr.free-electrons.com/ident?v=2.4.37;i=daemonize
We're going to have so many kernel_thread, but we don't advocate using it because this is the bottom-up interface for creating kernel threads, and using Kernel_thread to perform a lot of operations in the kernel, although the cost of creation is already very small, But for the performance of the Linux kernel is not tolerable
So we can only say that Kernel_thread is an old interface, and some parts of the kernel are still using this method, passing a function directly to the kernel to create kernel threads
Implementation of the new version
So after the linux-3.x, there is a better realization, that is
Delaying the creation of the kernel, handing the creation of kernel threads to a kernel thread, i.e. Kthreadd 2nd process
But before Kthreadd was created, we could only create it by kernel_thread this way,
The implementation of the Kernel_thread is also changed to be implemented by _do_fork (Do_fork in the early kernel), see KERNEL/FORK.C
pid_t kernel_thread(int (*fn)(voidvoidunsignedlong flags){ return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsignedlong)fn, (unsignedlong0);}
Kthread_create
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, constchar namefmt[], ...);#define kthread_create(threadfn, data, namefmt, arg...) \ ##arg)
The more common method of creating a kernel is the auxiliary function kthread_create, which creates a new kernel thread. The initial thread is stopped and needs to be started with wake_up_process.
Kthread_run
/** * kthread_run-create and wake a thread. * @threadfn: The function to run until signal_pending (current). * @data: Data ptr for @threadfn. * @namefmt: Printf-style name for the thread. * * Description:convenient wrapper for Kthread_create () followed by * Wake_up_process (). Returns the Kthread or Err_ptr (-ENOMEM). */#define Kthread_run (THREADFN, data, namefmt, ...) \({structTask_struct *__k = kthread_create (THREADFN, data, NAMEFMT,# # __va_args__); \ if(!is_err (__k)) wake_up_process (__k); __k; })
With Kthread_run, unlike Kthread_create, which wakes up a new thread immediately after it is created, it is essentially creating a kernel thread with kthread_create and then waking it up through wake_up_process
Kernel thread exit
Once the thread is started, it runs until the thread is actively called by the Do_exit function, or another process calls the Kthread_stop function to end the thread's run.
int kthread_stop(struct task_struct *thread);
Kthread_stop () sends a signal to the thread.
If the thread function is working on a very important task, it will not be interrupted. Of course, if the thread function never returns and does not check the signal, it will never stop.
The target thread must not exit when executing kthread_stop, otherwise it will oops. The reason is easy to understand that when the target thread exits, its corresponding task structure becomes invalid, and the Kthread_stop reference to the invalid task structure makes an error.
To avoid this situation, you need to ensure that the thread does not exit, as shown in the code:
thread_func(){ // do your work here // wait to exit while(!thread_could_stop()) { wait(); }}exit_code(){ kthread_stop(_task); //发信号给task,通知其可以退出了}
This exit mechanism is gentle, all under the control of the Thread_func (), the thread in the exit can calmly release resources, rather than inexplicably being "assassinated".
Linux kernel threads kernel thread details--linux process management and scheduling (10)