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)跟蹤開(kāi)始執(zhí)行時(shí)就已設(shè)置 SIGSTOP 信號(hào)的新進(jìn)程。
PTRACE_EVENT_CLONE: 使子進(jìn)程下次調(diào)用 clone() 時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開(kāi)始執(zhí)行時(shí)就已設(shè)置 SIGSTOP 信號(hào)的新進(jìn)程。
PTRACE_EVENT_FORK: 使子進(jìn)程下次調(diào)用 fork() 時(shí)停止其執(zhí)行,并自動(dòng)跟蹤開(kāi)始執(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,通過(guò)父進(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);
如果是通過(guò)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);
如果是通過(guò)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頁(yè)的內(nèi)核棧空間并返回頁(yè)地址
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存放在上面分配的兩頁(yè)??臻g的開(kāi)始出,讓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ù)不能超過(guò)他的限制,除非他具有管理員權(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ù)量是否超過(guò)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)度類(lèi),該函數(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)程的頁(yè)表副本但不復(fù)制頁(yè)的實(shí)際內(nèi)容,而是使用寫(xiě)時(shí)復(fù)制機(jī)制。
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;
共享父進(jìn)程的命名空間或是建立新的命名空間。下面對(duì)命名空間的做一點(diǎn)說(shuō)明:
將子系統(tǒng)的全局屬性封裝到命名空間中,每個(gè)進(jìn)程關(guān)聯(lián)到一個(gè)選定的命名空間。每個(gè)可以感知命名空間的子系統(tǒng)都必須提供一個(gè)數(shù)據(jù)結(jié)構(gòu),將所有通過(guò)命名空間形式提供的對(duì)象集中起來(lái)。struct nsproxy 用于匯集指向特定于子系統(tǒng)的命名空間包裝的指針:
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns; 包含內(nèi)核的名稱、版本、底層體系結(jié)構(gòu)類(lèi)型等信息
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)用就是通過(guò)它返回到子進(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)程有相同的組長(zhǎng)進(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)程是線程組組長(zhǎng)進(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í)做一下說(shuō)明,進(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ì)算出來(lái)的優(yōu)先級(jí),調(diào)度策略也可以通過(guò)系 統(tǒng)調(diào)用改變,所以即使靜態(tài)優(yōu)先級(jí)相同,普通優(yōu)先級(jí)也可能不同。
prio:在某些情況下內(nèi)核需要暫時(shí)提高進(jìn)程的優(yōu)先級(jí),所以就需要第三個(gè)成員來(lái)表示。調(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)重的問(wèn)題后面會(huì)講解。
set_load_weight(p);
p->sched_reset_on_fork = 0;
}
如果不是事實(shí)進(jìn)程,就設(shè)置調(diào)度類(lèi)為完全公平調(diào)度類(lèi)
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)度類(lèi),后面講解調(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)程來(lái)?yè)屨籍?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);
聯(lián)系客服