成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇學會內核線程的創建和運行

系統
通過 ps 命令可以看到紅色方框標出的都是父進程為2號進程的內核線程,2號進程即藍色方框標出的進程 kthreadd,1號進程是綠色方框標出的進程 init,它們的父進程號都是0。

[[407481]]

上面講完了用戶進程/線程的創建,這里我們看下內核是如何創建線程的。

通過 ps 命令可以看到紅色方框標出的都是父進程為2號進程的內核線程,2號進程即藍色方框標出的進程 kthreadd,1號進程是綠色方框標出的進程 init,它們的父進程號都是0。

下面我們一起看下,內核的0號,1號,2號線程的創建過程。

0號線程

linux 內核中為0號進程專門定義了一個靜態的 task_struct 的結構,稱為 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); 

這個結構體中的成員都是靜態定義的,這里看幾個比較重要的變量:

  • .thread_info = INIT_THREAD_INFO(init_task), 這個結構在 “task_struct, thread_info 和內核棧 sp 的關系” 中有詳細的描述
  • .stack = init_stack, init_stack 是內核棧的靜態定義,定義在鏈接腳本里
  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號進程的內核棧的基地址,__end_init_task 是0號進程的內核棧的結束地址。注意:__start_init_task = init_thread_union = init_task

  • .comm = INIT_TASK_COMM, 0號進程的名稱是 swapper

下面結合 Linux 內核啟動的部分代碼,看下是如何調用 __primary_switched 來設置0號進程的運行內核棧:

  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號進程的內核棧的基地址
  2. 設置堆棧指針 sp 的值,就是內核棧的棧底 + THREAD_SIZE的大小。現在 sp 指到了內核棧的頂端
  3. 跳轉到 linux 內核的入口

至此0號進程就已經運行起來了,0號進程,通常也被稱為 idle 進程,也稱為 swapper 進程。當系統中所有的進程起來后,0號進程也就蛻化為 idle 進程,當一個 CPU 上沒有任務可運行時就會去運行 idle 進程。一旦運行 idle 進程,則此 CPU 就可以進入低功耗模式了,在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); 

這里會創建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 最終會調用 do_fork 根據參數的不同來創建一個進程或者內核線程。do_fork 的實現我們在后面會做詳細的介紹。當內核線程創建成功后就會調用設置的回調函數。

當 kernel_thread(kernel_init, NULL, CLONE_FS) 返回時,1號進程已經創建成功了。而且會回調 kernel_init 函數,接下來看下 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,執行init可執行文件。init 就是1號線程,它最終會去創建所有的應用進程。確切來講,init 進程是用戶態的,kernel_init 是1號進程的內核態。

2號線程

上面講到的 kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 就是用來創建2號線程,2號線程的執行函數是 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. 通過設置 task_struct 的 comm 字段,使當前進程的名字為"kthreadd"
  2. 設置當前的進程的狀態是 TASK_INTERRUPTIBLE
  3. 如果鏈表 kthread_create_list 是空,說明沒有創建內核線程的請求,則直接調用 schedule 進行睡眠
  4. 如果不是空,while循環,從鏈表中取出一個,然后調用 create_kthread 去創建一個內核線程

所以2號線程 kthreadd 通過 create_kthread 去創建內核其它的線程,可謂是內核線程的祖先。

至此,我們已經知道 Linux 啟動的第一個線程,0號線程是靜態創建的。在0號線程啟動后會接連創建兩個線程,分別是1號線程和2和線程。1號進程最終會去調用可init可執行文件,init進程最終會去創建所有的應用進程。2號進程會在內核中負責創建所有的內核線程。所以說0號進程是1號和2號進程的父進程,1號進程是所有用戶態進程的父進程,2號進程是所有內核線程的父進程。

kthread 處理流程

