linux 之0号进程、1号进程、2号进程

一、0号进程

0号进程,通常也被称为idle进程,或者也称为swapper进程,其 pid 等于0。

0号进程是linux启动的第一个进程,它的task_struct的comm字段为"swapper",所以也称为swpper进程。

kernel/include/linux/init_task.h

#define INIT_TASK_COMM "swapper"

当系统中所有的进程起来后,0号进程也就蜕化为idle进程,当一个core上没有任务可运行时就会去运行idle进程。一旦运行idle进程则此core就可以进入低功耗模式了.

idle进程是唯一一个没有通过fork或者kernel_thread产生的进程,因为 init_task 是静态变量(初始化了的全局变量),其他进程的PCB都是fork或者kernel_thread动态申请内存创建的。

每个进程都有对应的一个函数,idle进程的函数是 start_kernel(),因为进入该函数前,栈指针SP已经指向 init_task 的栈顶了,处于什么进程,看SP指向哪个进程的栈。

重点:init_task

内核源码路径:kernel/init/init_task.c

/*
 * Set up the first task table, touch at your own risk!. Base=0,
 * limit=0x1fffff (=2MB)
 */
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
        __init_task_data
#endif
        __aligned(L1_CACHE_BYTES)
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
        .thread_info    = INIT_THREAD_INFO(init_task),
        .stack_refcount = REFCOUNT_INIT(1),
#endif
        .state          = 0,
        .stack          = init_stack,
        .usage          = REFCOUNT_INIT(2),
        .flags          = PF_KTHREAD,
        .prio           = MAX_PRIO - 20,
        .static_prio    = MAX_PRIO - 20,
        .normal_prio    = MAX_PRIO - 20,
        .policy         = SCHED_NORMAL,
        .cpus_ptr       = &init_task.cpus_mask,
        .cpus_mask      = CPU_MASK_ALL,
        .nr_cpus_allowed= NR_CPUS,
        .mm             = NULL,
        .active_mm      = &init_mm,
        .restart_block  = {
                .fn = do_no_restart_syscall,
        },
        .se             = {
                .group_node     = LIST_HEAD_INIT(init_task.se.group_node),
        },
        .rt             = {
                .run_list       = LIST_HEAD_INIT(init_task.rt.run_list),
                .time_slice     = RR_TIMESLICE,
        },
        .tasks          = LIST_HEAD_INIT(init_task.tasks),
#ifdef CONFIG_SMP
        .pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO),
#endif
.......

}
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_stask是在链接脚本中定义的。

路径:kernel/include/asm-generic/vmlinux.lds.h

#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_stask=__start_init_task的。

而0号进程的内核栈的结束地址等于__start_init_task + THREAD_SIZE, THREAD_SIZE的大小在ARM64一般是8K,2个4K页大小。则__end_init_task就是0号进程的内核栈的结束地址。

1.1 linux 内核如何启动 0号进程,也就是如何运行init_task进程?

linux内核的启动 ,一般都是有bootloader来完成装载,bootloader中会做一些硬件的初始化,然后会跳转到linux内核的运行地址上去。

如果熟悉ARM架构的盆友也清楚,ARM64架构分为EL0, EL1, EL2, EL3。正常的启动一般是从高特权模式向低特权模式启动的。通常来说ARM64是先运行EL3,再EL2,然后从EL2就trap到EL1,也就是我们的Linux内核。

我们来看下Linux内核启动的代码,代码路径:kernel/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).
 *
 * 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.
 */
        __HEAD
_head:
        /*
         * DO NOT MODIFY. Image header expected by Linux boot-loaders.
         */
#ifdef CONFIG_EFI
        /*
         * This add instruction has no meaningful effect except that
         * its opcode forms the magic "MZ" signature required by UEFI.
         */
        add     x13, x18, #0x16
        b       primary_entry
#else
        b       primary_entry                   // branch to kernel start, magic
        .long   0                               // reserved
#endif
        .quad   0                               // Image load offset from start of RAM, little-endian
        le64sym _kernel_size_le                 // Effective size of kernel image, little-endian
        le64sym _kernel_flags_le                // Informative flags, little-endian
        .quad   0                               // reserved
        .quad   0                               // reserved
        .quad   0                               // reserved
        .ascii  ARM64_IMAGE_MAGIC               // Magic number
