溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

linux中0號(hào)進(jìn)程的含義是什么

發(fā)布時(shí)間:2023-03-16 09:40:56 來(lái)源:億速云 閱讀:120 作者:iii 欄目:建站服務(wù)器

這篇文章主要介紹“l(fā)inux中0號(hào)進(jìn)程的含義是什么”的相關(guān)知識(shí),小編通過(guò)實(shí)際案例向大家展示操作過(guò)程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“l(fā)inux中0號(hào)進(jìn)程的含義是什么”文章能幫助大家解決問(wèn)題。

在linux中,0號(hào)進(jìn)程是指idle進(jìn)程,是linux啟動(dòng)的第一個(gè)進(jìn)程;它的task_struct的comm字段為“swapper”,所以也稱為swpper進(jìn)程。0號(hào)進(jìn)程是唯一一個(gè)沒(méi)有通過(guò)fork或者kernel_thread產(chǎn)生的進(jìn)程,因?yàn)閕nit_task是靜態(tài)變量(初始化了的全局變量),其他進(jìn)程的PCB都是fork或者kernel_thread動(dòng)態(tài)申請(qǐng)內(nèi)存創(chuàng)建的。

一、0號(hào)進(jìn)程

0號(hào)進(jìn)程,通常也被稱為idle進(jìn)程,或者也稱為swapper進(jìn)程。

每個(gè)進(jìn)程都有一個(gè)進(jìn)程控制塊PCB(Process Control Block),PCB的數(shù)據(jù)結(jié)構(gòu)類型是struct task_struct。idle進(jìn)程對(duì)應(yīng)的PCB是 struct task_struct init_task。

idle進(jìn)程是唯一一個(gè)沒(méi)有通過(guò)fork或者kernel_thread產(chǎn)生的進(jìn)程,因?yàn)?init_task 是靜態(tài)變量(初始化了的全局變量),其他進(jìn)程的PCB都是fork或者kernel_thread動(dòng)態(tài)申請(qǐng)內(nèi)存創(chuàng)建的。

每個(gè)進(jìn)程都有對(duì)應(yīng)的一個(gè)函數(shù),idle進(jìn)程的函數(shù)是 start_kernel(),因?yàn)檫M(jìn)入該函數(shù)前,棧指針SP已經(jīng)指向 init_task 的棧頂了,處于什么進(jìn)程,看SP指向哪個(gè)進(jìn)程的棧。

0號(hào)進(jìn)程是linux啟動(dòng)的第一個(gè)進(jìn)程,它的task_struct的comm字段為"swapper",所以也稱為swpper進(jìn)程。

#define INIT_TASK_COMM "swapper"

當(dāng)系統(tǒng)中所有的進(jìn)程起來(lái)后,0號(hào)進(jìn)程也就蛻化為idle進(jìn)程,當(dāng)一個(gè)core上沒(méi)有任務(wù)可運(yùn)行時(shí)就會(huì)去運(yùn)行idle進(jìn)程。一旦運(yùn)行idle進(jìn)程則此core就可以進(jìn)入低功耗模式了,在ARM上就是WFI。

我們本節(jié)重點(diǎn)關(guān)注是0號(hào)進(jìn)程是如何啟動(dòng)的。在linux內(nèi)核中為0號(hào)進(jìn)程專門定義了一個(gè)靜態(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);

這個(gè)結(jié)構(gòu)體中的成員都是靜態(tài)定義了,為了簡(jiǎn)單說(shuō)明,對(duì)這個(gè)結(jié)構(gòu)做了簡(jiǎn)單的刪減。同時(shí)我們只關(guān)注這個(gè)結(jié)構(gòu)中的以下幾個(gè)字段,別的先不關(guān)注。

  • .thread_info = INIT_THREAD_INFO(init_task), 這個(gè)結(jié)構(gòu)在thread_info和內(nèi)核棧的關(guān)系中有詳細(xì)的描述

  • .stack = init_stack, init_stack就是內(nèi)核棧的靜態(tài)的定義

  • .comm = INIT_TASK_COMM, 0號(hào)進(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 = .;

在鏈接腳本中定義了一個(gè)INIT_TASK_DATA的宏。

其中__start_init_task就是0號(hào)進(jìn)程的內(nèi)核棧的基地址,當(dāng)然了init_thread_union=init_task=__start_init_task的。

而0號(hào)進(jìn)程的內(nèi)核棧的結(jié)束地址等于__start_init_task + THREAD_SIZE, THREAD_SIZE的大小在ARM64一般是16K,或者32K。則__end_init_task就是0號(hào)進(jìn)程的內(nèi)核棧的結(jié)束地址。

idle進(jìn)程由系統(tǒng)自動(dòng)創(chuàng)建, 運(yùn)行在內(nèi)核態(tài),idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,也是唯一一個(gè)沒(méi)有通過(guò)fork或者kernel_thread產(chǎn)生的進(jìn)程。完成加載系統(tǒng)后,演變?yōu)檫M(jìn)程調(diào)度、交換。

