fork()源码

这篇博客详细解析了fork()函数的工作原理,从unistd.h中的syscall0宏展开到int $0x80系统调用,再到内核中的sys_fork()、do_fork()和copy_process()函数。描述了如何创建子进程的内核栈、task_struct、页表以及文件描述符,并强调了子进程与父进程的区别和共享资源的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

调用fork()函数执行到了unistd.h中的宏函数syscall0

 1 /* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
 2 /*
 3  * Don't remove the .ifnc tests; they are an insurance against
 4  * any hard-to-spot gcc register allocation bugs.
 5  */
 6 #define _syscall0(type,name) \
 7 type name(void) \
 8 { \
 9   register long __a __asm__ ("r10"); \
10   register long __n_ __asm__ ("r9") = (__NR_##name); \
11   __asm__ __volatile__ (".ifnc %0%1,$r10$r9\n\t" \
12             ".err\n\t" \
13             ".endif\n\t" \
14             "break 13" \
15             : "=r" (__a) \
16             : "r" (__n_)); \
17   if (__a >= 0) \
18      return (type) __a; \
19   errno = -__a; \
20   return (type) -1; \
21 }

将宏函数展开后变为

1 /* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
 2 /*
 3  * Don't remove the .ifnc tests; they are an insurance against
 4  * any hard-to-spot gcc register allocation bugs.
 5  */
 7 int fork(void) 
 8 { 
 9   register long __a __asm__ ("r10"); \
10   register long __n_ __asm__ ("r9") = (__NR_##name); \
11   __asm__ __volatile__ (".ifnc %0%1,$r10$r9\n\t" \
12             ".err\n\t" \
13             ".endif\n\t" \
14             "break 13" \
15             : "=r" (__a) \
16             : "r" (__n_)); \
17   if (__a >= 0) \
18      return (type) __a; \
19   errno = -__a; \
20   return (type) -1; \
21 }

当name=fork时,在宏中_NR##name就替换成了__NR_fork了。

int $0x80是所有系统调用函数的总入口,fork()是其中之一,”0”(_NR_fork)意思是将fork在sys_call_table[]中对应的函数编号_NR_fork也就是2传给eax寄存器。这个编号就是sys_fork()函数在sys_call_table中的偏移值。

产生int $0x80软件中断,CPU从3级特权的进程跳到0特权级内核代码中执行。中断使CPU硬件自动将SS、ESP、EFLAGGS、CS、EIP这五个寄存器的值按照这个顺序压人父进程的内核栈,这些压栈的数据将在后续的copy_process()函数中用来初始化进程1的任务状态描述符TSS

  CPU自动压栈完成后,跳转到system_call.s中的_system_call处执行,继续将DS、ES、FS、EDX、ECX、EBX压栈(这些压栈仍旧是为了初始化子进程中的任务状态描述符TSS做准备)。最终内核通过刚刚设置的eax的偏移值“2”查询sys_call_table[],知道此次系统调用对应的函数是sys_fork()。跳转到_sys_fork处执行。
 
 
sys_fork()

1 asmlinkage int sys_fork(void)
2 {
3 #ifndef CONFIG_MMU
4     /* fork almost works, enough to trick you into looking elsewhere:-( */
5     return -EINVAL;
6 #else
7     return do_fork(SIGCHLD, user_stack(__frame), __frame, 0, NULL, NULL);//调用do_fork函数
8 #endif
9 }

do_fork()

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
/**
 * 负责处理clone,fork,vfork系统调用。
 * clone_flags-与clone的flag参数相同
 * stack_start-与clone的child_stack相同
 * regs-指向通用寄存器的值。是在从用户态切换到内核态时被保存到内核态堆栈中的。
 * stack_size-未使用,总是为0
 * parent_tidptr,child_tidptr-clone中对应参数ptid,ctid相同
 */
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    /**
     * 通过查找pidmap_array位图,为子进程分配新的pid参数.
     */
    long pid = alloc_pidmap();

    if (pid < 0)
        return -EAGAIN;
    /**
     * 如果父进程正在被跟踪,就检查debugger程序是否想跟踪子进程.并且子进程不是内核进程(CLONE_UNTRACED未设置)
     * 那么就设置CLONE_PTRACE标志.
     */
    if (unlikely(current->ptrace)) {
        trace = fork_traceflag (clone_flags);
        if (trace)
            clone_flags |= CLONE_PTRACE;
    }

    /**
     * copy_process复制进程描述符.如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址.
     * 这是创建进程的关键步骤.
     */
    p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
    /*
     * Do this prior waking up the new thread - the thread pointer
     * might get invalid after that point, if the thread exits quickly.
     */
    if (!IS_ERR(p)) {
        struct completion vfork;

        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
        }

        /**
         * 如果设置了CLONE_STOPPED,或者必须跟踪子进程.
         * 就设置子进程为TASK_STOPPED状态,并发送SIGSTOP信号挂起它.
         */
        if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
            /*
             * We'll start up with an immediate SIGSTOP.
             */
            sigaddset(&p->pending.signal, SIGSTOP);
            set_tsk_thread_flag(p, TIF_SIGPENDING);
        }

        /**
         * 没有设置CLONE_STOPPED,就调用wake_up_new_task
         * 它调整父进程和子进程的调度参数.
         * 如果父子进程运行在同一个CPU上,并且不能共享同一组页表(CLONE_VM标志被清0).那么,就把子进程插入父进程运行队列.
         * 并且子进程插在父进程之前.这样做的目的是:如果子进程在创建之后执行新程序,就可以避免写时复制机制执行不必要时页面复制.
         * 否则,如果运行在不同的CPU上,或者父子进程共享同一组页表.就把子进程插入父进程运行队列的队尾.
         */
        if (!(clone_flags & CLONE_STOPPED))
            wake_up_new_task(p, clone_flags);
        else/*如果CLONE_STOPPED标志被设置,就把子进程设置为TASK_STOPPED状态。*/
            p->state = TASK_STOPPED;

        /**
         * 如果进程正被跟踪,则把子进程的PID插入到父进程的ptrace_message,并调用ptrace_notify
         * ptrace_notify使当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号.子进程的祖父进程是跟踪父进程的debugger进程.
         * dubugger进程可以通过ptrace_message获得被创建子进程的PID.
         */
        if (unlikely (trace)) {
            current->ptrace_message = pid;
            ptrace_notify ((trace << 8) | SIGTRAP);
        }

        /**
         * 如果设置了CLONE_VFORK,就把父进程插入等待队列,并挂起父进程直到子进程结束或者执行了新的程序.
         */
        if (clone_flags & CLONE_VFORK) {
            wait_for_completion(&vfork);
            if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
        }
    } else {
        free_pidmap(pid);
        pid = PTR_ERR(p);
    }
    return pid;
}

