Apakah proses nombor 0 dalam linux?

青灯夜游
青灯夜游asal
2023-03-15 19:31:193583semak imbas

Di Linux, proses No. 0 merujuk kepada proses terbiar, iaitu proses pertama yang dimulakan oleh Linux; medan kom task_structnya ialah "swapper", jadi ia juga dipanggil proses swpper. Proses No. 0 ialah satu-satunya proses yang tidak dijana melalui fork atau kernel_thread, kerana init_task ialah pembolehubah statik (pembolehubah global yang dimulakan), dan PCB proses lain dicipta oleh fork atau kernel_thread secara dinamik memohon memori.

Apakah proses nombor 0 dalam linux?

Persekitaran pengendalian tutorial ini: sistem linux7.3, komputer Dell G3.

1. Proses No. 0

Proses No. 0 biasanya juga dipanggil proses terbiar atau proses swapper.

Setiap proses mempunyai blok kawalan proses PCB (Process Control Block) Jenis struktur data PCB ialah struct task_struct. PCB yang sepadan dengan proses melahu ialah struct task_struct init_task.

Proses melahu ialah satu-satunya proses yang tidak dihasilkan melalui fork atau kernel_thread, kerana init_task ialah pembolehubah statik (pembolehubah global yang dimulakan), dan PCB proses lain dicipta oleh fork atau kernel_thread secara dinamik memohon memori .

Setiap proses mempunyai fungsi yang sepadan Fungsi proses melahu ialah start_kernel(), kerana sebelum memasuki fungsi ini, penuding tindanan SP telah menghala ke bahagian atas timbunan init_task yang mana anda berada bergantung pada SP yang menunjuk kepada timbunan Proses.

Proses No. 0 ialah proses pertama yang dimulakan oleh Linux Medan kom task_structnya ialah "swapper", jadi ia juga dipanggil proses swpper.

#define INIT_TASK_COMM "swapper"

Apabila semua proses dalam sistem selesai, proses 0 akan merosot kepada proses terbiar Apabila tiada tugasan untuk dijalankan pada teras, proses melahu akan berlaku lari. Setelah proses melahu berjalan, teras boleh memasuki mod kuasa rendah, iaitu WFI pada ARM.

Fokus kami dalam bahagian ini ialah bagaimana proses 0 dimulakan. Dalam kernel Linux, struktur task_struct statik ditakrifkan khas untuk proses No. 0, dipanggil 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);

Ahli dalam struktur ini semuanya ditakrifkan secara statik Untuk penjelasan mudah, struktur ini telah dipadamkan. Pada masa yang sama, kami hanya memberi perhatian kepada bidang berikut dalam struktur ini, dan tidak memberi perhatian kepada orang lain.

  • .thread_info = INIT_THREAD_INFO(init_task), struktur ini diterangkan secara terperinci dalam hubungan antara thread_info dan tindanan kernel

  • .stack = init_stack , init_stack ialah definisi statik tindanan kernel

  • .comm = INIT_TASK_COMM, nama proses No. 0.

Kedua-dua thread_info dan stack melibatkan Init_stack, jadi lihat dahulu tempat init_stack ditetapkan.

Akhirnya mendapati bahawa init_task ditakrifkan dalam skrip pemaut.

#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 = .;

Makro INIT_TASK_DATA ditakrifkan dalam skrip pautan.

Antaranya, __start_init_task ialah alamat asas timbunan kernel proses No. 0. Sudah tentu, init_thread_union=init_task=__start_init_task.

Alamat akhir tindanan kernel proses 0 adalah sama dengan __start_init_task + THREAD_SIZE biasanya 16K atau 32K dalam ARM64. Kemudian __end_init_task ialah alamat akhir timbunan kernel proses No. 0.

Proses melahu dibuat secara automatik oleh sistem dan berjalan dalam keadaan kernel Proses melahu mempunyai pid=0. Proses terbiar adalah proses pertama yang dibuat oleh sistem dan satu-satunya proses yang tidak dihasilkan melalui garpu atau kernel_thread. Selepas memuatkan sistem, ia berkembang menjadi penjadualan proses dan pertukaran.

2. Memulakan kernel Linux

Rakan yang biasa dengan kernel Linux tahu bahawa pemuat but biasanya digunakan untuk melengkapkan pemuatan kernel Linux akan melakukan Beberapa permulaan perkakasan kemudian akan melompat ke alamat yang sedang dijalankan bagi kernel Linux.

