自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇學(xué)會內(nèi)核線程的創(chuàng)建和運(yùn)行

系統(tǒng)
通過 ps 命令可以看到紅色方框標(biāo)出的都是父進(jìn)程為2號進(jìn)程的內(nèi)核線程,2號進(jìn)程即藍(lán)色方框標(biāo)出的進(jìn)程 kthreadd,1號進(jìn)程是綠色方框標(biāo)出的進(jìn)程 init,它們的父進(jìn)程號都是0。

[[407481]]

上面講完了用戶進(jìn)程/線程的創(chuàng)建,這里我們看下內(nèi)核是如何創(chuàng)建線程的。

通過 ps 命令可以看到紅色方框標(biāo)出的都是父進(jìn)程為2號進(jìn)程的內(nèi)核線程,2號進(jìn)程即藍(lán)色方框標(biāo)出的進(jìn)程 kthreadd,1號進(jìn)程是綠色方框標(biāo)出的進(jìn)程 init,它們的父進(jìn)程號都是0。

下面我們一起看下,內(nèi)核的0號,1號,2號線程的創(chuàng)建過程。

0號線程

linux 內(nèi)核中為0號進(jìn)程專門定義了一個靜態(tài)的 task_struct 的結(jié)構(gòu),稱為 init_task:

  1. /* include/linux/init_task.h */ 
  2. #define INIT_TASK_COMM "swapper" 
  3.  
  4. /* init/init_task.c */ 
  5. struct task_struct init_task 
  6. #ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK 
  7.         __init_task_data 
  8. #endif 
  9.         __aligned(L1_CACHE_BYTES) 
  10. = { 
  11. #ifdef CONFIG_THREAD_INFO_IN_TASK 
  12.         .thread_info    = INIT_THREAD_INFO(init_task), 
  13.         .stack_refcount = REFCOUNT_INIT(1), 
  14. #endif 
  15.         .state          = 0, 
  16.         .stack          = init_stack, 
  17.         .usage          = REFCOUNT_INIT(2), 
  18.         .flags          = PF_KTHREAD, 
  19.         .prio           = MAX_PRIO - 20, 
  20.         .static_prio    = MAX_PRIO - 20, 
  21.         .normal_prio    = MAX_PRIO - 20, 
  22.         .policy         = SCHED_NORMAL, 
  23.         .cpus_ptr       = &init_task.cpus_mask, 
  24.         .cpus_mask      = CPU_MASK_ALL, 
  25.         .nr_cpus_allowed= NR_CPUS, 
  26.         .mm             = NULL
  27.         .active_mm      = &init_mm, 
  28.   ...... 
  29.         .comm           = INIT_TASK_COMM, 
  30.         .thread         = INIT_THREAD, 
  31.         .fs             = &init_fs, 
  32.         .files          = &init_files, 
  33.   ...... 
  34. }; 
  35. EXPORT_SYMBOL(init_task); 

這個結(jié)構(gòu)體中的成員都是靜態(tài)定義的,這里看幾個比較重要的變量:

  • .thread_info = INIT_THREAD_INFO(init_task), 這個結(jié)構(gòu)在 “task_struct, thread_info 和內(nèi)核棧 sp 的關(guān)系” 中有詳細(xì)的描述
  • .stack = init_stack, init_stack 是內(nèi)核棧的靜態(tài)定義,定義在鏈接腳本里
  1. /* include/asm-generic/vmlinux.lds.h */ 
  2. #define INIT_TASK_DATA(align)                                           \ 
  3.         . = ALIGN(align);                                               \ 
  4.         __start_init_task = .;                                          \ 
  5.         init_thread_union = .;                                          \ 
  6.         init_stack = .;                                                 \ 
  7.         KEEP(*(.data..init_task))                                       \ 
  8.         KEEP(*(.data..init_thread_info))                                \ 
  9.         . = __start_init_task + THREAD_SIZE;                            \ 
  10.         __end_init_task = .; 