copy_process()
// copy_process复制进程描述符.如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址.
copy_process()的工作:
1、调用dup_task_struct()为子进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。此时子进程和父进程的描述符是完全相同的。

  p = dup_task_struct(current)—->(struct task_struct *tsk———->tsk = alloc_task_struct()从slab层分配了一个关于进程描述符的slab)

  2、检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。

  3、子进程着手使自己与父进程区别开来,为进程的task_struct、tss做个性化设置,进程描述符内的许多成员都要被清0或设置为初始值。那些不是继承而来的进程描述符成员,主要是统计信息。task_struct中的大多数数据都依然未被修改。

  4、为子进程创建第一个页表,将进程0的页表项内容赋给这个页表。

/*
 * This creates a new process as a copy of the old one,
 * but does not actually start it yet.
 *
 * It copies the registers, and all the appropriate
 * parts of the process environment (as per the clone
 * flags). The actual kick-off is left to the caller.
 */
/**
 * 创建进程描述符以及子进程执行所需要的所有其他数据结构
 * 它的参数与do_fork相同。外加子进程的PID。
 */
static task_t *copy_process(unsigned long clone_flags,
                 unsigned long stack_start,
                 struct pt_regs *regs,
                 unsigned long stack_size,
                 int __user *parent_tidptr,
                 int __user *child_tidptr,
                 int pid)
{
    int retval;
    struct task_struct *p = NULL;

    /**
     * 检查clone_flags所传标志的一致性。
     */

    /**
     * 如果CLONE_NEWNS和CLONE_FS标志都被设置,返回错误
     */
    if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
        return ERR_PTR(-EINVAL);

    /*
     * Thread groups must share signals as well, and detached threads
     * can only be started up within the thread group.
     */
    /**
     * CLONE_THREAD标志被设置,并且CLONE_SIGHAND没有设置。
     * (同一线程组中的轻量级进程必须共享信号)
     */
    if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
        return ERR_PTR(-EINVAL);

    /*
     * Shared signal handlers imply shared VM. By way of the above,
     * thread groups also imply shared VM. Blocking this case allows
     * for various simplifications in other code.
     */
    /**
     * CLONE_SIGHAND被设置,但是CLONE_VM没有设置。
     * (共享信号处理程序的轻量级进程也必须共享内存描述符)
     */
    if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
        return ERR_PTR(-EINVAL);

    /**
     * 通过调用security_task_create以及稍后调用security_task_alloc执行所有附加的安全检查。
     * LINUX2.6提供扩展安全性的钩子函数,与传统unix相比,它具有更加强壮的安全模型。
     */
    retval = security_task_create(clone_flags);
    if (retval)
        goto fork_out;

    retval = -ENOMEM;
    /**
     * 调用dup_task_struct为子进程获取进程描述符。
     */
    p = dup_task_struct(current);
    if (!p)
        goto fork_out;

    /**
     * 检查存放在current->sigal->rlim[RLIMIT_NPROC].rlim_cur中的限制值,是否小于或者等于用户所拥有的进程数。
     * 如果是,则返回错误码。当然,有root权限除外。
     * p->user表示进程的拥有者,p->user->processes表示进程拥有者当前进程数
     * xie.baoyou注:此处比较是用>=而不是>
     */
    retval = -EAGAIN;
    if (atomic_read(&p->user->processes) >=
            p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
        /**
         * 当然,用户有root权限就另当别论了
         */
        if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
                p->user != &root_user)
            goto bad_fork_free;
    }

    /**
     * 递增user结构的使用计数器
     */
    atomic_inc(&p->user->__count);
    /**
     * 增加用户拥有的进程计数。
     */
    atomic_inc(&p->user->processes);
    get_group_info(p->group_info);

    /*
     * If multiple threads are within copy_process(), then this check
     * triggers too late. This doesn't hurt, the check is only there
     * to stop root fork bombs.
     */
    /**
     * 检查系统中的进程数量(nr_threads)是否超过max_threads
     * max_threads的缺省值是由系统内存容量决定的。总的原则是:所有的thread_info描述符和内核栈所占用的空间
     * 不能超过物理内存的1/8。不过,系统管理可以通过写/proc/sys/kernel/thread-max文件来改变这个值。
     */
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

    /**
     * 如果新进程的执行域和可招待格式的内核函数都包含在内核中模块中,
     * 就递增它们的使用计数器。
     */
    if (!try_module_get(p->thread_info->exec_domain->module))
        goto bad_fork_cleanup_count;

    if (p->binfmt && !try_module_get(p->binfmt->module))
        goto bad_fork_cleanup_put_domain;

    /**
     * 设置几个与进程状态相关的关键字段。
     */

    /**
     * did_exec是进程发出的execve系统调用的次数,初始为0
     */
    p->did_exec = 0;
    /**
     * 更新从父进程复制到tsk_flags字段中的一些标志。
     * 首先清除PF_SUPERPRIV。该标志表示进程是否使用了某种超级用户权限。
     * 然后设置PF_FORKNOEXEC标志。它表示子进程还没有发出execve系统调用。
     */
    copy_flags(clone_flags, p);
    /**
     * 保存新进程的pid值。
     */
    p->pid = pid;
    retval = -EFAULT;
    /**
     * 如果CLONE_PARENT_SETTID标志被设置,就将子进程的PID复制到参数parent_tidptr指向的用户态变量中。
     * xie.baoyou:想想我们常常调用的pid = fork()语句吧。
     */
    if (clone_flags & CLONE_PARENT_SETTID)
        if (put_user(p->pid, parent_tidptr))
            goto bad_fork_cleanup;

    p->proc_dentry = NULL;

    /**
     * 初始化子进程描述符中的list_head数据结构和自旋锁。
     * 并为挂起信号,定时器及时间统计表相关的几个字段赋初值。
     */
    INIT_LIST_HEAD(&p->children);
    INIT_LIST_HEAD(&p->sibling);
    p->vfork_done = NULL;
    spin_lock_init(&p->alloc_lock);
    spin_lock_init(&p->proc_lock);

    clear_tsk_thread_flag(p, TIF_SIGPENDING);
    init_sigpending(&p->pending);

    p->it_real_value = 0;
    p->it_real_incr = 0;
    p->it_virt_value = cputime_zero;
    p->it_virt_incr = cputime_zero;
    p->it_prof_value = cputime_zero;
    p->it_prof_incr = cputime_zero;
    init_timer(&p->real_timer);
    p->real_timer.data = (unsigned long) p;

    p->utime = cputime_zero;
    p->stime = cputime_zero;
    p->rchar = 0;       /* I/O counter: bytes read */
    p->wchar = 0;       /* I/O counter: bytes written */
    p->syscr = 0;       /* I/O counter: read syscalls */
    p->syscw = 0;       /* I/O counter: write syscalls */
    acct_clear_integrals(p);

    /**
     * 把大内核锁计数器初始化为-1
     */
    p->lock_depth = -1;     /* -1 = no lock */
    do_posix_clock_monotonic_gettime(&p->start_time);
    p->security = NULL;
    p->io_context = NULL;
    p->io_wait = NULL;
    p->audit_context = NULL;
