本節我們將從linux啟動的第一個進程說起,以及后面第一個進程是如何啟動1號進程,然后啟動2號進程。然后系統中所有的進程關系圖做個簡單的介紹
一、0號進程
0號進程,通常也被稱為idle進程,或者也稱為swapper進程。
0號進程是linux啟動的第一個進程,它的task_struct的comm字段為"swapper",所以也稱為swpper進程。
1 #define INIT_TASK_COMM "swapper"
當系統中所有的進程起來后,0號進程也就蛻化為idle進程,當一個core上沒有任務可運行時就會去運行idle進程。一旦運行idle進程則此core就可以進入低功耗模式了,在ARM上就是WFI。
我們本節重點關注是0號進程是如何啟動的。在linux內核中為0號進程專門定義了一個靜態的task_struct的結構,稱為init_task。
1 /*
2 * Set up the first task table, touch at your own risk!. Base=0, 3 * limit=0x1fffff (=2MB) 4 */
5 struct task_struct init_task 6 = { 7 #ifdef CONFIG_THREAD_INFO_IN_TASK 8 .thread_info = INIT_THREAD_INFO(init_task), 9 .stack_refcount = ATOMIC_INIT(1), 10 #endif
11 .state = 0, 12 .stack = init_stack, 13 .usage = ATOMIC_INIT(2), 14 .flags = PF_KTHREAD, 15 .prio = MAX_PRIO - 20, 16 .static_prio = MAX_PRIO - 20, 17 .normal_prio = MAX_PRIO - 20, 18 .policy = SCHED_NORMAL, 19 .cpus_allowed = CPU_MASK_ALL, 20 .nr_cpus_allowed= NR_CPUS, 21 .mm = NULL, 22 .active_mm = &init_mm, 23 .tasks = LIST_HEAD_INIT(init_task.tasks), 24 .ptraced = LIST_HEAD_INIT(init_task.ptraced), 25 .ptrace_entry = LIST_HEAD_INIT(init_task.ptrace_entry), 26 .real_parent = &init_task, 27 .parent = &init_task, 28 .children = LIST_HEAD_INIT(init_task.children), 29 .sibling = LIST_HEAD_INIT(init_task.sibling), 30 .group_leader = &init_task, 31 RCU_POINTER_INITIALIZER(real_cred, &init_cred), 32 RCU_POINTER_INITIALIZER(cred, &init_cred), 33 .comm = INIT_TASK_COMM, 34 .thread = INIT_THREAD, 35 .fs = &init_fs, 36 .files = &init_files, 37 .signal = &init_signals, 38 .sighand = &init_sighand, 39 .blocked = {{0}}, 40 .alloc_lock = __SPIN_LOCK_UNLOCKED(init_task.alloc_lock), 41 .journal_info = NULL, 42 INIT_CPU_TIMERS(init_task) 43 .pi_lock = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock), 44 .timer_slack_ns = 50000, /* 50 usec default slack */
45 .thread_pid = &init_struct_pid, 46 .thread_group = LIST_HEAD_INIT(init_task.thread_group), 47 .thread_node = LIST_HEAD_INIT(init_signals.thread_head), 48 }; 49 EXPORT_SYMBOL(init_task);
這個結構體中的成員都是靜態定義了,為了簡單說明,對這個結構做了簡單的刪減。同時我們只關注這個結構中的以下幾個字段,別的先不關注。
-
- .thread_info = INIT_THREAD_INFO(init_task), 這個結構在thread_info和內核棧的關系中有詳細的描述
- .stack = init_stack, init_stack就是內核棧的靜態的定義
- .comm = INIT_TASK_COMM, 0號進程的名稱。
在這么thread_info和stack都涉及到了Init_stack, 所以先看下init_stack在哪里設置的。
最終發現init_task是在鏈接腳本中定義的。
1 #define INIT_TASK_DATA(align) \
2 . = ALIGN(align); \ 3 __start_init_task = .; \ 4 init_thread_union = .; \ 5 init_stack = .; \ 6 KEEP(*(.data..init_task)) \ 7 KEEP(*(.data..init_thread_info)) \ 8 . = __start_init_task + THREAD_SIZE; \ 9 __end_init_task = .;
在鏈接腳本中定義了一個INIT_TASK_DATA的宏。
其中__start_init_task就是0號進程的內核棧的基地址,當然了init_thread_union=init_task=__start_init_task的。
而0號進程的內核棧的結束地址等於__start_init_task + THREAD_SIZE, THREAD_SIZE的大小在ARM64一般是16K,或者32K。則__end_init_task就是0號進程的內核棧的結束地址。
idle進程由系統自動創建, 運行在內核態,idle進程其pid=0,其前身是系統創建的第一個進程,也是唯一一個沒有通過fork或者kernel_thread產生的進程。完成加載系統后,演變為進程調度、交換。
二、Linux內核的啟動
熟悉linux內核的朋友都知道,linux內核的啟動 ,一般都是有bootloader來完成裝載,bootloader中會做一些硬件的初始化,然后會跳轉到linux內核的運行地址上去。
如果熟悉ARM架構的盆友也清楚,ARM64架構分為EL0, EL1, EL2, EL3。正常的啟動一般是從高特權模式向低特權模式啟動的。通常來說ARM64是先運行EL3,再EL2,然后從EL2就trap到EL1,也就是我們的Linux內核。
我們來看下Linux內核啟動的代碼。
1 代碼路徑:arch/arm64/kernel/head.S文件中 2 /*
3 * Kernel startup entry point. 4 * --------------------------- 5 * 6 * The requirements are: 7 * MMU = off, D-cache = off, I-cache = on or off, 8 * x0 = physical address to the FDT blob. 9 * 10 * This code is mostly position independent so you call this at 11 * __pa(PAGE_OFFSET + TEXT_OFFSET). 12 * 13 * Note that the callee-saved registers are used for storing variables 14 * that are useful before the MMU is enabled. The allocations are described 15 * in the entry routines. 16 */
17
18 /*
19 * The following callee saved general purpose registers are used on the 20 * primary lowlevel boot path: 21 * 22 * Register Scope Purpose 23 * x21 stext() .. start_kernel() FDT pointer passed at boot in x0 24 * x23 stext() .. start_kernel() physical misalignment/KASLR offset 25 * x28 __create_page_tables() callee preserved temp register 26 * x19/x20 __primary_switch() callee preserved temp registers 27 */
28 ENTRY(stext) 29 bl preserve_boot_args 30 bl el2_setup // Drop to EL1, w0=cpu_boot_mode
31 adrp x23, __PHYS_OFFSET 32 and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
33 bl set_cpu_boot_mode_flag 34 bl __create_page_tables 35 /*
36 * The following calls CPU setup code, see arch/arm64/mm/proc.S for 37 * details. 38 * On return, the CPU will be ready for the MMU to be turned on and 39 * the TCR will have been set. 40 */
41 bl __cpu_setup // initialise processor
42 b __primary_switch 43 ENDPROC(stext)
上面就是內核在調用start_kernel之前做的主要工作了。
-
- preserve_boot_args用來保留bootloader傳遞的參數,比如ARM上通常的dtb的地址
- el2_setup:從注釋上來看是, 用來trap到EL1,說明我們在運行此指令前還在EL2
- __create_page_tables: 用來創建頁表,linux才有的是頁面管理物理內存的,在使用虛擬地址之前需要設置好頁面,然后會打開MMU。目前還是運行在物理地址上的
- __primary_switch: 主要任務是完成MMU的打開工作
1 __primary_switch: 2 adrp x1, init_pg_dir 3 bl __enable_mmu 4 ldr x8, =__primary_switched 5 adrp x0, __PHYS_OFFSET 6 br x8 7 ENDPROC(__primary_switch)
-
- 主要是調用__enable_mmu來打開mmu,之后我們訪問的就是虛擬地址了
- 調用__primary_switched來設置0號進程的運行內核棧,然后調用start_kernel函數
1 /* 2 * The following fragment of code is executed with the MMU enabled. 3 * 4 * x0 = __PHYS_OFFSET 5 */ 6 __primary_switched: 7 adrp x4, init_thread_union 8 add sp, x4, #THREAD_SIZE 9 adr_l x5, init_task 10 msr sp_el0, x5 // Save thread_info 11 12 adr_l x8, vectors // load VBAR_EL1 with virtual 13 msr vbar_el1, x8 // vector table address 14 isb 15 16 stp xzr, x30, [sp, #-16]! 17 mov x29, sp 18 19 str_l x21, __fdt_pointer, x5 // Save FDT pointer 20 21 ldr_l x4, kimage_vaddr // Save the offset between 22 sub x4, x4, x0 // the kernel virtual and 23 str_l x4, kimage_voffset, x5 // physical mappings 24 25 // Clear BSS 26 adr_l x0, __bss_start 27 mov x1, xzr 28 adr_l x2, __bss_stop 29 sub x2, x2, x0 30 bl __pi_memset 31 dsb ishst // Make zero page visible to PTW 32 33 add sp, sp, #16 34 mov x29, #0 35 mov x30, #0 36 b start_kernel 37 ENDPROC(__primary_switched)
- init_thread_union就是我們在鏈接腳本中定義的,也就是0號進程的內核棧的棧底
- add sp, x4, #THREAD_SIZE: 設置堆棧指針SP的值,就是內核棧的棧底+THREAD_SIZE的大小。現在SP指到了內核棧的頂端
- 最終通過b start_kernel就跳轉到我們熟悉的linux內核入口處了。
至此0號進程就已經運行起來了。
三、1號進程
3.1 1號進程的創建
當一條b start_kernel指令運行后,內核就開始的內核的全面初始化操作。
1 asmlinkage __visible void __init start_kernel(void) 2 { 3 char *command_line; 4 char *after_dashes; 5
6 set_task_stack_end_magic(&init_task); 7 smp_setup_processor_id(); 8 debug_objects_early_init(); 9
10 cgroup_init_early(); 11
12 local_irq_disable(); 13 early_boot_irqs_disabled = true; 14
15 /*
16 * Interrupts are still disabled. Do necessary setups, then 17 * enable them. 18 */
19 boot_cpu_init(); 20 page_address_init(); 21 pr_notice("%s", linux_banner); 22 setup_arch(&command_line); 23 /*
24 * Set up the the initial canary and entropy after arch 25 * and after adding latent and command line entropy. 26 */
27 add_latent_entropy(); 28 add_device_randomness(command_line, strlen(command_line)); 29 boot_init_stack_canary(); 30 mm_init_cpumask(&init_mm); 31 setup_command_line(command_line); 32 setup_nr_cpu_ids(); 33 setup_per_cpu_areas(); 34 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
35 boot_cpu_hotplug_init(); 36
37 build_all_zonelists(NULL); 38 page_alloc_init(); 39 。。。。。。。 40 acpi_subsystem_init(); 41 arch_post_acpi_subsys_init(); 42 sfi_init_late(); 43
44 /* Do the rest non-__init'ed, we're now alive */
45 arch_call_rest_init(); 46 } 47
48 void __init __weak arch_call_rest_init(void) 49 { 50 rest_init();
start_kernel函數就是內核各個重要子系統的初始化,比如mm, cpu, sched, irq等等。最后會調用一個rest_init剩余部分初始化,start_kernel在其最后一個函數rest_init的調用中,會通過kernel_thread來生成一個內核進程,后者則會在新進程環境下調 用kernel_init函數,kernel_init一個讓人感興趣的地方在於它會調用run_init_process來執行根文件系統下的 /sbin/init等程序。
1 noinline void __ref rest_init(void) 2 { 3 struct task_struct *tsk; 4 int pid; 5
6 rcu_scheduler_starting(); 7 /*
8 * We need to spawn init first so that it obtains pid 1, however 9 * the init task will end up wanting to create kthreads, which, if 10 * we schedule it before we create kthreadd, will OOPS. 11 */
12 pid = kernel_thread(kernel_init, NULL, CLONE_FS); 13 /*
14 * Pin init on the boot CPU. Task migration is not properly working 15 * until sched_init_smp() has been run. It will set the allowed 16 * CPUs for init to the non isolated CPUs. 17 */
18 rcu_read_lock(); 19 tsk = find_task_by_pid_ns(pid, &init_pid_ns); 20 set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id())); 21 rcu_read_unlock(); 22
23 numa_default_policy(); 24 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 25 rcu_read_lock(); 26 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); 27 rcu_read_unlock(); 28
29 /*
30 * Enable might_sleep() and smp_processor_id() checks. 31 * They cannot be enabled earlier because with CONFIG_PREEMPT=y 32 * kernel_thread() would trigger might_sleep() splats. With 33 * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled 34 * already, but it's stuck on the kthreadd_done completion. 35 */
36 system_state = SYSTEM_SCHEDULING; 37
38 complete(&kthreadd_done); 39
40 }
在這個rest_init函數中我們只關系兩點:
-
- pid = kernel_thread(kernel_init, NULL, CLONE_FS);
- pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
1 /*
2 * Create a kernel thread. 3 */
4 pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) 5 { 6 return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn, 7 (unsigned long)arg, NULL, NULL, 0); 8 }
很明顯這是創建了兩個內核線程,而kernel_thread最終會調用do_fork根據參數的不同來創建一個進程或者內核線程。關系do_fork的實現我們在后面會做詳細的介紹。當內核線程創建成功后就會調用設置的回調函數。
當kernel_thread(kernel_init)成功返回后,就會調用kernel_init內核線程,其實這時候1號進程已經產生了。1號進程的執行函數就是kernel_init, 這個函數被定義init/main.c中,接下來看下kernel_init主要做什么事情。
1 static int __ref kernel_init(void *unused) 2 { 3 int ret; 4
5 kernel_init_freeable(); 6 /* need to finish all async __init code before freeing the memory */
7 async_synchronize_full(); 8 ftrace_free_init_mem(); 9 free_initmem(); 10 mark_readonly(); 11
12 /*
13 * Kernel mappings are now finalized - update the userspace page-table 14 * to finalize PTI. 15 */
16 pti_finalize(); 17
18 system_state = SYSTEM_RUNNING; 19 numa_default_policy(); 20
21 rcu_end_inkernel_boot(); 22
23 if (ramdisk_execute_command) { 24 ret = run_init_process(ramdisk_execute_command); 25 if (!ret) 26 return 0; 27 pr_err("Failed to execute %s (error %d)\n", 28 ramdisk_execute_command, ret); 29 } 30
31 /*
32 * We try each of these until one succeeds. 33 * 34 * The Bourne shell can be used instead of init if we are 35 * trying to recover a really broken machine. 36 */
37 if (execute_command) { 38 ret = run_init_process(execute_command); 39 if (!ret) 40 return 0; 41 panic("Requested init %s failed (error %d).", 42 execute_command, ret); 43 } 44 if (!try_to_run_init_process("/sbin/init") ||
45 !try_to_run_init_process("/etc/init") ||
46 !try_to_run_init_process("/bin/init") ||
47 !try_to_run_init_process("/bin/sh")) 48 return 0; 49
50 panic("No working init found. Try passing init= option to kernel. "
51 "See Linux Documentation/admin-guide/init.rst for guidance."); 52 }
-
- kernel_init_freeable函數中就會做各種外設驅動的初始化。
- 最主要的工作就是通過execve執行/init可以執行文件。它按照配置文件/etc/initab的要求,完成系統啟動工作,創建編號為1號、2號...的若干終端注冊進程getty。每個getty進程設置其進程組標識號,並監視配置到系統終端的接口線路。當檢測到來自終端的連接信號時,getty進程將通過函數execve()執行注冊程序login,此時用戶就可輸入注冊名和密碼進入登錄過程,如果成功,由login程序再通過函數execv()執行shell,該shell進程接收getty進程的pid,取代原來的getty進程。再由shell直接或間接地產生其他進程。
我們通常將init稱為1號進程,其實在剛才kernel_init的時候1號線程已經創建成功,也可以理解kernel_init是1號進程的內核態,而我們所熟知的init進程是用戶態的,調用execve函數之前屬於內核態,調用之后就屬於用戶態了,執行的代碼段與0號進程不在一樣。
1號內核線程負責執行內核的部分初始化工作及進行系統配置,並創建若干個用於高速緩存和虛擬主存管理的內核線程。
至此1號進程就完美的創建成功了,而且也成功執行了init可執行文件。
3.2 init進程
隨后,1號進程調用do_execve運行可執行程序init,並演變成用戶態1號進程,即init進程。
init進程是linux內核啟動的第一個用戶級進程。init有許多很重要的任務,比如像啟動getty(用於用戶登錄)、實現運行級別、以及處理孤立進程。
它按照配置文件/etc/initab的要求,完成系統啟動工作,創建編號為1號、2號…的若干終端注冊進程getty。
每個getty進程設置其進程組標識號,並監視配置到系統終端的接口線路。當檢測到來自終端的連接信號時,getty進程將通過函數do_execve()執行注冊程序login,此時用戶就可輸入注冊名和密碼進入登錄過程,如果成功,由login程序再通過函數execv()執行shell,該shell進程接收getty進程的pid,取代原來的getty進程。再由shell直接或間接地產生其他進程。
上述過程可描述為:0號進程->1號內核進程->1號用戶進程(init進程)->getty進程->shell進程
注意,上述過程描述中提到:1號內核進程調用執行init函數並演變成1號用戶態進程(init進程),這里前者是init是函數,后者是進程。兩者容易混淆,區別如下:
-
- kernel_init函數在內核態運行,是內核代碼
- init進程是內核啟動並運行的第一個用戶進程,運行在用戶態下。
- 一號內核進程調用execve()從文件/etc/inittab中加載可執行程序init並執行,這個過程並沒有使用調用do_fork(),因此兩個進程都是1號進程。
當內核啟動了自己之后(已被裝入內存、已經開始運行、已經初始化了所有的設備驅動程序和數據結構等等),通過啟動用戶級程序init來完成引導進程的內核部分。因此,init總是第一個進程(它的進程號總是1)。
當init開始運行,它通過執行一些管理任務來結束引導進程,例如檢查文件系統、清理/tmp、啟動各種服務以及為每個終端和虛擬控制台啟動getty,在這些地方用戶將登錄系統。
在系統完全起來之后,init為每個用戶已退出的終端重啟getty(這樣下一個用戶就可以登錄)。init同樣也收集孤立的進程:當一個進程啟動了一個子進程並且在子進程之前終止了,這個子進程立刻成為init的子進程。對於各種技術方面的原因來說這是很重要的,知道這些也是有好處的,因為這便於理解進程列表和進程樹圖。init的變種很少。絕大多數Linux發行版本使用sysinit(由Miguel van Smoorenburg著),它是基於System V的init設計。UNIX的BSD版本有一個不同的init。最主要的不同在於運行級別:System V有而BSD沒有(至少是傳統上說)。這種區別並不是主要的。在此我們僅討論sysvinit。 配置init以啟動getty:/etc/inittab文件。
3.3 init程序
1號進程通過execve執行init程序來進入用戶空間,成為init進程,那么這個init在哪里呢
內核在幾個位置上來查尋init,這幾個位置以前常用來放置init,但是init的最適當的位置(在Linux系統上)是/sbin/init。如果內核沒有找到init,它就會試着運行/bin/sh,如果還是失敗了,那么系統的啟動就宣告失敗了。
因此init程序是一個可以又用戶編寫的進程, 如果希望看init程序源碼的朋友,可以參見。
init包 | 說明 | 學習鏈接 |
sysvinit | 早期一些版本使用的初始化進程工具, 目前在逐漸淡出linux歷史舞台, sysvinit 就是 system V 風格的 init 系統,顧名思義,它源於 System V 系列 UNIX。它提供了比 BSD 風格 init 系統更高的靈活性。是已經風行了幾十年的 UNIX init 系統,一直被各類 Linux 發行版所采用。 |
淺析 Linux 初始化 init 系統(1):sysvinit |
upstart | debian, Ubuntu等系統使用的initdaemon | 淺析 Linux 初始化 init 系統(2): UpStart |
systemd | Systemd 是 Linux 系統中最新的初始化系統(init),它主要的設計目標是克服 sysvinit 固有的缺點,提高系統的啟動速度 | 淺析 Linux 初始化 init 系統(3) Systemd |
Ubuntu等使用deb包的系統可以通過dpkg -S查看程序所在的包
CentOS等使用rpm包的系統可以通過rpm -qf查看系統程序所在的包
四、2號進程
2號進程,也是由0號進程創建的。而且2號進程是所有內核線程父進程。
2號進程就是剛才rest_init中創建的另外一個內核線程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
當kernel_thread(kthreadd)返回時,2號進程已經創建成功了。而且會回調kthreadd函數。
1 int kthreadd(void *unused) 2 { 3 struct task_struct *tsk = current; 4
5 /* Setup a clean context for our children to inherit. */
6 set_task_comm(tsk, "kthreadd"); 7 ignore_signals(tsk); 8 set_cpus_allowed_ptr(tsk, cpu_all_mask); 9 set_mems_allowed(node_states[N_MEMORY]); 10
11 current->flags |= PF_NOFREEZE; 12 cgroup_init_kthreadd(); 13
14 for (;;) { 15 set_current_state(TASK_INTERRUPTIBLE); 16 if (list_empty(&kthread_create_list)) 17 schedule(); 18 __set_current_state(TASK_RUNNING); 19
20 spin_lock(&kthread_create_lock); 21 while (!list_empty(&kthread_create_list)) { 22 struct kthread_create_info *create; 23
24 create = list_entry(kthread_create_list.next, 25 struct kthread_create_info, list); 26 list_del_init(&create->list); 27 spin_unlock(&kthread_create_lock); 28
29 create_kthread(create); 30
31 spin_lock(&kthread_create_lock); 32 } 33 spin_unlock(&kthread_create_lock); 34 } 35
36 return 0; 37 }
這段代碼大概的意思也很簡單明顯;
-
- 設置當前進程的名字為"kthreadd",也就是task_struct的comm字段
- 然后就是while循環,設置當前的進程的狀態是TASK_INTERRUPTIBLE是可以中斷的
- 判斷kthread_create_list鏈表是不是空,如果是空則就調度出去,讓出cpu
- 如果不是空,則從鏈表中取出一個,然后調用kthread_create去創建一個內核線程。
- 所以說所有的內核線程的父進程都是2號進程,也就是kthreadd。
五、總結
linux啟動的第一個進程是0號進程,是靜態創建的,稱為idle進程或者swapper進程。
在0號進程啟動后會接連創建兩個進程,分別是1號進程和2和進程。
1號進程最終會使用execve函數去調用可init可執行文件,init進程最終會去創建所有的應用進程,所以被稱為inti進程。
2號進程會在內核中負責創建所有的內核線程,被稱為kthreadd進程。
所以說0號進程是1號和2號進程的父進程;1號進程是所有用戶態進程的父進程;2號進程是所有內核線程的父進程。
我們通過ps命令就可以詳細的觀察到這一現象。
1 root@ubuntu:zhuxl$ ps -eF 2 UID PID PPID C SZ RSS PSR STIME TTY TIME CMD 3 root 1 0 0 56317 5936 2 Feb16 ? 00:00:04 /sbin/init 4 root 2 0 0 0 0 1 Feb16 ? 00:00:00 [kthreadd]
上面很清晰的顯示:PID=1的進程是init,PID=2的進程是kthreadd。而他們倆的父進程PPID=0,也就是0號進程。
1 UID PID PPID C SZ RSS PSR STIME TTY TIME CMD 2 root 4 2 0 0 0 0 Feb16 ? 00:00:00 [kworker/0:0H] 3 root 6 2 0 0 0 0 Feb16 ? 00:00:00 [mm_percpu_wq] 4 root 7 2 0 0 0 0 Feb16 ? 00:00:10 [ksoftirqd/0] 5 root 8 2 0 0 0 1 Feb16 ? 00:02:11 [rcu_sched] 6 root 9 2 0 0 0 0 Feb16 ? 00:00:00 [rcu_bh] 7 root 10 2 0 0 0 0 Feb16 ? 00:00:00 [migration/0] 8 root 11 2 0 0 0 0 Feb16 ? 00:00:00 [watchdog/0] 9 root 12 2 0 0 0 0 Feb16 ? 00:00:00 [cpuhp/0] 10 root 13 2 0 0 0 1 Feb16 ? 00:00:00 [cpuhp/1] 11 root 14 2 0 0 0 1 Feb16 ? 00:00:00 [watchdog/1] 12 root 15 2 0 0 0 1 Feb16 ? 00:00:00 [migration/1] 13 root 16 2 0 0 0 1 Feb16 ? 00:00:11 [ksoftirqd/1] 14 root 18 2 0 0 0 1 Feb16 ? 00:00:00 [kworker/1:0H] 15 root 19 2 0 0 0 2 Feb16 ? 00:00:00 [cpuhp/2] 16 root 20 2 0 0 0 2 Feb16 ? 00:00:00 [watchdog/2] 17 root 21 2 0 0 0 2 Feb16 ? 00:00:00 [migration/2] 18 root 22 2 0 0 0 2 Feb16 ? 00:00:11 [ksoftirqd/2] 19 root 24 2 0 0 0 2 Feb16 ? 00:00:00 [kworker/2:0H]
再來看下,所有內核線性的PPI=2, 也就是所有內核線性的父進程都是kthreadd進程。
1 UID PID PPID C SZ RSS PSR STIME TTY TIME CMD 2 root 362 1 0 21574 6136 2 Feb16 ? 00:00:03 /lib/systemd/systemd-journald 3 root 375 1 0 11906 2760 3 Feb16 ? 00:00:01 /lib/systemd/systemd-udevd 4 systemd+ 417 1 0 17807 2116 3 Feb16 ? 00:00:02 /lib/systemd/systemd-resolved 5 systemd+ 420 1 0 35997 788 3 Feb16 ? 00:00:00 /lib/systemd/systemd-timesyncd 6 root 487 1 0 43072 6060 0 Feb16 ? 00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers 7 root 489 1 0 8268 2036 2 Feb16 ? 00:00:00 /usr/sbin/cron -f 8 root 490 1 0 1138 548 0 Feb16 ? 00:00:01 /usr/sbin/acpid 9 root 491 1 0 106816 3284 1 Feb16 ? 00:00:00 /usr/sbin/ModemManager 10 root 506 1 0 27628 2132 2 Feb16 ? 00:00:01 /usr/sbin/irqbalance --foreground
所有用戶態的進程的父進程PPID=1,也就是1號進程都是他們的父進程。
六、參考文章
https://dragonkingzhu.blog.csdn.net/article/details/104363832?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-5.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-5.control
https://blog.csdn.net/lyl194458/article/details/93201849?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242