标签:
因为生活的复杂,这是一个并行的世界,在同一时刻,会发生很多奇妙的事情,北方下雪,南方下雨,这里在吃饭,那边在睡觉,有人在学习,有人在运动,所以这时一个多彩多姿的世界,每天都发生着很多事情,所以要想很好的表现这个世界,协调完成一件事儿,就得用到多进程或者多线程。所以进程是程序猿一定会接触到的一个东西,他能使我们的程序效率提高,高效的完成多任务,并行执行。下面主要看看产生进程或线程的三个函数。
fork,vfork,clone都是linux的系统调用,这三个函数分别调用了sys_fork、sys_vfork、sys_clone,最终都调用了do_fork函数,差别在于参数的传递和一些基本的准备工作不同,主要用来linux创建新的子进程或线程(vfork创造出来的是线程)。
进程的四要素:
(1)有一段程序供其执行(不一定是一个进程所专有的),就像一场戏必须有自己的剧本。一、fork()
fork()函数调用成功:返回两个值; 父进程:返回子进程的PID;子进程:返回0;
失败:返回-1;
fork 创造的子进程复制了父亲进程的资源(写时复制技术),包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。
说到fork(),就不得不说一个技术:(Copy-On-Write)写时复制技术。
盗用一张图,感觉描述的确实挺到位:
我们都知道fork创建进程的时候,并没有真正的copy内存(听着好像矛盾了,资源的赋值为什么有没有真正的赋值呢?),因为我们知道,对于fork来讲,有一个很讨厌的东西叫exec系列的系统调用,它会勾引子进程另起炉灶。如果创建子进程就要内存拷贝的的话,一执行exec,辛辛苦苦拷贝的内存又被完全放弃了。由于fork()后会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,处于效率考虑,linux中引入了“写时复制技术-Copy-On-Write”。
换言之,在fork()之后exec之前两个进程用的是相同的物理空间(内存区),先把页表映射关系建立起来,并不真正将内存拷贝。子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父进程中有更改相应段的行为发生时,如进程写访问,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。fork时子进程获得父进程数据空间、堆和栈的复制所以变量的地址(当然是虚拟地址)是一样的。
具体过程是这样的:#include"stdio.h" int main() { int count = 1; int child; if(0== fork()) //子进程成功返回0; { //开始创建子进程 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()); } }
运行结果:
#include"stdio.h" int main() { int count = 1; int child; int i; if(!(child = fork())) { for(i = 0; i <20; i++) { printf("This is son, his count is: %d. and his pid is: %d\n", i, getpid()); } } else { for(i=0;i<20;i++) printf("This is father, his count is: %d, his pid is: %d\n", count, getpid()); } }
运行结果:
二、vfork()
vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。
vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少经常要素(4),独立的内存资源,看下面的程序:
#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); } }
运行结果:
从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的count变量,这一次是指针复制,2者的指针指向了同一个内存,所以子进程修改了count变量,父进程的 count变量同样受到了影响。
另外由vfork创建的子进程要先于父进程执行,子进程执行时,父进程处于挂起状态,子进程执行完,唤醒父进程。除非子进程exit或者execve才会唤起父进程,看下面程序:
#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); count++; if(i == 20) { 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); } }
运行结果:
3.clone
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。
clone函数功能强大,带了众多参数,它提供了一个非常灵活自由的常见进程的方法。因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sched.h> #define FIBER_STACK 8192 int a; void * stack; int do_something(){ a=10; 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); }
运行的结果:
son的PID:10692;
father的PID:10691;
parent和son中的a都为10;所以证明他们公用了一份变量a,是指针的复制,而不是值的复制。
问题:clone和fork的区别:
(1) clone和fork的调用方式很不相同,clone调用需要传入一个函数,该函数在子进程中执行。
(2)clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。
博客资料参考:
http://blog.csdn.net/xy010902100449/article/details/44851453
http://www.cnblogs.com/blankqdb/archive/2012/08/23/2652386.html
http://blog.chinaunix.net/uid-24774106-id-3361500.html
http://www.linuxidc.com/Linux/2015-03/114888.htm
http://igaozh.iteye.com/blog/1677969
http://blog.chinaunix.net/uid-24410388-id-195503.html
http://blog.chinaunix.net/uid-18921523-id-265538.html
http://blog.csdn.net/wdjhzw/article/details/25614969
感谢各位博主的分享!
标签:
原文地址:http://blog.csdn.net/gogokongyin/article/details/51178257