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