可以看出,__start_init_task 是0號進(jìn)程的內(nèi)核棧的基地址,__end_init_task 是0號進(jìn)程的內(nèi)核棧的結(jié)束地址。注意:__start_init_task = init_thread_union = init_task

  • .comm = INIT_TASK_COMM, 0號進(jìn)程的名稱是 swapper

下面結(jié)合 Linux 內(nèi)核啟動的部分代碼,看下是如何調(diào)用 __primary_switched 來設(shè)置0號進(jìn)程的運(yùn)行內(nèi)核棧:

  1. /* arch/arm64/kernel/head.S */ 
  2. SYM_FUNC_START_LOCAL(__primary_switched) 
  3.         adrp    x4, init_thread_union        ------(1) 
  4.         add     sp, x4, #THREAD_SIZE         ------(2) 
  5.         adr_l   x5, init_task 
  6.         msr     sp_el0, x5                      // Save thread_info 
  7.         ...... 
  8.         b       start_kernel 
  9. SYM_FUNC_END(__primary_switched)             ------(3) 
  1. init_thread_union 是0號進(jìn)程的內(nèi)核棧的基地址
  2. 設(shè)置堆棧指針 sp 的值,就是內(nèi)核棧的棧底 + THREAD_SIZE的大小。現(xiàn)在 sp 指到了內(nèi)核棧的頂端
  3. 跳轉(zhuǎn)到 linux 內(nèi)核的入口

至此0號進(jìn)程就已經(jīng)運(yùn)行起來了,0號進(jìn)程,通常也被稱為 idle 進(jìn)程,也稱為 swapper 進(jìn)程。當(dāng)系統(tǒng)中所有的進(jìn)程起來后,0號進(jìn)程也就蛻化為 idle 進(jìn)程,當(dāng)一個 CPU 上沒有任務(wù)可運(yùn)行時就會去運(yùn)行 idle 進(jìn)程。一旦運(yùn)行 idle 進(jìn)程,則此 CPU 就可以進(jìn)入低功耗模式了,在ARM上就是WFI。

1號線程

  1. asmlinkage __visible void __init __no_sanitize_address start_kernel(void) 
  2.   ...... 
  3.   arch_call_rest_init(); 
  4.   ...... 
  5.  
  6. void __init __weak arch_call_rest_init(void) 
  7.         rest_init(); 
  8.  
  9. noinline void __ref rest_init(void) 
  10.         struct task_struct *tsk; 
  11.         int pid; 
  12.  
  13.         rcu_scheduler_starting(); 
  14.  
  15.         pid = kernel_thread(kernel_init, NULL, CLONE_FS); 
  16.  
  17.         rcu_read_lock(); 
  18.         tsk = find_task_by_pid_ns(pid, &init_pid_ns); 
  19.         set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id())); 
  20.         rcu_read_unlock(); 
  21.  
  22.         numa_default_policy(); 
  23.         pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 
  24.         rcu_read_lock(); 
  25.         kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); 
  26.         rcu_read_unlock(); 
  27.  
  28.         system_state = SYSTEM_SCHEDULING; 
  29.  
  30.         complete(&kthreadd_done); 
  31.  
  32.         schedule_preempt_disabled(); 
  33.         /* Call into cpu_idle with preempt disabled */ 
  34.         cpu_startup_entry(CPUHP_ONLINE); 

這里會創(chuàng)建1號,2號兩個線程:

  • pid = kernel_thread(kernel_init, NULL, CLONE_FS);
  • pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  1. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) 
  2.  return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn, 
  3.   (unsigned long)arg, NULLNULL, 0); 

可以看出,kernel_thread 最終會調(diào)用 do_fork 根據(jù)參數(shù)的不同來創(chuàng)建一個進(jìn)程或者內(nèi)核線程。do_fork 的實(shí)現(xiàn)我們在后面會做詳細(xì)的介紹。當(dāng)內(nèi)核線程創(chuàng)建成功后就會調(diào)用設(shè)置的回調(diào)函數(shù)。

