多进程编程总结

时间:2024-04-07 13:18:31

1>进程的创建:

pid_t fork(void);

该函数每次调用都会返回两次,在父进程中返回子进程ID,在子进程中返回0,所以当fork()==0的时候就是子进程了,失败就返回-1


2>子进程复制了父进程哪些数据(读时共享,写时复制)

    1.进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs))

    2.环境(environment)

    3.内存四区(堆区new,栈区:局部非static变量,代码区:text,全局区:全局变量,static)

    4.打开的文件描述符

    --------------------------------------------------------------------------------------------------

    这里穿插个讨论:

    1.文件描述符怎么继承:

    多进程编程总结

    由上图可知:子进程不仅copy了父进程的进程表中的文件表象(文件表肯定不可能是读时共享的),文件表也是拷贝(读时可能共享,也可能早就复制了,这里不纠结,因为导致的结果是一样的。我们重点是关注偏移量也被拷贝了,父子进程同时写一个文件就会导致交错)。

    -------------------------------------------------------------------------------------------------

    6.执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描 述符设置,POSIX.1
要求所有目录流都必须在exec函数调用时关闭。更详细说明, 参见《UNIX环境高级编程》 W. R. Stevens, 1993, 

尤晋元等译(以下简称《高级编程》), 3.13节和8.9节)

    7.信号(signal)控制设定(信号的处理函数。但是如果调用了exec之后,信号的处理函数回归到默认

    8.nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级, 数值越小,优先级越高)

    9.进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,

根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)

    10.进程组号

    11.对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期 (session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见《高级编程》 9.5节)

    12.当前工作目录

    13.根目录

    14.文件方式创建屏蔽字(file mode creation mask (umask))

    15.资源限制

    16.控制终端

3>子进程独有的:

    1.进程号

    2.父进程号

    3.自己打开的文件描述符(目录当然也算文件描述符)

    4.子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks) (译者注:锁定内存指被锁定的虚拟内存页,锁定后, 不允许内核将其在必要时换出(page out), 详细说明参见《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2节)

    5.在tms结构中的系统时间(译者注:tms结构可由times函数获得, 它保存四个数据用于记录进程使用*处理器 

(CPU:Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)

    6.资源使用(resource utilizations)设定为0

    7.阻塞信号集初始化为空集(译者注:原文此处不明确, 译文根据fork函数手册页稍做修改)

    8.不继承由timer_create函数创建的计时器

    9.不继承异步输入和输出


4>子进程的应用:

当我们需要在子进程中执行其他程序,也就是替换当前进程程序的时候这就需要使用exec系列函数,这个我不想多讲了。


5>僵尸进程:

对于多进程程序而言,父进程一般需要跟踪子进程的退出状态,因此,当子进程结束运行时,内核不会立即释放该进程的进程表表项,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。如果父进程没有回收子进程的资源的话,其实就类似内存泄漏。此时子进程就变成了僵尸进程。所以我们一般要父进程wait和waitpid回收子进程,注意:这里没有类似多线程那种的detach(游离态)。

6->孤儿进程

父进程结束或者异常中止而子进程继续运行,那么此时子进程的PPID将被操作系统设置为1,也就是init接管了他,并等待他结束,那么在父进程结束,子进程退出前,改进程就是孤儿进程。这种状态没有什么危害性,比如说守护进程,我们还故意把他父进程杀死,托管给init。


7>僵尸进程的解决方案:

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int* stat_loc);

pid_t waipid(pid_t pid,int *stat_loc,int options);


wait函数作用:wait函数将阻塞进程,直到该进程的某个子进程结束运行为止。返回结束运行的子进程的PID,并将该子进程的退出状态信息存储于stat_loc参数指向的内存中。


waitpid函数的作用:waitpid只等待由pid参数指定的子进程,如果pid取值为-1,那么它和wait函数相同,等待任意一个子进程结束,stat_loc参数的含义和wait函数的stat_loc相同,options参数可以控制waitpid函数的行为。该参数最常用的取值是WNOHANG,作用是:当pid指定的目标子进程还没有结束或者意外中止,那么waitpid立即返回0,如果目标子进程确实正常退出了,则waitpid返回该子进程的PID,waitpid调用失败时返回-1,并设置errno。当然这里pid还有很多选项,这里因为是总结就不做赘述了。


信号SIGCHLD表明的是子进程的退出, 父进程可以通过捕获SIGCHLD信号在信号处理函数中调用waitpid彻底结束一个子进程。