二、Linux內(nèi)核的啟動(dòng)

熟悉linux內(nèi)核的朋友都知道,linux內(nèi)核的啟動(dòng) ,一般都是有bootloader來(lái)完成裝載,bootloader中會(huì)做一些硬件的初始化,然后會(huì)跳轉(zhuǎn)到linux內(nèi)核的運(yùn)行地址上去。

如果熟悉ARM架構(gòu)的盆友也清楚,ARM64架構(gòu)分為EL0, EL1, EL2, EL3。正常的啟動(dòng)一般是從高特權(quán)模式向低特權(quán)模式啟動(dòng)的。通常來(lái)說(shuō)ARM64是先運(yùn)行EL3,再EL2,然后從EL2就trap到EL1,也就是我們的Linux內(nèi)核。

我們來(lái)看下Linux內(nèi)核啟動(dòng)的代碼。

代碼路徑: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_switch
ENDPROC(stext)

上面就是內(nèi)核在調(diào)用start_kernel之前做的主要工作了。

preserve_boot_args用來(lái)保留bootloader傳遞的參數(shù),比如ARM上通常的dtb的地址

el2_setup:從注釋上來(lái)看是, 用來(lái)trap到EL1,說(shuō)明我們?cè)谶\(yùn)行此指令前還在EL2

__create_page_tables: 用來(lái)創(chuàng)建頁(yè)表,linux才有的是頁(yè)面管理物理內(nèi)存的,在使用虛擬地址之前需要設(shè)置好頁(yè)面,然后會(huì)打開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    x8
ENDPROC(__primary_switch)

主要是調(diào)用__enable_mmu來(lái)打開mmu,之后我們?cè)L問(wèn)的就是虛擬地址了

調(diào)用__primary_switched來(lái)設(shè)置0號(hào)進(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_kernel
ENDPROC(__primary_switched)

init_thread_union就是我們?cè)阪溄幽_本中定義的,也就是0號(hào)進(jìn)程的內(nèi)核棧的棧底

add sp, x4, #THREAD_SIZE: 設(shè)置堆棧指針SP的值,就是內(nèi)核棧的棧底+THREAD_SIZE的大小。現(xiàn)在SP指到了內(nèi)核棧的頂端

最終通過(guò)b start_kernel就跳轉(zhuǎn)到我們熟悉的linux內(nèi)核入口處了?! ≈链?號(hào)進(jìn)程就已經(jīng)運(yùn)行起來(lái)了。

三、1號(hào)進(jìn)程

3.1 1號(hào)進(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)核各個(gè)重要子系統(tǒng)的初始化,比如mm, cpu, sched, irq等等。最后會(huì)調(diào)用一個(gè)rest_init剩余部分初始化,start_kernel在其最后一個(gè)函數(shù)rest_init的調(diào)用中,會(huì)通過(guò)kernel_thread來(lái)生成一個(gè)內(nèi)核進(jìn)程,后者則會(huì)在新進(jìn)程環(huán)境下調(diào) 用kernel_init函數(shù),kernel_init一個(gè)讓人感興趣的地方在于它會(huì)調(diào)用run_init_process來(lái)執(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);
}