#ifdef CONFIG_EFI
        .long   pe_header - _head               // Offset to the PE header.

pe_header:
        __EFI_PE_HEADER
#else
        .long   0                               // reserved
#endif

        __INIT

        /*
         * The following callee saved general purpose registers are used on the
         * primary lowlevel boot path:
         *
         *  Register   Scope                      Purpose
         *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0
         *  x23        primary_entry() .. start_kernel()        physical misalignment/KASLR offset
         *  x28        __create_page_tables()                   callee preserved temp register
         *  x19/x20    __primary_switch()                       callee preserved temp registers
         *  x24        __primary_switch() .. relocate_kernel()  current RELR displacement
         */
SYM_CODE_START(primary_entry)
        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
SYM_CODE_END(primary_entry)

在内核调用start_kernel 之前的主要汇编函数工作:

  • preserve_boot_args用来保留bootloader传递的参数,比如ARM上通常的dtb的地址

  • el2_setup:从注释上来看是, 用来trap到EL1,说明我们在运行此指令前还在EL2

  • __create_page_tables: 用来创建页表,linux才有的是页面管理物理内存的,在使用虚拟地址之前需要设置好页面,然后会打开MMU。目前还是运行在物理地址上的

  • __primary_switch: 主要任务是完成MMU的打开工作

    SYM_FUNC_START_LOCAL(__primary_switch)
    #ifdef CONFIG_RANDOMIZE_BASE
    mov x19, x0 // preserve new SCTLR_EL1 value
    mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value
    #endif

          adrp    x1, init_pg_dir
          bl      __enable_mmu
    

    #ifdef CONFIG_RELOCATABLE
    #ifdef CONFIG_RELR
    mov x24, #0 // no RELR displacement yet
    #endif
    bl __relocate_kernel
    #ifdef CONFIG_RANDOMIZE_BASE
    ldr x8, =__primary_switched
    adrp x0, __PHYS_OFFSET
    blr x8

          /*
           * If we return here, we have a KASLR displacement in x23 which we need
           * to take into account by discarding the current kernel mapping and
           * creating a new one.
           */
          pre_disable_mmu_workaround
          msr     sctlr_el1, x20                  // disable the MMU
          isb
          bl      __create_page_tables            // recreate kernel mapping
    
          tlbi    vmalle1                         // Remove any stale TLB entries
          dsb     nsh
          isb
    
          msr     sctlr_el1, x19                  // re-enable the MMU
          isb
          ic      iallu                           // flush instructions fetched
          dsb     nsh                             // via old mapping
          isb
    
          bl      __relocate_kernel
    

    #endif
    #endif
    ldr x8, =__primary_switched
    adrp x0, __PHYS_OFFSET
    br x8
    SYM_FUNC_END(__primary_switch)

  • 主要是调用__enable_mmu来打开mmu,之后我们访问的就是虚拟地址了

  • 调用__primary_switched来设置0号进程的运行内核栈,然后调用start_kernel函数

    SYM_FUNC_START_LOCAL(__primary_switched)
    adrp x4, init_thread_union
    add sp, x4, #THREAD_SIZE
    adr_l x5, init_task
    msr sp_el0, x5 // Save thread_info

    #ifdef CONFIG_ARM64_PTR_AUTH
    __ptrauth_keys_init_cpu x5, x6, x7, x8
    #endif

          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
    

    #ifdef CONFIG_SHADOW_CALL_STACK
    adr_l scs_sp, init_shadow_call_stack // Set shadow call stack
    #endif

          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
    

    #ifdef CONFIG_KASAN
    bl kasan_early_init
    #endif
    #ifdef CONFIG_RANDOMIZE_BASE
    tst x23, ~(MIN_KIMG_ALIGN - 1) // already running randomized?
    b.ne 0f
    mov x0, x21 // pass FDT address in x0
    bl kaslr_early_init // parse FDT for KASLR options
    cbz x0, 0f // KASLR disabled? just proceed
    orr x23, x23, x0 // record KASLR offset
    ldp x29, x30, [sp], #16 // we must enable KASLR, return
    ret // to __primary_switch()
    0:
    #endif
    add sp, sp, #16
    mov x29, #0
    mov x30, #0
    b start_kernel
    SYM_FUNC_END(__primary_switched)

    • init_thread_union就是我们在链接脚本中定义的,也就是0号进程的内核栈的栈底
    • add sp, x4, #THREAD_SIZE: 设置堆栈指针SP的值,就是内核栈的栈底+THREAD_SIZE的大小。现在SP指到了内核栈的顶端
    • 最终通过b start_kernel就跳转到我们熟悉的linux内核入口处了。

