搜尋
首頁運維linux運維linux中0號進程是什麼

linux中0號進程是什麼

Mar 15, 2023 pm 07:31 PM
linux

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

linux中0號進程是什麼

本教學操作環境: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 发行版所采用。

upstart debian, Ubuntu等系统使用的initdaemon
systemd Systemd 是 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號進程是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
了解Linux的維護模式:必需品了解Linux的維護模式:必需品Apr 14, 2025 am 12:04 AM

Linux維護模式通過在啟動時添加init=/bin/bash或single參數進入。 1.進入維護模式:編輯GRUB菜單,添加啟動參數。 2.重新掛載文件系統為讀寫模式:mount-oremount,rw/。 3.修復文件系統:使用fsck命令,如fsck/dev/sda1。4.備份數據並謹慎操作,避免數據丟失。

Debian如何提升Hadoop數據處理速度Debian如何提升Hadoop數據處理速度Apr 13, 2025 am 11:54 AM

本文探討如何在Debian系統上提升Hadoop數據處理效率。優化策略涵蓋硬件升級、操作系統參數調整、Hadoop配置修改以及高效算法和工具的運用。一、硬件資源強化確保所有節點硬件配置一致,尤其關注CPU、內存和網絡設備性能。選擇高性能硬件組件對於提升整體處理速度至關重要。二、操作系統調優文件描述符和網絡連接數:修改/etc/security/limits.conf文件,增加系統允許同時打開的文件描述符和網絡連接數上限。 JVM參數調整:在hadoop-env.sh文件中調整

Debian syslog如何學習Debian syslog如何學習Apr 13, 2025 am 11:51 AM

本指南將指導您學習如何在Debian系統中使用Syslog。 Syslog是Linux系統中用於記錄系統和應用程序日誌消息的關鍵服務,它幫助管理員監控和分析系統活動,從而快速識別並解決問題。一、Syslog基礎知識Syslog的核心功能包括:集中收集和管理日誌消息;支持多種日誌輸出格式和目標位置(例如文件或網絡);提供實時日誌查看和過濾功能。二、安裝和配置Syslog(使用Rsyslog)Debian系統默認使用Rsyslog。您可以通過以下命令安裝:sudoaptupdatesud

Debian中Hadoop版本怎麼選Debian中Hadoop版本怎麼選Apr 13, 2025 am 11:48 AM

選擇適合Debian系統的Hadoop版本,需要綜合考慮以下幾個關鍵因素:一、穩定性與長期支持:對於追求穩定性和安全性的用戶,建議選擇Debian穩定版,例如Debian11(Bullseye)。該版本經過充分測試,擁有長達五年的支持週期,能夠確保系統穩定運行。二、軟件包更新速度:如果您需要使用最新的Hadoop功能和特性,則可以考慮Debian的不穩定版(Sid)。但需注意,不穩定版可能存在兼容性問題和穩定性風險。三、社區支持與資源:Debian擁有龐大的社區支持,可以提供豐富的文檔和

Debian上TigerVNC共享文件方法Debian上TigerVNC共享文件方法Apr 13, 2025 am 11:45 AM

本文介紹如何在Debian系統上使用TigerVNC共享文件。你需要先安裝TigerVNC服務器,然後進行配置。一、安裝TigerVNC服務器打開終端。更新軟件包列表:sudoaptupdate安裝TigerVNC服務器:sudoaptinstalltigervnc-standalone-servertigervnc-common二、配置TigerVNC服務器設置VNC服務器密碼:vncpasswd啟動VNC服務器:vncserver:1-localhostno

Debian郵件服務器防火牆配置技巧Debian郵件服務器防火牆配置技巧Apr 13, 2025 am 11:42 AM

配置Debian郵件服務器的防火牆是確保服務器安全性的重要步驟。以下是幾種常用的防火牆配置方法,包括iptables和firewalld的使用。使用iptables配置防火牆安裝iptables(如果尚未安裝):sudoapt-getupdatesudoapt-getinstalliptables查看當前iptables規則:sudoiptables-L配置

Debian郵件服務器SSL證書安裝方法Debian郵件服務器SSL證書安裝方法Apr 13, 2025 am 11:39 AM

在Debian郵件服務器上安裝SSL證書的步驟如下:1.安裝OpenSSL工具包首先,確保你的系統上已經安裝了OpenSSL工具包。如果沒有安裝,可以使用以下命令進行安裝:sudoapt-getupdatesudoapt-getinstallopenssl2.生成私鑰和證書請求接下來,使用OpenSSL生成一個2048位的RSA私鑰和一個證書請求(CSR):openss

Debian郵件服務器虛擬主機配置方法Debian郵件服務器虛擬主機配置方法Apr 13, 2025 am 11:36 AM

在Debian系統上配置郵件服務器的虛擬主機通常涉及安裝和配置郵件服務器軟件(如Postfix、Exim等),而不是ApacheHTTPServer,因為Apache主要用於Web服務器功能。以下是配置郵件服務器虛擬主機的基本步驟:安裝Postfix郵件服務器更新系統軟件包:sudoaptupdatesudoaptupgrade安裝Postfix:sudoapt

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境