在這個(gè)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)建了兩個(gè)內(nèi)核線程,而kernel_thread最終會(huì)調(diào)用do_fork根據(jù)參數(shù)的不同來(lái)創(chuàng)建一個(gè)進(jìn)程或者內(nèi)核線程。關(guān)系do_fork的實(shí)現(xiàn)我們?cè)诤竺鏁?huì)做詳細(xì)的介紹。當(dāng)內(nèi)核線程創(chuàng)建成功后就會(huì)調(diào)用設(shè)置的回調(diào)函數(shù)。

  當(dāng)kernel_thread(kernel_init)成功返回后,就會(huì)調(diào)用kernel_init內(nèi)核線程,其實(shí)這時(shí)候1號(hào)進(jìn)程已經(jīng)產(chǎn)生了。1號(hào)進(jìn)程的執(zhí)行函數(shù)就是kernel_init, 這個(gè)函數(shù)被定義init/main.c中,接下來(lái)看下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ù)中就會(huì)做各種外設(shè)驅(qū)動(dòng)的初始化。

  • 最主要的工作就是通過(guò)execve執(zhí)行/init可以執(zhí)行文件。它按照配置文件/etc/initab的要求,完成系統(tǒng)啟動(dòng)工作,創(chuàng)建編號(hào)為1號(hào)、2號(hào)...的若干終端注冊(cè)進(jìn)程getty。每個(gè)getty進(jìn)程設(shè)置其進(jìn)程組標(biāo)識(shí)號(hào),并監(jiān)視配置到系統(tǒng)終端的接口線路。當(dāng)檢測(cè)到來(lái)自終端的連接信號(hào)時(shí),getty進(jìn)程將通過(guò)函數(shù)execve()執(zhí)行注冊(cè)程序login,此時(shí)用戶就可輸入注冊(cè)名和密碼進(jìn)入登錄過(guò)程,如果成功,由login程序再通過(guò)函數(shù)execv()執(zhí)行shell,該shell進(jìn)程接收getty進(jìn)程的pid,取代原來(lái)的getty進(jìn)程。再由shell直接或間接地產(chǎn)生其他進(jìn)程。