至此0号进程就已经运行起来了,开始执行start_kernel 函数。

当一条b start_kernel指令运行后,内核就开始的内核的全面初始化操作,执行 start_kernel:

源码路径:kernel/init/main.c

asmlinkage __visible void __init __no_sanitize_address 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);
        early_security_init();
        setup_arch(&command_line);
        setup_boot_config(command_line);
        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();
......

        cpuset_init();
        cgroup_init();
        taskstats_init_early();
        delayacct_init();

        poking_init();
        check_bugs();

        acpi_subsystem_init();
        arch_post_acpi_subsys_init();
        sfi_init_late();
        kcsan_init();

        /* Do the rest non-__init'ed, we're now alive */
        arch_call_rest_init();

        prevent_tail_call_optimization();
}

start_kernel函数就是内核各个重要子系统的初始化,比如mm, cpu, sched, irq等等。

其中:start_kernel()->sched_init()->init_idle()

void __init init_idle(struct task_struct *idle, int cpu)
{
        struct rq *rq = cpu_rq(cpu);
        unsigned long flags;

        __sched_fork(0, idle);


       rq->idle = idle; // 把当前进程(0号进程)置为每个rq运行队列的idle上

...

}

1.2 idle进程的运行时机

idle 并不在运行队列中参与调度,而是在运行队列结构中含idle指针,指向idle进程,在调度器发现运行队列为空的时候运行,调入运行。

二、1 号进程

1号进程称为 init进程,其 pid 等于1。

最后会调用一个arch_call_rest_init 剩余部分初始化,start_kernel在其最后一个函数arch_call_rest_init的调用中,会通过kernel_thread来生成一个内核进程,后者则会在新进程环境下调 用kernel_init函数,kernel_init一个让人感兴趣的地方在于它会调用run_init_process来执行根文件系统下的 /sbin/init等程序。

1号进程的创建:

start_kernel()->arch_call_rest_init()-> rest_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);// 创建的1号进程
        /*
         * 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);//创建2号进程
        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_PREEMPTION=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);

        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        schedule_preempt_disabled();
        /* Call into cpu_idle with preempt disabled */
        cpu_startup_entry(CPUHP_ONLINE);
}

补充一点:1号进程创建之后,什么时候开始运行???

目前还是运行在0号进程中,只有执行到 rest_int()->schedule_preempt_disabled()->schedule()时,才会启动调度器进行进程切换,1号进程开始执行kernel_init 函数。

在进程切换到0号进程时,执行cpu_startup_entry(CPUHP_ONLINE);

//kernel/kernel/sched/idle.c
void cpu_startup_entry(enum cpuhp_state state)
{
        arch_cpu_idle_prepare();
        cpuhp_online_idle(state);
        while (1)
                do_idle();
}

可以看出,执行while(1)循环, 永不退出,留在内核中。

上面的问题结束,接着看kernel_init 函数:

kernel_thread :

//kernel/kernel/fork.c
/*
 * Create a kernel thread.
 */
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
        struct kernel_clone_args args = {
                .flags          = ((lower_32_bits(flags) | CLONE_VM |
                                    CLONE_UNTRACED) & ~CSIGNAL),
                .exit_signal    = (lower_32_bits(flags) & CSIGNAL),
                .stack          = (unsigned long)fn,
                .stack_size     = (unsigned long)arg,
        };

        return kernel_clone(&args);
}

当kernel_thread(kernel_init)成功返回后,就会调用kernel_init内核线程,其实这时候1号进程已经产生了。1号进程的执行函数就是kernel_init,接下来看一下:

tatic 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();
        kprobe_free_init_mem();
        ftrace_free_init_mem();
        kgdb_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();

        do_sysctl_args();

        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 (CONFIG_DEFAULT_INIT[0] != '\0') {
                ret = run_init_process(CONFIG_DEFAULT_INIT);
                if (ret)
                        pr_err("Default init %s failed (error %d)\n",
                               CONFIG_DEFAULT_INIT, ret);
                else
                        return 0;
        }

        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.");
}

