0号进程在内核中是一个比较特殊的进程,因为它是静态创建的,不是通过do_fork()创建的。所以从0号进程出发有助于理解一个进程的创建需要为它准备好哪些条件。
进程的内核表示
进程可以简单理解为Linux操作系统中的一个运行实体,在内核中用数据结构task_struct表示,这个结构体表示了进程运行时所需要的所有信息,所以这是内核中较为复杂的一个结构体,基于我们此篇文章所需要表达的知识,我们重点介绍下该结构体的成员thread_info,这个成员所分配的内存空间与该进程的栈连在一起,通常是8Kb,并且在thread_info结构体中有个成员task指向进程描述符。所以它们的关系如下图所以:
进程0的创建
对于32位的x86平台,进程0的描述符定义在arch/i386/kernel/init_task.c中,变量名为init_task,在这个文件中同时也定义了进程描述符所指向的下表,也能从代码中看到对它们的初始化。
- init_thread_union - 包含堆栈和thread_info的区域
- init_mm - 内存管理相关
- init_fs
- init_files
- init_signals
- init_sighand
对init_task的初始化如下所示:
/**
* 初始化进程0的任务描述符。
*/
#define INIT_TASK(tsk) \
{ \
.state = 0, \
.thread_info = &init_thread_info, \
.usage = ATOMIC_INIT(2), \
.flags = 0, \
.lock_depth = -1, \
.prio = MAX_PRIO-20, \
.static_prio = MAX_PRIO-20, \
.policy = SCHED_NORMAL, \
.cpus_allowed = CPU_MASK_ALL, \
.mm = NULL, \
.active_mm = &init_mm, \
.run_list = LIST_HEAD_INIT(tsk.run_list), \
.time_slice = HZ, \
.tasks = LIST_HEAD_INIT(tsk.tasks), \
.ptrace_children= LIST_HEAD_INIT(tsk.ptrace_children), \
.ptrace_list = LIST_HEAD_INIT(tsk.ptrace_list), \
.real_parent = &tsk, \
.parent = &tsk, \
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
.real_timer = { \
.function = it_real_fn \
}, \
.group_info = &init_groups, \
.cap_effective = CAP_INIT_EFF_SET, \
.cap_inheritable = CAP_INIT_INH_SET, \
.cap_permitted = CAP_FULL_SET, \
.keep_capabilities = 0, \
.user = INIT_USER, \
.comm = "swapper", \
.thread = INIT_THREAD, \
.fs = &init_fs, \
.files = &init_files, \
.signal = &init_signals, \
.sighand = &init_sighand, \
.pending = { \
.list = LIST_HEAD_INIT(tsk.pending.list), \
.signal = {{0}}}, \
.blocked = {{0}}, \
.alloc_lock = SPIN_LOCK_UNLOCKED, \
.proc_lock = SPIN_LOCK_UNLOCKED, \
.switch_lock = SPIN_LOCK_UNLOCKED, \
.journal_info = NULL, \
}
进程0的运行
对于0号进程的软件抽象实体已经通过静态的方式定义好了,那如何让这个实体运行呢?回想一下,进程的软件抽象是由描述进程相关的成员及执行时的栈空间组成,对于进程描述符描述的是进程的相关静态属性,而栈空间是进程执行时动态空间。因为在进程0初次执行时,对于进程的调度程序其实还没初始化,所以进程0的首次运行肯定不是通过内核中的调度程序加载的,而是简单的通过将栈寄存器(esp)指向进程0的栈底位置,从而表示了在接下来的程序执行是在进程0的栈空间中,也就是进程0在执行了。
进程0的栈起始位置(arch/i386/kernel/head.S):
ENTRY(stack_start)
.long init_thread_union+THREAD_SIZE
.long __BOOT_DS
使能完分页机制后,将进程0的栈起始位置赋值给esp寄存器,至此,后面的程序就是在进程0的上下文执行了:
/*
* Enable paging
*/
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
1:
/* Set up the stack pointer */
/* 意味着后面的程序在进程0的上下文开始执行了 */
lss stack_start,%esp