【问题标题】:Which Linux kernel function creates the 'process 0'?哪个 Linux 内核函数创建了“进程 0”?
【发布时间】:2020-09-23 23:50:48
【问题描述】:

我正在尝试更多地了解process 0,例如,它是否具有内存描述符(非NULLtask_struct->mm 字段),以及它与交换或空闲进程有何关系。在我看来,在引导 cpu 上创建了一个“进程 0”,然后idle_threads_init 为每个其他 cpu 创建了一个空闲线程,但我没有找到第一个(我假设是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_structinit_mm都是静态定义。
  2. 只有active_mm 是非NULL 的事实意味着process 0 是一个内核进程。另外,init_task.flags=PF_KTHREAD
  3. 在未压缩的内核映像开始执行后不久,启动 cpu starts 以使用 init_stack 作为内核堆栈。这使得current 宏有意义(自机器启动以来第一次),这使得fork() 成为可能。在此之后,内核实际上在process 0 的上下文中运行。
  4. start_kernel -> arch_call_rest_init -> rest_init,在这个函数内部,process 1&2 被分叉了。在为process 1 安排的kernel_init 函数中,为每个其他逻辑CPU 创建了一个新线程(带有CLONE_VM)并将其挂接到CPU 的运行队列的rq->idle
  5. 有趣的是,所有空闲线程共享相同的tid 0(不仅仅是tgid)。通常线程共享tgid,但有不同的tid,这实际上是Linux 的process 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_init结束时,do_idle接管,表示所有CPU都有空闲进程。
  8. 以前有一些事情让我很困惑,但现在变得清晰了:init_task/init_mm/init_stackinit_* 对象/标签都被process 0 使用,而不是init process,即process 1

【问题讨论】:

  • 请不要编辑问题的答案。将其作为答案发布,它可以与问题分开获得投票。 (并且在将来的某个时候,如果它变得过时,不会被置于其他答案之上。)
  • 好的。有些东西我不确定,因此它们出现在问题中,但你说的有道理。我可以将它们放在单独的答案线程中以收集反馈。
  • 哦,如果这仍然是您要求确认的内容,并且不确定是否正确,那么提出问题是有道理的。
  • 好的,我会在正文中明确说明。

标签: c assembly linux-kernel x86 boot


【解决方案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作为堆栈溢出标志。这是结构图。

进程0是静态定义的。这是唯一不是由kernel_threadfork 创建的进程。

/*
 * 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);

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

  1. INIT_THREAD_INFO(init_task)thread_info 设置为上图。
  2. 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)

所以定义了默认大小。

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

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

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();
}

最后,如果您想进一步了解 Linux 内核,这里有一个很好的gitbook

【讨论】:

  • 谢谢,非常有用的信息!准确地说,而不是仍然做准备工作的 set_task_stack_end_magic,可以公平地说进程 0 是由 /arch/x86/kernel/head_64.S 中的“movq initial_stack(%rip), %rsp”这一行启动的,因为正是这一行用进程 0 的内核堆栈填充 RSP?
  • 我认为您将init_taskinit_stack 混合使用。他们不是一回事。进程0是我上面提到的gcc结构初始化构建的,你可以认为它就像我们在程序中放置一个“int a = 1”一样。
  • 不完全是。我将更新帖子以使其清楚。
  • 也许您可以编辑我的答案以使其完整。我认为您的版本现在可以完全解决问题。
  • 是的,最初我建议将函数 set_task_stack_end_magic 的描述替换为上述 mov 指令,并完成它。但是当我开始为自己写一个总结时,很多相关的东西进来了,其中一些我不太确定,比如'所有空闲线程共享 tid 0',这对我来说听起来有点不真实。
猜你喜欢
  • 1970-01-01
  • 2020-04-24
  • 1970-01-01
  • 2011-09-18
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 2011-12-22
  • 1970-01-01
相关资源
最近更新 更多