linux進(jìn)程管理之進(jìn)程創(chuàng)建 -電腦資料

電腦資料 時(shí)間:2019-01-01 我要投稿
【clearvueentertainment.com - 電腦資料】

所謂進(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é).

最新文章