上面 kthreadd 線程會循環查看鏈表 kthread_create_list,如果有線程的創建申請,則從鏈表中取出一個,然后調用 create_kthread 去創建一個內核線程。

  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 內核線程創建的內核線程的執行函數是 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. 取出傳遞過來的線程創建信息
  2. 取出線程執行函數
  3. 取出傳遞給線程執行函數的參數
  4. 分配 kthread 結構
  5. 獲得 done 完成量
  6. 賦值 self->threadfn 為線程執行函數
  7. 賦值 self->data 為線程執行函數的參數
  8. 設置內核線程狀態為 TASK_UNINTERRUPTIBLE,但此時還沒有睡眠
  9. 用于返回當前任務的 tsk
  10. 喚醒等待 done 完成量的任務
  11. 睡眠
  12. 喚醒的時候從此開始執行
  13. 判斷 self->flags 是否為 KTHREAD_SHOULD_STOP (kthread_stop 會設置)
  14. 執行真正的線程執行函數
  15. 退出當前任務

內核線程的創建和運行

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

這里舉例看一個 peter 線程的創建和運行的簡單例子:

  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 函數創建內核線程,然后通過 wake_up_process 喚醒線程,使之運行。

下面我們結合上面的 kthreadd,剖析下內核線程創建和運行的本質。

kthread_create

kthread_create 的調用流程是: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. 靜態定義并初始化一個完成量
  2. 分配 kthread_create_info 結構
  3. 填充 kthread_create_info 結構
  4. 將 kthread_create_info 結構添加到 kthread_create_list 鏈表
  5. 喚醒 kthreadd 來處理創建內核線程請求
  6. 等待 kthreadd 創建完成這個內核線程
  7. 獲得創建完成的內核線程的 tsk
  8. 設置內核線程的名字
  9. 設置調度策略和優先級
  10. 設置 CPU 親和性

wake_up_process

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

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

 

 

責任編輯:武曉燕 來源: 人人都是極客
相關推薦

2021-12-14 08:28:08

Java多線程線程

2022-02-07 11:01:23

ZooKeeper

2022-01-02 08:43:46

Python

2021-10-26 10:40:26

代理模式虛擬

2021-12-04 22:05:02

Linux

2022-05-17 08:02:55

GoTryLock模式

2022-06-30 22:53:18

數據結構算法

2021-08-01 07:19:16

語言OpenrestyNginx

2021-07-26 05:07:23

Swift萬花尺代碼

2021-07-02 08:51:29

源碼參數Thread

2021-09-28 08:59:30

復原IP地址

2021-10-14 10:22:19

逃逸JVM性能

2022-04-12 08:30:52

回調函數代碼調試

2021-10-27 09:59:35

存儲

2021-07-16 22:43:10

Go并發Golang

2023-03-13 21:38:08

TCP數據IP地址

2023-11-01 09:07:01

Spring裝配源碼

2021-04-29 10:18:18

循環依賴數組

2022-03-11 10:21:30

IO系統日志

2022-10-20 07:39:26

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜影院黄| 羞羞网站在线免费观看 | 国产高清一区二区三区 | 久久成人18免费网站 | 成人国产精品久久 | 亚洲九九色 | 免费的一级视频 | 亚洲人成人一区二区在线观看 | 欧美性吧 | a级片在线 | 成人在线视频网站 | 欧美视频一区二区三区 | 九九免费在线视频 | 国产成人在线免费 | 亚洲最大看片网站 | 日韩在线一区二区三区 | 国产在线视频在线观看 | 日韩高清一区二区 | 少妇性l交大片免费一 | 国产精品18久久久久久白浆动漫 | 精品一区二区三区在线观看 | 欧美日韩精品中文字幕 | 精品久久久网站 | 久久久久久国 | 国产精品毛片一区二区在线看 | 国产精品久久久久久久岛一牛影视 | 久久区二区 | 国产欧美在线观看 | 亚洲 成人 av| 老子午夜影院 | 草久久 | 国产精品99久久久久久动医院 | 国产精品久久久久久久久久久新郎 | 欧美一区免费在线观看 | 精品国产18久久久久久二百 | 亚洲另类自拍 | 亚洲网站在线观看 | 欧美精品v| 国产精品一区二区三区四区 | 91免费在线看 | 一级免费毛片 |