當(dāng) kernel_thread(kernel_init, NULL, CLONE_FS) 返回時,1號進(jìn)程已經(jīng)創(chuàng)建成功了。而且會回調(diào) kernel_init 函數(shù),接下來看下 kernel_init 主要做什么事情:

  1. static int __ref kernel_init(void *unused) 
  2.         int ret; 
  3.  
  4.         kernel_init_freeable(); 
  5.         ...... 
  6.         if (!try_to_run_init_process("/sbin/init") || 
  7.             !try_to_run_init_process("/etc/init") || 
  8.             !try_to_run_init_process("/bin/init") || 
  9.             !try_to_run_init_process("/bin/sh")) 
  10.                 return 0; 
  11.  
  12.         panic("No working init found.  Try passing init= option to kernel. " 
  13.               "See Linux Documentation/admin-guide/init.rst for guidance."); 

最主要的工作就是通過 execve,執(zhí)行init可執(zhí)行文件。init 就是1號線程,它最終會去創(chuàng)建所有的應(yīng)用進(jìn)程。確切來講,init 進(jìn)程是用戶態(tài)的,kernel_init 是1號進(jìn)程的內(nèi)核態(tài)。

2號線程

上面講到的 kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 就是用來創(chuàng)建2號線程,2號線程的執(zhí)行函數(shù)是 kthreadd:

kthreadd 處理流程

  1. int kthreadd(void *unused) 
  2.         struct task_struct *tsk = current
  3.  
  4.         /* Setup a clean context for our children to inherit. */ 
  5.         set_task_comm(tsk, "kthreadd");                 ------(1) 
  6.         ignore_signals(tsk); 
  7.         set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_FLAG_KTHREAD)); 
  8.         set_mems_allowed(node_states[N_MEMORY]); 
  9.  
  10.         current->flags |= PF_NOFREEZE; 
  11.         cgroup_init_kthreadd(); 
  12.  
  13.         for (;;) { 
  14.                 set_current_state(TASK_INTERRUPTIBLE);  ------(2) 
  15.                 if (list_empty(&kthread_create_list))    
  16.                         schedule();                     ------(3) 
  17.                 __set_current_state(TASK_RUNNING); 
  18.  
  19.                 spin_lock(&kthread_create_lock); 
  20.                 while (!list_empty(&kthread_create_list)) { 
  21.                         struct kthread_create_info *create
  22.  
  23.                         create = list_entry(kthread_create_list.next
  24.                                             struct kthread_create_info, list); 
  25.                         list_del_init(&create->list); 
  26.                         spin_unlock(&kthread_create_lock); 
  27.  
  28.                         create_kthread(create);         ------(4) 
  29.  
  30.                         spin_lock(&kthread_create_lock); 
  31.                 } 
  32.                 spin_unlock(&kthread_create_lock); 
  33.         } 
  34.  
  35.         return 0; 
  1. 通過設(shè)置 task_struct 的 comm 字段,使當(dāng)前進(jìn)程的名字為"kthreadd"
  2. 設(shè)置當(dāng)前的進(jìn)程的狀態(tài)是 TASK_INTERRUPTIBLE
  3. 如果鏈表 kthread_create_list 是空,說明沒有創(chuàng)建內(nèi)核線程的請求,則直接調(diào)用 schedule 進(jìn)行睡眠
  4. 如果不是空,while循環(huán),從鏈表中取出一個,然后調(diào)用 create_kthread 去創(chuàng)建一個內(nèi)核線程

所以2號線程 kthreadd 通過 create_kthread 去創(chuàng)建內(nèi)核其它的線程,可謂是內(nèi)核線程的祖先。

至此,我們已經(jīng)知道 Linux 啟動的第一個線程,0號線程是靜態(tài)創(chuàng)建的。在0號線程啟動后會接連創(chuàng)建兩個線程,分別是1號線程和2和線程。1號進(jìn)程最終會去調(diào)用可init可執(zhí)行文件,init進(jìn)程最終會去創(chuàng)建所有的應(yīng)用進(jìn)程。2號進(jìn)程會在內(nèi)核中負(fù)責(zé)創(chuàng)建所有的內(nèi)核線程。所以說0號進(jìn)程是1號和2號進(jìn)程的父進(jìn)程,1號進(jìn)程是所有用戶態(tài)進(jìn)程的父進(jìn)程,2號進(jìn)程是所有內(nèi)核線程的父進(jìn)程。