#ifdef CONFIG_NUMA
    p->mempolicy = mpol_copy(p->mempolicy);
    if (IS_ERR(p->mempolicy)) {
        retval = PTR_ERR(p->mempolicy);
        p->mempolicy = NULL;
        goto bad_fork_cleanup;
    }
#endif

    p->tgid = p->pid;
    if (clone_flags & CLONE_THREAD)
        p->tgid = current->tgid;

    if ((retval = security_task_alloc(p)))
        goto bad_fork_cleanup_policy;
    if ((retval = audit_alloc(p)))
        goto bad_fork_cleanup_security;
    /* copy all the process information */
    /**
     * copy_semundo,copy_files,copy_fs,copy_sighand,copy_signal
     * copy_mm,copy_keys,copy_namespace创建新的数据结构,并把父进程相应数据结构的值复制到新数据结构中。
     * 除非clone_flags参数指出它们有不同的值。
     */
    if ((retval = copy_semundo(clone_flags, p)))
        goto bad_fork_cleanup_audit;
    if ((retval = copy_files(clone_flags, p)))
        goto bad_fork_cleanup_semundo;
    if ((retval = copy_fs(clone_flags, p)))
        goto bad_fork_cleanup_files;
    if ((retval = copy_sighand(clone_flags, p)))
        goto bad_fork_cleanup_fs;
    if ((retval = copy_signal(clone_flags, p)))
        goto bad_fork_cleanup_sighand;
    if ((retval = copy_mm(clone_flags, p)))
        goto bad_fork_cleanup_signal;
    if ((retval = copy_keys(clone_flags, p)))
        goto bad_fork_cleanup_mm;
    if ((retval = copy_namespace(clone_flags, p)))
        goto bad_fork_cleanup_keys;
    /**
     * 调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中)
     * 来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应字段的值(这是fork和clone系统调用在子进程中的返回值)
     * 强行置为0。子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。
     * 如果父进程使用IO权限位图。则子进程获取该位图的一个拷贝。
     * 最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。
     */
    retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
    if (retval)
        goto bad_fork_cleanup_namespace;

    /**
     * 如果clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID
     * 就把child_tidptr参数的值分别复制到set_child_tid或clear_child_tid字段。
     * 这些标志说明:必须改变子进程用户态地址空间的dhild_tidptr所指向的变量的值
     * 不过实际的写操作要稍后再执行。
     */
    p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
    /*
     * Clear TID on mm_release()?
     */
    p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;

    /*
     * Syscall tracing should be turned off in the child regardless
     * of CLONE_PTRACE.
     */
    /**
     * 清除TIF_SYSCALL_TRACE标志。使ret_from_fork函数不会把系统调用结束的消息通知给调试进程。
     * 也不应该通知给调试进程,因为子进程并没有调用fork.
     */
    clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);

    /* Our parent execution domain becomes current domain
       These must match for thread signalling to apply */

    p->parent_exec_id = p->self_exec_id;

    /* ok, now we should be set up.. */
    /**
     * 用clone_flags参数低位的信号数据编码统建始化tsk_exit_signal字段。
     * 如CLONE_THREAD标志被置位,就把exit_signal字段初始化为-1。
     * 这样做是因为:当创建线程时,即使被创建的线程死亡,都不应该给领头进程的父进程发送信号。
     * 而应该是领头进程死亡后,才向其领头进程的父进程发送信号。
     */
    p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
    p->pdeath_signal = 0;
    p->exit_state = 0;

    /* Perform scheduler related setup */
    /**
     * 调用sched_fork完成对新进程调度程序数据结构的初始化。
     * 该函数把新进程的状态置为TASK_RUNNING,并把thread_info结构的preempt_count字段设置为1,
     * 从而禁止抢占。
     * 此外,为了保证公平调度,父子进程共享父进程的时间片。
     */
    sched_fork(p);

    /*
     * Ok, make it visible to the rest of the system.
     * We dont wake it up yet.
     */
    p->group_leader = p;
    INIT_LIST_HEAD(&p->ptrace_children);
    INIT_LIST_HEAD(&p->ptrace_list);

    /* Need tasklist lock for parent etc handling! */
    write_lock_irq(&tasklist_lock);

    /*
     * The task hasn't been attached yet, so cpus_allowed mask cannot
     * have changed. The cpus_allowed mask of the parent may have
     * changed after it was copied first time, and it may then move to
     * another CPU - so we re-copy it here and set the child's CPU to
     * the parent's CPU. This avoids alot of nasty races.
     */
    p->cpus_allowed = current->cpus_allowed;
    /**
     * 初始化子线程的cpu字段。
     */
    set_task_cpu(p, smp_processor_id());

    /*
     * Check for pending SIGKILL! The new thread should not be allowed
     * to slip out of an OOM kill. (or normal SIGKILL.)
     */
    if (sigismember(&current->pending.signal, SIGKILL)) {
        write_unlock_irq(&tasklist_lock);
        retval = -EINTR;
        goto bad_fork_cleanup_namespace;
    }

    /* CLONE_PARENT re-uses the old parent */
    /**
     * 初始化表示亲子关系的字段,如果CLONE_PARENT或者CLONE_THREAD被设置了
     * 就用current->real_parent初始化,否则,当前进程就是初创建进程的父进程。
     */
    if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
        p->real_parent = current->real_parent;
    else
        p->real_parent = current;
    p->parent = p->real_parent;

    if (clone_flags & CLONE_THREAD) {
        spin_lock(&current->sighand->siglock);
        /*
         * Important: if an exit-all has been started then
         * do not create this new thread - the whole thread
         * group is supposed to exit anyway.
         */
        if (current->signal->flags & SIGNAL_GROUP_EXIT) {
            spin_unlock(&current->sighand->siglock);
            write_unlock_irq(&tasklist_lock);
            retval = -EAGAIN;
            goto bad_fork_cleanup_namespace;
        }
        p->group_leader = current->group_leader;

        if (current->signal->group_stop_count > 0) {
            /*
             * There is an all-stop in progress for the group.
             * We ourselves will stop as soon as we check signals.
             * Make the new thread part of that group stop too.
             */
            current->signal->group_stop_count++;
            set_tsk_thread_flag(p, TIF_SIGPENDING);
        }

        spin_unlock(&current->sighand->siglock);
    }

    /** 
     * 把新进程加入到进程链表
     */
    SET_LINKS(p);

    /**
     * PT_PTRACED表示子进程必须被跟踪,就把current->parent赋给tsk->parent,并将子进程插入调试程序的跟踪链表中。
     */
    if (unlikely(p->ptrace & PT_PTRACED))
        __ptrace_link(p, current->parent);

    /**
     * 把新进程描述符的PID插入pidhash散列表中。
     */
    attach_pid(p, PIDTYPE_PID, p->pid);
    attach_pid(p, PIDTYPE_TGID, p->tgid);

    /**
     * 如果子进程是线程组的领头进程(CLONE_THREAD标志被清0)
     */
    if (thread_group_leader(p)) {
        /**
         * 将进程插入相应的散列表。
         */
        attach_pid(p, PIDTYPE_PGID, process_group(p));
        attach_pid(p, PIDTYPE_SID, p->signal->session);
        if (p->pid)
            __get_cpu_var(process_counts)++;
    }

    /**
     * 计数
     */
    nr_threads++;
    total_forks++;
    write_unlock_irq(&tasklist_lock);
    retval = 0;

