Linux內核可以看作一個服務進程(管理軟硬件資源,響應用戶進程的種種合理以及不合理的請求)。內核需要多個執行流並行,為了防止可能的阻塞,支持多線程是必要的。內核線程就是內核的分身,一個分身可以處理一件特定事情。內核線程的調度由內核負責,一個內核線程處於阻塞狀態時不影響其他的內核線程,因為其是調度的基本單位。這與用戶線程是不一樣的。因為內核線程只運行在內核態,因此,它只能使用大於PAGE_OFFSET(3G)的地址空間。內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間,mm指針被設置為NULL;它只在 內核空間運行,從來不切換到用戶空間去;並且和普通進程一樣,可以被調度,也可以被搶占。
內核線程(thread)或叫守護進程(daemon),在操作系統中占據相當大的比例,當Linux操作系統啟動以后,你可以用”ps -ef”命令查看系統中的進程,這時會發現很多以”d”結尾的進程名,確切說名稱顯示里面加 "[]"的,這些進程就是內核線程。
創建內核線程最基本的兩個接口函數是:
kthread_run(threadfn, data, namefmt, ...)
和
kernel_thread(int(* fn)(void *),void * arg,unsigned long flags)
這里我們主要介紹kthread_run,后面會專門分析這兩個函數的異同。
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, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \ wake_up_process(__k); \ __k; \ })
kthread_run()負責內核線程的創建,它由kthread_create()和wake_up_process()兩部分組成,這樣的好處是用kthread_run()創建的線程可以直接運行。外界調用kthread_run創建運行線程。kthread_run是個宏定義,首先調用kthread_create()創建線程,如果創建成功,再調用wake_up_process()喚醒新創建的線程。kthread_create()根據參數向kthread_create_list中發送一個請求,並喚醒kthreadd,之后會調用wait_for_completion(&create.done)等待線程創建完成。新創建的線程開始運行后,入口在kthread(),kthread()調用complete(&create->done)喚醒阻塞的模塊進程,並使用schedule()調度出去。kthread_create()被喚醒后,設置新線程的名稱,並返回到kthread_run中。kthread_run調用wake_up_process()重新喚醒新創建線程,此時新線程才開始運行kthread_run參數中的入口函數。
在介紹完如何創建線程之后,下面來介紹另外兩個基本的函數:
int kthread_stop(struct task_struct *k);
int kthread_should_stop(void);
kthread_stop()負責結束創建的線程,參數是創建時返回的task_struct指針。kthread設置標志should_stop,並等待線程主動結束,返回線程的返回值。在調用 kthread_stop()結束線程之前一定要檢查該線程是否還在運行(通過 kthread_run 返回的 task_stuct 是否有效),否則會造成災難性的后果。kthread_run的返回值tsk。不能用tsk是否為NULL進行檢查,而要用IS_ERR()宏定義檢查,這是因為返回的是錯誤碼,大致從0xfffff000~0xffffffff。
kthread_should_stop()返回should_stop標志(參見 struct kthread )。它用於創建的線程檢查結束標志,並決定是否退出。
kthread() (注:原型為:static int kthread(void *_create) )的實現在kernel/kthread.c中,頭文件是include/linux/kthread.h。內核中一直運行一個線程kthreadd,它運行kthread.c中的kthreadd函數。在kthreadd()中,不斷檢查一個kthread_create_list鏈表。kthread_create_list中的每個節點都是一個創建內核線程的請求,kthreadd()發現鏈表不為空,就將其第一個節點退出鏈表,並調用create_kthread()創建相應的線程。create_kthread()則進一步調用更深層的kernel_thread()創建線程,入口函數設在kthread()中。
外界調用kthread_stop()刪除線程。kthread_stop首先設置結束標志should_stop,然后調用wake_for_completion(&kthread->exited)上,這個其實是新線程task_struct上的vfork_done,會在線程結束調用do_exit()時設置。
附:
struct kthread { int should_stop; struct completion exited; }; 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); set_mems_allowed(node_states[N_HIGH_MEMORY]); current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG; for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (list_empty(&kthread_create_list)) 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; } /** * kthread_stop - stop a thread created by kthread_create(). * @k: thread created by kthread_create(). * * Sets kthread_should_stop() for @k to return true, wakes it, and * waits for it to exit. This can also be called after kthread_create() * instead of calling wake_up_process(): the thread will exit without * calling threadfn(). * * If threadfn() may call do_exit() itself, the caller must ensure * task_struct can't go away. * * Returns the result of threadfn(), or %-EINTR if wake_up_process() * was never called. */ int kthread_stop(struct task_struct *k) { struct kthread *kthread; int ret; trace_sched_kthread_stop(k); get_task_struct(k); kthread = to_kthread(k); barrier(); /* it might have exited */ if (k->vfork_done != NULL) { kthread->should_stop = 1; wake_up_process(k); wait_for_completion(&kthread->exited); } ret = k->exit_code; put_task_struct(k); trace_sched_kthread_stop_ret(ret); return ret; }
