天天看點

Linux核心學習之2号程序kthreadd一、Linux的2号程序二、kthreadd程序的建立三、kthreadd程序執行體四、create_kthread函數五、小結

Linux核心學習之2号程式kthreadd一、Linux的2号程式二、kthreadd程式的建立三、kthreadd程式執行體四、create_kthread函數五、小結
  • Author       : Toney
  • Email         : [email protected]
  • Date          : 2020.12.04
  • Copyright : 未經同意不得轉載!!!
  • Version    : Linux-4.19.y
  • Reference:https://www.linux.org/

目錄

一、Linux的2号程序

二、kthreadd程序的建立

三、kthreadd程序執行體

四、create_kthread函數

五、小結

一、Linux的2号程序

說起Linux程序,學習Linux系統的大部分人都知道1号程序為init程序,人們就是這樣隻記得第一,卻很少人記得第二。(經典問題:第一個宇航員是加加林,第二呢? 世界最高峰為珠穆朗瑪峰,第二、第三、第四呢?…經典翻車問題)。下面直接看看2号程序:

UID         PID   PPID  C STIME TTY          TIME CMD

root          1      0  0 11月21 ?      00:00:17 /sbin/init splash

root          2      0  0 11月21 ?      00:00:00 [kthreadd]

root          3      2  0 11月21 ?      00:00:00 [rcu_gp]

root          4      2  0 11月21 ?      00:00:00 [rcu_par_gp]

root          6      2  0 11月21 ?      00:00:00 [kworker/0:0H-kb]

root         10      2  0 11月21 ?      00:00:15 [ksoftirqd/0]

root         24      2  0 11月21 ?      00:00:00 [khugepaged]

root         71      2  0 11月21 ?      00:00:00 [kblockd]

root         78      2  0 11月21 ?      00:00:00 [watchdogd]

root        138      2  0 11月21 ?      00:00:00 [kworker/u257:0]

root        151      2  0 11月21 ?      00:00:00 [charger_manager]

root        228      2  0 11月21 ?      00:00:01 [irq/16-vmwgfx]

root        229      2  0 11月21 ?      00:00:00 [scsi_tmf_7]

root        230      2  0 11月21 ?      00:00:00 [ttm_swap]

是的,kthreadd就是Linux的2号程序,這個程序在Linux核心中非常的重要,他是其他核心線程的父程序或者祖先程序(這個可以通過上面的PPID為2的程序可以看出,這些重要線程包括kworker、kblockd、khugepaged…),下面便慢慢來介紹下kthreadd程序。

二、kthreadd程序的建立

kthreadd程序是在核心初始化start_kernel()的最後rest_init()函數中,由0号程序(swapper程序)建立了兩個程序:

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

核心中的其他線程PPID都是2, 說明這些線程都是由kthreadd程序建立的,是以可以說kthreadd程序負責核心線程的建立、維護等工作,是其他線程的基礎。實際上也确實如此,kthreadd就是專門負責核心線程管理工作的。

static noinline void __ref rest_init(void)

{

     struct task_struct *tsk;

     int pid;

     … …

     rcu_scheduler_starting();

     pid = kernel_thread(kernel_init, NULL, CLONE_FS);

     … …

     pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

     … …

     complete(&kthreadd_done);

}

由于Linux-2.6.12版本中rest_init函數中隻建立了init程序,而kthreadd程序的建立我沒有找到,是以這裡引用了Linux-4.19版本中的rest_init函數部分代碼。這個版本中清楚的顯示了建立init程序和kthreadd程序。

建立完畢後,Linux系統需要借助ktheadd程序實作騰飛,是以在這裡等待kthreadd程序建立完畢。

三、kthreadd程序執行體

在建立線程時,是需要傳遞線程執行函數的,從rest_init()中使用kernel_thread建立線程可知kthreadd線程執行體是kthreadd()函數。

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, cpu_all_mask);/*允許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))/*如果沒有線程需要建立,則主動出讓cpu*/
			schedule();
		__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);

			create_kthread(create);/*建立線程*/

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);/*去鎖*/
	}

	return 0;
}
           

從上述代碼中可以看出:kthreadd程序的任務就是等待建立線程,如果任務隊列為空,則線程主動讓出cpu(調用schedule後會讓出cpu,本線程會睡眠):如果不為空,則依次從任務隊列中取出任務,然後建立相應的線程。如此往複,直到永遠…

四、create_kthread函數

在create_kthread函數中會通過調用kernel_thread函數來建立新程序,且新程序的執行函數為kthread(所有經過kthreadd程序建立的程序執行體都為kthead, 看名字有點暈哈…)。

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_thread接口剛才在rest_init接口中遇到過,核心就是通過kernel_thread接口建立的init程序和kthreadd程序。這裡再次使用它建立新線程,新的線程執行體統一為kthead。下面我們看看kthread函數的内容:

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);
}
           

從kthread函數可以看出,新線程建立成功後,會一直睡眠(使用schedule主動讓出CPU并睡眠),直到有人喚醒它(wake_up_process);線程被喚醒後,并且不需要stop, 則執行指定的函數體( threadfn(data) )。

五、小結

我使用一幅圖來簡單的描述下核心中kthreadd的工作流程:

Linux核心學習之2号程式kthreadd一、Linux的2号程式二、kthreadd程式的建立三、kthreadd程式執行體四、create_kthread函數五、小結

上圖中顯示了核心建立線程的基本流程:

①某一個線程A(左上那個圈)調用kthread_create函數來建立新線程,調用後阻塞;kthread_create會将任務封裝後添加到kthreadd監控的工作隊列中;

②kthreadd程序檢測到工作隊列中有任務,則結束休眠狀态,通過調用create_kthread函數建立線程,最後調用到kernel_thread --> do_fork來建立線程,且新線程執行體為kthead

③新線程建立成功後,執行kthead,kthreadd線程則繼續睡眠等待建立新程序;

④線程A調用kthread_create傳回後,在合适的時候通過wake_up_process(pid)來喚醒新建立的線程

⑤新建立的線程在kthead執行體中被喚醒,檢測到是否需要stop,在不需要stop時,執行使用者指定的線程執行體。(線程執行體發生了變化:先執行預設的kthead,然後才是使用者指定的threadfn,當然也可能直接執行do_exit退出線程)

繼續閱讀