c - 哪个 Linux 内核函数创建了 'process 0' ?

标签 c assembly linux-kernel x86 boot

我想了解更多关于 process 0例如,它是否有内存描述符(非NULL task_struct->mm 字段),以及它与交换或空闲进程有什么关系。在我看来,在引导 cpu 上创建了一个“进程 0”,然后 idle_threads_init 为每个其他 cpu 创建了一个空闲线程。 ,但我没有找到第一个(我假设是 process 0 )的创建位置。

更新

鉴于live book tychen 引用的,这是我对 process 0 的最新理解(对于 x86_64),有人可以确认/反驳以下项目吗?

  • init_task 输入 task_struct是静态定义的,具有任务的内核堆栈 init_task.stack = init_stack , 内存描述符 init_task.mm=NULLinit_task.active_mm=&init_mm ,其中堆栈区 init_stack mm_struct init_mm 都是静态定义的。
  • 事实只有active_mm非 NULL 意味着 process 0是一个内核进程。另外, init_task.flags=PF_KTHREAD .
  • 未压缩的内核镜像开始执行后不久,启动 cpu starts使用 init_stack作为内核堆栈。这使得 current宏有意义(自机器启动以来第一次),这使得 fork()可能的。在这一点之后,内核实际上运行在 process 0 中。的上下文。
  • start_kernel -> arch_call_rest_init -> rest_init ,在这个函数中,process 1&2 fork 。内 kernel_init 预定的功能 process 1 ,创建一个新线程(带有 CLONE_VM )并挂接到 CPU 的运行队列的 rq->idle ,对于每个其他逻辑 CPU。
  • 有趣的是,所有空闲线程共享相同的 tid 0 (不仅 tgid )。通常线程共享tgid但有明显的tid , 这真的是 Linux 的 process id .我想它不会破坏任何东西,因为空闲线程被锁定到它们自己的 CPU 上。
  • kernel_init加载 init可执行文件(通常为 /sbin/init ),并同时切换 current -> mmactive_mm到非 NULL mm_struct ,并清除 PF_KTHREAD标志,这使得 process 1一个合法的用户空间进程。虽然 process 2不调整 mm ,意味着它仍然是一个内核进程,与 process 0 相同.
  • rest_init , do_idle接管,这意味着所有 CPU 都有一个空闲进程。
  • 之前有些东西让我困惑,但现在变得清楚了:init_*对象/标签,例如 init_task/init_mm/init_stack都被process 0使用了,而不是 init process , 即 process 1 .
  • 最佳答案

    我们真的从 start_kernel 开始Linux内核, 进程 0/idle 也从这里开始。

    开头start_kernel ,我们调用set_task_stack_end_magic(&init_stack) .该函数将设置 init_task 的堆栈边界,即进程 0/idle。

    void set_task_stack_end_magic(struct task_struct *tsk)
    {
        unsigned long *stackend;
    
        stackend = end_of_stack(tsk);
        *stackend = STACK_END_MAGIC;    /* for overflow detection */
    }
    

    很容易理解,这个函数获取限制地址并将底部设置为STACK_END_MAGIC作为堆栈溢出标志。这是结构图。

    enter image description here

    进程 0 是静态定义的。这是唯一一个不是由 kernel_thread 创建的进程也不是 fork .

    /*
     * 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
    = {
    #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,
        ......
        .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);
    
    

    以下是我们需要明确说明的一些重要事项。
  • INIT_THREAD_INFO(init_task)设置 thread_info如上图。
  • init_stack定义如下

  • extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];
    

    其中 THREAD_SIZE 等于

    #ifdef CONFIG_KASAN
    #define KASAN_STACK_ORDER 1
    #else
    #define KASAN_STACK_ORDER 0
    #endif
    #define THREAD_SIZE_ORDER   (2 + KASAN_STACK_ORDER)
    #define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)
    

    所以定义了默认大小。
  • 进程 0 只会在内核空间运行,但在我上面提到的某些情况下它需要一个虚拟内存空间,所以我们设置以下

  •     .mm     = NULL,
        .active_mm  = &init_mm,
    

    让我们回顾一下 start_kernel , rest_init将初始化 kernel_initkthreadd .

    noinline void __ref rest_init(void)
    {
    ......
        pid = kernel_thread(kernel_init, NULL, CLONE_FS);
    ......
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    ......
    }
    
    
    kernel_init将运行 execve然后进入用户空间,改为init通过运行处理,即进程 1。

    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;
    
    kthread成为管理和调度其他内核的守护进程 task_struts ,这是过程2。

    做完这一切,进程0就会变成空闲进程并跳出rq这意味着它只会在 rq 时运行是空的。

    noinline void __ref rest_init(void)
    {
    ......
        /*
         * 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);
    }
    
    
    void cpu_startup_entry(enum cpuhp_state state)
    {
        arch_cpu_idle_prepare();
        cpuhp_online_idle(state);
        while (1)
            do_idle();
    }
    

    最后,这里有个好消息gitbook如果您想更多地了解 Linux 内核,那么为您提供帮助。

    关于c - 哪个 Linux 内核函数创建了 'process 0' ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62204047/

    相关文章:

    assembly - 为什么linux在entry_SYSCALL_64函数中从rcx寄存器读取ip寄存器?

    汇编条件跳转

    linux-kernel - Linux 设备驱动程序中针对多线程应用程序的读/写和释放处理

    linux-kernel - 适用于 ARM 处理器的 Linux 内核 3.9 KVM

    python - 将 C 结构传递给 C DLL 中的函数

    c - C 中的解码声明(数组和函数指针的组合)

    c - 将 C 语言翻译成 ARM 汇编语言

    linux-kernel - Linux 内核 ARM 转换表库(TTB0 和 TTB1)

    c++ - 对于 C/C++ 程序,是否有等效于 python 的 virtualenv?

    c - C 中递归的递归