我們通常將init稱為1號(hào)進(jìn)程,其實(shí)在剛才kernel_init的時(shí)候1號(hào)線程已經(jīng)創(chuàng)建成功,也可以理解kernel_init是1號(hào)進(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號(hào)進(jìn)程不在一樣。

1號(hào)內(nèi)核線程負(fù)責(zé)執(zhí)行內(nèi)核的部分初始化工作及進(jìn)行系統(tǒng)配置,并創(chuàng)建若干個(gè)用于高速緩存和虛擬主存管理的內(nèi)核線程。

至此1號(hào)進(jìn)程就完美的創(chuàng)建成功了,而且也成功執(zhí)行了init可執(zhí)行文件?! ?/p>

3.2 init進(jìn)程

  隨后,1號(hào)進(jìn)程調(diào)用do_execve運(yùn)行可執(zhí)行程序init,并演變成用戶態(tài)1號(hào)進(jìn)程,即init進(jìn)程。

  init進(jìn)程是linux內(nèi)核啟動(dòng)的第一個(gè)用戶級(jí)進(jìn)程。init有許多很重要的任務(wù),比如像啟動(dòng)getty(用于用戶登錄)、實(shí)現(xiàn)運(yùn)行級(jí)別、以及處理孤立進(jìn)程。

  它按照配置文件/etc/initab的要求,完成系統(tǒng)啟動(dòng)工作,創(chuàng)建編號(hào)為1號(hào)、2號(hào)…的若干終端注冊(cè)進(jìn)程getty。

  每個(gè)getty進(jìn)程設(shè)置其進(jìn)程組標(biāo)識(shí)號(hào),并監(jiān)視配置到系統(tǒng)終端的接口線路。當(dāng)檢測(cè)到來(lái)自終端的連接信號(hào)時(shí),getty進(jìn)程將通過(guò)函數(shù)do_execve()執(zhí)行注冊(cè)程序login,此時(shí)用戶就可輸入注冊(cè)名和密碼進(jìn)入登錄過(guò)程,如果成功,由login程序再通過(guò)函數(shù)execv()執(zhí)行shell,該shell進(jìn)程接收getty進(jìn)程的pid,取代原來(lái)的getty進(jìn)程。再由shell直接或間接地產(chǎn)生其他進(jìn)程。

  上述過(guò)程可描述為:0號(hào)進(jìn)程->1號(hào)內(nèi)核進(jìn)程->1號(hào)用戶進(jìn)程(init進(jìn)程)->getty進(jìn)程->shell進(jìn)程

  注意,上述過(guò)程描述中提到:1號(hào)內(nèi)核進(jìn)程調(diào)用執(zhí)行init函數(shù)并演變成1號(hào)用戶態(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)核啟動(dòng)并運(yùn)行的第一個(gè)用戶進(jìn)程,運(yùn)行在用戶態(tài)下。

  • 一號(hào)內(nèi)核進(jìn)程調(diào)用execve()從文件/etc/inittab中加載可執(zhí)行程序init并執(zhí)行,這個(gè)過(guò)程并沒(méi)有使用調(diào)用do_fork(),因此兩個(gè)進(jìn)程都是1號(hào)進(jìn)程。

  當(dāng)內(nèi)核啟動(dòng)了自己之后(已被裝入內(nèi)存、已經(jīng)開始運(yùn)行、已經(jīng)初始化了所有的設(shè)備驅(qū)動(dòng)程序和數(shù)據(jù)結(jié)構(gòu)等等),通過(guò)啟動(dòng)用戶級(jí)程序init來(lái)完成引導(dǎo)進(jìn)程的內(nèi)核部分。因此,init總是第一個(gè)進(jìn)程(它的進(jìn)程號(hào)總是1)。

  當(dāng)init開始運(yùn)行,它通過(guò)執(zhí)行一些管理任務(wù)來(lái)結(jié)束引導(dǎo)進(jìn)程,例如檢查文件系統(tǒng)、清理/tmp、啟動(dòng)各種服務(wù)以及為每個(gè)終端和虛擬控制臺(tái)啟動(dòng)getty,在這些地方用戶將登錄系統(tǒng)。

  在系統(tǒng)完全起來(lái)之后,init為每個(gè)用戶已退出的終端重啟getty(這樣下一個(gè)用戶就可以登錄)。init同樣也收集孤立的進(jìn)程:當(dāng)一個(gè)進(jìn)程啟動(dòng)了一個(gè)子進(jìn)程并且在子進(jìn)程之前終止了,這個(gè)子進(jìn)程立刻成為init的子進(jìn)程。對(duì)于各種技術(shù)方面的原因來(lái)說(shuō)這是很重要的,知道這些也是有好處的,因?yàn)檫@便于理解進(jìn)程列表和進(jìn)程樹圖。init的變種很少。絕大多數(shù)Linux發(fā)行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init設(shè)計(jì)。UNIX的BSD版本有一個(gè)不同的init。最主要的不同在于運(yùn)行級(jí)別:System V有而BSD沒(méi)有(至少是傳統(tǒng)上說(shuō))。這種區(qū)別并不是主要的。在此我們僅討論sysvinit。 配置init以啟動(dòng)getty:/etc/inittab文件。

3.3 init程序

  1號(hào)進(jìn)程通過(guò)execve執(zhí)行init程序來(lái)進(jìn)入用戶空間,成為init進(jìn)程,那么這個(gè)init在哪里呢

  內(nèi)核在幾個(gè)位置上來(lái)查尋init,這幾個(gè)位置以前常用來(lái)放置init,但是init的最適當(dāng)?shù)奈恢茫ㄔ贚inux系統(tǒng)上)是/sbin/init。如果內(nèi)核沒(méi)有找到init,它就會(huì)試著運(yùn)行/bin/sh,如果還是失敗了,那么系統(tǒng)的啟動(dòng)就宣告失敗了。

  因此init程序是一個(gè)可以又用戶編寫的進(jìn)程, 如果希望看init程序源碼的朋友,可以參見。

init包說(shuō)明
sysvinit

早期一些版本使用的初始化進(jìn)程工具, 目前在逐漸淡出linux歷史舞臺(tái), 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ā)行版所采用。

upstartdebian, Ubuntu等系統(tǒng)使用的initdaemon
systemdSystemd 是 Linux 系統(tǒng)中最新的初始化系統(tǒng)(init),它主要的設(shè)計(jì)目標(biāo)是克服 sysvinit 固有的缺點(diǎn),提高系統(tǒng)的啟動(dòng)速度

  Ubuntu等使用deb包的系統(tǒng)可以通過(guò)dpkg -S查看程序所在的包

