union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
我们可以发现init_task是用INIT_THREAD_INFO宏进行初始化的, 这个才是我们真正体系结构相关的部分, 他与init_thread_info定义在一起,被定义在/arch/对应体系/include/asm/thread_info.h中,以下为x86架构的定义
参见
http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h?v=4.5#L65
#define INIT_THREAD_INFO(tsk) \
{ \
.task = &tsk, \
.flags = 0, \
.cpu = 0, \
.addr_limit = KERNEL_DS, \
}
其他体系结构的定义请参见
/arch/对应体系/include/asm/thread_info.h中
x86 |
arch/x86/include/asm/thread_info.h |
arm64 |
arch/arm64/include/asm/thread_info.h |
init_thread_info定义中的__init_task_data表明该内核栈所在的区域位于内核映像的init data区,我们可以通过编译完内核后所产生的System.map来看到该变量及其对应的逻辑地址
cat System.map-3.1.6 | grep init_thread_union
进程内存空间
init_task的虚拟地址空间,也采用同样的方法被定义
由于init_task是一个运行在内核空间的内核线程, 因此其虚地址段mm为NULL, 但是必要时他还是需要使用虚拟地址的,因此avtive_mm被设置为init_mm
参见
http://lxr.free-electrons.com/source/include/linux/init_task.h?v=4.5#L202
.mm = NULL, \
.active_mm = &init_mm, \
其中init_mm被定义为init-mm.c中,参见 http://lxr.free-electrons.com/source/mm/init-mm.c?v=4.5#L16
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir,
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
.mmlist = LIST_HEAD_INIT(init_mm.mmlist),
INIT_MM_CONTEXT(init_mm)
};
0号进程演化
rest_init创建init进程(PID =1)和kthread进程(PID=2)
Linux在无进程概念的情况下将一直从初始化部分的代码执行到start_kernel,然后再到其最后一个函数调用rest_init
大致是在vmlinux的入口startup_32(head.S)中为pid号为0的原始进程设置了执行环境,然后原是进程开始执行start_kernel()完成Linux内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。
从rest_init开始,Linux开始产生进程,因为init_task是静态制造出来的,pid=0,它试图将从最早的汇编代码一直到start_kernel的执行都纳入到init_task进程上下文中。
这个函数其实是由0号进程执行的, 他就是在这个函数中, 创建了init进程和kthreadd进程
这部分代码如下:
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
smpboot_thread_init();
/*
* 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.
*/
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
- 调用kernel_thread()创建1号内核线程, 该线程随后转向用户空间, 演变为init进程
- 调用kernel_thread()创建kthreadd内核线程。
- init_idle_bootup_task():当前0号进程init_task最终会退化成idle进程,所以这里调用init_idle_bootup_task()函数,让init_task进程隶属到idle调度类中。即选择idle的调度相关函数。
- 调用schedule()函数切换当前进程,在调用该函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程也是刚刚被创建的。调用该函数后,1号进程kernel_init将会运行!
- 调用cpu_idle(),0号线程进入idle函数的循环,在该循环中会周期性地检查。
创建kernel_init
在rest_init函数中,内核将通过下面的代码产生第一个真正的进程(pid=1):
kernel_thread(kernel_init, NULL, CLONE_FS);
这个进程就是着名的pid为1的init进程,它会继续完成剩下的初始化工作,然后execve(/sbin/init), 成为系统中的其他所有进程的祖先。
但是这里我们发现一个问题,