fork_out:
    if (retval)
        return ERR_PTR(retval);
    return p;

bad_fork_cleanup_namespace:
    exit_namespace(p);
bad_fork_cleanup_keys:
    exit_keys(p);
bad_fork_cleanup_mm:
    if (p->mm)
        mmput(p->mm);
bad_fork_cleanup_signal:
    exit_signal(p);
bad_fork_cleanup_sighand:
    exit_sighand(p);
bad_fork_cleanup_fs:
    exit_fs(p); /* blocking */
bad_fork_cleanup_files:
    exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
    exit_sem(p);
bad_fork_cleanup_audit:
    audit_free(p);
bad_fork_cleanup_security:
    security_task_free(p);
bad_fork_cleanup_policy:
#ifdef CONFIG_NUMA
    mpol_free(p->mempolicy);
#endif
bad_fork_cleanup:
    if (p->binfmt)
        module_put(p->binfmt->module);
bad_fork_cleanup_put_domain:
    module_put(p->thread_info->exec_domain->module);
bad_fork_cleanup_count:
    put_group_info(p->group_info);
    atomic_dec(&p->user->processes);
    free_uid(p->user);
bad_fork_free:
    free_task(p);
    goto fork_out;
}

调用dup_task_struct为子进程获取进程描述符。

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;

    /**
     * prepare_to_copy中会调用unlazy_fpu。
     * 它把FPU、MMX和SSE/SSE2寄存器的内容保存到父进程的thread_info结构中。
     * 稍后,dup_task_struct将把这些值复制到子进程的thread_info中。
     */
    prepare_to_copy(orig);

    /**
     * alloc_task_struct宏为新进程获取进程描述符,并将描述符保存到tsk局部变量中。
     */
    tsk = alloc_task_struct();
    if (!tsk)
        return NULL;

    /**
     * alloc_thread_info宏获取一块空闲内存区,用来存放新进程的thread_info结构和内核栈。
     * 这块内存区字段的大小是8KB或者4KB。
     */
    ti = alloc_thread_info(tsk);
    if (!ti) {
        free_task_struct(tsk);
        return NULL;
    }
    /** 
     * 将current进程描述符的内容复制到tsk所指向的task_struct结构中,然后把tsk_thread_info置为ti
     * 将current进程的thread_info内容复制给ti指向的结构中,并将ti_task置为tsk.
     */
    *ti = *orig->thread_info;
    *tsk = *orig;
    tsk->thread_info = ti;
    ti->task = tsk;

    /* One for us, one for whoever does the "release_task()" (usually parent) */
    /**
     * 把新进程描述符的使用计数器usage设置为2,用来表示描述符正在被使用而且其相应的进程处于活动状态。
     * 进程状态既不是EXIT_ZOMBIE,也不是EXIT_DEAD
     */
    atomic_set(&tsk->usage,2);
    return tsk;
}
 #define alloc_task_struct()    kmem_cache_alloc(task_struct_cachep, GFP_KERNEL)
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
    return __cache_alloc(cachep, flags);
}
static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
    unsigned long save_flags;
    void* objp;
    struct array_cache *ac;

    cache_alloc_debugcheck_before(cachep, flags);

    local_irq_save(save_flags);
    /**
     * 首先试图从本地高速缓存获得一个空闲对象。
     */
    ac = ac_data(cachep);
    /**
     * 如果本地高速缓存有空闲对象,那么avail字段就包含最后被释放的对象的项在本地高速缓存中的下标。
     */
    if (likely(ac->avail)) {
        STATS_INC_ALLOCHIT(cachep);
        ac->touched = 1;
        /**
         * 因为本地高速缓存数组正好存放在ac描述符的后面。
         * 所以(void**)(ac+1)[--ac->avail]获得空闲对象的地址,并递减ac->avail的值。
         */
        objp = ac_entry(ac)[--ac->avail];
    } else {/* 本地高速缓存中没有空闲对象。 */
        STATS_INC_ALLOCMISS(cachep);
        /**
         * cache_alloc_refill重新填充本地高速缓存并获得一个空闲对象。
         */
        objp = cache_alloc_refill(cachep, flags);
    }
    local_irq_restore(save_flags);
    objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
    return objp;
}