kthread 處理流程

上面 kthreadd 線程會循環(huán)查看鏈表 kthread_create_list,如果有線程的創(chuàng)建申請,則從鏈表中取出一個,然后調(diào)用 create_kthread 去創(chuàng)建一個內(nèi)核線程。

  1. static void create_kthread(struct kthread_create_info *create
  2.         int pid; 
  3.  
  4. #ifdef CONFIG_NUMA 
  5.         current->pref_node_fork = create->node; 
  6. #endif 
  7.         /* We want our own signal handler (we take no signals by default). */ 
  8.         pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD); 
  9.         if (pid < 0) { 
  10.                 /* If user was SIGKILLed, I release the structure. */ 
  11.                 struct completion *done = xchg(&create->done, NULL); 
  12.  
  13.                 if (!done) { 
  14.                         kfree(create); 
  15.                         return
  16.                 } 
  17.                 create->result = ERR_PTR(pid); 
  18.                 complete(done); 
  19.         } 

可以看出,由 kthreadd 內(nèi)核線程創(chuàng)建的內(nèi)核線程的執(zhí)行函數(shù)是 kthread。

  1. static int kthread(void *_create) 
  2.         /* Copy data: it's on kthread's stack */ 
  3.         struct kthread_create_info *create = _create;      ------(1) 
  4.         int (*threadfn)(void *data) = create->threadfn;    ------(2) 
  5.         void *data = create->data;                         ------(3) 
  6.         struct completion *done; 
  7.         struct kthread *self; 
  8.         int ret; 
  9.  
  10.         self = kzalloc(sizeof(*self), GFP_KERNEL);         ------(4) 
  11.         set_kthread_struct(self); 
  12.  
  13.         /* If user was SIGKILLed, I release the structure. */ 
  14.         done = xchg(&create->done, NULL);                  ------(5) 
  15.         if (!done) { 
  16.                 kfree(create); 
  17.                 do_exit(-EINTR); 
  18.         } 
  19.  
  20.         if (!self) { 
  21.                 create->result = ERR_PTR(-ENOMEM); 
  22.                 complete(done); 
  23.                 do_exit(-ENOMEM); 
  24.         } 
  25.  
  26.         self->threadfn = threadfn;                         ------(6) 
  27.         self->data = data;                                 ------(7) 
  28.         init_completion(&self->exited); 
  29.         init_completion(&self->parked); 
  30.         current->vfork_done = &self->exited; 
  31.  
  32.         /* OK, tell user we're spawned, wait for stop or wakeup */ 
  33.         __set_current_state(TASK_UNINTERRUPTIBLE);         ------(8) 
  34.         create->result = current;                          ------(9) 
  35.         /* 
  36.          * Thread is going to call schedule(), do not preempt it, 
  37.          * or the creator may spend more time in wait_task_inactive(). 
  38.          */ 
  39.         preempt_disable(); 
  40.         complete(done);                                    ------(10) 
  41.         schedule_preempt_disabled();                       ------(11) 
  42.         preempt_enable();                                  ------(12) 
  43.  
  44.         ret = -EINTR; 
  45.         if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {------(13) 
  46.                 cgroup_kthread_ready(); 
  47.                 __kthread_parkme(self); 
  48.                 ret = threadfn(data);                      ------(14) 
  49.         } 
  50.         do_exit(ret);                                      ------(15) 
  1. 取出傳遞過來的線程創(chuàng)建信息
  2. 取出線程執(zhí)行函數(shù)
  3. 取出傳遞給線程執(zhí)行函數(shù)的參數(shù)
  4. 分配 kthread 結(jié)構(gòu)
  5. 獲得 done 完成量
  6. 賦值 self->threadfn 為線程執(zhí)行函數(shù)
  7. 賦值 self->data 為線程執(zhí)行函數(shù)的參數(shù)
  8. 設(shè)置內(nèi)核線程狀態(tài)為 TASK_UNINTERRUPTIBLE,但此時還沒有睡眠
  9. 用于返回當(dāng)前任務(wù)的 tsk
  10. 喚醒等待 done 完成量的任務(wù)
  11. 睡眠
  12. 喚醒的時候從此開始執(zhí)行
  13. 判斷 self->flags 是否為 KTHREAD_SHOULD_STOP (kthread_stop 會設(shè)置)
  14. 執(zhí)行真正的線程執(zhí)行函數(shù)
  15. 退出當(dāng)前任務(wù)

