
本文共 2799 字,大约阅读时间需要 9 分钟。
一、fork;
在UNIX系统里。只有一个系统调用可以用来创建新进程:fork。这个系统调用会创建一个与调用进程相同的副本。创建的进程称为父进程,被创建的进程则称为子进程。两个进程拥有相同的内存映射、相同的环境字符串和同样的打开文件。
1、fork被调用一次,返回两次,
#include<unistd.h> pid_t fork(void); 返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1;
2、linux的fork();使用写时拷贝页实现的。写时拷贝是一种延迟拷贝甚至于免除拷贝的技术,只有在需要写入的时候数据才会被拷贝,试想子进程一被创建还没有 发生写操作时立即执行exec(),此情况就不会发生拷贝。这种优化可以避免拷贝大量根本就不需要的数据。fork的实际开销就是复制父进程的页表以及给子进程创建的唯一的进程描述符。
3、文件共享
fork的一个特性时父进程的所有打开文件描述符都被复制到子进程中
fork的系统调用过程:
执行过程:
fork() 调用 clone() ,clone() 调用 do_fork() 。
do_fork() 执行了大部分工作。
do_fork() 调用 copy_process() ,让进程开始运行
1)copy_process() 调用 dup_task_struct() 为新进程创建一个内核栈、
thread_info 结构和 task_struct ,这些值与当前进程的值相同。此时,
子进程和父进程的描述符是完全相同的。
2) 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。
3)子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。那些不是继承而来的进程描述符成员,主要是统计信息。task_struct 中的大多数数据都依然未被修改。
4)子进程的状态被设置为 TASK_UNINTERRUPTIBLE ,以保证它不会投入运行。
5)copy_process() 调用 copy_flags() 以更新 task_struct 的flags成员。表明进程是否拥有超级用户权限的 PF_SUPERPRIV 标志被清0。表明进程还没有调用 exec() 函数的 PF_FORKNOEXEX 标志被设置。
6)调用 alloc_pid() 为新进程分配一个有效的PID。
7)根据传递给 clone() 的参数标志, copy_process() 拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程都是不同的,因此被拷贝到这里。
8)最后, copy_process() 做扫尾工作并返回一个指向子进程的指针。
回到 do_fork() 函数。如果 copy_process() 函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行,因为一般子进程都会马上调用 exec() 函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入
**fork函数演示:**
#include"apue.h"#include<unistd.h>int globvar=6;char buf[]="a write to stdout\n";int main(void){ int var; int pid; var=88; if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1) err_sys("write error"); printf("before fork!\n"); if((pid=fork())<0) err_sys("fork error"); else if(pid==0) //child { globvar++; var++; } else sleep(2); //父进程 printf("pid=%d,glob=%d,var=%d \n",(long)getpid(),globvar,var); exit(0);}
运行结果:子进程的变量值变化。父进程的变量值没有变化。
二、vfork
除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。
vfork函数与fork函数的区别:
1、语义不同。vfork函数用于创建一个新进程,而该进程的目的是exec一个新程序 。但是vfork并不将父进程的地址空间完全复制到子进程中,因为会立即调用exec。这种优化方式在某些UNIX系统的实现中提高了效率,但exec调用前,子进程还是在父进程的空间中运行,如果子进程修改数据、进行函数调用、或者没有执行exec或者exit就返回可能会带来未知的后果。
2、子进程作为父进程的一个单独的线程在它的地址空间运行,父进程阻塞,直到子进程退出或者执行exec()。
vfork的系统调用过程:
vfork() 系统调用是通过向 clone() 系统调用传递一个特殊标志位来进行的
1)在调用 copy_process() 时, task_struct 的 vfor_done 成员变量被设置为NULL。
2)在执行 do_fork() 时,如果给定特别标志,则 vfor_done 会指向一个特定地址。
3)子进程先开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过 vfor_done 指向向它发送信号。
4)在调用 mm_release() 时,该函数用于进程退出内存地址空间,并且检查vfor_done 是否为空,如果不为空,则会向父进程发送信号。
5)回到 do_fork() ,父进程醒来并返回。
#include"apue.h"#include<unistd.h>int globvar=6;int main(void){ int var; pid_t pid; var=88; printf("before vfork!\n"); if((pid=vfork())<0) err_sys("vfork error"); else if(pid==0) { globvar++; var++; _exit(0); } printf("pid=%d,globvar=%d,var=%d",(long)getpid(),globvar,var);//父进程 exit(0);}
运行结果:子进程对变量做+1的操作,改变了父进程中的变量值,因为子进程在父进程的地址空间运行。
参考 :《linux内核设计与实现》、《UNIX环境高级编程》
发表评论
最新留言
关于作者
