所謂進(jìn)程就是程序執(zhí)行時(shí)的一個(gè)實(shí)例. 它是現(xiàn)代操作系統(tǒng)中一個(gè)很重要的抽象,我們從進(jìn)程的生命周期:創(chuàng)建,執(zhí)行,消亡來分析一下Linux上的進(jìn)程管理實(shí)現(xiàn).
一:前言
進(jìn)程管理結(jié)構(gòu);
在內(nèi)核中,每一個(gè)進(jìn)程對應(yīng)一個(gè)task.就是以前所講的PCB.它的結(jié)構(gòu)如下(include/linux/sched.h):
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; atomic_t usage; unsigned int flags; /* per process flags, defined below */ unsigned int ptrace; int lock_depth; /* BKL lock depth */ …… …… }
由于這個(gè)結(jié)構(gòu)包含了進(jìn)程的所有信息,所以十分龐大,我們在以后的分析中再來分析各成員的含義,
linux進(jìn)程管理之進(jìn)程創(chuàng)建
。Task_struct的存放:
在系統(tǒng)運(yùn)行過程中,進(jìn)程切換十分頻繁,所以我們需要一種方式能夠快速獲得當(dāng)前進(jìn)程的task_struct。linux的task_struct存放如下圖所示:
如上圖所示:進(jìn)程內(nèi)核堆棧底部存放著struct thread_struct.該結(jié)構(gòu)中有一個(gè)成員指向當(dāng)前進(jìn)程的task_struct.在內(nèi)核中有一個(gè)獲取當(dāng)前進(jìn)程的thread_struct 的宏。它的定義如下:
#define GET_THREAD_INFO(reg) movl $THREAD, reg; andl %esp, reg THREAD_SIZE定義如下: #ifdef CONFIG_4KSTACKS #define THREAD_SIZE (4096) #else #define THREAD_SIZE (8192) #endif
我們討論常規(guī)的8K棧的情況。-THREAD_SIZE即為:0xFFFFE000.因?yàn)闂1旧硎琼撁鎸R的.所以只要把低13位屏弊掉就是thread_struct.的地址.
進(jìn)程鏈表:
每一個(gè)進(jìn)程都有父進(jìn)程,相應(yīng)的每個(gè)進(jìn)程都會(huì)管理自己的子進(jìn)程.在linux系統(tǒng)中,所有進(jìn)程都是由init進(jìn)程派生而來.init進(jìn)程的進(jìn)程描述符由init_task靜態(tài)生成.它的定義如下所示:
struct task_struct init_task = INIT_TASK(init_task); #define INIT_TASK(tsk) { .state = 0, .stack = &init_thread_info, .usage = ATOMIC_INIT(2), …… …… .dirties = INIT_PROP_LOCAL_SINGLE(dirties), INIT_TRACE_IRQFLAGS INIT_LOCKDEP }
每個(gè)進(jìn)程都有一個(gè)parent指向它的父進(jìn)程,都有一個(gè)children指針指向它的子進(jìn)程.上面代碼將init進(jìn)程描述符的parent指針指向其本身.children指針為一個(gè)初始化的空鏈表.
綜上所述,我們只要從init_task的children鏈表中遍歷,就可以找到系統(tǒng)中所有的用戶進(jìn)程.這是由do_each_thread宏實(shí)現(xiàn)的.代碼如下所示:
#define do_each_thread(g, t)
for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do
next_task定義如下所示:
#define next_task(p) list_entry(rcu_dereference((p)->tasks.next), struct task_struct, tasks)
不過,用這種方法去尋找一個(gè)進(jìn)程太浪費(fèi)時(shí)間了.所以在根據(jù)條件尋找進(jìn)程的話一般使用哈希表
二:創(chuàng)建進(jìn)程
在用戶空間創(chuàng)建進(jìn)程的接口為:fork(),vfork(),clone()接下來我們看下在linux內(nèi)核中是如何處理這些請求的.
上述幾個(gè)接口在經(jīng)過系統(tǒng)調(diào)用進(jìn)入內(nèi)核,在內(nèi)核中的相應(yīng)處理函數(shù)為:sys_fork().sys_vfork().sys_clone()/如下所示:
asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, ®s, 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, ®s, 0, parent_tidptr, child_tidptr); } asmlinkage int sys_vfork(struct pt_regs regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL); }
從上面可以看出幾種調(diào)用都會(huì)進(jìn)入同一個(gè)接口:do_fork.不同的時(shí),所帶的標(biāo)志不同/標(biāo)志的含義如下:
#define SIGCHLD 17
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
從上可以看出.最低的兩位通常表示信號(hào)位,即子進(jìn)程終止的時(shí)候應(yīng)該向父進(jìn)程發(fā)送的信號(hào).一般為SIGCHLD
其余的位是共享位. 設(shè)置CLONE_VM時(shí),子進(jìn)程會(huì)跟父進(jìn)程共享VM區(qū)域. CLONE_VFORK標(biāo)志設(shè)置時(shí).子進(jìn)程運(yùn)行時(shí)會(huì)使父進(jìn)程投入睡眠,直到子進(jìn)程不再使用父進(jìn)程的內(nèi)存或者子進(jìn)程退出去才會(huì)將父進(jìn)程喚醒.這樣做是因?yàn)楦缸舆M(jìn)程共享同一個(gè)地址區(qū)域,所以,創(chuàng)建進(jìn)程完后,子進(jìn)程退出,父進(jìn)程找不到自己的返回地址.
Clone會(huì)設(shè)置自己的標(biāo)志,并且可以指定自己的棧的地址/
轉(zhuǎn)入到do_fork():
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; //分配一個(gè)新的pid struct pid *pid = alloc_pid(); long nr; if (!pid) return -EAGAIN; nr = pid->nr; //如果當(dāng)前進(jìn)程被跟蹤,子進(jìn)程如果設(shè)置了相關(guān)被跟蹤標(biāo)志,則設(shè)置CLONE_PTRACE位 if (unlikely(current->ptrace)) { trace = fork_traceflag (clone_flags); if (trace) clone_flags |= CLONE_PTRACE; } //copy父進(jìn)程的一些信息 p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); if (!IS_ERR(p)) { struct completion vfork; //如果帶有CLONE_VFORK標(biāo)志.賦值并初始化vfork_done if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); } //如果進(jìn)子進(jìn)程被跟蹤,或者子進(jìn)程初始化成STOP狀態(tài) //則發(fā)送SIGSTOP信號(hào).由于子進(jìn)程現(xiàn)在還沒有運(yùn)行,信號(hào)不能被處理 //所以設(shè)置TIF_SIGPENDING標(biāo)志 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); } //如果子進(jìn)程末定義CLONE_STOPPED標(biāo)志,將其置為RUNNING.等待下一次調(diào)度 //否則將子進(jìn)程狀態(tài)更改為TASK_STOPPED if (!(clone_flags & CLONE_STOPPED)) wake_up_new_task(p, clone_flags); else p->state= TASK_STOPPED; //如果子進(jìn)程被定義,通發(fā)送通告 if (unlikely (trace)) { current->ptrace_message = nr; ptrace_notify ((trace << 8) | SIGTRAP); } //如果定義了CLONE_VFORK標(biāo)志.則將當(dāng)前進(jìn)程投入睡眠 if (clone_flags & CLONE_VFORK) { freezer_do_not_count(); wait_for_completion(&vfork); freezer_count(); if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) { current->ptrace_message = nr; ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); } } } else { //如果copy父進(jìn)程相關(guān)信息失敗了.釋放分配的pid free_pid(pid); nr = PTR_ERR(p); } return nr; }
我們在開始的時(shí)候分析過VFORK標(biāo)志的作用,在這里我們注意一下VFORK標(biāo)志的處理:
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) { …… …… /* static inline void init_completion(struct completion *x) { //done標(biāo)志為0。表示子進(jìn)程還沒有將父進(jìn)程喚醒 x->done = 0; //初始化一個(gè)等待隊(duì)列 init_waitqueue_head(&x->wait); } */ if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); } …… …… //如果定義了CLONE_VFORK標(biāo)志.則將當(dāng)前進(jìn)程投入睡眠 if (clone_flags & CLONE_VFORK) { freezer_do_not_count(); wait_for_completion(&vfork); freezer_count(); if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) { current->ptrace_message = nr; ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); } } …… }
跟蹤一下wait_for_completion():
void fastcall __sched wait_for_completion(struct completion *x) { might_sleep(); spin_lock_irq(&x->wait.lock); if (!x->done) { //初始化一個(gè)等待隊(duì)列 DECLARE_WAITQUEUE(wait, current); wait.flags |= WQ_FLAG_EXCLUSIVE; //將其加入到子進(jìn)程的等待隊(duì)列 __add_wait_queue_tail(&x->wait, &wait); do { //設(shè)置進(jìn)程狀態(tài)為TASK_UNINTERRUPTIBLE __set_current_state(TASK_UNINTERRUPTIBLE); spin_unlock_irq(&x->wait.lock); //重新調(diào)度 //一般來說,在這里的時(shí)候就會(huì)退出當(dāng)前進(jìn)程,去調(diào)度另外的進(jìn)程,直到被子進(jìn)程喚醒 schedule(); spin_lock_irq(&x->wait.lock); } while (!x->done); //一直到x->done標(biāo)志被設(shè)置。這里是為了防止異常情況將進(jìn)程喚醒 //從等待隊(duì)列中移除 __remove_wait_queue(&x->wait, &wait); } x->done--; spin_unlock_irq(&x->wait.lock); }
接著分析do_fork(),copy_proces()是它的核心函數(shù)。重點(diǎn)分析一下:
static struct task_struct *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, struct pid *pid) { int retval; struct task_struct *p = NULL; //clone_flags參數(shù)的有效性判斷 //不能同時(shí)定義CLONE_NEWNS,CLONE_FS if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) return ERR_PTR(-EINVAL); //如果定義CLONE_THREAD,則必須要定義CLONE_SIGHAND if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND)) return ERR_PTR(-EINVAL); //如果定義CLONE_SIGHAND,則必須要定義CLONE_VM if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM)) return ERR_PTR(-EINVAL); retval = security_task_create(clone_flags); if (retval) goto fork_out; retval = -ENOMEM; //從父進(jìn)程中復(fù)制出一個(gè)task p = dup_task_struct(current); if (!p) goto fork_out; rt_mutex_init_task(p); #ifdef CONFIG_TRACE_IRQFLAGS DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled); DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled); #endif retval = -EAGAIN; //如果用戶的進(jìn)程總數(shù)超過了限制 if (atomic_read(&p->user->processes) >= p->signal->rlim[RLIMIT_NPROC].rlim_cur) { if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) && p->user != current->nsproxy->user_ns->root_user) goto bad_fork_free; } //更新進(jìn)程用戶的相關(guān)計(jì)數(shù) atomic_inc(p->user->__count); atomic_inc(&p->user->processes); get_group_info(p->group_info); //當(dāng)前進(jìn)程數(shù)是否大于系統(tǒng)規(guī)定的最大進(jìn)程數(shù) if (nr_threads >= max_threads) goto bad_fork_cleanup_count; //加載進(jìn)程的相關(guān)執(zhí)行模塊 if (!try_module_get(task_thread_info(p)->exec_domain->module)) goto bad_fork_cleanup_count; if (p->binfmt && !try_module_get(p->binfmt->module)) goto bad_fork_cleanup_put_domain; //子進(jìn)程還在進(jìn)行初始化,沒有execve p->did_exec = 0; delayacct_tsk_init(p); /* Must remain after dup_task_struct() */ //copy父進(jìn)程的所有標(biāo)志,除了PF_SUPERPRIV(超級(jí)權(quán)限) //置子進(jìn)程的PF_FORKNOEXEC標(biāo)志,表示正在被FORK copy_flags(clone_flags, p); //賦值子進(jìn)程的pid p->pid = pid_nr(pid); retval = -EFAULT; if (clone_flags & CLONE_PARENT_SETTID) if (put_user(p->pid, parent_tidptr)) goto bad_fork_cleanup_delays_binfmt; //初始化子進(jìn)程的幾個(gè)鏈表 INIT_LIST_HEAD(&p->children); INIT_LIST_HEAD(&p->sibling); p->vfork_done = NULL; spin_lock_init(&p->alloc_lock); //父進(jìn)程的TIF_SIGPENDING被復(fù)制進(jìn)了子進(jìn)程,這個(gè)標(biāo)志表示有末處理的信號(hào) //這個(gè)標(biāo)志子進(jìn)程是不需要的 clear_tsk_thread_flag(p, TIF_SIGPENDING); init_sigpending(&p->pending); //初始化子進(jìn)程的time p->utime = cputime_zero; p->stime = cputime_zero; p->prev_utime = cputime_zero; …… …… //tgid = pid p->tgid = p->pid; if (clone_flags & CLONE_THREAD) p->tgid = current->tgid; //copy父進(jìn)程的其它資源.比例打開的文件,信號(hào),VM等等 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 */ 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_namespaces(clone_flags, p))) goto bad_fork_cleanup_keys; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespaces; 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; p->robust_list = NULL; #ifdef CONFIG_COMPAT p->compat_robust_list = NULL; #endif INIT_LIST_HEAD(&p->pi_state_list); p->pi_state_cache = NULL; /* * sigaltstack should be cleared when sharing the same VM */ if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM) p->sas_ss_sp = p->sas_ss_size = 0; /* * Syscall tracing should be turned off in the child regardless * of CLONE_PTRACE. */ clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE); #ifdef TIF_SYSCALL_EMU clear_tsk_thread_flag(p, TIF_SYSCALL_EMU); #endif /* 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.. */ //exit_signal: 子進(jìn)程退出時(shí)給父進(jìn)程發(fā)送的信號(hào) p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL); //pdeath_signal:進(jìn)程退出時(shí).給其下的子進(jìn)程發(fā)送的信號(hào) p->pdeath_signal = 0; p->exit_state = 0; …… …… if (likely(p->pid)) { add_parent(p); if (unlikely(p->ptrace & PT_PTRACED)) __ptrace_link(p, current->parent); if (thread_group_leader(p)) { p->signal->tty = current->signal->tty; p->signal->pgrp = process_group(current); set_signal_session(p->signal, process_session(current)); attach_pid(p, PIDTYPE_PGID, task_pgrp(current)); attach_pid(p, PIDTYPE_SID, task_session(current)); list_add_tail_rcu(&p->tasks, &init_task.tasks); __get_cpu_var(process_counts)++; } attach_pid(p, PIDTYPE_PID, pid); //當(dāng)前進(jìn)程數(shù)遞增 nr_threads++; } //被fork的進(jìn)程數(shù)計(jì)數(shù)遞增 total_forks++; spin_unlock(¤t->sighand->siglock); write_unlock_irq(&tasklist_lock); proc_fork_connector(p); return p; …… …… }
這個(gè)函數(shù)比較復(fù)雜,里面涉及到了內(nèi)核的很多子系統(tǒng),我們暫時(shí)只分析與內(nèi)存相關(guān)的部份,其它的子系統(tǒng)待專題分析的時(shí)候再討論,
電腦資料
《linux進(jìn)程管理之進(jìn)程創(chuàng)建》(http://clearvueentertainment.com)。請關(guān)注本站更新 ^_^.分析一下里面調(diào)用的幾個(gè)重要的子函數(shù)。static struct task_struct *dup_task_struct(struct task_struct *orig) { struct task_struct *tsk; struct thread_info *ti; //保存FPU信息,并設(shè)置TS標(biāo)志 prepare_to_copy(orig); //分配一個(gè)進(jìn)程描述符 tsk = alloc_task_struct(); if (!tsk) return NULL; //分配thread_info ti = alloc_thread_info(tsk); if (!ti) { //如果分配thread_info失敗.則釋放分配的task free_task_struct(tsk); return NULL; } //復(fù)制task信息 *tsk = *orig; //使task->stack指向thread_info tsk->stack = ti; //copy父進(jìn)程的thread_info信息 //并使thread_info.task指向task setup_thread_stack(tsk, orig); #ifdef CONFIG_CC_STACKPROTECTOR tsk->stack_canary = get_random_int(); #endif /* One for us, one for whoever does the "release_task()" (usually parent) */ atomic_set(&tsk->usage,2); atomic_set(&tsk->fs_excl, 0); #ifdef CONFIG_BLK_DEV_IO_TRACE tsk->btrace_seq = 0; #endif tsk->splice_pipe = NULL; return tsk; }
如果進(jìn)程使用了FPU,MMX,XMM寄存器,就會(huì)將進(jìn)程flag設(shè)置TS_USEDFPU標(biāo)志位。在fork子過程的時(shí)候,這幾個(gè)寄存器的值子進(jìn)程是不需要的,所以沒必要復(fù)制到子進(jìn)程中。為了避免不必要的保存,I386采取了特殊的機(jī)制。在CR0中有一個(gè)特殊的標(biāo)志位:TS。當(dāng)這個(gè)標(biāo)志被設(shè)置,如果要訪問FPU,MMX,XMM就會(huì)產(chǎn)生一個(gè)設(shè)備通用保護(hù)異常。對于父進(jìn)程來說,它對這幾個(gè)特殊處理器的處理如下:
如果進(jìn)程使用了FPU,MMX,XMM寄存器(看父進(jìn)程是否設(shè)置了TS_USEDFPU位),就會(huì)將寄存器里的值保存起來,并設(shè)置TS標(biāo)志。
如果父進(jìn)程以后要使用MMX,XMM,F(xiàn)PU等寄存器,由于TS標(biāo)志被設(shè)置,就產(chǎn)生一個(gè)異常,再由異常處理程序從task的相關(guān)字段中恢復(fù)這幾個(gè)寄存器的值(如果task相關(guān)字段有保存這幾個(gè)特殊寄存器值的話),或者將這幾個(gè)寄存器初始化。
上述的這個(gè)過程是由prepare_to_copy()進(jìn)行處理的。具體代碼如下:
void prepare_to_copy(struct task_struct *tsk) { unlazy_fpu(tsk); } Unlazy_fpu() à __unlazy_fpu(): #define __unlazy_fpu( tsk ) do { //如果使用了MMX,M,F(xiàn)PU寄存器 if (task_thread_info(tsk)->status & TS_USEDFPU) { //保存相關(guān)寄存器 __save_init_fpu(tsk); //設(shè)置TS stts(); } else tsk->fpu_counter = 0; } while (0)
值得注意的是thread_info的內(nèi)存分配。如下所示:
#define alloc_thread_info(tsk) ((struct thread_info *)
__get_free_pages(GFP_KERNEL, get_order(THREAD_SIZE)))
也就是說給thread_info分配了THREAD_SIZE(8K)的空間,回憶一下之前所分析的進(jìn)程描述符的存放。
子進(jìn)程要運(yùn)行的話,必須要有自己的進(jìn)程空間。這個(gè)進(jìn)程空間或者是共享父進(jìn)程的,或者是擁有自己獨(dú)立的,為是在copy_mm()處理的:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk) { struct mm_struct * mm, *oldmm; int retval; //初始化task中與VMA有關(guān)的成員 tsk->min_flt = tsk->maj_flt = 0; tsk->nvcsw = tsk->nivcsw = 0; //task是從父進(jìn)程COPY過來的,所以將mm.active_mm設(shè)成NULL tsk->mm = NULL; tsk->active_mm = NULL; /* * Are we cloning a kernel thread? * * We need to steal a active VM for that.. */ ldmm = current->mm; if (!oldmm) return 0; //如果設(shè)置了CLONE_VM標(biāo)志,也就是父子進(jìn)程共享同一個(gè)內(nèi)存空間 //只要增加父進(jìn)程的MM引用計(jì)數(shù)即可 if (clone_flags & CLONE_VM) { atomic_inc(&oldmm->mm_users); mm = oldmm; goto good_mm; } //如果沒有定義CLONE_VM.那就將父進(jìn)程的VM復(fù)制過來.增加映射的頁面的使用 //計(jì)數(shù),并且將頁面設(shè)為只讀.如果父子進(jìn)程中任意一個(gè)去改寫頁面,就會(huì)產(chǎn)生一個(gè) //頁面異常,由do_page_fault分配一個(gè)新的頁面.并將舊頁面的只讀標(biāo)志去了 //詳情請參考本站的另一篇文章《linux內(nèi)存管理之頁面異常處理》 retval = -ENOMEM; mm = dup_mm(tsk); if (!mm) goto fail_nomem; good_mm: /* Initializing for Swap token stuff */ mm->token_priority = 0; mm->last_interval = 0; //設(shè)置task的mm,active_mm字段 tsk->mm = mm; tsk->active_mm = mm; return 0; fail_nomem: return retval; }
先思考一個(gè)問題,復(fù)制父進(jìn)程的映射關(guān)系時(shí),要不要把父進(jìn)程的映射關(guān)系全部都COPY過來呢?其實(shí)它對于父進(jìn)程的內(nèi)核空間映射,子進(jìn)程是不需要的。所以只需要將父進(jìn)程的用戶空間的映射關(guān)系復(fù)制過來即可。接著看代碼。Dup_mm的實(shí)現(xiàn)如下所示:
static struct mm_struct *dup_mm(struct task_struct *tsk) { struct mm_struct *mm, *oldmm = current->mm; int err; //如果當(dāng)前進(jìn)程的MM不存在,出錯(cuò)退出 if (!oldmm) return NULL; //為mm為配一個(gè)存儲(chǔ)空間 mm = allocate_mm(); if (!mm) goto fail_nomem; //復(fù)制當(dāng)前進(jìn)程的mm memcpy(mm, oldmm, sizeof(*mm)); /* Initializing for Swap token stuff */ mm->token_priority = 0; mm->last_interval = 0; //mm初始化 if (!mm_init(mm)) goto fail_nomem; if (init_new_context(tsk, mm)) goto fail_nocontext; //具體的復(fù)制過程 err = dup_mmap(mm, oldmm); if (err) goto free_pt; mm->hiwater_rss = get_mm_rss(mm); mm->hiwater_vm = mm->total_vm; return mm; free_pt: mmput(mm); fail_nomem: return NULL; fail_nocontext: /* * If init_new_context() failed, we cannot use mmput() to free the mm * because it calls destroy_context() */ mm_free_pgd(mm); free_mm(mm); return NULL; }
我們先來看一下mm的初始化。它是在mm_init中完成的。代碼如下:
static struct mm_struct * mm_init(struct mm_struct * mm) { //初始化mm相關(guān)字段 atomic_set(&mm->mm_users, 1); atomic_set(&mm->mm_count, 1); init_rwsem(&mm->mmap_sem); INIT_LIST_HEAD(&mm->mmlist); mm->flags = (current->mm) ? current->mm->flags : MMF_DUMP_FILTER_DEFAULT; mm->core_waiters = 0; mm->nr_ptes = 0; set_mm_counter(mm, file_rss, 0); set_mm_counter(mm, anon_rss, 0); spin_lock_init(&mm->page_table_lock); rwlock_init(&mm->ioctx_list_lock); mm->ioctx_list = NULL; mm->free_area_cache = TASK_UNMAPPED_BASE; mm->cached_hole_size = ~0UL; //為子進(jìn)程分配并初始PGD if (likely(!mm_alloc_pgd(mm))) { mm->def_flags = 0; return mm; } free_mm(mm); return NULL; }
Mm_alloc_pgd的實(shí)現(xiàn)如下:
static inline int mm_alloc_pgd(struct mm_struct * mm) { mm->pgd = pgd_alloc(mm); if (unlikely(!mm->pgd)) return -ENOMEM; return 0; } pgd_t *pgd_alloc(struct mm_struct *mm) { int i; pgd_t *pgd = quicklist_alloc(0, GFP_KERNEL, pgd_ctor); if (PTRS_PER_PMD == 1 || !pgd) return pgd; //從0開始到UNSHARED_PTRS_PER_PGD,建立PGD->PMD的映射 for (i = 0; i < UNSHARED_PTRS_PER_PGD; ++i) { pmd_t *pmd = pmd_cache_alloc(i); if (!pmd) goto out_oom; paravirt_alloc_pd(__pa(pmd) >> PAGE_SHIFT); set_pgd(&pgd[i], __pgd(1 + __pa(pmd))); } return pgd; out_oom: for (i--; i >= 0; i--) { pgd_t pgdent = pgd[i]; void* pmd = (void *)__va(pgd_val(pgdent)-1); paravirt_release_pd(__pa(pmd) >> PAGE_SHIFT); pmd_cache_free(pmd, i); } quicklist_free(0, pgd_dtor, pgd); return NULL; }
參照下面的這個(gè)圖:
明確了棧頂與當(dāng)前棧指針位置之后, 把父進(jìn)程的pt_regs 放入棧的頂部, 這樣實(shí)際上構(gòu)造了一次系統(tǒng)調(diào)用., 這樣子進(jìn)程被調(diào)度之后就可以沿父進(jìn)程的路徑返回. 為了區(qū)分子進(jìn)程跟父進(jìn)程, 把子進(jìn)程的返回值設(shè)為了0. 我們可以思考一下: 為什么上面要空8 個(gè)空間呢? 這是因?yàn)樵谥袛喟l(fā)生時(shí). 如果優(yōu)先級(jí)別一樣就不會(huì)把SS,ESP 壓入內(nèi)核棧, 這時(shí)候pt_regs 結(jié)構(gòu)體中的esp,xss 不存在, 為了防止非法訪問, 總在內(nèi)核棧上空8 個(gè)字節(jié).