內(nèi)核線程的創(chuàng)建和運(yùn)行

現(xiàn)在我們知道 kthreadd 會從鏈表 kthread_create_list 中取出一個,然后調(diào)用 create_kthread 去創(chuàng)建一個內(nèi)核線程。kthreadd 是所有內(nèi)核線程的父線程,但是子線程如何把請求加入 kthread_create_list 鏈表,如何讓子線程運(yùn)行,還沒有深入介紹。

這里舉例看一個 peter 線程的創(chuàng)建和運(yùn)行的簡單例子:

  1. int my_kernel_thread(void *arg)   
  2. {   
  3.  printk("%s: %d\n", __func__);   
  4.   
  5.  return 0;   
  6. }   
  7. static int __init test_init_module(void)   
  8. {   
  9.  printk("%s:\n", __func__);   
  10.   
  11.  peter = kthread_create(my_kernel_thread, NULL"practice task");  ------(1) 
  12.   
  13.  if(!IS_ERR(peter))   
  14.   wake_up_process(peter);                                          ------(2) 
  15.   
  16.  return 0;   
  17. }   
  18.    
  19. static void __exit test_exit_module(void)   
  20. {   
  21.  printk("%s:\n", __func__);   
  22.  kthread_stop(peter);   
  23. }   
  24.    
  25. module_init(test_init_module);   
  26. module_exit(test_exit_module);   

很簡單,通過 kthread_create 函數(shù)創(chuàng)建內(nèi)核線程,然后通過 wake_up_process 喚醒線程,使之運(yùn)行。

下面我們結(jié)合上面的 kthreadd,剖析下內(nèi)核線程創(chuàng)建和運(yùn)行的本質(zhì)。

kthread_create

