哪个 Linux 内核函数创建了“进程 0”?

QnA*_*QnA 8 c boot x86 assembly linux-kernel

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

更新

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

  1. 一个init_task类型化的task_struct定义是静态的,随着任务的内核堆栈init_task.stack = init_stack,内存描述符init_task.mm=NULLinit_task.active_mm=&init_mm,其中堆栈区init_stackmm_struct init_mm都静态定义。
  2. 只有active_mm非空的事实意味着process 0是一个内核进程。还有,init_task.flags=PF_KTHREAD
  3. 不长的未压缩的内核映像开始执行后,开机CPU开始使用init_stack的内核堆栈。这使得current宏有意义(自机器启动以来第一次),这使得fork()可能。在这一点之后,内核实际上运行 inprocess 0的上下文。
  4. start_kernel-> arch_call_rest_init-> rest_init,在这个函数内部,process 1&2是分叉的。在kernel_init为 调度的函数中,为每个其他逻辑 CPU 创建process 1一个新线程(具有CLONE_VM)并挂接到 CPU 的运行队列的rq->idle
  5. 有趣的是,所有空闲线程共享相同的tid 0(不仅tgid)。通常线程共享tgid但具有不同tidprocess id. 我想它不会破坏任何东西,因为空闲线程被锁定到它们自己的 CPU 上。
  6. kernel_init加载init可执行文件(通常为/sbin/init),并切换current->mmactive_mm非 NULL mm_struct,并清除PF_KTHREAD标志,这使得process 1合法的用户空间进程。虽然process 2不调整mm,这意味着它仍然是一个内核进程,与process 0.
  7. 在结束时rest_initdo_idle接管,这意味着所有 CPU 都有一个空闲进程。
  8. 以前的东西搞糊涂了,但现在变得清晰:该init_*物体/标签,如init_task/ init_mm/init_stack都在使用process 0,而不是init process,这是process 1

tyC*_*hen 3

我们真正从 启动 Linux 内核start_kernel,进程 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 */
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

进程0是静态定义的。这是唯一不是由kernel_threadnor创建的进程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);

Run Code Online (Sandbox Code Playgroud)

这里有一些重要的事情我们需要说清楚。

  1. INIT_THREAD_INFO(init_task)设置thread_info 如上图所示。
  2. init_stack定义如下
extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];
Run Code Online (Sandbox Code Playgroud)

其中 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)
Run Code Online (Sandbox Code Playgroud)

因此定义了默认大小。

  1. 进程0只会运行在内核空间,但在某些情况下,正如我上面提到的,它需要虚拟内存空间,所以我们设置如下
    .mm     = NULL,
    .active_mm  = &init_mm,
Run Code Online (Sandbox Code Playgroud)

让我们回顾一下start_kernelrest_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);
......
}

Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

最后,如果你想更多地了解 Linux 内核,这里有一本很好的gitbook 。