由进程0创建的内核线程执行init() 函数,init()函数依次完成内核初始化。init()调用execve()系统调用装入可执行程序 init。结果,init内核线程变为一个普通进程,且拥有自己的每进程(per-process)内核数据结构。在系统关闭之前,init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动!

现在让我们进入代码:

`</p>

rest_init() -> kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND)`

kernel_thread 用来启动一个内核线程,fn 表示要执行函数指针,arg 为参数

`</p>

/*
 * Create a kernel thread
 */
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
    struct pt_regs regs;
    此结构用于保存进程硬件上下文。

    memset(&regs, 0, sizeof(regs));

    regs.ebx = (unsigned long) fn;
    regs.edx = (unsigned long) arg;

    regs.xds = __USER_DS;
    regs.xes = __USER_DS;
    regs.orig_eax = -1;
    regs.eip = (unsigned long) kernel_thread_helper;
    regs.xcs = __KERNEL_CS;
    regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;

    /* Ok, create the new process.. */
    return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
    核心函数!标志flags中 CLONE_VM 表示共享地址空间,CLONE_UNTRACED 表示不被跟踪调试
}`

以上代码手工设置了一个假的硬件上下文,因为硬件上下文并不对应于一个真实存在的一个进程。在后面调用函数 do_fork() 时会根据该假的硬件上下文和当前内核线程的其他上下文信息组合起来创建一个进程描述符,也就是说函数 do_fork() 会依照这个假的硬件上下文创建一个内核线程,此时新创建内核线程的进程描述符并不是当前内核线程的进程描述符的拷贝!

在之后的代码中,会将此结构复制到新建内核进程的内核栈中,此结构被模拟为从用户态的系统调用进入内核态。当新建进程被 schedule() 后运行时,iret 指令会从栈中弹出regs.eip,regs.xcs 到 寄存器 eip 和 cs 中,开始真正执行进程 1 的代码!(从kernel_thread_helper()处执行!kernel_thread_helper()函数暂不分析!)

pt_regs 结构:

`</p>

/* this struct defines the way the registers are stored on the
   stack during a system call. */

struct pt_regs {
    long ebx;
    long ecx;
    long edx;
    long esi;
    long edi;
    long ebp;
    long eax;
    int xds;
    int xes;
    long orig_eax;
    long eip;
    int xcs;
    long eflags;
    long esp;
    int xss;
}`