linux中0號程序是什麼

2023-03-15 22:00:38

在linux中,0號程序是指idle程序,是linux啟動的第一個程序;它的task_struct的comm欄位為「swapper」,所以也稱為swpper程序。0號程序是唯一一個沒有通過fork或者kernel_thread產生的程序,因為init_task是靜態變數(初始化了的全域性變數),其他程序的PCB都是fork或者kernel_thread動態申請記憶體建立的。

本教學操作環境:linux7.3系統、Dell G3電腦。

一、0號程序

0號程序,通常也被稱為idle程序,或者也稱為swapper程序。

每個程序都有一個過程控制塊PCB(Process Control Block),PCB的資料結構型別是struct task_struct。idle程序對應的PCB是 struct task_struct init_task。

idle程序是唯一一個沒有通過fork或者kernel_thread產生的程序,因為 init_task 是靜態變數(初始化了的全域性變數),其他程序的PCB都是fork或者kernel_thread動態申請記憶體建立的。

每個程序都有對應的一個函數,idle程序的函數是 start_kernel(),因為進入該函數前,棧指標SP已經指向 init_task 的棧頂了,處於什麼程序,看SP指向哪個程序的棧。

0號程序是linux啟動的第一個程序,它的task_struct的comm欄位為"swapper",所以也稱為swpper程序。

#define INIT_TASK_COMM "swapper"
登入後複製

當系統中所有的程序起來後,0號程序也就蛻化為idle程序,當一個core上沒有任務可執行時就會去執行idle程序。一旦執行idle程序則此core就可以進入低功耗模式了,在ARM上就是WFI。

我們本節重點關注是0號程序是如何啟動的。在linux核心中為0號程序專門定義了一個靜態的task_struct的結構,稱為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);
登入後複製

這個結構體中的成員都是靜態定義了,為了簡單說明,對這個結構做了簡單的刪減。同時我們只關注這個結構中的以下幾個欄位,別的先不關注。

  • .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是在連結指令碼中定義的。

#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號程序的核心棧的基地址,當然了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核心啟動的程式碼。

程式碼路徑: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)
登入後複製

上面就是核心在呼叫start_kernel之前做的主要工作了。

preserve_boot_args用來保留bootloader傳遞的引數,比如ARM上通常的dtb的地址

el2_setup:從註釋上來看是, 用來trap到EL1,說明我們在執行此指令前還在EL2

__create_page_tables: 用來建立頁表,linux才有的是頁面管理實體記憶體的,在使用虛擬地址之前需要設定好頁面,然後會開啟MMU。目前還是執行在實體地址上的

__primary_switch: 主要任務是完成MMU的開啟工作

__primary_switch:
    adrp    x1, init_pg_dir
    bl    __enable_mmu
    ldr    x8, =__primary_switched
    adrp    x0, __PHYS_OFFSET
    br    x8
ENDPROC(__primary_switch)
登入後複製

主要是呼叫__enable_mmu來開啟mmu,之後我們存取的就是虛擬地址了

呼叫__primary_switched來設定0號程序的執行核心棧,然後呼叫start_kernel函數

/*
 * 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就是我們在連結指令碼中定義的,也就是0號程序的核心棧的棧底

add sp, x4, #THREAD_SIZE: 設定堆疊指標SP的值,就是核心棧的棧底+THREAD_SIZE的大小。現在SP指到了核心棧的頂端

最終通過b start_kernel就跳轉到我們熟悉的linux核心入口處了。  至此0號程序就已經執行起來了。

三、1號程序

3.1 1號程序的建立

  當一條b start_kernel指令執行後,核心就開始的核心的全面初始化操作。

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函數就是核心各個重要子系統的初始化,比如mm, cpu, sched, irq等等。最後會呼叫一個rest_init剩餘部分初始化,start_kernel在其最後一個函數rest_init的呼叫中,會通過kernel_thread來生成一個核心程序,後者則會在新程序環境下調 用kernel_init函數,kernel_init一個讓人感興趣的地方在於它會呼叫run_init_process來執行根檔案系統下的 /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函數中我們只關係兩點:

  • 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);
}
登入後複製

  很明顯這是建立了兩個核心執行緒,而kernel_thread最終會呼叫do_fork根據引數的不同來建立一個程序或者核心執行緒。關係do_fork的實現我們在後面會做詳細的介紹。當核心執行緒建立成功後就會呼叫設定的回撥函數。

  當kernel_thread(kernel_init)成功返回後,就會呼叫kernel_init核心執行緒,其實這時候1號程序已經產生了。1號程序的執行函數就是kernel_init, 這個函數被定義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函數中就會做各種外設驅動的初始化。

  • 最主要的工作就是通過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 發行版所採用。

upstartdebian, Ubuntu等系統使用的initdaemon
systemdSystemd 是 Linux 系統中最新的初始化系統(init),它主要的設計目標是克服 sysvinit 固有的缺點,提高系統的啟動速度

  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函數。

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;
}
登入後複製

這段程式碼大概的意思也很簡單明顯;

    • 設定當前程序的名字為"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命令就可以詳細的觀察到這一現象。

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的程序是init,PID=2的程序是kthreadd。而他們倆的父程序PPID=0,也就是0號程序。

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]
登入後複製

再來看下,所有核心線性的PPI=2, 也就是所有核心線性的父程序都是kthreadd程序。

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
登入後複製

所有使用者態的程序的父程序PPID=1,也就是1號程序都是他們的父程序。

相關推薦:《Linux視訊教學

以上就是linux中0號程序是什麼的詳細內容,更多請關注TW511.COM其它相關文章!