Linux中的特殊进程:idle进程(0号进程)、init进程(1号进程,被systemd 取代 )、kthreadd进程(2号进程)

1. Linux中的三个特殊进程:

idle进程,或者也称为swapper进程。:

  • 该进程是Linux中的第一个进程(线程),PID为0;

  • idle进程是init进程和kthreadd进程(内核线程)的父进程;

init进程:

  • init进程是Linux中第一个用户空间的进程,PID为1;

  • init进程是其他用户空间进程的直接或间接父进程;

需要注意的是,在新版本的centos系统中,已经使用 systemd 取代 init 作为1号进程

kthreadd(内核线程):

  • kthreadd线程是内核空间其他内核线程的直接或间接父进程,PID为2;

  • kthreadd线程负责内核线程的创建工作;

2. idle进程、init进程、kthreadd进程的创建

kthreadd进程是在内核初始化start_kernel()的最后rest_init()函数中,由0号进程(swapper进程)创建了两个进程:

  • init进程(PID = 1, PPID = 0)
  • kthreadd进程(PID = 2, PPID = 0)

在start_kernel函数中调用了rest_init函数,在rest_init函数中创建了kernel_init、kthreadd内核线程:

rest_init是idle进程调用的,因此 idle进程 是所有进程的最上层ppid,即0号进程

 

noinline void __ref rest_init(void)
{
    
    
	struct task_struct *tsk;
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	pid = kernel_thread(kernel_init, NULL, CLONE_FS);                 //创建init进程,入参kernel_init是进程的执行体,类似java线程的run函数
	/*
	 * Pin init on the boot CPU. Task migration is not properly working
	 * until sched_init_smp() has been run. It will set the allowed
	 * CPUs for init to the non isolated CPUs.
	 */
	rcu_read_lock();
	tsk = find_task_by_pid_ns(pid, &init_pid_ns);
	set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
	rcu_read_unlock();

	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);       //创建kthreadd进程,入参kthreadd是进程的执行体,类似java线程的run函数
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();

	/*
	 * Enable might_sleep() and smp_processor_id() checks.
	 * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
	 * kernel_thread() would trigger might_sleep() splats. With
	 * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
	 * already, but it's stuck on the kthreadd_done completion.
	 */
	system_state = SYSTEM_SCHEDULING;

	complete(&kthreadd_done);                   // 等待kthreadd创建完毕

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);
}
 

3 kthreadd进程

insert image description here
kthreaddIt is the No. 2 process of Linux. This process is very important in the Linux kernel. It is the parent process or ancestor process of other kernel threads (this can be seen from the process with PPID 2 above. These important threads include kworker, kblockd, khugepaged... ), that is, other processes are created by process No. 2, and process No. 2 polls the task queue of the created process. The following will introduce the kthreadd process slowly.

3.1 Start of kthreadd process

kthreadd is the execution body of the process, let's see what this process does? :

int kthreadd(void *unused)
{
    
    
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_FLAG_KTHREAD));   /*允许kthreadd在任意cpu上执行*/
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	for (;;) {
    
    
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list)) /* 判断内核线程链表是否为空 */
			schedule(); /* 若没有需要创建的内核线程,进行一次调度,让出cpu */
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
    
        /*依次取出任务*/
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);       /*从任务列表中摘除*/
			spin_unlock(&kthread_create_lock);
                        /* 只要kthread_create_list不为空,就根据表中元素创建内核线程 */
			create_kthread(create);       //创建子进程

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}
            

It can be seen from the above code that kthreaddthe task of the process is 不断轮询to wait for the thread to be created. If the task queue is empty, the thread will actively give up the cpu (after calling the schedule, the thread will give up the cpu, and the thread will sleep): if it is not empty, then Take out tasks from the task queue in turn, and then create the corresponding thread. And so on and on forever...

3.2 kthreadd process creates child processes

From the previous chapter, we know that the kthreadd process is responsible for polling the queue and creating child processes, so let's see how to create child processes

To create a child process, the create_kthread function will be called. In the create_kthread function, a new process will be created by calling the kernel_thread function, and the execution function of the new process is kthread (similar to the run method of java thread)

static void create_kthread(struct kthread_create_info *create)
{
    
    
	int pid;
 
#ifdef CONFIG_NUMA
	current->pref_node_fork = create->node;
#endif
	/* We want our own signal handler (we take no signals by default). */
	pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);/*开始创建线程,会阻塞*/
	if (pid < 0) {
    
    
		/* If user was SIGKILLed, I release the structure. */
		struct completion *done = xchg(&create->done, NULL);
 
		if (!done) {
    
    
			kfree(create);
			return;
		}
		create->result = ERR_PTR(pid);
		complete(done);
	}
}

