下面分析一下 copy_thread 函数,个人认为此函数非常非常重要.

int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,unsigned long unused,
    struct task_struct * p, struct pt_regs * regs)
{
    struct pt_regs * childregs;
    struct task_struct *tsk;
    int err;

    childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;
    childregs指向新创建的进程的内核栈(栈底减去一个struct pt_regs 结构体的大小)的内核栈处!

    childregs = (struct pt_regs *) ((unsigned long) childregs - 8);
    childregs再指向childregs减去8字节的内核栈

    *childregs = *regs;
    将之前设置的假的硬件上下文信息保存到新创建进程的内核栈中
    此结构中保存着新创建进程的执行的代码指针

    childregs->eax = 0;
    将pt_regs结构中的eax字段设为0,这就是为什么fork()系统调用返回时子进程得到的返回值是0的原因。在新创建的进程得到处理器时,在执行真正的新创建进程的代码前,会把内核栈中pt_regs结构中内容弹出,而childregs->eax 被弹出到eax寄存器中,所以返回值是0

    childregs->esp = esp;
    根据参数esp更新新创建进程的继承自父进程的用户态栈栈指针esp,在新创建进程返回到用户态时,其将作为新创建进程用户态栈的栈顶指针寄存器esp的值!
    因为现在的情况是创建进程1,当最后执行iret指令后,从栈中弹出cs,ip以及EFLAGS的内容,此时cpu检查并没有发生特权级的改变所以不会弹出ss,esp的内容!也就是说进程1是一个内核态进程,根本就没有用户态!

    p->thread.esp = (unsigned long) childregs;
    更新新创建进程描述符的 thread 成员变量,用来保存进程的硬件上下文。在 switch_to() 执行进程切换时,会根据 thread 中内容来判断新进程的执行地址!
    thread.esp为内核栈栈顶。当新建进程在内核态执行时,会根据此字段设置内核态栈。

    p->thread.esp0 = (unsigned long) (childregs+1);
    该成员变量指向内核态栈低减8字节。在通过中断门、陷阱门进入到内核态时,处理器的控制器会根据任务状态段esp0的值来设置内核态栈顶。

    p->thread.eip = (unsigned long) ret_from_fork;
    thread成员变量 eip 的值为标记 ret_from_fork() 的地址。在新创建进程获得处理器时,内核主调度器 schedule() 会根据 thread中 的信息来设置硬件上下文,设置任务状态段中的内核态栈栈底字段 ESP0 为此处成员变量 esp0 的值;设置处理器的栈指针寄存器 esp 为此处的成员变量 esp 的值;设置 eip 为此处成员变量 eip 的值。在新创建进程获得处理器时,将从代码 ret_from_fork() 开始执行!

    savesegment(fs,p->thread.fs);
    savesegment(gs,p->thread.gs);
    把fs,ds段寄存器内容保存到 thread中!
 
    tsk = current;

    if (unlikely(NULL != tsk->thread.io_bitmap_ptr)) {
        p->thread.io_bitmap_ptr = kmalloc(IO_BITMAP_BYTES, GFP_KERNEL);
        if (!p->thread.io_bitmap_ptr) {
            p->thread.io_bitmap_max = 0;

            return -ENOMEM;
        }
        memcpy(p->thread.io_bitmap_ptr, tsk->thread.io_bitmap_ptr,
            IO_BITMAP_BYTES);
    }
    在父进程包含I/O访问许可权限位图的情况下,使新创建进程继承父进程的I/O访问许可权限位图!

    /*
     * Set a new TLS for the child thread?
     */
    if (clone_flags & CLONE_SETTLS) {
        struct desc_struct *desc;\
        struct user_desc info;
        int idx;

        err = -EFAULT;
        if (copy_from_user(&info, (void __user *)childregs->esi, sizeof(info)))
            goto out;

        err = -EINVAL;

        if (LDT_empty(&info))
            goto out;

        idx = info.entry_number;

        if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
            goto out;

        desc = p->thread.tls_array + idx - GDT_ENTRY_TLS_MIN;
        desc->a = LDT_entry_a(&info);
        desc->b = LDT_entry_b(&info);

    }
    在参数 clone_flags 包含 CLONE_SETTLS 标记的情况下,设置进程的TLS 在此不详细分析!

    err = 0;
 out:

    if (err && p->thread.io_bitmap_ptr) {
        kfree(p->thread.io_bitmap_ptr);
        p->thread.io_bitmap_max = 0;
    }

    return err;
    返回错误号!
}

为了更好的分析栈中情况,图如下:

process_switch_stack

栈底的8字节空间废掉了,留作他用!

copy_process() 函数结束后返回到 do_fork() 函数中! 在do_fork()中有这样一段代码:

`</p>

if (!(clone_flags & CLONE_STOPPED))
      wake_up_new_task(p, clone_flags);
else
      p->state = TASK_STOPPED;`

若含有 CLONE_STOPPED 标记则将新创建进程状态设置为 TASK_STOPPED ,说明新创建的进程不会运行,除非有进程通过信号唤醒它! 若没有 把当前进程添加到当前处理器队列中!

马上进入 wake_up_new_task() 函数!