linux中0號(hào)進(jìn)程的含義是什么

  CentOS等使用rpm包的系統(tǒng)可以通過(guò)rpm -qf查看系統(tǒng)程序所在的包

linux中0號(hào)進(jìn)程的含義是什么

linux中0號(hào)進(jìn)程的含義是什么

四、2號(hào)進(jìn)程

2號(hào)進(jìn)程,也是由0號(hào)進(jìn)程創(chuàng)建的。而且2號(hào)進(jìn)程是所有內(nèi)核線程父進(jìn)程。

2號(hào)進(jìn)程就是剛才rest_init中創(chuàng)建的另外一個(gè)內(nèi)核線程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

當(dāng)kernel_thread(kthreadd)返回時(shí),2號(hào)進(jìn)程已經(jīng)創(chuàng)建成功了。而且會(huì)回調(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;
}

這段代碼大概的意思也很簡(jiǎn)單明顯;


    • 設(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

    • 如果不是空,則從鏈表中取出一個(gè),然后調(diào)用kthread_create去創(chuàng)建一個(gè)內(nèi)核線程。

    • 所以說(shuō)所有的內(nèi)核線程的父進(jìn)程都是2號(hào)進(jìn)程,也就是kthreadd。

五、總結(jié)

linux啟動(dòng)的第一個(gè)進(jìn)程是0號(hào)進(jìn)程,是靜態(tài)創(chuàng)建的,稱為idle進(jìn)程或者swapper進(jìn)程。

在0號(hào)進(jìn)程啟動(dòng)后會(huì)接連創(chuàng)建兩個(gè)進(jìn)程,分別是1號(hào)進(jìn)程和2和進(jìn)程。

1號(hào)進(jìn)程最終會(huì)使用execve函數(shù)去調(diào)用可init可執(zhí)行文件,init進(jìn)程最終會(huì)去創(chuàng)建所有的應(yīng)用進(jìn)程,所以被稱為inti進(jìn)程。

2號(hào)進(jìn)程會(huì)在內(nèi)核中負(fù)責(zé)創(chuàng)建所有的內(nèi)核線程,被稱為kthreadd進(jìn)程。

所以說(shuō)0號(hào)進(jìn)程是1號(hào)和2號(hào)進(jìn)程的父進(jìn)程;1號(hào)進(jìn)程是所有用戶態(tài)進(jìn)程的父進(jìn)程;2號(hào)進(jìn)程是所有內(nèi)核線程的父進(jìn)程。

我們通過(guò)ps命令就可以詳細(xì)的觀察到這一現(xiàn)象。

root@ubuntu:zhuxl$ ps -eF
UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          1      0  0 56317  5936   2 Feb16 ?        00:00:04 /sbin/init
root          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號(hào)進(jìn)程。

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          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]

再來(lái)看下,所有內(nèi)核線性的PPI=2, 也就是所有內(nèi)核線性的父進(jìn)程都是kthreadd進(jìn)程。

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root        362      1  0 21574  6136   2 Feb16 ?        00:00:03 /lib/systemd/systemd-journald
root        375      1  0 11906  2760   3 Feb16 ?        00:00:01 /lib/systemd/systemd-udevd
systemd+    417      1  0 17807  2116   3 Feb16 ?        00:00:02 /lib/systemd/systemd-resolved
systemd+    420      1  0 35997   788   3 Feb16 ?        00:00:00 /lib/systemd/systemd-timesyncd
root        487      1  0 43072  6060   0 Feb16 ?        00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root        489      1  0  8268  2036   2 Feb16 ?        00:00:00 /usr/sbin/cron -f
root        490      1  0  1138   548   0 Feb16 ?        00:00:01 /usr/sbin/acpid
root        491      1  0 106816 3284   1 Feb16 ?        00:00:00 /usr/sbin/ModemManager
root        506      1  0 27628  2132   2 Feb16 ?        00:00:01 /usr/sbin/irqbalance --foreground

所有用戶態(tài)的進(jìn)程的父進(jìn)程PPID=1,也就是1號(hào)進(jìn)程都是他們的父進(jìn)程。

關(guān)于“l(fā)inux中0號(hào)進(jìn)程的含義是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI