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)的最新理解,有人可以确认/反驳以下项目吗?
init_task类型化的task_struct定义是静态的,随着任务的内核堆栈init_task.stack = init_stack,内存描述符init_task.mm=NULL和init_task.active_mm=&init_mm,其中堆栈区init_stack和mm_struct init_mm都静态定义。active_mm非空的事实意味着process 0是一个内核进程。还有,init_task.flags=PF_KTHREAD。init_stack的内核堆栈。这使得current宏有意义(自机器启动以来第一次),这使得fork()可能。在这一点之后,内核实际上运行 inprocess 0的上下文。start_kernel-> arch_call_rest_init-> rest_init,在这个函数内部,process 1&2是分叉的。在kernel_init为 调度的函数中,为每个其他逻辑 CPU 创建process 1一个新线程(具有CLONE_VM)并挂接到 CPU 的运行队列的rq->idle。tid 0(不仅tgid)。通常线程共享tgid但具有不同tid的process id. 我想它不会破坏任何东西,因为空闲线程被锁定到它们自己的 CPU 上。kernel_init加载init可执行文件(通常为/sbin/init),并切换current->mm和active_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。我们真正从 启动 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)
这里有一些重要的事情我们需要说清楚。
INIT_THREAD_INFO(init_task)设置thread_info 如上图所示。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)
因此定义了默认大小。
.mm = NULL,
.active_mm = &init_mm,
Run Code Online (Sandbox Code Playgroud)
让我们回顾一下start_kernel,rest_init将初始化kernel_init和kthreadd。
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 。