kthread_create 的調(diào)用流程是:kthread_create->kthread_create_on_node->__kthread_create_on_node

  1. struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data), 
  2.                                                     void *data, int node, 
  3.                                                     const char namefmt[], 
  4.                                                     va_list args) 
  5.         DECLARE_COMPLETION_ONSTACK(done);                               ------(1) 
  6.         struct task_struct *task; 
  7.         struct kthread_create_info *create = kmalloc(sizeof(*create), 
  8.                                                      GFP_KERNEL);       ------(2) 
  9.  
  10.         if (!create
  11.                 return ERR_PTR(-ENOMEM); 
  12.         create->threadfn = threadfn;                                    ------(3) 
  13.         create->data = data; 
  14.         create->node = node; 
  15.         create->done = &done; 
  16.  
  17.         spin_lock(&kthread_create_lock); 
  18.         list_add_tail(&create->list, &kthread_create_list);             ------(4) 
  19.         spin_unlock(&kthread_create_lock); 
  20.  
  21.         wake_up_process(kthreadd_task);                                 ------(5) 
  22.         /* 
  23.          * Wait for completion in killable state, for I might be chosen by 
  24.          * the OOM killer while kthreadd is trying to allocate memory for 
  25.          * new kernel thread. 
  26.          */ 
  27.         if (unlikely(wait_for_completion_killable(&done))) {            ------(6) 
  28.                 /* 
  29.                  * If I was SIGKILLed before kthreadd (or new kernel thread) 
  30.                  * calls complete(), leave the cleanup of this structure to 
  31.                  * that thread. 
  32.                  */ 
  33.                 if (xchg(&create->done, NULL)) 
  34.                         return ERR_PTR(-EINTR); 
  35.                 /* 
  36.                  * kthreadd (or new kernel thread) will call complete() 
  37.                  * shortly. 
  38.                  */ 
  39.                 wait_for_completion(&done); 
  40.         } 
  41.         task = create->result;                                          ------(7) 
  42.         if (!IS_ERR(task)) { 
  43.                 static const struct sched_param param = { .sched_priority = 0 }; 
  44.                 char name[TASK_COMM_LEN]; 
  45.  
  46.                 /* 
  47.                  * task is already visible to other tasks, so updating 
  48.                  * COMM must be protected. 
  49.                  */ 
  50.                 vsnprintf(name, sizeof(name), namefmt, args); 
  51.                 set_task_comm(task, name);                              ------(8) 
  52.                 /* 
  53.                  * root may have changed our (kthreadd's) priority or CPU mask. 
  54.                  * The kernel thread should not inherit these properties. 
  55.                  */ 
  56.                 sched_setscheduler_nocheck(task, SCHED_NORMAL, &param); ------(9) 
  57.                 set_cpus_allowed_ptr(task,                              ------(10) 
  58.                                      housekeeping_cpumask(HK_FLAG_KTHREAD)); 
  59.         } 
  60.         kfree(create); 
  61.         return task; 
  1. 靜態(tài)定義并初始化一個完成量
  2. 分配 kthread_create_info 結(jié)構(gòu)
  3. 填充 kthread_create_info 結(jié)構(gòu)
  4. 將 kthread_create_info 結(jié)構(gòu)添加到 kthread_create_list 鏈表
  5. 喚醒 kthreadd 來處理創(chuàng)建內(nèi)核線程請求
  6. 等待 kthreadd 創(chuàng)建完成這個內(nèi)核線程
  7. 獲得創(chuàng)建完成的內(nèi)核線程的 tsk
  8. 設(shè)置內(nèi)核線程的名字
  9. 設(shè)置調(diào)度策略和優(yōu)先級
  10. 設(shè)置 CPU 親和性

wake_up_process

上面通過 kthread_create 分配填充 kthread_create_info 結(jié)構(gòu),然后將該結(jié)構(gòu)添加到 kthread_create_list 鏈表,喚醒 kthreadd 去創(chuàng)建 peter 線程,然后調(diào)用 schedule_preempt_disabled 使 peter 線程睡眠。等待被 wake_up_process 喚醒,一旦執(zhí)行 wake_up_process,則喚醒 peter 線程,去調(diào)用它的執(zhí)行函數(shù) threadfn(data)。

為了更好理解,這里用一張圖來總結(jié)父線程 kthreadd 和其子線程 peter 的關(guān)系:

 

 

責(zé)任編輯:武曉燕 來源: 人人都是極客
相關(guān)推薦

2021-12-14 08:28:08

Java多線程線程

2022-01-02 08:43:46

Python

2022-02-07 11:01:23

ZooKeeper

2021-08-01 07:19:16

語言OpenrestyNginx

2022-06-30 22:53:18

數(shù)據(jù)結(jié)構(gòu)算法

2021-07-26 05:07:23

Swift萬花尺代碼

2021-10-26 10:40:26

代理模式虛擬

2021-12-04 22:05:02

Linux

2022-05-17 08:02:55

GoTryLock模式

2023-01-03 08:31:54

Spring讀取器配置

2021-05-11 08:54:59

建造者模式設(shè)計(jì)

2021-07-05 22:11:38

MySQL體系架構(gòu)

2021-07-06 08:59:18

抽象工廠模式

2022-08-26 09:29:01

Kubernetes策略Master

2023-11-28 08:29:31

Rust內(nèi)存布局

2021-07-02 09:45:29

MySQL InnoDB數(shù)據(jù)

2022-08-23 08:00:59

磁盤性能網(wǎng)絡(luò)

2021-07-02 08:51:29

源碼參數(shù)Thread

2021-07-16 22:43:10

Go并發(fā)Golang

2021-09-28 08:59:30

復(fù)原IP地址
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號