kernel_threadThe interface has just rest_initbeen encountered in the interface, and the kernel is the init process and kthreadd process created through the kernel_thread interface. Here it is used again to create a new thread, and the new thread execution body is unified as kthead. Let's take a look at the content of the kthread function:

static int kthread(void *_create)
{
    
    
	/* Copy data: it's on kthread's stack */
	struct kthread_create_info *create = _create;
	int (*threadfn)(void *data) = create->threadfn;
	void *data = create->data;
	struct completion *done;
	struct kthread *self;
	int ret;
 
	self = kzalloc(sizeof(*self), GFP_KERNEL);
	set_kthread_struct(self);
 
	/* If user was SIGKILLed, I release the structure. */
	done = xchg(&create->done, NULL);
	if (!done) {
    
    
		kfree(create);
		do_exit(-EINTR);
	}
 
	if (!self) {
    
    
		create->result = ERR_PTR(-ENOMEM);
		complete(done);
		do_exit(-ENOMEM);
	}
 
	self->data = data;
	init_completion(&self->exited);
	init_completion(&self->parked);
	current->vfork_done = &self->exited;
 
	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(done);
	schedule();/*睡眠,一直。直到被唤醒*/
 
	ret = -EINTR;
	if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {
    
    /*唤醒后如果此线程不需要stop*/
		cgroup_kthread_ready();
		__kthread_parkme(self);
		ret = threadfn(data);/*执行指定的函数体*/
	}
	do_exit(ret);
}

It can be seen from the kthread function that after the new thread is successfully created, it will sleep (use the schedule to actively give up the CPU and sleep) until someone wakes it up (wake_up_process); after the thread is woken up and does not need to stop, the specified function will be executed body ( threadfn(data) ).

3.3 Summary of the workflow of kthreadd

insert image description here
The above figure shows the basic flow of the kernel to create threads:

① A certain thread A (the upper left circle) calls the kthread_create function to create a new thread, and blocks after the call; kthread_create will encapsulate the task and add it to the work queue monitored by kthreadd;

②The kthreadd process detects that there are tasks in the work queue, then ends the dormant state, creates a thread by calling the create_kthread function, and finally calls kernel_thread --> do_fork to create a thread, and the new thread execution body is kthead

③ After the new thread is successfully created, execute kthead, and the kthreadd thread will continue to sleep and wait for the creation of a new process;

④After thread A calls kthread_create and returns, it wakes up the newly created thread through wake_up_process(pid) at an appropriate time

⑤ The newly created thread is awakened in the kthead execution body, and it is detected whether stop is required, and when stop is not required, the thread execution body specified by the user is executed. (The thread execution body has changed: first execute the default kthead, and then the user-specified threadfn, of course, it is also possible to directly execute do_exit to exit the thread)

4. init process

For more information on the role of the init process, see [linux] Detailed explanation of the init process

The init process is created by idle through kernel_thread. After the kernel space is initialized, the init program is loaded

Here we mainly explain the init process. The init process is created by the 0 process and completes the initialization of the system. It is the ancestor process of all other user processes in the system.

All processes in Linux are created and run by the init process. First, the Linux kernel is started, then the init process is started in user space, and then other system processes are started. After the system startup is complete, init will become a daemon process to monitor other processes in the system.

Therefore, the init process is one of the indispensable programs in the operation of the Linux system. If the kernel cannot find the init process, it will try to run /bin/sh. If the operation fails, the system startup will also fail.

4.1 Start of the init process

From the previous article, we know that process No. 1 is created and the body of the kernel_init method is passed in. Let's see what we have done:

static int __ref kernel_init(void *unused)
{
    
    
	int ret;

	kernel_init_freeable();             //kernel_init_freeable函数中就会做各种外设驱动的初始化
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	kprobe_free_init_mem();
	ftrace_free_init_mem();
	free_initmem();
	mark_readonly();

	/*
	 * Kernel mappings are now finalized - update the userspace page-table
	 * to finalize PTI.
	 */
	pti_finalize();

	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	rcu_end_inkernel_boot();

	do_sysctl_args();

	if (ramdisk_execute_command) {
    
    
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
    
    
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}

	if (CONFIG_DEFAULT_INIT[0] != '\0') {
    
    
		ret = run_init_process(CONFIG_DEFAULT_INIT);
		if (ret)
			pr_err("Default init %s failed (error %d)\n",
			       CONFIG_DEFAULT_INIT, ret);
		else
			return 0;
	}
/* 调用系统根目录下的init文件,会在以下几个路径中加载init进程 */
/* 在Android源码中,init进程源码的位置为:https://www.androidos.net.cn/android/10.0.0_r6/xref/system/core/init/init.cpp */
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;        

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}

  • The initialization of various peripheral drivers will be done in the kernel_init_freeable function
  • The main job is to call the init executable file, that is, the program that runs automatically after booting, and the Init configuration file will be automatically read in the agreed path

