fork,vfork和clone底层实现

来源:互联网 发布:中国税务网络大学手机 编辑:程序博客网 时间:2024/05/13 20:20

ork,vfork,clone都是linux的系统调用,用来创建子进程的(确切说vfork创造出来的是线程)。

先介绍下进程必须的4要点:

a.要有一段程序供该进程运行,就像一场戏剧要有一个剧本一样。该程序是可以被多个进程共享的,多场戏剧用一个剧本一样。

b.有起码的私有财产,就是进程专用的系统堆栈空间。

c.有“户口”,既操作系统所说的进程控制块,在linux中具体实现是task_struct

d.有独立的存储空间。

当一个进程缺少d条件时候,我们称其为线程。

1.fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。下面的例子可以看出

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) { //开始创建子进程
                printf("This is son, his count is: %d. and his pid is: %d/n", ++count, getpid());//子进程的内容
        } else {
                printf("This is father, his count is: %d, his pid is: %d/n", count, getpid());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
This is son, his count is: 2. and his pid is: 3019
This is father, his count is: 1, his pid is: 3018
[root@liumengli program]# 
从代码里面可以看出2者的pid不同,内存资源count是值得复制,子进程改变了count的值,而父进程中的count没有被改变。有人认为这样大批量的复制会导致执行效率过低。其实在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表,这意味着上面的程序,我们没有执行 count++前,其实子进程和父进程的count指向的是同一块内存。而当子进程改变了父进程的变量时候,会通过copy_on_write的手段为所涉及的页面建立一个新的副本。所以当我们执行++count后,这时候子进程才新建了一个页面复制原来页面的内容,基本资源的复制是必须的,而且是高效的。整体看上去就像是父进程的独立存储空间也复制了一遍。

 

其次,我们看到子进程和父进程直接没有互相干扰,明显2者资源都独立了。我们看下面程序

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) {
                int i;
                for(i = 0; i < 200; i++) {
                        printf("This is son, his count is: %d. and his pid is: %d/n", i, getpid());
                }
        } else {
                printf("This is father, his count is: %d, his pid is: %d/n", count, getpid());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
...

This is son, his count is: 46. and his pid is: 4092
This is son, his count is: 47. and his pid is: 4092
This is son, his count is: 48. and his pid is: 4092
This is son, his count is: 49. and his pid is: 4092
This is son, his count is: 50. and his pid is: 4092
This is father, his count is: 1, his pid is: 4091
[root@liumengli program]# This is son, his count is: 51. and his pid is: 4092
This is son, his count is: 52. and his pid is: 4092
...

(运气很衰,非要200多个才有效果,郁闷)从结果可以看出父子2个进程是同步运行的。这和下面的vfork有区别。

 

2.vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少了我们上面提到的进程的四要素的第4项,独立的内存资源,看下面的程序

[root@liumengli program]# cat testVfork.c
#include "stdio.h"

int main() {
        int count = 1;
        int child;

        printf("Before create son, the father's count is:%d/n", count);
        if(!(child = vfork())) {
                printf("This is son, his pid is: %d and the count is: %d/n", getpid(), ++count);
                exit(1);
        } else {
                printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d/n", getpid(), count, child);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork 
Before create son, the father's count is:1
This is son, his pid is: 4185 and the count is: 2
After son, This is father, his pid is: 4184 and the count is: 2, and the child is: 4185
[root@liumengli program]# 
从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的count变量,这一次是指针复制,2者的指针指向了同一个内存,所以子进程修改了 count变量,父进程的 count变量同样受到了影响。另外由vfork创造出来的子进程还会导致父进程挂起,除非子进程exit或者execve才会唤起父进程,看下面程序:

[root@liumengli program]# cat testVfork.c
#include "stdio.h"

int main() {
        int count = 1;
        int child;

        printf("Before create son, the father's count is:%d/n", count);
        if(!(child = vfork())) {
                int i;
                for(i = 0; i < 100; i++) {
                        printf("This is son, The i is: %d/n", i);
                        if(i == 70)
                                exit(1);
                }
                printf("This is son, his pid is: %d and the count is: %d/n", getpid(), ++count);
                exit(1);
        } else {
                printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d/n", getpid(), count, child);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork 
...

This is son, The i is: 68
This is son, The i is: 69
This is son, The i is: 70
After son, This is father, his pid is: 4433 and the count is: 1, and the child is: 4434
[root@liumengli program]# 
从这里就可以看到父进程总是等子进程执行完毕后才开始继续执行。

 

3.clone函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值

标志                     含义

CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”

CLONE_FS         子进程与父进程共享相同的文件系统,包括root、当前目录、umask

CLONE_FILES    子进程与父进程共享相同的文件描述符(file descriptor)表

CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy

CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表

CLONE_PTRACE 若父进程被trace,子进程也被trace

CLONE_VFORK   父进程被挂起,直至子进程释放虚拟内存资源

CLONE_VM         子进程与父进程运行于相同的内存空间

CLONE_PID        子进程在创建时PID与父进程一致

CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。

[root@liumengli program]# cat test_clone.c
#include "stdio.h"
#include "sched.h"
#include "signal.h"
#define FIBER_STACK 8192
int a;
void * stack;
int do_something(){
        printf("This is son, the pid is:%d, the a is: %d/n", getpid(), ++a);
        free(stack); //这里我也不清楚,如果这里不释放,不知道子线程死亡后,该内存是否会释放,知情者可以告诉下,谢谢
        exit(1);
}
int main() {
        void * stack;
        a = 1;
        stack = malloc(FIBER_STACK);//为子进程申请系统堆栈
        if(!stack) {
                printf("The stack failed/n");
                exit(0);
        }

        printf("creating son thread!!!/n");

        clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//创建子线程
         printf("This is father, my pid is: %d, the a is: %d/n", getpid(), a);
         exit(1);
}
[root@liumengli program]# gcc test_clone.c -o test_clone
[root@liumengli program]# ./test_clone
creating son thread!!!
This is son, the pid is:7326, the a is: 2
This is father, my pid is: 7325, the a is: 2
[root@liumengli program]#

读者可以试试其它的资源继承方式。

 

 

[cpp] view plaincopy
  1. 这里介绍fork, vfork和 clone的具体实现  
  2. 它们具体实现的代码如下:  
  3. asmlinkage int sys_fork(struct pt_regs regs)  
  4. {  
  5.     return do_fork(SIGCHLD, regs.esp, ®s, 0);  
  6. }  
  7. asmlinkage int sys_clone(struct pt_regs regs)  
  8. {  
  9.     unsigned long clone_flags;  
  10.     unsigned long newsp;  
  11.     clone_flags = regs.ebx;  
  12.     newsp = regs.ecx;  
  13.     if (!newsp)  
  14.         newsp = regs.esp;  
  15.     return do_fork(clone_flags, newsp, ®s, 0);  
  16. }  
  17. asmlinkage int sys_vfork(struct pt_regs regs)  
  18. {  
  19.     return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0);  
  20. }  
  21. 这里可以看到它们都是对do_fork的调用,不过是参数不同而已下面是 do_fork函数(很长)  
  22. int do_fork(unsigned int clone_flags, unsigned long stack_start, struct pt_regs * regs, unsigned long stack_size) {  
  23. // 对于clone_flags是由2部分组成,最低字节为信号类型,用于规定子进程去世时向父进程发出的信号。我们可以看到在fork和vfork中这个信号就是SIGCHLD,而clone则可以由用户自己定义。而第2部分是资源表示资源和特性的标志位(前面我们见过这些标志了),对于 fork我们可以看出第2部分全部是0表现对有关资源都要复制而不是通过指针共享。而对于vfork则是CLONE_VFORK|CLONE_VM(看了 fork,vfork,clone,应该很熟悉了)表示对虚存空间的共享和对父进程的挂起和唤醒,至于clone则是由用户自己来定义的  
  24.     int retval = -ENOMEM;  
  25.     struct task_struct *p;  
  26.     DECLARE_MUTEX_LOCKED(sem); //定义和创建了一个用于进程互斥和同步的信号量,这里不做讨论  
  27.       
  28.     if(clone_flags & CLONE_PID)  
  29.  { //CLONE_PID信号是子进程和父进程拥有相同的PID号,这只有一种情况可以使用,就是父进程的PID为0,这里是做这个保证  
  30.         if(current->pid)  
  31.             return -EPERM;  
  32.     }  
  33.       
  34.     current->vfork_sem = sem;  
  35.       
  36.     p = alloc_task_struct();//为子进程分配2个页面(为什么是2个,前面看过也该明白用来做系统堆栈和存放task_struct的)  
  37.     if(!p)  
  38.         goto fork_out;  
  39.           
  40.     *p = *current; //将父进程的task_struct赋值到2个页面中  
  41.       
  42.     retval = -EAGAIN;  
  43.     if(atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur) //p->user 指向该进程所属用户的数据结构,这个数据结构见下(内核进程不属于任何用户,所以它的p->user = 0),p->rlim是对进程资源的限制,而p->rlim[RLIMIT_NPROC]则规定了该进程所属用户可以拥有的进程数量,如果超过这个数量就不可以再fork了  
  44.         goto bad_fork_free;  
  45.     atomic_inc(&p->user->__count);  
  46.     atomic_inc(&p->user->processes);  
  47.       
  48.     if(nr_threads >= max_threads) //上面是对用户进程的限制,这里是对内核进程的数量限制  
  49.         goto bad_fork_cleanup_count;  
  50.           
  51.     get_exec_domain(p->exec_domain); //p->exec_domain指向一个exec_domain结构,定义见下。  
  52.       
  53.     if(p->binfmt && p->binfmt->module) //每个进程都属于某种可执行的印象格式如a.out或者elf,对这些格式的支持都是通过动态安装驱动模块来实现的,binfmt就是用来指向这些格式驱动  
  54.         __MOD_INC_USE_COUNT(p->binfmt->module);  
  55.       
  56.     p->did_exec = 0;  
  57.     p->swappable = 0;  
  58.     p->state = TASK_UNINTERRUPTIBLE; //为下面设置PID做准备,明显get_pid是一种独占行为,不能多个进程同时去get_pid,因此在这里可能需要将当前进程睡眠,所以设置这个  
  59.       
  60.     copy_flags(clone_flags, p);  
  61.     p->pid = get_pid(clone_flags); //设置新建进程的PID  
  62.       
  63.     p->run_list.next = NULL;  
  64.     p->run_list.prev = NULL;  
  65.       
  66.     if((clone_flags & CLONE_VFORK) || !(clone_flags & CLONE_PARENT))   
  67. {  
  68.         p->p_opptr = current;  
  69.         if(!(p->trace & PT_PTRACED))  
  70.             p->p_pptr = current;  
  71.     }  
  72.     p->p_cptr = NULL;  
  73.     init_waitqueue_head(&p->wait_childexit); //wait4()与wait3()函数是一个进程等待子进程完成使命后再继续执行,这个队列为此做准备,这里是做初始化  
  74.     p->vfork_sem = NULL;  
  75.     spin_lock_init(&p->alloc_lock);  
  76.       
  77.     p->sigpending = 0;  
  78.     init_sigpending(&p->sigpending); //对子进程待处理信号队列和有关结构成分初始化  
  79.       
  80.     p->it_real_value = p->it_virt_value = p->it_prof_value = 0;  
  81.     p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;  
  82.     init_timer(&p->real_timer);  
  83.     p->real_timer.data = (unsigned long)p;  
  84.       
  85.     p->leader = 0;  
  86.     p->tty_old_pgrp = 0;  
  87.     p->times.tms_utime = p->times.tms_stime = 0;  
  88.     p->times.tms_curtime = p->times.tms_cstime = 0; //对进程各种记时器的初始化  
  89. #ifdef CONFIG_SMP  
  90.     {  
  91.         int i;  
  92.         p->has_cpu = 0;  
  93.         p->processor = current->processor;  
  94.           
  95.         for(i = 0; i < smp_num_cpus; i++)  
  96.             p->per_cpu_utime[i] = p->per_cpu_stime[i] = 0;  
  97.         spin_lock_init(&p->sigmask_lock);  
  98.     }  
  99. #endif //多处理器相关  
  100.     p->lock_death = -1;  
  101.     p->start_time = jiffies; //对进程初始时间的初始化,jeffies是时钟中断记录的记时器,到这里task_struct基本初始化完毕  
  102.       
  103.     retval = -ENOMEM;  
  104.     if(copy_files(clone_flags,p)) //copy_files是复制已打开文件的控制结构,但只有才clone_flags中CLONE_FILES标志才能进行,否则只是共享  
  105.         goto bad_fork_cleanup;  
  106.     if(copy_fs(clone_flags, p)); //依然是对文件的,详细的参考文件系统  
  107.         goto bad_fork_cleanup_files;  
  108.     if(copy_sighand(clone_flags, p))//和上面一样,这里是对信号的处理方式  
  109.         goto bad_fork_cleanpu_fs;  
  110.     if(copy_mm(clone_flags, p))//内存,下面给出了copy_mm的代码  
  111.         goto bad_fork_cleanup_sighand; //到这里所有需要有条件复制的资源全部结束  
  112.     retval  = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); //4个资源中,还剩系统堆栈资源没有复制,这里是解决这个问题的  
  113.     if(retval)  
  114.         goto bad_fork_cleanup_sighand;  
  115.     p->semundo = NULL;  
  116.       
  117.     p->parent_exec_id = p->self_exec_id; //parent_exec_id父进程的执行域  
  118.         /* ok, now we should be set up.. */  
  119.     p->swappable = 1;//表示本进程的页面可以被换出  
  120.     p->exit_signal = clone_flags & CSIGNAL;  
  121.     p->pdeath_signal = 0;  
  122.     p->counter = (current->counter + 1) >> 1;  
  123.     current->counter >>= 1;//父进程的分配的时间额被分成2半  
  124.     if (!current->counter)  
  125.         current->need_resched = 1; //让父子进程各拥有时间的一半  
  126.       
  127.     retval = p->pid;  
  128.     p->tgid = retval;  
  129.     INIT_LIST_HEAD(&p->thread_group);  
  130.     write_lock_irq(&tasklist_lock);  
  131.     if (clone_flags & CLONE_THREAD) {  
  132.         p->tgid = current->tgid;  
  133.         list_add(&p->thread_group, ¤t->thread_group);  
  134.     }  
  135.     SET_LINKS(p); //将子进程的PCB放入进程队列,让它可以接受调度  
  136.     hash_pid(p);    //将子进程放入hash表中  
  137.     nr_threads++;  
  138.     write_unlock_irq(&tasklist_lock);  
  139.     if (p->ptrace & PT_PTRACED)  
  140.         send_sig(SIGSTOP, p, 1);  
  141.     wake_up_process(p); /* do this last *///将子进程唤醒,到这里子进程已经完成了  
  142.     ++total_forks;  
  143.       
  144. fork_out:  
  145.     if ((clone_flags & CLONE_VFORK) && (retval > 0))  
  146.         down(&sem); //这里就是达到扣留一个进程的目的  
  147.     return retval;  
  148. //进程虽然创建结束,但有个特殊情况有待考虑就是调用者是 vfork,标志位CLONE_VFORK,此时由于决定采用的是CLONE_VM,父子2个进程是共享用户空间的,对堆栈空间的写入更是致命,因为会导致其中一个因为非法越界而死亡,所以做法是扣留其中一个进程  
  149. struct user_struct { //描述用户的数据结构  
  150.     atomic_t __count;    /* reference count */  
  151.     atomic_t processes;    /* How many processes does this user have? */  
  152.     atomic_t files;        /* How many open files does this user have? */  
  153.     /* Hash table maintenance information */  
  154.     struct user_struct *next, **pprev; //用于杂凑表,对用户名施以杂凑运算  
  155.     uid_t uid;  
  156. };  
  157. struct exec_domain   
  158. {  
  159.     const char        *name;        /* name of the execdomain */  
  160.     handler_t        handler;    /* handler for syscalls */  
  161.     unsigned char        pers_low;    /* lowest personality */ //指向某种域的代码,有PER_LILNUX, PER_SVR4,PER_BSD和PER_SOLARIS这是表示进程的执行域  
  162.     unsigned char        pers_high;    /* highest personality */  
  163.     unsigned long        *signal_map;    /* signal mapping */  
  164.     unsigned long        *signal_invmap;    /* reverse signal mapping */  
  165.     struct map_segment    *err_map;    /* error mapping */  
  166.     struct map_segment    *socktype_map;    /* socket type mapping */  
  167.     struct map_segment    *sockopt_map;    /* socket option mapping */  
  168.     struct map_segment    *af_map;    /* address family mapping */  
  169.     struct module        *module;    /* module context of the ed. */ //在linux系统中设备驱动程序"动态安装模块",使其运行动态的安装和拆除  
  170.     struct exec_domain    *next;        /* linked list (internal) */  
  171. };  
  172. static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)   
  173. {  
  174.     struct mm_struct * mm, *old_mm;  
  175.     int retval;  
  176.       
  177.     tsk->min_flt = tsk->maj_flt = 0;  
  178.     tsk->cmin_flt = tsk->cmaj_flt = 0;  
  179.     tsk->nswap = tsk->cnswap = 0;  
  180.       
  181.     tsk->mm = NULL;  
  182.     tsk->active_mm = NULL;  
  183.       
  184.     old_mm = current->mm;  
  185.     if(!old_mm)  
  186.         return 0;  
  187.       
  188.     if(clone_flags & CLONE_VM) {//从这里可以看出,如果是共享内存的话,只是将mm由父进程赋值给了子进程,2个进程将会指向同一块内存  
  189.         atomic_inc(&old_mm->mm_users);  
  190.         mm = oldmm;  
  191.         goto good_mm;  
  192.     }  
  193.       
  194.     retval = -ENOMEM;  
  195.     mm = allocate_mm();  
  196.     if(!mm)  
  197.         goto fail_nomem;  
  198.       
  199.     memcpy(mm, oldmm, sizeof(*mm));  
  200.     if(!mm_init(mm));  
  201.         goto fail_nomem;  
  202.       
  203.     down(&oldmm->mmap_sem);  
  204.     retval = dup_mmap(mm); //这里完成了对vm_area_struct和页面表的复制  
  205.     up(&oldmm->mmap_sem);  
  206.       
  207.     if(retval)  
  208.         goto free_pt;  
  209.       
  210.     copy_segments(tsk, mm);  
  211.       
  212.     if(init_new_context(tsk, mm));  
  213.         goto free_pt;  
  214.       
  215. good_mm:  
  216.     tsk->mm = mm;  
  217.     tsk->active_mm = mm;  
  218.     return 0;  
  219. free_pt:  
  220.     mmput(mm);  
  221. fail_nomem:  
  222.     return retval;  
  223. }  
  224. static inline int dup_mmap(struct mm_struct * mm) {  
  225.     struct vm_area_struct * mpnt, * tmp, **prev;  
  226.     int retval;  
  227.       
  228.     flush_cache_mm(current->mm);  
  229.     mm->locked_vm = 0;  
  230.     mm->mmap = NULL;  
  231.     mm->mmap_avl = NULL;  
  232.     mm->mmap_cache = NULL;  
  233.     mm->map_count = 0;  
  234.     mm->cpu_vm_mask = 0;  
  235.     mm->swap_cnt = 0;  
  236.     mm->swap_address = 0;  
  237.     pprev = &mm->mmap;  
  238.       
  239.     for(mpnt = current->mm_mmap; mpnt; mpnt= mpnt->vm_next) { //遍历队列,对属于父进程的所有mm_struct开始遍历  
  240.         struct file * file;  
  241.           
  242.         retval = -ENOMEM;  
  243.         if(mpnt->vm_flags & VM_DONTCOPY)  
  244.             continue;  
  245.         tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//给TMP申请缓存  
  246.         if(!tmp)  
  247.             goto fail_nomem;  
  248.         *tmp = *mpnt;  
  249.         tmp->vm_flags &= ~VM_LOCKED;  
  250.         tmp->vm_mm = mm;  
  251.         mm->map_count++;  
  252.         tmp->vm_next = NULL;  
  253.         file = tmp->vm_file;  
  254.         if(file) {  
  255.             struct inode *inode = file->f_dentry->d_inode;  
  256.             get_file(file);  
  257.             if(tmp->vm_flags & VM_DENYWRITE)  
  258.                 atomic_dec(&inode->i_writecount);  
  259.               
  260.             spin_lock(&inode->i_mapping->i_shared_lock);  
  261.             if((tmp->vm_next_share = mpnt->vm_next_share) != NULL)  
  262.                 mpnt->vm_next_share->vm_pprev_share = &tmp->vm_next_share;  
  263.             mpnt->vm_next_share = tmp;  
  264.             tmp->vm_pprev_share = &mpnt->vm_next_share;  
  265.             spin_unlock(&inode->i_mapping->i_shared_lock);  
  266.         }  
  267.           
  268.         retval = (mm, current->mm, tmp);  
  269.         if(!retval && tmp->tmp->vm_ops && tmp->vm_ops->open)  
  270.             tmp->vm_ops->open(tmp);  
  271.           
  272.         *pprev = tmp;  
  273.         pprev = &tmp->vm_next;  
  274.           
  275.         if(retval)  
  276.             goto fail_nomem;  
  277.     }  
  278.     retval = 0;  
  279.     if(mm->map_count >= AVL_MIN_MAP_COUNT)  
  280.         build_mmap_avl(mm);  
  281. fail_nomem;  
  282.     flush_tlb_mm(current->mm);  
  283.     return retval;  
  284. }  
  285. int copy_page_range(struct mm_struct * dst, struct mm_struct * src, struct vm_area_struct * vma) {  
  286.     pgd_t * src_pgd, * dst_pgd;  
  287.     unsigned long address = vma->vm_start;  
  288.     unsigned long end = vma->vm_end;  
  289.     unsigned long cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;  
  290.       
  291.     src_pgd = pgd_offset(src, address) - 1;  
  292.     dst_pgd = pgd_offset(dst, address) - 1;  
  293.       
  294.     for(;;) { //对页面目录表项的循环  
  295.         pmd_t * src_pmd, * dst_pmd;  
  296.           
  297.         src_pgd++;  
  298.         dst_pgd++;  
  299.           
  300.         if(pgd_none(*src_pgd))  
  301.             goto skip_copy_pmd_range;  
  302.         if(pgd_bad(* src_pgd)) {  
  303.             pgd_ERROR(*src_pgd);  
  304.             pgd_clear(src_pgd);  
  305. skip_copy_pmd_range:  
  306.             address = (address + PGDIR_SIZE) &PGDIR_MASK;  
  307.             if(!address || (address >= end))  
  308.                 goto out;  
  309.             continue;  
  310.         }  
  311.           
  312.         if(pgd_none(*dst_pgd)) {  
  313.             if(!pmd_alloc(dst_pgd, 0))  
  314.                 goto nomem;  
  315.         }  
  316.           
  317.         src_pmd = pmd_offset(src_pgd, address);  
  318.         dst_pmd = pmd_offset(dst_pgd, address);  
  319.           
  320.         do//对中间目录的循环  
  321.             pte_t * src_pte, * dst_pte;  
  322.               
  323.             if(pmd_none(*src_pmd))  
  324.                 goto skip_copy_pte_range;  
  325.             if(pmd_bad(*src_pmd)) {  
  326.                 pmd_ERROR(*src_pmd);  
  327.                 pmd_clear(src_pmd);  
  328. skip_copy_pte_range:  
  329.                 address = (address + PMD_SIZE) & PMD_MASK;  
  330.                 if(address >= end)  
  331.                     goto out;  
  332.                 goto cont_copy_pmd_range;  
  333.             }  
  334.             if(pmd_none(*dst_pmd))   
  335.             {  
  336.                 if(!pte_alloc(dst_pmd, 0))  
  337.                     goto nomem;  
  338.             }  
  339.               
  340.             src_pte = pte_offset(src_pmd, address);  
  341.             dst_pte = pte_offset(dst_pmd, address);  
  342.               
  343.             do//对页面表的循环  
  344.                 pte_t pte = *src__pte;  
  345.                 struct page * ptepage;  
  346.                   
  347.                 if(pte_none(pte)) //映射尚未建立的表项,直接跳过  
  348.                     goto cont_copy_pte_range_noset;  
  349.                 if(!pte_present(pte)) { //说明该页面被交换到了磁盘,只是对盘上页面用户计数加一  
  350.                     swap_duplicate(pte_to_swp_entry(pte));  
  351.                     goto cont_copy_pte_range;  
  352.                 }  
  353.                 ptepage = pte_page(pte);  
  354.                 if((!VALLID_PAGE(ptepage)) || PageReserved(ptepage)) //不是有效页面,此页面对应的表项直接复制到子进程的页面表中  
  355.                     goto cont_copy_pte_range;  
  356.                       
  357.                 if(cow) { //使用copy_on_write机制,这里就是子进程本来应该从父进程中复制出来的页面  
  358.                     ptep_set_wrprotect(src_pte); //将原来父进程的可惜页面改成写保护  
  359.                     pte = * src_pte;  
  360.                 }  
  361.                   
  362.                 if(vma->vm_flags& VM_SHARED)  
  363.                     pte = pte_mkclean(pte); //将父进程的页面表项复制到子进程中  
  364. //从这里我们就看到,不是一开始就是为子进程开辟一个新的内存页面,然后将对应的父进程中的页面内容复制到该内存中,这种消耗过大,实际做法是先将这个内存改成写保护,然后将页面表项复制给子进程,最后,若真的父进程或者子进程会对这个页面执行写操作,便会发生写保护异常,异常处理程序中才将这个页面复制出来从而达到了"父子分家"  
  365.                 pte = pte_mkold(pte);  
  366.                 get_page(ptepage);  
  367. cont_copy_pte_range:  
  368.                 set_pte(dst_pte, pte); //直接复制页面表项  
  369. cont_copy_pte_range_noset:  
  370.                 if(address >= end)  
  371.                     goto out;  
  372.                 src_pte++;  
  373.                 dst_pte++;  
  374.             } while((unsigned long)src_pte & PTE_TABLE_MASK);  
  375. cont_copy_pmd_rang:  
  376.             src_pmd++;  
  377.             dst_pmd++;  
  378.         } while((unsigned long) src_pmd & PMD_TABLE_MASK);  
  379.     }  
  380. out:  
  381.     return 0;  
  382. nomem:  
  383.     return -ENOMEM;  
  384. //从这里我们看到一个页面都没复制,这就是为什么fork也能达到vfork 创建线程那么快的效率  
  385. int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,  
  386. unsigned long unused,  
  387. struct task_struct * p, struct pt_regs * regs)  
  388. {  
  389.     struct pt_regs * childregs;  
  390.   
  391.     childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p)) - 1; //中断前夕,系统堆栈的高部保存了各个部分的寄存器的信息  
  392.     struct_cpy(childregs, regs); //将父进程的内容全部复制给子进程  
  393.     childregs->eax = 0; //对子进程的系统堆栈做少量调整,首先是对 eax寄存器内容置0  
  394.     childregs->esp = esp;//将esp指定成给定的esp  
  395.        //task_thread记载了一些关键性信息,包括进程切换时到系统态的堆栈指针,取指令地址,明显这些父子2个进程是不可以完全复制的,一下是对这些的修改  
  396.     p->thread.esp = (unsigned long) childregs; //将堆栈指针指向正确的位置  
  397.     p->thread.esp0 = (unsigned long) (childregs+1);//堆栈的顶部也指向真确的位置  
  398.   
  399.     p->thread.eip = (unsigned long) ret_from_fork;//这是当进程下一次切换时将进入的切入点,在进程切换里会详细提到  
  400.   
  401.     savesegment(fs,p->thread.fs);  
  402.     savesegment(gs,p->thread.gs);  
  403.   
  404.     unlazy_fpu(current);  
  405.     struct_cpy(&p->thread.i387, ¤t->thread.i387);  
  406.   
  407.     return 0;  
  408. }  
0 0
原创粉丝点击