一 概述
本文主要叙述进程生命周期的控制,进程间通信的方式,进程间交换大量数据的方式,多进程管理,进程与硬件核心绑定等方面来叙述一些多进程编程的技巧及常规使用
二 进程生命周期的控制
1.进程的创建
#include <unistd.h>
pid_t fork(void);
pit_t vfork();
主进程调用该接口创建子进程后,在主进程内,该接口返回值为子进程的进程号,在子进程内,该函数的返回值为0
fork和vfork的区别:
使用vfork创建的子进程可以共享父进程的内存,因此在子进程调用exec()或_exit()之前,将暂停父进程的执行
此处需要注意fork之后,父进程和子进程的存在竞争情况,无法确定谁先访问CPU,因此需要通过其他同步互斥手段来避免这一情况带来的恶性影响;
2.进程的终止
#include <unistd.h>
void _exit(int status);
void exit(int status);
一般不直接调用_exit(),而是调用exit();
调用exit时,会执行以下动作:
1.调用退出处理程序(通过atxeit()和on_exit()注册的函数),执行顺序和注册顺序相反
2.刷新stdio流缓冲区
3.执行_exit()
#include <stdlib.h>
int atexit (void (*func)(void));
int on_exit(void (*func)(int ,void*), void *arg);
on_exit注册的回调函数原型如下:
void fun(int status, void *arg);
此处arg由注册时的on_exit函数的arg传入
3.进程的回收
#include <sys/wait.h> //主进程用该函数来等待任意一个子进程的的结束,即回收已经退出的进程 pid_t wait(int *status); //等待特定的子进程结束
pid_t waitpid(pid_t pid, int *status, int options);
pint_t waittid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
在子进程结束后,将会变成僵尸进程,因此必须在父进程内对子进程进行回收操作(创建daemon进程时例外);
关于status:
-
当进程正常终止时,status的高8位为退出状态,低八位为子进程返回给父进程的一个值
-
当进程为信号所杀时,高八位未使用,第八位为内核转储(core dumped)标志,低七位为终止信号
-
当进程为信号所停止时,高八位为终止信号,低八位为0x7f
-
当进程通过信号恢复运行时,status为0xffff
如何根据status来判断当前进程的状态,Linux提供了一些宏用于执行该判断操作
-
WIFEXITED(status) 判断子进程是否正常结束,正常结束则返回真
-
WIFSIGNED(status) 判断子进程是否是被信号杀死,若进程被信号杀死,则返回真
-
WIFSTOPED(status) 当进程因为信号而终止时,返回真
-
WIFCONTINUED(status) 当进程因信号而恢复执行时返回真
同时父进程也可以监控SIGCHLD信号,来对子进程的回收进行处理
三 进程间通信方式
四 进程间交换大量数据的方式概述
若进程间需要交换大量数据,如果考虑到性能方面的原因,则首选共享内存作为数据交换的载体,但只使用共享内存是不够的,下面从几个方面来分析该情景
1.确定共享内存的数据存储格式
通常进程减使用共享内存传输数据时,在同一时刻,共享内存中存在这多个数据对象,因此需要对每个数据对象加一个数据标签头,按照常规的TLV格式,可以对数据头加一个时间戳/序号等表示数据对象的顺序,然后用一个成员来存储数据的长度。共享内存存储数据时的示意图如下:对与 “T” 的确认,需要根据数据的使用情况来决定如何定义该值;
2.数据如何存储/读取数据
一般而言对于共享内存而言,是循环存储,即先放入的数据先取出,用指针来表示当前共享内存内未使用的数据区域的起始位置和结束位置,存储时,从结束位置往后存储,
示意图如下:
P1指向未使用数据的起始位置,P2指向未使用数据的结束位置,
读取数据时从P1的位置先读取每块数据的T和L,再去读L长度的数据体,同时P1需要向上面示意图的右侧移动,指向待读取的下一个数据块。读取数据时需要考虑当前共享内存剩下的空间是否大于需要读取的数据长度,若剩余空间大于数据长度,则直接读取;否则读取共享内存剩余的长度 L2后,再从共享内存的起始位置,读取(L-L2)长度的数据。
存储数据时起始位置从P2开始,同样需要考虑共享内存剩下的空间是否能够完成本次存储,如果不能则需要在存储完共享内存剩下的空间后,从共享内存的起始位置开始存储。
以上是读取数据按照存储顺序时的做法,同时,在一些场景夏,也需要考虑读取数据的顺序与存储数据顺序不同的情况:
此时,一般时先读取数据块的T来决定是否读取当前的数据体的,同样从示意图P1处读取第一个数据块的T和L,若不符合要求,则从P1位置向由便宜第一个数据块的长度后,读取第二个数据块的T和L,再次判断,直至找到目标数据块,找到目标数据块后,记录当前取用数据块的起始位置P3,结束位置P4,并在数据取用完成后,将P1到P3的数据向右移动(P4-P3)的长度,同时更新P1的值,即完成了本次数据的读取。
存储数据的方式未发生改变。
3.共享内存的管理
需要管理未使用数据的起始,结束位置,用以标明数据的位置;其次需要采用互斥手段,确保在同一时刻不会由两个对像同时取读/写操作共享内存对象;
五 多进程任务管理
在使用多进程任务管理时,首先创建一个管理进程,用于对所有功能进程进行管理和生命周期的管控,管理进程主要做以下事情
1.控制/监控功能进程的状态
监控进程起来后,创建功能进程,并实时监控功能进程的生存状态,(可以通过信号,心跳等手段监测功能进程的状态);并根据具体情况对功能进程的生命周期做出控制。
2.提供通信/数据交互渠道
作为通知/消息中转中心,为功能进程提供通知分发(接收来自功能进程的通知,并分发给部分/全部进程);
提供数据交互相关功能,负责简历两个进程进行数据交互的通道(例如,两个进程若使用socket交互数据,则管理进程负责两个进程地址的通知,确保socket建立后能够正常交互)
3.资源分配回收
负责分配公共资源,当功能进程需要使用公共资源时,向管理进程申请,并根据申请结果执行后续操作。
负责公共资源的回收,当某个进程超时占有公共资源时,进行强制回收;当某个进程消亡时,回收该进程占用的资源。
4.进程优先级的调度
根据业务逻辑,对功能进程进行任务任务调度,