《LINUX学习:进程管理之fork函数》要点:
本文介绍了LINUX学习:进程管理之fork函数,希望对您有用。如果有疑问,可以联系我们。
fork函数的定义
#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);
fork函数在父进程中返回子进程的pid,在子进程中返回0.注意在子进程中返回的0,并不是子进程的pid,子进程的pid在父进程的返回值中保存.而子进程的返回值是为了标识它是子进程,用来区分父子进程的.那么为什么这样设计父子进程的返回值呢?我的理解是这样的:第一,对于父进程来说,它可能同时有多个子进程,并且没有一个函数可以获得所有子进程的pid,所以它需要知道每个子进程的pid,这样便于管理各个子进程;第二,对于子进程来说,它父进程只能有一个,所以它可以不必实现和父进程类似的机制,获取父进程的pid,它可以通过getpid函数获取父进程的pid.而在内核中,进程pid为0,总是在内核交换进程时使用,所以进程的真实pid不可能为0.
资源相关问题
读时共享,写时复制
fork函数并不是在被调用时,为子进程copy一份父进程的副本,而是将父进程数据段、堆区和栈区的权限改变为只读.当父子进程中任何一个进程要修改该区域的值时,内核只会为修改区域的那块内存制作一个副本,通常是虚拟内存中的一页.这样的机制无需copy所有的父进程资源(这些资源在子进程中不一定有用),提高了程序的效率.但要注意,父子进程共享代码段.
复制的资源
- 能够复制fork函数会复制数据段、堆区和栈区的资源
- 能够复制参数表和环境表
- 能够复制各个ID,但不包括PID
- 不能够继承文件锁
1)数据段、堆区、栈区、参数表和环境表和上文说的一样,遵循读时共享,写时复制的机制;
2)环境表中只能改变其本身进程和其子进程的环境变量,无法改变其父进程的环境变量;
3)像是实际用户ID、实际组ID、有效用户ID、有效组ID、进程组ID和附属ID,这些都是进程的属性,所以存在于PCB中,每个进程都有自己的PCB节点,其中部分数据是继承父进程的.当然pid不会.
4)对于文件锁,子进程是不会被继承的.子进程虽然复制了文件描述符表,但都指向同一个文件表,当然了,文件表也就指向了同一个i节点.在i节点中保存着和文件锁相关的链表头节点(struct lockf)的地址.i节点资源不属于进程资源,而是文件资源,所以不会继承文件锁.
调用使用
常见使用办法
- if…else结构:最常见的使用办法,很简单.
- 和管道结合使用:主要用来父子进程间通信,使用时要注意,子进程会复制父进程的管道文件描述符,因此,都可以对对管道进行读写操作.需要注意的是,在使用管道这个临界资源时,不要忘记在软件逻辑上避免进程饥饿现象的产生.
-
子进程的管理:别忘了了父进程能返回子进程的pid.当有多个子进程时,如果会用到各个子进程的pid的话,不要只是定义一个 pid_t pid
的变量.这样的话,只能管理一个子进程哟!
避免僵尸进程的办法
- 使用wait和waitpid函数:阻塞等待子进程结束,回收进程表资源
-
安插信号:利用signal函数安插SIGCHLD信号.因为在子进程结束后,父进程会收到该信号.再自己写个回调函数,在函数中调用wait或waitpid函数,回收进程表资源;如果父进程对子进程结束不感兴趣,则可以利用“signal(SIGCHLD,SIG_IGN)”,将回收子进程资源的工作交给内核来做;但要注意,SIGCHLD信号是传统的不可靠信号,信号处理函数执行期间会暂时阻塞,在这期前,如果又有了SIGCHLD信号,则会被抛弃,即无法处理多个SIGCHLD信号.所以信号处理函数的正确写法是:
void handler(int signs) { int tmp_errno=errno; while(waitpid(-1,&status,WNOHANG)>0) { //处理返回信息 } errno=tmp_errno; }
但仍要注意的是,当waitpid返回值为-1时,会改变全局变量errno的值,如果这是在主程序中检测errno的值时,就很有可能发生冲突.因此,在进入信号处理函数之前要保存errno 的值,最后再回复errno的值.
-
利用孤儿进程的机制:在创建的子进程中在调用fork函数,创建一个孙子进程,然后子进程终止,那么孙子进程就会被init进程收养.再当孙子进程结束时,回收进程资源的工作就交由了init进程去做.当然,有人会问,那么子进程结束后它的进程资源谁去回收?我说,那当然是父进程回收.这个时候能用父进程去回收子进程资源,是因为这是父子进程已经不在时异步的关系了.换句直白点的话说就是,父进程知道子进程在不久的将来一定会结束.回头想想,安插信号的办法的本质,不就是通过信号捕捉,确定了子进程会在不久的将来会结束吗?即在调用wait和外套pid时,不会阻塞等待很长时间就能返回,不会影响父进程自身的工作.
注意:安插信号和利用孤儿进程的机制来避免僵尸进程的办法,不仅仅能避免僵尸进程,还能不影响父进程本身的工作任务,这一点是非常有用的.
本文永久更新链接地址:
脚本之家PHP培训学院每天发布《LINUX学习:进程管理之fork函数》等实战技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培养人才。