|
目錄
一、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的工作流程:
上圖中顯示了核心建立線程的基本流程:
①某一個線程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退出線程)