Jika anda biasa dengan seni bina ARM, anda juga tahu bahawa seni bina ARM64 dibahagikan kepada EL0, EL1, EL2 dan EL3. Permulaan biasa biasanya bermula dari mod keistimewaan tinggi kepada mod keistimewaan rendah. Secara umumnya, ARM64 menjalankan EL3 dahulu, kemudian EL2, dan kemudian memerangkap dari EL2 ke EL1, iaitu kernel Linux kami.

Mari kita lihat kod untuk permulaan kernel Linux.

Laluan kod: fail 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)

Di atas ialah kerja utama yang dilakukan oleh kernel sebelum memanggil start_kernel.

preserve_boot_args digunakan untuk mengekalkan parameter yang diluluskan oleh pemuat but, seperti alamat dtb biasa pada ARM

el2_setup: Berdasarkan komen, ia digunakan untuk memerangkap EL1, menunjukkan bahawa kita menjalankan arahan ini sebelum Masih dalam EL2

__create_page_tables: Digunakan untuk mencipta jadual halaman Linux hanya mempunyai halaman untuk mengurus memori fizikal, anda perlu menetapkan halaman dan kemudian membuka MMU . Pada masa ini ia masih berjalan pada alamat fizikal

__primary_switch: Tugas utama adalah untuk melengkapkan pembukaan 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,在这些地方用户将登录系统。

Selepas sistem siap sepenuhnya, init memulakan semula getty untuk setiap terminal yang pengguna telah keluar (supaya pengguna seterusnya boleh log masuk). init juga mengumpul proses yatim: apabila proses memulakan proses kanak-kanak dan ditamatkan sebelum proses kanak-kanak, proses kanak-kanak serta-merta menjadi proses kanak-kanak init. Ini penting untuk pelbagai sebab teknikal, tetapi ia juga berfaedah untuk mengetahui perkara ini kerana ia memudahkan untuk memahami senarai proses dan memproses rajah pepohon. Terdapat sedikit variasi init. Kebanyakan pengedaran Linux menggunakan sysinit (ditulis oleh Miguel van Smoorenburg), yang berdasarkan reka bentuk init Sistem V. Versi BSD UNIX mempunyai init yang berbeza. Perbezaan utama ialah runlevel: Sistem V memilikinya dan BSD tidak (sekurang-kurangnya secara tradisional). Perbezaan ini bukan utama. Di sini kita hanya membincangkan sysvinit. Konfigurasikan init untuk memulakan fail getty:/etc/inittab.

3.3 program init

Proses No. 1 melaksanakan program init melalui execve untuk memasuki ruang pengguna dan menjadi proses init adakah ini init?

Kernel mencari init di beberapa lokasi Lokasi ini biasanya digunakan untuk meletakkan init pada masa lalu, tetapi lokasi yang paling sesuai untuk init (pada sistem Linux) ialah /sbin/init. Jika kernel tidak menemui init, ia akan cuba menjalankan /bin/sh Jika ia masih gagal, permulaan sistem akan gagal.

Oleh itu, program init adalah proses yang boleh ditulis oleh pengguna Jika anda ingin melihat kod sumber program init, anda boleh merujuknya.

pakej init Penerangan
sysvinit
init包 说明
sysvinit

早期一些版本使用的初始化进程工具, 目前在逐渐淡出linux历史舞台, sysvinit 就是 system V 风格的 init 系统,顾名思义,它源于 System V 系列 UNIX。它提供了比 BSD 风格 init 系统更高的灵活性。是已经风行了几十年的 UNIX init 系统,一直被各类 Linux 发行版所采用。

upstart debian, Ubuntu等系统使用的initdaemon
systemd Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度
Alat proses permulaan yang digunakan dalam beberapa versi awal kini beransur-ansur memudar daripada peringkat sejarah Linux sysvinit ialah sistem init gaya-V , ia berasal daripada siri Sistem V UNIX. Ia memberikan fleksibiliti yang lebih besar daripada sistem init gaya BSD. Ia adalah sistem init UNIX yang telah popular selama beberapa dekad dan telah digunakan oleh pelbagai pengedaran Linux.
permulaan initdaemon yang digunakan oleh debian, Ubuntu dan sistem lain
systemd Systemd ialah sistem permulaan (init) terkini dalam sistem Linux Matlamat reka bentuk utamanya adalah untuk mengatasi kelemahan yang wujud pada sysvinit dan meningkatkan kelajuan permulaan. sistem

  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视频教程

Atas ialah kandungan terperinci Apakah proses nombor 0 dalam linux?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn