kthread_run创建内核线程的原理
生活随笔
收集整理的這篇文章主要介紹了
kthread_run创建内核线程的原理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
kthread_run是一個宏,用來創建一個進程,并且將其喚醒,其定義在頭文件include/linux/kthread.h中.
#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;?? ??? ??? ??? ??? ??? ??? ??? ??? \
})
?? ?先來看看這個宏的參數,threadfn是該線程的執行函數,data是執行函數的傳入參數,namefmt是該線程的printf風格的線程名,最后的...類似printf的可變參數列表.從這個宏的實現可以看出kthread_run是通過kthread_create來創建一個進程,并返回一個task_struct,然后使用wake_up_process函數將新創建的進程喚醒.下面需要關注一下kthread_create的實現,其實它也是一個宏,同樣也定義在頭文件include/linux/kthread.h中.
#define kthread_create(threadfn, data, namefmt, arg...) \
?? ?kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
?? ?該宏的參數同kthread_run,不解釋了,其實現就是調用了函數kthread_create_on_node,下面來看該函數的實現.
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
?? ??? ??? ??? ??? ??? void *data, int node,
?? ??? ??? ??? ??? ??? const char namefmt[],
?? ??? ??? ??? ??? ??? ...)
{
?? ?DECLARE_COMPLETION_ONSTACK(done);
?? ?struct task_struct *task;
?? ?struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);
?? ?if (!create)
?? ??? ?return ERR_PTR(-ENOMEM);
?? ?create->threadfn = threadfn;//線程執行函數
?? ?create->data = data;//線程執行函數的參數
?? ?create->node = node;//NUMA系統上會用到,這里不介紹.
?? ?create->done = &done;//completion結構,一會兒會通過它判斷該線程的創建是否完成.
?? ?spin_lock(&kthread_create_lock);
?? ?list_add_tail(&create->list, &kthread_create_list);
?? ?spin_unlock(&kthread_create_lock);
?? ?wake_up_process(kthreadd_task);//喚醒kthreadd_task內核線程.
?? ?if (unlikely(wait_for_completion_killable(&done))) {//進入睡眠,等待線程創建的完成.
?? ??? ?if (xchg(&create->done, NULL))
?? ??? ??? ?return ERR_PTR(-EINTR);
?? ??? ?wait_for_completion(&done);
?? ?}
?? ?task = create->result;
?? ?if (!IS_ERR(task)) {
?? ??? ?static const struct sched_param param = { .sched_priority = 0 };
?? ??? ?va_list args;
?? ??? ?va_start(args, namefmt);
?? ??? ?vsnprintf(task->comm, sizeof(task->comm), namefmt, args);//設置線程名字
?? ??? ?va_end(args);
?? ?
?? ??? ?sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);
?? ??? ?set_cpus_allowed_ptr(task, cpu_all_mask);
?? ?}
?? ?kfree(create);
?? ?return task;
}
?? ?該函數首先創建一個kthread_create_info結構,該結構封裝了要創建的內核進程所需要的信息,然后將該結構添加到鏈表kthread_create_list的末尾,接著該函數調用wake_up_process函數喚醒kthreadd_task所表示的內核線程,然后調用wait_for_completion系列的函數使當前進程進入睡眠狀態,在這里我們可以猜想kthreadd_task表示的內核線程應該會從鏈表kthread_create_list中依次的取下每個節點根據對應的kthread_create_info結構中的線程信息來創建內核線程,然后通過completion函數喚醒當前線程,這時kthread_create_info中的result應該已經被賦值為剛創建的內核線程的task_struct結構了,kthread_create_on_node函數會檢查該task_struct結構,判斷內核線程創建是否成功,如果成功,會進一步設置線程task的名稱,以及調度器,最后返回新創建的task_struct結構體.
?? ?剛才猜想了kthreadd_task表示的內核線程的作用是從鏈表kthread_create_list上依次的取下kthread_create_info結構,然后創建相應的內核線程.現在來驗證這個猜想.在內核源碼中搜索kthreadd_task,會在init/main.c文件中找到為kthreadd_task賦值的地方.在函數rest_init中.
static noinline void __init_refok rest_init(void)
{
?? ?int pid;
?? ?rcu_scheduler_starting();
?? ?
?? ?kernel_thread(kernel_init, NULL, CLONE_FS);
?? ?numa_default_policy();
?? ?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);
?? ?
?? ?......
}
?? ?rest_init算是kernel再啟動函數start_kernel中執行的最后一個函數,kthreadd_task表示的內核線程就是在這里創建的,這行代碼kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns)是通過pid來查找其對應的task_struct的結構體,再看pid是上面的這行代碼產生的pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),kernel_thread函數是通過do_fork來創建線程的,然后返回該線程的pid,從這里可以看出kthreadd_task表示的內核線程的執行函數是kthreadd,下面來看這個函數的實現.
int kthreadd(void *unused)
{
?? ?struct task_struct *tsk = current;
?? ?......
?? ?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;
}
?? ?可以看出這個函數的主體是一個for循環,重點是其中的那個while循環,很明顯是在遍歷鏈表kthread_create_list,獲取將其上面的每個節點所表示的kthread_create_info結構,然后將該結構傳遞給函數create_kthread,顧名思義,該函數就是來創建線程的,下面看看create_kthread的代碼.
static void create_kthread(struct kthread_create_info *create)
{
?? ?int pid;
?? ?......
?? ?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函數創建了一個以kthread函數為線程執行函數,以kthread_create_info結構為執行函數參數的內核線程,其實這個新創建的線程就是最終需要創建的那個內核線程,那為什么執行函數是kthread,而不是kthread_create_info結構中指定的線程執行函數.看完kthread函數的實現就知道了.
static int kthread(void *_create)
{
?? ?struct kthread_create_info *create = _create;
?? ?int (*threadfn)(void *data) = create->threadfn;//獲取線程執行函數賦值給函數指針threadfn
?? ?void *data = create->data;//線程執行函數的參數
?? ?struct completion *done;
?? ?struct kthread self;
?? ?int ret;
?? ?......
?? ?
?? ?done = xchg(&create->done, NULL);可以理解為將create->done賦值給done
?? ?if (!done) {
?? ??? ?kfree(create);
?? ??? ?do_exit(-EINTR);
?? ?}
?? ?
?? ?__set_current_state(TASK_UNINTERRUPTIBLE);
?? ?create->result = current;//將該線程的task_struct結構賦值給create->result
?? ?complete(done);//調用complete函數喚醒調用kthread_create_on_node的那個線程,通知它創建的線程已經創建完畢.
?? ?schedule();//調用schedule交出處理器,給剛喚醒的調用kthread_create_on_node的線程以運行的機會,使其繼續執行kthread_create_on_node函數剩下的代碼.
?? ?
?? ?//再次調度到該線程,從這里開始執行.
?? ?ret = -EINTR;
?? ?if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {//判斷如果該線程沒有被停止掉.
?? ??? ?......
?? ??? ?ret = threadfn(data);//我們通過kthread_create_on_node函數傳遞進來的線程執行函數最終是在這個位置被執行的.
?? ?}
?? ?do_exit(ret);
}
?? ?可以看出,如果在kthread函數調用schedule交出處理器和再次調度到該線程之間的這段時間里,在別的地方調用了kthread_stop函數停止了該函數的話,那么線程執行函數是得不到執行的,該線程就直接退出了.
?? ?以上就是通過kthread_run和kthread_create創建線程的整個過程.可以看出,它們并不直接創建線程,而是將要創建的線程的相關信息打包到kthread_create_info結構中,然后委托給內核線程kthreadd來做的,從函數kthreadd也可以看出,該線程的主要工作就是遍歷鏈表kthread_create_list創建線程.
#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;?? ??? ??? ??? ??? ??? ??? ??? ??? \
})
?? ?先來看看這個宏的參數,threadfn是該線程的執行函數,data是執行函數的傳入參數,namefmt是該線程的printf風格的線程名,最后的...類似printf的可變參數列表.從這個宏的實現可以看出kthread_run是通過kthread_create來創建一個進程,并返回一個task_struct,然后使用wake_up_process函數將新創建的進程喚醒.下面需要關注一下kthread_create的實現,其實它也是一個宏,同樣也定義在頭文件include/linux/kthread.h中.
#define kthread_create(threadfn, data, namefmt, arg...) \
?? ?kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
?? ?該宏的參數同kthread_run,不解釋了,其實現就是調用了函數kthread_create_on_node,下面來看該函數的實現.
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
?? ??? ??? ??? ??? ??? void *data, int node,
?? ??? ??? ??? ??? ??? const char namefmt[],
?? ??? ??? ??? ??? ??? ...)
{
?? ?DECLARE_COMPLETION_ONSTACK(done);
?? ?struct task_struct *task;
?? ?struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);
?? ?if (!create)
?? ??? ?return ERR_PTR(-ENOMEM);
?? ?create->threadfn = threadfn;//線程執行函數
?? ?create->data = data;//線程執行函數的參數
?? ?create->node = node;//NUMA系統上會用到,這里不介紹.
?? ?create->done = &done;//completion結構,一會兒會通過它判斷該線程的創建是否完成.
?? ?spin_lock(&kthread_create_lock);
?? ?list_add_tail(&create->list, &kthread_create_list);
?? ?spin_unlock(&kthread_create_lock);
?? ?wake_up_process(kthreadd_task);//喚醒kthreadd_task內核線程.
?? ?if (unlikely(wait_for_completion_killable(&done))) {//進入睡眠,等待線程創建的完成.
?? ??? ?if (xchg(&create->done, NULL))
?? ??? ??? ?return ERR_PTR(-EINTR);
?? ??? ?wait_for_completion(&done);
?? ?}
?? ?task = create->result;
?? ?if (!IS_ERR(task)) {
?? ??? ?static const struct sched_param param = { .sched_priority = 0 };
?? ??? ?va_list args;
?? ??? ?va_start(args, namefmt);
?? ??? ?vsnprintf(task->comm, sizeof(task->comm), namefmt, args);//設置線程名字
?? ??? ?va_end(args);
?? ?
?? ??? ?sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);
?? ??? ?set_cpus_allowed_ptr(task, cpu_all_mask);
?? ?}
?? ?kfree(create);
?? ?return task;
}
?? ?該函數首先創建一個kthread_create_info結構,該結構封裝了要創建的內核進程所需要的信息,然后將該結構添加到鏈表kthread_create_list的末尾,接著該函數調用wake_up_process函數喚醒kthreadd_task所表示的內核線程,然后調用wait_for_completion系列的函數使當前進程進入睡眠狀態,在這里我們可以猜想kthreadd_task表示的內核線程應該會從鏈表kthread_create_list中依次的取下每個節點根據對應的kthread_create_info結構中的線程信息來創建內核線程,然后通過completion函數喚醒當前線程,這時kthread_create_info中的result應該已經被賦值為剛創建的內核線程的task_struct結構了,kthread_create_on_node函數會檢查該task_struct結構,判斷內核線程創建是否成功,如果成功,會進一步設置線程task的名稱,以及調度器,最后返回新創建的task_struct結構體.
?? ?剛才猜想了kthreadd_task表示的內核線程的作用是從鏈表kthread_create_list上依次的取下kthread_create_info結構,然后創建相應的內核線程.現在來驗證這個猜想.在內核源碼中搜索kthreadd_task,會在init/main.c文件中找到為kthreadd_task賦值的地方.在函數rest_init中.
static noinline void __init_refok rest_init(void)
{
?? ?int pid;
?? ?rcu_scheduler_starting();
?? ?
?? ?kernel_thread(kernel_init, NULL, CLONE_FS);
?? ?numa_default_policy();
?? ?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);
?? ?
?? ?......
}
?? ?rest_init算是kernel再啟動函數start_kernel中執行的最后一個函數,kthreadd_task表示的內核線程就是在這里創建的,這行代碼kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns)是通過pid來查找其對應的task_struct的結構體,再看pid是上面的這行代碼產生的pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),kernel_thread函數是通過do_fork來創建線程的,然后返回該線程的pid,從這里可以看出kthreadd_task表示的內核線程的執行函數是kthreadd,下面來看這個函數的實現.
int kthreadd(void *unused)
{
?? ?struct task_struct *tsk = current;
?? ?......
?? ?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;
}
?? ?可以看出這個函數的主體是一個for循環,重點是其中的那個while循環,很明顯是在遍歷鏈表kthread_create_list,獲取將其上面的每個節點所表示的kthread_create_info結構,然后將該結構傳遞給函數create_kthread,顧名思義,該函數就是來創建線程的,下面看看create_kthread的代碼.
static void create_kthread(struct kthread_create_info *create)
{
?? ?int pid;
?? ?......
?? ?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函數創建了一個以kthread函數為線程執行函數,以kthread_create_info結構為執行函數參數的內核線程,其實這個新創建的線程就是最終需要創建的那個內核線程,那為什么執行函數是kthread,而不是kthread_create_info結構中指定的線程執行函數.看完kthread函數的實現就知道了.
static int kthread(void *_create)
{
?? ?struct kthread_create_info *create = _create;
?? ?int (*threadfn)(void *data) = create->threadfn;//獲取線程執行函數賦值給函數指針threadfn
?? ?void *data = create->data;//線程執行函數的參數
?? ?struct completion *done;
?? ?struct kthread self;
?? ?int ret;
?? ?......
?? ?
?? ?done = xchg(&create->done, NULL);可以理解為將create->done賦值給done
?? ?if (!done) {
?? ??? ?kfree(create);
?? ??? ?do_exit(-EINTR);
?? ?}
?? ?
?? ?__set_current_state(TASK_UNINTERRUPTIBLE);
?? ?create->result = current;//將該線程的task_struct結構賦值給create->result
?? ?complete(done);//調用complete函數喚醒調用kthread_create_on_node的那個線程,通知它創建的線程已經創建完畢.
?? ?schedule();//調用schedule交出處理器,給剛喚醒的調用kthread_create_on_node的線程以運行的機會,使其繼續執行kthread_create_on_node函數剩下的代碼.
?? ?
?? ?//再次調度到該線程,從這里開始執行.
?? ?ret = -EINTR;
?? ?if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {//判斷如果該線程沒有被停止掉.
?? ??? ?......
?? ??? ?ret = threadfn(data);//我們通過kthread_create_on_node函數傳遞進來的線程執行函數最終是在這個位置被執行的.
?? ?}
?? ?do_exit(ret);
}
?? ?可以看出,如果在kthread函數調用schedule交出處理器和再次調度到該線程之間的這段時間里,在別的地方調用了kthread_stop函數停止了該函數的話,那么線程執行函數是得不到執行的,該線程就直接退出了.
?? ?以上就是通過kthread_run和kthread_create創建線程的整個過程.可以看出,它們并不直接創建線程,而是將要創建的線程的相關信息打包到kthread_create_info結構中,然后委托給內核線程kthreadd來做的,從函數kthreadd也可以看出,該線程的主要工作就是遍歷鏈表kthread_create_list創建線程.
總結
以上是生活随笔為你收集整理的kthread_run创建内核线程的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux SPI总线和设备驱动架构之四
- 下一篇: kthread_work和kthread