copy_fs()函数为子进程复制父进程的页目录项

static inline int copy_fs(unsigned long clone_flags, struct task_struct * tsk)
{
    if (clone_flags & CLONE_FS) {
        atomic_inc(&current->fs->count);
        return 0;
    }
    tsk->fs = __copy_fs_struct(current->fs);
    if (!tsk->fs)
        return -ENOMEM;
    return 0;
}

__copy_fs_struct()

static inline struct fs_struct *__copy_fs_struct(struct fs_struct *old)
{
    struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
    /* We don't need to lock fs - think why ;-) */
    if (fs) {
        atomic_set(&fs->count, 1);
        rwlock_init(&fs->lock);
        fs->umask = old->umask;
        read_lock(&old->lock);
        fs->rootmnt = mntget(old->rootmnt);
        fs->root = dget(old->root);
        fs->pwdmnt = mntget(old->pwdmnt);
        fs->pwd = dget(old->pwd);
        if (old->altroot) {
            fs->altrootmnt = mntget(old->altrootmnt);
            fs->altroot = dget(old->altroot);
        } else {
            fs->altrootmnt = NULL;
            fs->altroot = NULL;
        }
        read_unlock(&old->lock);
    }
    return fs;
}

fs_struct数据结构,这个数据结构将VFS层里面的描述页目录对象的结构体进行了实例化,这样就可以为子进程创建一个页目录项,同时这个fs_strcut结构体和为子进程分配内核栈一样都是通过页高速缓存实现的:struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);

