本文主要是介绍Linux内核-进程之fork、vfork和clone,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
各种大神的混合,做个笔记。
http://blog.sina.com.cn/s/blog_7598036901019fcg.html
http://blog.csdn.net/kennyrose/article/details/7532912
http://blog.sina.com.cn/s/blog_92554f0501013pl3.html
http://www.cnblogs.com/peteryj/archive/2007/08/05/1944905.html
进程的四大要素:
Linux进程所需具备的四要素:
(1)程序代码。代码不一定是进程专有,可以与其它进程共用。
(2)系统堆栈空间,这是进程专用的。
(3)在内核中维护相应的进程控制块。只有这样,该进程才能成为内核调度的基本单位,接受调度。并且,该结构也记录了进程所占用的各项资源。
(4)有独立的存储空间,表明进程拥有专有的用户空间。
以上四条,缺一不可。如果缺少第四条,那么就称其为“线程”。如果完全没有用户空间,称其为“内核线程”;如果是共享用户空间,则称其为“用户线程”。
do_fork函数
Linux的用户进程不能直接被创建出来,因为不存在这样的API。它只能从某个进程中复制出来,再通过exec这样的API来切换到实际想要运行的程序文件。
复制的API包括三种:fork、clone、vfork。
这三个API的内部实际都是调用一个内核内部函数do_fork,只是填写的参数不同而已。
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值:
标志
内核线程是由kernel_thread(
int kernel_thread(int (*fn)(void *), void * arg,
{
}
fork函数(unistd.h)
由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。 对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。
fork创建一个进程时,子进程只是完全复制父进程的资源,复制出来的子进程有自己的task_struct结构和pid,但却复制父进程其它所有的资源。例如,要是父进程打开了五个文件,那么子进程也有五个打开的文件,而且这些文件的当前读写指针也停在相同的地方。所以,这一步所做的是复制。这样得到的子进程独立于父进程, 具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制,如:pipe,共享内存等机制, 另外通过fork创建子进程,需要将上面描述的每种资源都复制一个副本。但由于现在Linux中是采取了copy-on-write(COW写时复制)技术,为了降低开销,fork最初并不会真的产生两个不同的拷贝,因为在那个时候,大量的数据其实完全是一样的。写时复制是在推迟真正的数据拷贝。若后来确实发生了写入,那意味着parent和child的数据不一致了,于是产生复制动作,每个进程拿到属于自己的那一份,这样就可以降低系统调用的开销。
指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的),但是父子进程谁先被调用就得看操作系统的调度程序了。]
创建了一个子进程之后,父进程和子进程争夺CPU,抢到CPU者执行,另外一个进程等待挂起。如果想要父进程等待子进程执行完后再执行,可以在fork操作之后调用wait或waitpid。
子进程会继承父进程的很多属性:包含用户ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字、信号屏蔽字、上下文环境、共享的存储段、资源限制等。但是子进程不会继承父进程设置的文件锁、父进程设置的警告,同时子进程未决信号被清空。
范例1:
int main(){ int num = 1; int child; if(!(child = fork())) { printf("This is son, his num is: %d. and his pid is: %d\n", ++num, getpid()); } else { printf("This is father, his num is: %d, his pid is: %d\n", num, getpid()); }
}
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效率并不低。
注:fork创建子进程时,子进程继承了父进程父进程的全局变量和局部变量。即不管是全局变量还是局部变量,子进程与父进程的修改互不影响。
范例2:
#include <unistd.h>
#include <stdio.h>
int main(void)
{ int i=0; for(i=0;i<3;i++){ pid_t fpid=fork(); if(fpid==0) printf("son/n"); else printf("father/n"); } return 0; }
这里就不做详细解释了,只做一个大概的分析。