一、基本概念
进程间通信:
Inter-Process Communication,简称IPC,指的是两个以上的进程之间进行数据交换的过程。
进程之间为什么需要通信:当解决一个庞大复杂的问题时需要多进程合作完成,而进程之间是相互独立的(fork创建出的进程子进程拷贝父进程,vfork是子进程替换父进程),进程之间想要协同配合就需要进行通信。
进程间通信技术的分类:
简单的进程间通信:
命令行参数、环境变量:通过vfork创建子进程时可以附加一些参数和环境变量。
信号:只能传递简单的数据
文件:不能同步、不及时、可能有错漏。
传统的进程间通信:管道技术,但速度较慢,同一时刻只能单向传递。
XSI进程间通信:X/Open System Interface
共享内存、消息队列、信号量
网络进程间通信:
本地套接字、网络套接字
二、管道通信
管道是UNIX系统最古老的一种进程间通信方式,历史上的管道是半双工(只能单向传递数据),现在的基本上都是全双工(可以双向通信,但同一时刻只能一方传递另一方接收)。
有名管道:基本文件名(管道文件)的通信。
创建管道文件:
命令:mkfifo 文件
函数:int mkfifo(const char *pathname, mode_t mode);
功能:创建管道文件
pathname:路径
mode:权限
返回值:成功返回0,失败返回-1。
编程模型
进程A 进程B
创建管道文件
打开管道文件 打开管道文件
写入数据 读取数据
关闭管道 关闭管道
删除管道
如果A写完数据等着接收B的数据,但在B把数据接收到之间,A就可能已经把数据拿走了,B就读不到了,因此要想实现双向通信需要两个管道文件。
匿名管道:调用系统api,系统会自己创建一个管道,并返回两个文件描述符。
int pipe(int pipefd[2]);
功能:获取两个管道文件的文件描述符。
注意:只能fork创建进行父子进程间的通信。
pipefd[0]:读取数据
pipefd[1]:写入数据
三、XSI进程间通信
1、IPC标识
1)、内核会为每个进程间通信维护一个结构体形式的IPC对象。
2)、每一个IPC对象通过一个非负整数(IPC标识)来引用。
3)、与文件描述符不同的是,IPC标识在使用时持续加1,达到最大值时就变成0。
2、IPC键值
1)、IPC键值是创建IPC对象时提供的凭证(类似创建文件时所提供的文件名)。
2)、两个进程之间如果都提供同一个IPC键值就能获取同一个IPC对象。
3)、无论什么时候,创建、获取IPC对象都必须提供IPC键值。
4)、IPC键值的类型是 unsigned int 定义在sys/types.h文件中,key_t。
5)、创建、获取IPC对象时,不建议填写,一般使用函数计算。
key_t ftok(const char *pathname, int proj_id);
功能:让系统计算出一个独立无二的IPC键值
pathname:项目路径
proj_id:项目编号,取值范围 0~255
注意:计算键值依靠的不是字符本身,而是路径。
四、XSI进程间通信之共享内存
1、基本特点
1)、多个进程之间共享一块由于系统负责维护的内存区域,这堆内存可以映射到进程的堆地址、栈地址。
2)、不需要复制数据,就可以通信,因此它是一种最快的进程间通信方式。
3)、缺点是需要解决同步问题,需要其它技术配合。
2、操作函数
int shmget(key_t key, size_t size, int shmflg);
功能:创建/获取一块共享内存
key:IPC键
size:共享内存的大小
shmflg:
IPC_CREAT:创建共享内存,不存在就创建
IPC_EXCL:存在就不创建,并出错
返回值:IPC标识
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存
shmid:共享内存对象的IPC标识
shmaddr:进程提供的映射地址
shmflg:
0 以读写权限映射
SHM_RND 当shmaddr为NULL时,系统自动分配映射地址。
SHM_RDONLY 以只读权限映射。
返回值:映射成功后的地址。
int shmdt(const void *shmaddr);
功能:取消共享内存映射
shmaddr:映射过的共享内存首地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制共享内存,常用于销毁。
shmid:共享内存对象的IPC标识
cmd:
IPC_STAT:获取共享内存属性
IPC_SET:设置共享内存属性
IPC_RMID:删除共享内存
buf:共享内存属性结构体
3、编程模型:
进程A 进程B
创建共享内存 获取共享内存
映射 映射
取消映射 取消映射
删除
四、XSI进程间通信之消息队列
1、基本特点
1)、消息队列是由内核维护的一个双向链表,负责存储和管理进程间通信的数据。
2)、消息队列发送的数据可以排队、消息长度不固定、并且可以按类型发送、接收消息。
2、操作函数
int msgget(key_t key, int msgflg);
功能:创建/获取消息队列
key:IPC键值
msgflg:
IPC_CREAT:不存在就创建,存在就获取。
IPC_EXCL:如果存在,就出错返回负1。
返回值:消息队列IPC标识。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送数据
msqid:消息队列IPC标识,msgget的返回值
msgp:要发送的数据的首地址
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgsz:消息长度,不包括类型
msgflg:
0:当消息发送成功时才返回。
IPC_NOWAIT:当消息队列满时,不等待。
返回值:成功返回0,失败返回-1。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列接收数据
msqid:消息队列IPC标识,msgget的返回值
msgp:要数据存放的缓冲区首地址
msgsz:缓冲区的大小
msgtyp:消息类型
msgflg:
0:消息不存在时等待,直到接收成功为止。
IPC_NOWAI:消息不存在时,不等待,立即返回。
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
功能:控制/销毁消息队列
功能:从消息队列接收数据
cmd:
IPC_RMID 删除消息队列
IPC_STAT 获取消息队列属性
IPC_SET 设置消息队列属性
buf:消息队列属性结构体
3、编程模型
进程A 进程B
创建消息队列 获取消息队列
向消息队列发送数据 从消息队列获取数据
从消息队列获取数据 向消息队列发送数据
销毁消息队列
四、XSI进程间通信之信号量
1、基本特点:内核维护一个整型变量,用于限制多个进程对有限资源的访问。
2、多个进程获取有限资源的操作方式:
1)测试控制该资源的信号量(整型变量)。
2)如果信号量的值大于0,表示该资源还可以获取,将信号进行减1操作。
3)如果信号量的值等于0,表示该资源已经耗尽,进程自动进入休眠,直到信号量大于0,进程则被唤醒。
4)当进程使用完资源时则对信号时执行加1操作,而等待信号量的其它进程将被唤醒。
2、常用函数
int semget(key_t key, int nsems, int semflg);
功能:创建/获取信号量
key:IPC键值
nsems:创建信号量的数量(类似数组的长度)。
semflg:
IPC_CREAT 创建/获取信号量
IPC_EXCL 如果信号量存在则出错
mode 权限
返回值:信号量标识
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:操作信号量,对信号量执行加n或减n操作
semid:信号量标识,semget的返回值
sops:信号量操作结构体
unsigned short sem_num; 信号量的下标
short sem_op; 对信号时的操作
short sem_flg; 对信号量操作是否等待
SEM_UNDO 等待
IPC_NOWAIT 不等待
nsops:如果值是0,则对单个的信号进行操作,非零则对所有信号量集体操作。
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout);
功能:有倒计时的对信号时进行操作,如果时间到了还不能对信号量进行操作,则立即返回。
timeout:时间结构体
__time_t tv_sec; 等待的秒数
long int tv_nsec; 等待的纳秒数
int semctl(int semid, int semnum, int cmd, ...);
功能:对信号量进行操作(销毁、设置属性、获取属性、初始化信号量、获取信号量的值)
semnum:信号时的编号(下标)
cmd:
IPC_STAT 获取信号量的属性
IPC_SET 设置信号量的属性
IPC_RMID 删除信号量
SEM_INFO 获取信号时的信息
SET_STAT 获取某个信号量的属性
GETALL 获取所有信号量的值
GETNCNT 获取信号量的数量
GETVAL 获取某个信号量的值
SETVAL 设置某个信号量的值
SETALL 设置所有信号量的值
3、编程模型
进程A 进程B
创建信号量 获取信号量
初始化信号量的值 获取信号量的值
-1 获取资源 -1 如果不能减则阻塞
… …
+1 归还资源 +1 归还资源
销毁信号量