Chinaunix首页 | 论坛 | 博客
  • 博客访问: 284194
  • 博文数量: 67
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 620
  • 用 户 组: 普通用户
  • 注册时间: 2015-07-12 19:56
文章分类

全部博文(67)

文章存档

2019年(1)

2018年(1)

2017年(4)

2016年(34)

2015年(27)

我的朋友

分类: LINUX

2015-09-15 16:24:49

fork,vfork,clone都是linux的系统调用,用来创建子进程的,但是大家在使用时经常混淆,这里给出具体例子讲解三者的联系与区别。
  我们知道,进程由4个要素组成:
  1.进程控制块:进程标志     2.进程程序块:可与其他进程共享  
  3.进程数据块:进程专属空间,用于存放各种私有数据以及堆栈空间。4.独立的空间(如果没有4则认为是线程)
   一、fork
    fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。如:

  1.     int main() {
  2.         int num = 1;
  3.         int child;
  4.         if(!(child = fork())) {
  5.                 printf("This is son, his num is: %d. and his pid is: %d\n", ++num, getpid());
  6.         } else {
  7.                 printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
  8.         }
  9.    }
复制代码


执行结果为:This is son, his num is: 2. and his pid is: 2139
This is father, his num is: 1, his pid is: 2138
  从代码里面可以看出2者的pid不同,子进程改变了num的值,而父进程中的num没有改变。
总结:优点是子进程的执行独立于父进程,具有良好的并发性。缺点是两者的通信需要专门的通信机制,如pipe、fifo和system V等。有人认为这 样大批量的复制会导致执行效率过低。其实在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表,在子进程运行前,两者指向同一页面。而当子进程改变了父进程的变量时候,会通过copy_on_write的手 段为所涉及的页面建立一个新的副本。因此fork效率并不低。
  二、vfork
  vfork函数创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见。这与fork是完全不同的,fork进程是独立的空间。另外一点不同的是vfork创建的子进程后,父进程会被阻塞,直到子进程执行exec()和exit()。如:
  1. int main() {
  2.         int num = 1;
  3.         int child;
  4.         if(!(child = vfork())) {
  5.                 printf("This is son, his num is: %d. and his pid is: %d\n", ++num, getpid());
  6.         } else {
  7.                 printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
  8.         }
  9.    }
复制代码


运行结果为:This is son, his num is: 2. and his pid is:4139
This is father, his num is: 2, his pid is: 4138
从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的num变量,这一次是指针复制,2者的指针指向了同一个内存
  总结:当创建子进程的目的仅仅是为了调用exec()执行另一个程序时,子进程不会对父进程的地址空间又任何引用。因此,此时对地址空间的复制是多余的,通过vfork可以减少不必要的开销。
  三、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就是传给子进程的参数)。
总结:
clone, fork, vfork实现方式
  •    大致相同:
    • 系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成.
      • do_fork的参数与clone系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的

  • 区别在于:
    • clone:
      •     clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
      •     sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
      •     sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
    • fork, vfork:
      •     服务例程就是直接调用do_fork, 不过参数稍加修改
      •     clone_flags:
        •    sys_fork: SIGCHLD|0;
        •    sys_vfork: SIGCHLD| (clone_vfork | clone_vm)
      •     用户栈: 都是父进程的栈.
      •     parent_tidptr, child_ctidptr都是NULL.
阅读(1225) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~