So far, process No. 1 has been successfully created, and the init executable file has also been successfully executed.

4. Summary

Summarize:

  • The first process started by linux is process 0, which is statically created
  • After process 0 starts, two processes will be created one after another, namely process 1 and process 2.
  • Process No. 1 will eventually call the init executable file, and the init process will eventually create all application processes.
  • Process No. 2 will be responsible for creating all kernel threads in the kernel
  • So process 0 is the parent process of processes 1 and 2; process 1 is the parent process of all user-mode processes; process 2 is the parent process of all kernel threads.

We can observe this phenomenon in detail through the ps command:

root@ubuntu:zhuxl$ ps -eF
UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          1      0  0 56317  5936   2 Feb16 ?        00:00:04 /sbin/init
root          2      0  0     0     0   1 Feb16 ?        00:00:00 [kthreadd]

The above shows clearly: the process with PID=1 is init, and the process with PID=2 is kthreadd. And the parent process PPID=0 of the two of them, that is, process 0.


Look at it again, all kernel linear PPI=2, that is, the parent process of all kernel linear is the kthreadd process.

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          4      2  0     0     0   0 Feb16 ?        00:00:00 [kworker/0:0H]
root          6      2  0     0     0   0 Feb16 ?        00:00:00 [mm_percpu_wq]
root          7      2  0     0     0   0 Feb16 ?        00:00:10 [ksoftirqd/0]
root          8      2  0     0     0   1 Feb16 ?        00:02:11 [rcu_sched]
root          9      2  0     0     0   0 Feb16 ?        00:00:00 [rcu_bh]
root         10      2  0     0     0   0 Feb16 ?        00:00:00 [migration/0]
root         11      2  0     0     0   0 Feb16 ?        00:00:00 [watchdog/0]
root         12      2  0     0     0   0 Feb16 ?        00:00:00 [cpuhp/0]
root         13      2  0     0     0   1 Feb16 ?        00:00:00 [cpuhp/1]
root         14      2  0     0     0   1 Feb16 ?        00:00:00 [watchdog/1]
root         15      2  0     0     0   1 Feb16 ?        00:00:00 [migration/1]
root         16      2  0     0     0   1 Feb16 ?        00:00:11 [ksoftirqd/1]
root         18      2  0     0     0   1 Feb16 ?        00:00:00 [kworker/1:0H]
root         19      2  0     0     0   2 Feb16 ?        00:00:00 [cpuhp/2]
root         20      2  0     0     0   2 Feb16 ?        00:00:00 [watchdog/2]
root         21      2  0     0     0   2 Feb16 ?        00:00:00 [migration/2]
root         22      2  0     0     0   2 Feb16 ?        00:00:11 [ksoftirqd/2]
root         24      2  0     0     0   2 Feb16 ?        00:00:00 [kworker/2:0H]

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root        362      1  0 21574  6136   2 Feb16 ?        00:00:03 /lib/systemd/systemd-journald
root        375      1  0 11906  2760   3 Feb16 ?        00:00:01 /lib/systemd/systemd-udevd
systemd+    417      1  0 17807  2116   3 Feb16 ?        00:00:02 /lib/systemd/systemd-resolved
systemd+    420      1  0 35997   788   3 Feb16 ?        00:00:00 /lib/systemd/systemd-timesyncd
root        487      1  0 43072  6060   0 Feb16 ?        00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root        489      1  0  8268  2036   2 Feb16 ?        00:00:00 /usr/sbin/cron -f
root        490      1  0  1138   548   0 Feb16 ?        00:00:01 /usr/sbin/acpid
root        491      1  0 106816 3284   1 Feb16 ?        00:00:00 /usr/sbin/ModemManager
root        506      1  0 27628  2132   2 Feb16 ?        00:00:01 /usr/sbin/irqbalance --foreground

The parent process PPID of all user-mode processes is 1, that is, process No. 1 is their parent process.

reference

Special processes in Linux: idle process, init process, kthreadd process
Linux kernel learning No. 2 process kthreadd
Linux No. 0 process, No. 1 process, No. 2 process

おすすめ

転載: blog.csdn.net/m0_45406092/article/details/130657532