1.Linux提供了三个系统调用用于创建进程,分别是fork,vfork,clone:

fork系统调用: 内核采用写时复制技术对传统的fork函数进行了下面的优化.即子进程创建后,父子进程以只读的方式共享父进程的资源(并不包括父进程的页表项).当子进程需要修改进程地址空间的某一页时,才为子进程复制该页.采用这样的技术可以避免对父进程中某些数据不必要的复制.

vfork系统调用: 使用vfork函数创建的子进程会完全共享父进程的地址空间,包括父进程的页表项.父子进程任意一方对任何数据的修改使得另一方都可以感知到.为了使得双方不受这种影响,vfork函数创建了子进程后,父进程便被阻塞,直至子进程调用了exec()或exit().由于现在fork函数引入了写时复制技术,在不考虑复制父进程页表项的情况下,vfork函数几乎不会被使用.

clone系统调用: clone函数创建子进程时灵活度比较大,因为它可以通过传递不同的参数来选择性的复制父进程的资源.

系统调用fork,vfork和clone在内核中对应的服务例程分别为sys_fork(),sys_vfork()和sys_clone():

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, 0, NULL, NULL);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;
    int __user *parent_tidptr, *child_tidptr;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    parent_tidptr = (int __user *)regs.edx;
    child_tidptr = (int __user *)regs.edi;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}

asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, 0, NULL, NULL);
}

可以看到do_fork()均被上述三个服务例程调用.三个系统调用的执行过程如下图所示:

这里写图片描述

2.do_fork()函数的参数解释:

do_fork()函数的原型:

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

clone_flags: 该标志位的4个字节分为两部分.最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD(用于wait系统调用).剩余的三个字节则是各种clone标志的组合.通过clone标志可以有选择的对父进程的资源进行复制.
这里写图片描述

statck_start: 子进程用户态堆栈的地址

stack_size:未被使用,通常被赋值为0

parent_tidptr:父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义

child_tidptr:子进程在用户态下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义

3.do_fork()函数 部分代码:

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;

        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }

    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace); //得到一个进程描述符, 内核栈, thread_info与父进程一样的子进程描述符

    if (!IS_ERR(p)) {
        struct completion vfork; //定义了一个完成变量, 以便clone_flags标志设置了CLONE_VFORK时, 父进程阻塞, 直到子进程调用exec或exit时 

        trace_sched_process_fork(current, p);

        nr = task_pid_vnr(p); //返回子进程的pid

        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);

        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork); //初始化完成变量
            get_task_struct(p);
        }

        wake_up_new_task(p); //将子进程加入红黑树, 并判断子进程是否可以抢占父进程, 此时子进程已经处于运行状态

        if (unlikely(trace))
            ptrace_event(trace, nr);

        //如果设置了CLONE_VFORK标志, 则父进程使用完成变量阻塞, 直到子进程调用exec或exit
        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
        }
    } else {
        nr = PTR_ERR(p);
    }
    return nr; //返回子进程的进程描述符
}

4.copy_process()函数 部分代码:

static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    unsigned long stack_size,
                    int __user *child_tidptr,
                    struct pid *pid,
                    int trace)
{
    int retval;
    struct task_struct *p;

    //检查clone_flags标志的设置是否正确
    if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
        return ERR_PTR(-EINVAL);

    if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
        return ERR_PTR(-EINVAL);

    if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
        return ERR_PTR(-EINVAL);

    if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
        return ERR_PTR(-EINVAL);

    if ((clone_flags & CLONE_PARENT) &&
                current->signal->flags & SIGNAL_UNKILLABLE)
        return ERR_PTR(-EINVAL);

    if (clone_flags & CLONE_SIGHAND) {
        if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
            (task_active_pid_ns(current) !=
                current->nsproxy->pid_ns_for_children))
            return ERR_PTR(-EINVAL);
    }

    p = dup_task_struct(current); //新创建一个进程描符, 线程描述符, 内核栈
    //此时父子进程的内容完全一样, 接下来就是重新设置一些子进程的值了

    //判断系统中的进程数是不是超过了限制
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

    copy_flags(clone_flags, p); //将clone_flags复制给子进程

    sched_fork(clone_flags, p); //该函数调用task_fork_fair函数, 设置子进程的vruntime值

    //根据clone_flags值, 拷贝或共享父子进程之间的一些数据结构 

    retval = perf_event_init_task(p);
    retval = audit_alloc(p);
    retval = copy_semundo(clone_flags, p);
    retval = copy_files(clone_flags, p); //文件描述符
    retval = copy_fs(clone_flags, p); //进程当前工作目录信息
    retval = copy_sighand(clone_flags, p); //信号处理表
    retval = copy_signal(clone_flags, p); //信号值的处理
    retval = copy_mm(clone_flags, p); //内存描述符
    retval = copy_namespaces(clone_flags, p); //命名空间
    retval = copy_io(clone_flags, p); 
    retval = copy_thread(clone_flags, stack_start, stack_size, p); //dup_task_struct函数中会在创建子进程的进程描符之前, 把父进程此时的寄存器环境保存在父进程的内核栈中, 子进程的内核栈再复制父进程的内核栈值, 此函数就是用这些值更新子进程寄存器的值. 另外, 子进程对应的thread_info结构中的esp字段会被初始化为子进程内核栈的基址

    if (pid != &init_struct_pid) {
        retval = -ENOMEM;
        pid = alloc_pid(p->nsproxy->pid_ns_for_children); //分配子进程的进程描述符 号
    }

    return pid;
}

5.dup_task_struct()函数 部分代码:

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;
    unsigned long *stackend;
    int node = tsk_fork_get_node(orig); 
    int err;

    tsk = alloc_task_struct_node(node); //申请进程描述符
    if (!tsk)
        return NULL;

    ti = alloc_thread_info_node(tsk, node); // 申请线程描述符
    if (!ti)
        goto free_tsk;

    err = arch_dup_task_struct(tsk, orig); //将父进程的进程描述符,线程描述符,内核栈的值赋值给子进程

    //此时, 父子进程的内容完全一样

    if (err)
        goto free_ti;

    tsk->stack = ti; //子进程的进程描述符的stack指针设为子进程的线程描述符

    return tsk;

free_ti:
    free_thread_info(ti);
free_tsk:
    free_task_struct(tsk);
    return NULL;
}

本文引述自:
http://edsionte.com/techblog/archives/2103
http://edsionte.com/techblog/archives/2131
http://edsionte.com/techblog/archives/2141

Logo

更多推荐