1 struct fs_struct {
2     atomic_t count;
3     rwlock_t lock;
4     int umask;
5     struct dentry * root, * pwd, * altroot;                     //struct denty 页目录项结构体
6     struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
7 };

copy_files()函数,为子进程复制父进程的页表,共享父进程的文件

/**
 * 复制进程文件描述符
 */
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
    struct files_struct *oldf, *newf;
    struct file **old_fds, **new_fds;
    int open_files, size, i, error = 0, expand;

    /*
     * A background process may not have any files ...
     */
    oldf = current->files;
    if (!oldf)
        goto out;

    if (clone_flags & CLONE_FILES) {
        atomic_inc(&oldf->count);
        goto out;
    }

    /*
     * Note: we may be using current for both targets (See exec.c)
     * This works because we cache current->files (old) as oldf. Don't
     * break this.
     */
    tsk->files = NULL;
    error = -ENOMEM;
    newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);
    if (!newf) 
        goto out;

    atomic_set(&newf->count, 1);

    spin_lock_init(&newf->file_lock);
    newf->next_fd       = 0;
    newf->max_fds       = NR_OPEN_DEFAULT;
    newf->max_fdset     = __FD_SETSIZE;
    newf->close_on_exec = &newf->close_on_exec_init;
    newf->open_fds      = &newf->open_fds_init;
    newf->fd        = &newf->fd_array[0];

    spin_lock(&oldf->file_lock);

    open_files = count_open_files(oldf, oldf->max_fdset);
    expand = 0;

    /*
     * Check whether we need to allocate a larger fd array or fd set.
     * Note: we're not a clone task, so the open count won't  change.
     */
    if (open_files > newf->max_fdset) {
        newf->max_fdset = 0;
        expand = 1;
    }
    if (open_files > newf->max_fds) {
        newf->max_fds = 0;
        expand = 1;
    }

    /* if the old fdset gets grown now, we'll only copy up to "size" fds */
    if (expand) {
        spin_unlock(&oldf->file_lock);
        spin_lock(&newf->file_lock);
        error = expand_files(newf, open_files-1);
        spin_unlock(&newf->file_lock);
        if (error < 0)
            goto out_release;
        spin_lock(&oldf->file_lock);
    }

    old_fds = oldf->fd;
    new_fds = newf->fd;

    memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
    memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);

    for (i = open_files; i != 0; i--) {
        struct file *f = *old_fds++;
        if (f) {
            get_file(f);
        } else {
            /*
             * The fd may be claimed in the fd bitmap but not yet
             * instantiated in the files array if a sibling thread
             * is partway through open().  So make sure that this
             * fd is available to the new process.
             */
            FD_CLR(open_files - i, newf->open_fds);
        }
        *new_fds++ = f;
    }
    spin_unlock(&oldf->file_lock);

    /* compute the remainder to be cleared */
    size = (newf->max_fds - open_files) * sizeof(struct file *);

    /* This is long word aligned thus could use a optimized version */ 
    memset(new_fds, 0, size); 

    if (newf->max_fdset > open_files) {
        int left = (newf->max_fdset-open_files)/8;
        int start = open_files / (8 * sizeof(unsigned long));

        memset(&newf->open_fds->fds_bits[start], 0, left);
        memset(&newf->close_on_exec->fds_bits[start], 0, left);
    }

    tsk->files = newf;
    error = 0;
