zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    VS2008编写MFC程序--使用opencv2.4()
    November 02nd, 2017 Week 44th Thursday
    November 01st, 2017 Week 44th Wednesday
    October 31st, 2017 Week 44th Tuesday
    October 30th, 2017 Week 44th Monday
    October 29th, 2017 Week 44th Sunday
    October 28th, 2017 Week 43rd Saturday
    October 27th, 2017 Week 43rd Friday
    October 26th, 2017 Week 43rd Thursday
    October 25th, 2017 Week 43rd Wednesday
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/15187310.html
Copyright © 2011-2022 走看看