2號進程 內核初始化rest_init函數中,由進程 0 (swapper 進程)創建了兩個process init 進程 (pid = 1, ppid = 0) kthreadd (pid = 2, ppid = 0) 所有其它的內核線程的ppid 都是 2,也就是說它們都是由kthreadd t ...
2號進程
內核初始化rest_init函數中,由進程 0 (swapper 進程)創建了兩個process
- init 進程 (pid = 1, ppid = 0)
- kthreadd (pid = 2, ppid = 0)
所有其它的內核線程的ppid 都是 2,也就是說它們都是由kthreadd thread創建的
所有的內核線程在大部分時間里都處於阻塞狀態(TASK_INTERRUPTIBLE)只有在系統滿足進程需要的某種資源的情況下才會運行
它的任務就是管理和調度其他內核線程kernel_thread, 會迴圈執行一個kthreadd的函數,該函數的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當我們調用kernel_thread創建的內核線程會被加入到此鏈表中,因此所有的內核線程都是直接或者間接的以kthreadd為父進程
2號進程的創建
在rest_init函數中創建2號進程的代碼如下
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
2號進程的事件迴圈
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;
for (;;) {
/* 首先將線程狀態設置為 TASK_INTERRUPTIBLE, 如果當前
沒有要創建的線程則主動放棄 CPU 完成調度.此進程變為阻塞態*/
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list)) // 沒有需要創建的內核線程
schedule(); // 什麼也不做, 執行一次調度, 讓出CPU
/* 運行到此表示 kthreadd 線程被喚醒(就是我們當前)
設置進程運行狀態為 TASK_RUNNING */
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock); // 加鎖,
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
/* 從鏈表中取得 kthread_create_info 結構的地址,在上文中已經完成插入操作(將
kthread_create_info 結構中的 list 成員加到鏈表中,此時根據成員 list 的偏移
獲得 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的核心是一for和while迴圈體。
在for迴圈中,如果發現kthread_create_list是一空鏈表,則調用schedule調度函數,因為此前已經將該進程的狀態設置為TASK_INTERRUPTIBLE,所以schedule的調用將會使當前進程進入睡眠。
如果kthread_create_list不為空,則進入while迴圈,在該迴圈體中會遍歷該kthread_create_list列表,對於該列表上的每一個entry,都會得到對應的類型為struct kthread_create_info的節點的指針create.
然後函數在kthread_create_list中刪除create對應的列表entry,接下來以create指針為參數調用create_kthread(create).
create_kthread的過程如下
create_kthread完成內核線程創建
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).
其實就是調用首先構造一個假的上下文執行環境,最後調用 do_fork()
返回進程 id, 創建後的線程執行 kthread 函數
*/
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);
}
}
在create_kthread()函數中,會調用kernel_thread來生成一個新的進程,該進程的內核函數為kthread,調用參數為
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
我們可以看到,創建的內核線程執行的事件kthread
此時回到 kthreadd thread,它在完成了進程的創建後繼續迴圈,檢查 kthread_create_list 鏈表,如果為空,則 kthreadd 內核線程昏睡過去
那麼我們現在回想我們的操作
我們在內核中通過kernel_create或者其他方式創建一個內核線程, 然後kthreadd內核線程被喚醒, 來執行內核線程創建的真正工作,於是這裡有三個線程
- kthreadd已經光榮完成使命(接手執行真正的創建工作),睡眠
- 喚醒kthreadd的線程由於新創建的線程還沒有創建完畢而繼續睡眠 (在 kthread_create函數中)
- 新創建的線程已經正在運行kthread,但是由於還有其它工作沒有做所以還沒有最終創建完成.
新創建的內核線程kthread函數
static int kthread(void *_create)
{
/* Copy data: it's on kthread's stack
create 指向 kthread_create_info 中的 kthread_create_info */
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.flags = 0;
self.data = data;
init_completion(&self.exited);
init_completion(&self.parked);
current->vfork_done = &self.exited;
/* If user was SIGKILLed, I release the structure. */
done = xchg(&create->done, NULL);
if (!done) {
kfree(create);
do_exit(-EINTR);
}
/* OK, tell user we're spawned, wait for stop or wakeup
設置運行狀態為 TASK_UNINTERRUPTIBLE */
__set_current_state(TASK_UNINTERRUPTIBLE);
/* current 表示當前新創建的 thread 的 task_struct 結構 */
create->result = current;
complete(done);
/* 至此線程創建完畢 , 執行任務切換,讓出 CPU */
schedule();
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}
線程創建完畢:
創建新 thread 的進程恢復運行 kthread_create() 並且返回新創建線程的任務描述符
新創建的線程由於執行了 schedule() 調度,此時並沒有執行.
直到我們使用wake_up_process(p);喚醒新創建的線程
線程被喚醒後, 會接著執行threadfn(data)
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret)
總結
kthreadd進程由idle通過kernel_thread創建,並始終運行在內核空間, 負責所有內核線程的調度和管理,它的任務就是管理和調度其他內核線程kernel_thread, 會迴圈執行一個kthreadd的函數,該函數的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當我們調用kernel_thread創建的內核線程會被加入到此鏈表中,因此所有的內核線程都是直接或者間接的以kthreadd為父進程
我們在內核中通過kernel_create或者其他方式創建一個內核線程, 然後kthreadd內核線程被喚醒, 來執行內核線程創建的真正工作,新的線程將執行kthread函數, 完成創建工作,創建完畢後讓出CPU,因此新的內核線程不會立刻運行.需要手工 wake up, 被喚醒後將執行自己的真正工作函數
- 任何一個內核線程入口都是 kthread()
- 通過 kthread_create() 創建的內核線程不會立刻運行.需要手工 wake up.
- 通過 kthread_create() 創建的內核線程有可能不會執行相應線程函數threadfn而直接退出