out:
    return error;

out_release:
    free_fdset (newf->close_on_exec, newf->max_fdset);
    free_fdset (newf->open_fds, newf->max_fdset);
    free_fd_array(newf->fd, newf->max_fds);
    kmem_cache_free(files_cachep, newf);
    goto out;
}

files_struct结构体,files_struct结构保存了进程打开的所有文件表数据,描述一个正被打开的文件。

 1 struct files_struct {  
 2     atomic_t        count;              //自动增量  
 3     struct fdtable  *fdt;  
 4     struct fdtable  fdtab;  
 5     fd_set      close_on_exec_init;     //执行exec时
 6 需要关闭的文件描述符初值集合  
 7     fd_set      open_fds_init;          //当前打开文件
 8 的文件描述符屏蔽字  
 9     struct file         * fd_array[NR_OPEN_DEFAULT];  
10     spinlock_t      file_lock;  /* Protects concurrent
11 writers.  Nests inside tsk->alloc_lock */  
12 }; 

从上面的分析可以看出fork()的流程大概是:

  (1)、p = dup_task_struct(current); 为新进程创建一个内核栈、thread_iofo和task_struct,这里完全copy父进程的内容,所以到目前为止,父进程和子进程是没有任何区别的。

  (2)、为新进程在其内存上建立内核堆栈

  (3)、对子进程task_struct任务结构体中部分变量进行初始化设置,检查所有的进程数目是否已经超出了系统规定的最大进程数,如果没有的话,那么就开始设置进程描诉符中的初始值,从这开始,父进程和子进程就开始区别开了。

  (4)、把父进程的有关信息复制给子进程,建立共享关系

  (5)、设置子进程的状态为不可被TASK_UNINTERRUPTIBLE,从而保证这个进程现在不能被投入运行,因为还有很多的标志位、数据等没有被设置

  (6)、复制标志位(falgs成员)以及权限位(PE_SUPERPRIV)和其他的一些标志

  (7)、调用get_pid()给子进程获取一个有效的并且是唯一的进程标识符PID

  (8)、return ret_from_fork;返回一个指向子进程的指针,开始执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值