fork()源碼

調用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;返回一個指向子進程的指針,開始執行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章