unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; 如果創(chuàng)建新的user namespace就需要具備管理員權(quán)限或者CAP_SETUID或者CAP_SETGID。 if (clone_flags & CLONE_NEWUSER) { if (clone_flags & CLONE_THREAD) return -EINVAL; if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) || !capable(CAP_SETGID)) return -EPERM; } 檢查是否跟蹤子進(jìn)程,如果是,就根據(jù)不同的跟蹤行為發(fā)送不同的跟蹤事件。 PTRACE_EVENT_VFORK: 使子進(jìn)程下次調(diào)用 vfork() 時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開始執(zhí)行時(shí)就已設(shè)置 SIGSTOP 信號(hào)的新進(jìn)程。 PTRACE_EVENT_CLONE: 使子進(jìn)程下次調(diào)用 clone() 時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開始執(zhí)行時(shí)就已設(shè)置 SIGSTOP 信號(hào)的新進(jìn)程。 PTRACE_EVENT_FORK: 使子進(jìn)程下次調(diào)用 fork() 時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開始執(zhí)行時(shí)就已設(shè)置 SIGSTOP 信號(hào)的新進(jìn)程。 if (likely(user_mode(regs)) && !(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; } 分配進(jìn)程描述符task_struct和內(nèi)核堆??臻g,通過父進(jìn)程的相關(guān)信息初始化一些資源。這個(gè)函數(shù)在進(jìn)程創(chuàng)建中做了很多重要工作后面將詳細(xì)講述。 p = copy_process(clone_flags, stack_start, regs, stack_size,child_tidptr, NULL, trace); if (!IS_ERR(p)) { struct completion vfork; 獲取在當(dāng)前進(jìn)程的PID命名空間中的id號(hào)。 nr = task_pid_vnr(p); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); 如果是通過vfork創(chuàng)建進(jìn)程則初始化完成量vfork,它保證了子進(jìn)程先運(yùn)行。 if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } 將新進(jìn)程插入調(diào)度隊(duì)列,等待調(diào)度運(yùn)行。后面詳細(xì)分析。 wake_up_new_task(p); 發(fā)送跟蹤事件 if (unlikely(trace)) ptrace_event(trace, nr); 如果是通過vfork創(chuàng)建進(jìn)程則掛起當(dāng)前進(jìn)程等待子進(jìn)程運(yùn)行結(jié)束后再運(yùn)行。 if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); } } else { nr = PTR_ERR(p); } 返回到發(fā)出創(chuàng)建新進(jìn)程系統(tǒng)調(diào)用的進(jìn)程中。如fork系統(tǒng)調(diào)用會(huì)兩次返回,一次返回到父進(jìn)程,返回值為子進(jìn)程的進(jìn)程id,另一次返回到子進(jìn)程,后面會(huì)講到返回到子進(jìn)程的情況。 return nr; } 上面有一些函數(shù)需要進(jìn)一步分析如下: 【do_fork--->copy_process】 static struct task_struct *copy_process(unsigned long clone_flags,unsigned long stack_start, struct pt_regs *regs,unsigned long stack_size, int __user *child_tidptr,struct pid *pid, int trace) { int retval; struct task_struct *p; int cgroup_callbacks_done = 0; 如果創(chuàng)建新的命名空間就不能同時(shí)又拷貝父進(jìn)程的文件系統(tǒng)信息。 if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) return ERR_PTR(-EINVAL); 同一線程組中的進(jìn)程必須共享信號(hào) if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND)) return ERR_PTR(-EINVAL); 共享信號(hào)處理方法就必須共享用戶虛擬空間 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); 執(zhí)行附加的安全檢查。 retval = security_task_create(clone_flags); if (retval) goto fork_out; retval = -ENOMEM; 函數(shù)dup_task_struct要做的就是下面幾件事: tsk = alloc_task_struct_node(node); 為新進(jìn)程分配一個(gè)task_struct結(jié)構(gòu) ti = alloc_thread_info_node(tsk, node); 分配2頁的內(nèi)核??臻g并返回頁地址 err = arch_dup_task_struct(tsk, orig); 將父進(jìn)程的進(jìn)程描述符結(jié)構(gòu)task_struct的內(nèi)容拷貝到子進(jìn)程的進(jìn)程描述符結(jié)構(gòu)task_struct中 tsk->stack = ti;線程描述符結(jié)構(gòu)thread_info存放在上面分配的兩頁棧空間的開始出,讓tsk->stack指向線程描述符結(jié)構(gòu)。 setup_thread_stack(tsk, orig);將父進(jìn)程的線程描述符結(jié)構(gòu)內(nèi)容拷貝到子進(jìn)程中。 p = dup_task_struct(current); if (!p) goto fork_out; ...... retval = -EAGAIN; 每個(gè)進(jìn)程都屬于某個(gè)用戶,每個(gè)用戶都有它的資源限制,所以一個(gè)用戶所能創(chuàng)建的進(jìn)程數(shù)不能超過他的限制,除非他具有管理員權(quán)限。 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) && p->real_cred->user != INIT_USER) goto bad_fork_free; } current->flags &= ~PF_NPROC_EXCEEDED; 結(jié)構(gòu)體struct cred包含了uid、gid、user等安全權(quán)能相關(guān)的東西。在函數(shù)copy_creds中先判斷是不是創(chuàng)建線程CLONE_THREAD,如果是就共享父進(jìn)程的cred結(jié)構(gòu),如果不是就重新分配一個(gè)cred結(jié)構(gòu),并用父進(jìn)程中cred的內(nèi)容拷貝到新分配的cred中。 retval = copy_creds(p, clone_flags); if (retval < 0) goto bad_fork_free; retval = -EAGAIN; 檢查系統(tǒng)中的進(jìn)程數(shù)量是否超過max_threads。這個(gè)變量的缺省值取決于系統(tǒng)內(nèi)存容量。 if (nr_threads >= max_threads) goto bad_fork_cleanup_count; 如果實(shí)現(xiàn)新的進(jìn)程的執(zhí)行腳本執(zhí)行域和可執(zhí)行格式的內(nèi)核函數(shù)都包含在內(nèi)核模塊中,則遞增它們的使用計(jì)數(shù)器。 if (!try_module_get(task_thread_info(p)->exec_domain->module)) goto bad_fork_cleanup_count; did_exec記錄了進(jìn)程發(fā)出execve調(diào)用的次數(shù) p->did_exec = 0; ...... 函數(shù)sched_fork中初始化了調(diào)度實(shí)體結(jié)構(gòu)struct sched_entity se;,設(shè)置了進(jìn)程優(yōu)先級(jí),設(shè)置了進(jìn)程調(diào)度類,該函數(shù)后面詳細(xì)講解。 sched_fork(p); retval = perf_event_init_task(p); if (retval) goto bad_fork_cleanup_policy; retval = audit_alloc(p); if (retval) goto bad_fork_cleanup_policy; 如果設(shè)置了COPY_SYSVSEM,則使用父進(jìn)程的System V信號(hào)量。 retval = copy_semundo(clone_flags, p); if (retval) goto bad_fork_cleanup_audit; 如果CLONE_FILES置位,則使用父進(jìn)程的文件描述符列表,否則創(chuàng)建新的文件描述符列表,并將父進(jìn)程的文件描述符列表拷到新的列表中。 retval = copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_semundo; 如果CLONE_FS置位,則使用父進(jìn)程的文件系統(tǒng)上下文,否者創(chuàng)建自己的 struct fs_struct結(jié)構(gòu)并用父進(jìn)程的 struct fs_struct初始化新的 struct fs_struct。該結(jié)構(gòu)中包含根目錄、當(dāng)前工作目錄等。 retval = copy_fs(clone_flags, p); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; 如果CLONE_SIGHAND置位,則使用父進(jìn)程的信號(hào)處理程序,否則創(chuàng)建新的sighand_struct,并將父進(jìn)程的信號(hào)處理結(jié)構(gòu)內(nèi)容拷到新的列信號(hào)處理結(jié)構(gòu)中。 retval = copy_signal(clone_flags, p); if (retval) goto bad_fork_cleanup_sighand; 如果CLONE_VM置位,則共享父進(jìn)程的地址空間,兩個(gè)進(jìn)程共用同一mm_struct結(jié)構(gòu),否者創(chuàng)建一個(gè)父進(jìn)程的頁表副本但不復(fù)制頁的實(shí)際內(nèi)容,而是使用寫時(shí)復(fù)制機(jī)制。 retval = copy_mm(clone_flags, p); if (retval) goto bad_fork_cleanup_signal; 共享父進(jìn)程的命名空間或是建立新的命名空間。下面對(duì)命名空間的做一點(diǎn)說明: 將子系統(tǒng)的全局屬性封裝到命名空間中,每個(gè)進(jìn)程關(guān)聯(lián)到一個(gè)選定的命名空間。每個(gè)可以感知命名空間的子系統(tǒng)都必須提供一個(gè)數(shù)據(jù)結(jié)構(gòu),將所有通過命名空間形式提供的對(duì)象集中起來。struct nsproxy 用于匯集指向特定于子系統(tǒng)的命名空間包裝的指針: struct nsproxy { atomic_t count; struct uts_namespace *uts_ns; 包含內(nèi)核的名稱、版本、底層體系結(jié)構(gòu)類型等信息 struct ipc_namespace *ipc_ns;進(jìn)程間通信的IPC有關(guān)信息。 struct mnt_namespace *mnt_ns;已經(jīng)裝載的文件系統(tǒng)的視圖。 struct pid_namespace *pid_ns;有關(guān)進(jìn)程ID的信息 struct net *net_ns;半酣所有網(wǎng)絡(luò)相關(guān)的命名空間參數(shù) }; 與上面命名空間相對(duì)應(yīng)的幾個(gè)標(biāo)志: CLONE_NEWUTS:創(chuàng)建新的utsname組 CLONE_NEWIPC :創(chuàng)建新的IPC命名空間 CLONE_NEWUSER:創(chuàng)建新的用戶命名空間 CLONE_NEWPID:創(chuàng)建新的PID命名空間 CLONE_NEWNET:創(chuàng)建新的網(wǎng)絡(luò)命名空間 retval = copy_namespaces(clone_flags, p); if (retval) goto bad_fork_cleanup_mm; retval = copy_io(clone_flags, p); if (retval) goto bad_fork_cleanup_namespaces; 復(fù)制進(jìn)程中特定于線程的數(shù)據(jù)并且讓CPU上下文中的pc字段指向函數(shù)ret_from_fork,該函數(shù)用匯編實(shí)現(xiàn),fork調(diào)用就是通過它返回到子進(jìn)程中的。 retval = copy_thread(clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_io; 為進(jìn)城分配pid,下面函數(shù)做了很多處理,將在后面分析。 if (pid != &init_struct_pid) { retval = -ENOMEM; pid = alloc_pid(p->nsproxy->pid_ns); if (!pid) goto bad_fork_cleanup_io; } 字段p->pid中保存的是進(jìn)程全局pid,所以它的pid應(yīng)當(dāng)從最頂層命名空間中獲取,nr = pid->numbers[0].nr;(每一個(gè)進(jìn)程在它之上的每一級(jí)命名空間中都有一個(gè)id)。 p->pid = pid_nr(pid); p->tgid = p->pid; 如果創(chuàng)建的是線程,則它的線程組id于當(dāng)前進(jìn)程的線程組id相同 if (clone_flags & CLONE_THREAD) p->tgid = current->tgid; p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL; ...... if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM) ...... 如果創(chuàng)建線程就將 p->exit_signal設(shè)為-1,因?yàn)橹挥挟?dāng)線程組的最后一個(gè)成員死亡才會(huì)產(chǎn)生一個(gè)信號(hào)已通知線程組首領(lǐng)進(jìn)程的父進(jìn)程。 if (clone_flags & CLONE_THREAD) p->exit_signal = -1; else if (clone_flags & CLONE_PARENT) p->exit_signal = current->group_leader->exit_signal; else p->exit_signal = (clone_flags & CSIGNAL); ...... p->group_leader = p; INIT_LIST_HEAD(&p->thread_group); INIT_HLIST_HEAD(&p->task_works); cgroup_fork_callbacks(p); cgroup_callbacks_done = 1; write_lock_irq(&tasklist_lock); 如果創(chuàng)建線程,新進(jìn)程與當(dāng)前進(jìn)程有相同的父進(jìn)程,否則當(dāng)前進(jìn)程就是新進(jìn)程的父進(jìn)程。 if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) { p->real_parent = current->real_parent; p->parent_exec_id = current->parent_exec_id; } else { p->real_parent = current; p->parent_exec_id = current->self_exec_id; } spin_lock(¤t->sighand->siglock); recalc_sigpending(); 如果當(dāng)前進(jìn)程還有未決信號(hào)就錯(cuò)誤返回。 if (signal_pending(current)) { spin_unlock(¤t->sighand->siglock); write_unlock_irq(&tasklist_lock); retval = -ERESTARTNOINTR; goto bad_fork_free_pid; } 如果創(chuàng)建進(jìn)程就將線程數(shù)遞增1,并且讓新進(jìn)程與當(dāng)前進(jìn)程有相同的組長進(jìn)程 if (clone_flags & CLONE_THREAD) { current->signal->nr_threads++; atomic_inc(¤t->signal->live); atomic_inc(¤t->signal->sigcnt); p->group_leader = current->group_leader; list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group); } if (likely(p->pid)) { ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); 如果新進(jìn)程是線程組組長進(jìn)程,就進(jìn)入下面分支 if (thread_group_leader(p)) { if (is_child_reaper(pid)) p->nsproxy->pid_ns->child_reaper = p; 將當(dāng)前進(jìn)程組進(jìn)程的組id和會(huì)話id掛到新進(jìn)程哈希數(shù)組task->pids[]中對(duì)應(yīng)位置。 p->signal->leader_pid = pid; p->signal->tty = tty_kref_get(current->signal->tty); attach_pid(p, PIDTYPE_PGID, task_pgrp(current)); attach_pid(p, PIDTYPE_SID, task_session(current)); list_add_tail(&p->sibling, &p->real_parent->children); list_add_tail_rcu(&p->tasks, &init_task.tasks); __this_cpu_inc(process_counts); } 將新的pid結(jié)構(gòu)掛到新進(jìn)程哈希數(shù)組task->pids[]中對(duì)應(yīng)位置。 attach_pid(p, PIDTYPE_PID, pid); 新進(jìn)程加入進(jìn)程集合,遞增nr_threads。 nr_threads++; } 變量total_forks記錄被創(chuàng)建進(jìn)程的數(shù)量 total_forks++; return p; ...... } 【do_fork--->copy_process--->sched_fork】 void sched_fork(struct task_struct *p) { unsigned long flags; int cpu = get_cpu(); 初始化進(jìn)程調(diào)度實(shí)體se的相關(guān)成員 __sched_fork(p); 將進(jìn)程狀態(tài)置于運(yùn)行狀態(tài) p->state = TASK_RUNNING; 新進(jìn)程的優(yōu)先級(jí)繼承于父進(jìn)程的普通優(yōu)先級(jí)。對(duì)于優(yōu)先級(jí)做一下說明,進(jìn)程有三個(gè)優(yōu)先級(jí):static_prio:靜態(tài)優(yōu)先級(jí)是進(jìn)程啟動(dòng)時(shí)分配的優(yōu)先級(jí)。它可以用nice系統(tǒng)調(diào)用修改。 normal_prio:基于進(jìn)程的靜態(tài)優(yōu)先級(jí)和調(diào)度策略計(jì)算出來的優(yōu)先級(jí),調(diào)度策略也可以通過系 統(tǒng)調(diào)用改變,所以即使靜態(tài)優(yōu)先級(jí)相同,普通優(yōu)先級(jí)也可能不同。 prio:在某些情況下內(nèi)核需要暫時(shí)提高進(jìn)程的優(yōu)先級(jí),所以就需要第三個(gè)成員來表示。調(diào)度 器算法中要考慮的優(yōu)先級(jí)就保存在prio。 p->prio = current->normal_prio; sched_reset_on_fork用于判斷是否恢復(fù)默認(rèn)的優(yōu)先級(jí)或調(diào)度策略。 if (unlikely(p->sched_reset_on_fork)) { if (task_has_rt_policy(p)) { p->policy = SCHED_NORMAL; p->static_prio = NICE_TO_PRIO(0); p->rt_priority = 0; } else if (PRIO_TO_NICE(p->static_prio) < 0) p->static_prio = NICE_TO_PRIO(0); p->prio = p->normal_prio = __normal_prio(p); 設(shè)置進(jìn)程權(quán)重,關(guān)于權(quán)重的問題后面會(huì)講解。 set_load_weight(p); p->sched_reset_on_fork = 0; } 如果不是事實(shí)進(jìn)程,就設(shè)置調(diào)度類為完全公平調(diào)度類 if (!rt_prio(p->prio)) p->sched_class = &fair_sched_class; if (p->sched_class->task_fork) p->sched_class->task_fork(p); raw_spin_lock_irqsave(&p->pi_lock, flags); 設(shè)置進(jìn)程在那個(gè)cpu上運(yùn)行; set_task_cpu(p, cpu); raw_spin_unlock_irqrestore(&p->pi_lock, flags); ...... } 【do_fork--->copy_process--->alloc_pid】 struct pid *alloc_pid(struct pid_namespace *ns) { struct pid *pid; enum pid_type type; int i, nr; struct pid_namespace *tmp; struct upid *upid; 分配一個(gè)pid結(jié)構(gòu),一個(gè)進(jìn)程對(duì)應(yīng)著一個(gè)這樣的結(jié)構(gòu),該結(jié)構(gòu)用于內(nèi)核空間對(duì)進(jìn)程的管理 pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); PID命名空間結(jié)構(gòu)中有一個(gè)位圖pid_ns->pidmap[],每一個(gè)位就表示在該命名空間中的一個(gè) 進(jìn)程號(hào),如果該位位1表示該位對(duì)應(yīng)的進(jìn)程號(hào)已經(jīng)被分配。所以在該命名空間分配進(jìn)程號(hào)就是在位圖中找第一個(gè)為0的位。ns->level表示該命名空間所在的層次,其值越小表示層次越高。上面提到pid結(jié)構(gòu)是內(nèi)核對(duì)進(jìn)程管理的結(jié)構(gòu),upid就對(duì)應(yīng)著用戶空間的進(jìn)程號(hào)。進(jìn)程PID命名空間可能有很多層次,每一個(gè)層次中就對(duì)應(yīng)著一個(gè)這樣的結(jié)構(gòu),所以新進(jìn)程建立是要為它在它本命名空間和上層所有命名空間中分配一個(gè)id。 tmp = ns; for (i = ns->level; i >= 0; i--) { nr = alloc_pidmap(tmp); if (nr < 0) goto out_free; pid->numbers[i].nr = nr; pid->numbers[i].ns = tmp; tmp = tmp->parent; } 獲取新進(jìn)程的命名空間。 get_pid_ns(ns); pid->level = ns->level; atomic_set(&pid->count, 1); for (type = 0; type < PIDTYPE_MAX; ++type) INIT_HLIST_HEAD(&pid->tasks[type]); upid = pid->numbers + ns->level; spin_lock_irq(&pidmap_lock); 將進(jìn)程的所有upid結(jié)構(gòu)都置于pid哈希表中。 for ( ; upid >= pid->numbers; --upid) hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); spin_unlock_irq(&pidmap_lock); ...... } 【do_fork--->wake_up_new_task】 void wake_up_new_task(struct task_struct *p) { #ifdef CONFIG_SMP 設(shè)置進(jìn)程的調(diào)度隊(duì)列完全公平調(diào)度隊(duì)列p->se.cfs_rq或者實(shí)時(shí)調(diào)度隊(duì)列p->rt.rt_rq。 set_task_cpu(p, select_task_rq(p, SD_BALANCE_FORK, 0)); #endif rq指向就緒隊(duì)列,每個(gè)CPU只有一個(gè)這樣的隊(duì)列,所有將要在該CPU上運(yùn)行等待調(diào)度的進(jìn)程都在該隊(duì)列中。 rq = __task_rq_lock(p); 函數(shù)activate_task將新進(jìn)程插入隊(duì)列中p->sched_class->enqueue_task(rq, p, flags);等待調(diào)度,該函數(shù)特定于調(diào)度類,后面講解調(diào)度算法時(shí)講解。 activate_task(rq, p, 0); 現(xiàn)在進(jìn)程已經(jīng)在隊(duì)列中了。 p->on_rq = 1; trace_sched_wakeup_new(p, true); 用一個(gè)新?lián)Q新的進(jìn)程來搶占當(dāng)前進(jìn)程,當(dāng)然,能否搶占得了,還得看其優(yōu)先級(jí)等相關(guān)參數(shù)。 check_preempt_curr(rq, p, WF_FORK); #ifdef CONFIG_SMP if (p->sched_class->task_woken) p->sched_class->task_woken(rq, p); #endif task_rq_unlock(rq, p, &flags); |
|