2.1 内核态1号进程的功能

kernel_init()--->kernel_init_freeable()--->console_on_rootfs():把当前进程的文件描述符0(stdin)、1(stdout)、2(stderr)都对应到 /dev/console。

进程PCB内有一个记录打开了的文件的文件描述符表current->files->fdt->fd[] 存储着文件描述符对应的文件

文件描述符表 == 数组fd[]

文件描述符 == 数组fd[]的索引。

文件 == struct file*类型变量,即数组fd[]的成员

/* Open /dev/console, for stdin/stdout/stderr, this should never fail */
void __init console_on_rootfs(void)
{
        struct file *file = filp_open("/dev/console", O_RDWR, 0);

        if (IS_ERR(file)) {
                pr_err("Warning: unable to open an initial console.\n");
                return;
        }
        init_dup(file);
        init_dup(file);
        init_dup(file);
        fput(file);
}

**kernel_init()--->kernel_init_freeable()--->prepare_namespace():**挂在根文件系统。uboot会把根文件系统所在的分区传给linux,比如"root=/dev/mmcblk1p2 rootwait rw"就表示根文件系统在/dev/mmcblk1p2 中,也就是 SD卡控制器2控制的SD卡或EMMC等设备的分区 2 中。

kernel_init_freeable函数中就会做各种外设驱动的初始化。

2.2 1号进程从内核态进入到用户态

kernel_init()->run_init_process()/try_to_run_init_process()

kernel_init() 最后会启动用户态的处于根文件系统存储的 init 进程,从而实现init 内核态到 init 用户态的转化

我们通常将init称为1号进程,其实在刚才kernel_init的时候1号线程已经创建成功,也可以理解kernel_init是1号进程的内核态,而我们所熟知的init进程是用户态的,调用execve函数之前属于内核态,调用之后就属于用户态了,执行的代码段与0号进程不在一样。

1号内核线程负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。

至此1号进程就完美的创建成功了,而且也成功执行了init可执行文件。

2.3 1号进程进入到用户态,即init 进程,也是systemd 进程

随后,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文件。

系统中的1号进程:

ps 命令是看不到 0号进程,系统层也看不到。

三、2号进程

2号进程,也是由0号进程创建的。而且2号进程是所有内核线程父进程,并始终运行在内核空间。

2号进程就是刚才rest_init中创建的另外一个内核线程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

当kernel_thread(kthreadd)返回时,2号进程已经创建成功了。而且会回调kthreadd函数。

源码路径:kernel/kernel/kthread.c

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, housekeeping_cpumask(HK_FLAG_KTHREAD));
        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
    • 如果不是空,则从链表中取出一个,然后调用create_kthread去创建一个内核线程。
    • 所以说所有的内核线程的父进程都是2号进程,也就是kthreadd。

内核线程的创建通过API:kthread_run()

四、总结

linux启动的第一个进程是0号进程,是静态创建的,称为idle进程或者swapper进程。

在0号进程启动后会接连创建两个进程,分别是1号进程和2和进程。

1号进程最终会使用execve函数去调用可init可执行文件,init进程最终会去创建所有的应用进程,所以被称为inti进程。

2号进程会在内核中负责创建所有的内核线程,被称为kthreadd进程。

所以说0号进程是1号和2号进程的父进程;1号进程是所有用户态进程的父进程;2号进程是所有内核线程的父进程。

相关推荐
friklogff16 分钟前
【C#生态园】构建你的C#操作系统:框架选择与实践
服务器·开发语言·c#
TravisBytes22 分钟前
linux 系统是如何收发数据包
linux·运维·服务器
平头哥在等你1 小时前
《计算机网络名词解释》
服务器·网络·计算机网络
德迅--文琪2 小时前
SCDN是服务器吗?SCDN防御服务器有什么特点?
运维·服务器
ice___Cpu2 小时前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
秋已杰爱2 小时前
HTTP中的Cookie与Session
服务器·网络协议·http
狐心kitsune3 小时前
erlang学习:Linux常用命令1
linux·学习·erlang
code bean3 小时前
【C#基础】函数传参大总结
服务器·开发语言·c#
shelby_loo3 小时前
通过 Docker 部署 WordPress 服务器
服务器·docker·容器