• Linux0号进程,1号进程,2号进程


      本节我们将从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)
    ",
    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

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15187310.html

  • 相关阅读:
    文件夹生成zip
    html 字符串 生成 pdf 完美解决中文不显示
    layui 数据表格+分页+搜索+checkbox+缓存选中项数据
    排序算法总结
    排序算法(10)--Distribution Sorting--分布排序[2]--Radix Sort--基数排序
    排序算法(8)--Merge Sorting--归并排序--Merge sort--归并排序
    [Android]在Dagger 2中使用RxJava来进行异步注入(翻译)
    [Android]使用Dagger 2进行依赖注入
    [Android]Android端ORM框架——RapidORM(v2.1)
    [Android]使用MVP解决技术债务(翻译)
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/15187310.html
Copyright © 2020-2023  润新知