进程间通信
基本概念:
什么是进程间通信:
是指两个或多个进程之间交互数据的过程,是因为进程之间是相互独立的,为了协同工作的需要必须交互数据
进程间通信的分类:
简单的进程间通信:信号、文件、环境变量、命令行参数
传统的进程间通信:管道文件
XSI进程间通信: 共享内存、消息队列、信号量
网络进程间通信: 套接字Socket
传统的进程通信-管道(FIFO):
管道是UNIX系统中最古老的进程通信方式,古老意味着所有系统都支持,早期的管道文件都是半双工,现有的一些系统的管道是全双工的
管道是一种特殊的文件,它的数据在文件中是流动的,读取之后就会消失,若文件中没有任何数据,读取时会阻塞
有名管道:基于有文件名的管道文件的通信
编程模型:
进程A 进程B
创建管道
打开管道 打开管道
写数据 读数据
关闭管道 关闭管道
删除管道
创建管道文件:
1.mkfifo filename
2.函数
int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道文件
pathname:管道文件路径
mode:管道文件权限
匿名管道:
注意:匿名管道只适合通过fork创建的的父子进程之间使用
int pipe(int pipefd[2]);
功能:创建一个匿名管道文件,返回管道文件的读权限fd和写权限fd
pipefd:返回用于存储管道文件读写fd的数组,输出型参数
pipefd[0] 用于读
pipefd[1] 用于写
编程模型:
父进程 子进程
获取一对fd 共享了一对fd
关闭读 关闭写
写数据 读数据
关闭写 关闭写
XSI进程间通信:
X/Open公司指定用于进程之间通信的系统接口
XSI进程间通信技术都需要借助系统内核,需要创建内核对象,内核对象会以整数形式返回给用户态,相当于文件描述符,也叫做IPC标识符
文件的创建、打开需要借助文件名,同样的,IPC内核对象创建需要借助IPC键值(整数),必须要确保IPC键值是独一无二的
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:计算出一个独一无二的IPC键值
pathname:项目路径,不是依靠字符串计算,而是依靠路径的位置以及项目编号计算的,所以不能提供加路径,否则可能产生同样的IPC键值
proj_id:项目编号
返回值:计算出来的IPC值
共享内存:
基本特点:
两个或多个进程之间共享一块内存,该内存可与多个进程的虚拟内存进行映射
优点:不需要复制信息,是最快的IPC通信机制
缺点:需要考虑同步访问的问题,一般需要借助信号 来解决
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:创建、获取共享内存
key:由进程提供的一个独一无二的IPC键值
size:共享内存的大小,获取时,该参数无效,一般给0
shmflag:
IPC_CREAT 创建共享内存
IPC_EXCL 共享内存如果已存在,则返回失败
获取时直接给0
mode_flags 创建共享内存时需要提供权限, IPC_CREAT|0644
返回值:IPC标识符,错误返回-1
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:让虚拟内存与共享内存隐射
shmid:IPC标识符
shmaddr:想要映射的虚拟内存首地址,为NULL时系统回自动操作
shmflag:
SHM_RDONLY: 以只读方式映射共享内存
SHM_RND: 只有当shmaddr参数不为NULL时有效,表示对shaddr参数向下取内存页的整数倍,作为映射地址
如果都不需要,则写0
返回值:与共享内存映射后的虚拟内存首地址,失败返回(void *)-1或者 0xFFFFFFFF
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射过的虚拟内存数首地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能;删除、控制共享内存
shmid:IPC标识符
cmd:
IPC_SET 设置共享内存属性,buf为输入型参数
IPC_RMID 删除共享内存,buf为NULL
SHM_STAT 获取共享内存属性,buf为输出型参数
buf:
struct shmid_ds {
struct ipc_perm shm_perm; //所有者的相关信息
size_t shm_segsz; //共享内存的字节数
time_t shm_atime; //最后映射的时间
time_t shm_dtime; //最后取消映射的时间
time_t shm_ctime; //最后改变的时间
pid_t shm_cpid; //创建者的进程号
pid_t shm_lpid; //最后映射、取消映射者的进程号
shmatt_t shm_nattch; //当前映射的次数
...
};
struct ipc_perm {
key_t __key; //创建共享内存的IPC键值
uid_t uid; //当前使用共享内存的用户ID
gid_t gid; //当前使用共享内存的组ID
uid_t cuid; //创建共享内存的用户ID
gid_t cgid; //创建共享内存的用户组ID
unsigned short mode; //共享内存的权限
unsigned short __seq; //共享内存的序列号
};
编程模型:
进程A 进程B
创建共享内存 获取共享内存
映射共享内存 映射共享内存
写数据并通知其他进程 接收到通知后读数据
接收到通知后读数据 写数据并通知其他进程
取消映射 取消映射
删除共享内存
消息队列:
基本特点:
由内核维护管理的数据链表,是通过消息类型收发数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:创建、获取消息队列
key:IPC键值
msgflg:
IPC_CREAT 创建消息队列
IPC_EXCL 如果消息队列已存在,则返回错误
mode: 当创建消息队列时需要提供权限
返回值:成功返回IPC标识符,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送数据
msqid:IPC标识符
msgp:要发送的消息首地址
struct msgbuf {
long mtype; /*消息类型 */
char mtext[n]; /* 数据 */
};
msgsz:数据的字节数,是不包含信息类型的
msgflg:
阻塞一般写0
IPC_NOWAIT 当消息队列满是,不等待立即返回
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中读取数据
msqid:IPC标识符
msgp:存储数据结构体首地址
msgsz:数据结构体的字节数
msgtyp:要读取的消息的类型,按照类型的数值来读取消息,而不是按照顺序
>0 读取消息队列中第一条等于msgtyp的消息
=0 读取消息队列中的第一条消息
<0 读取消息类型小于abs(msgtyp)的消息,如果有多个满足,则读取值最小的
msgflg:
IPC_NOWAIT 消息类型不符合时不阻塞,立即返回
MSG_EXCEPT 如果msgtyp>0,则读取第一个消息类型不是msgtyp的消息
MSG_NOERROR 如果不包含此标记,当消息的实际长度>msgsz,则会返回错误,并且读取失败;如果包含此标记,则最多读取msgsz个字节,确保一定成功
返回值:成功读取到的字节数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除、控制消息队列
msqid:IPC标识符
cmd:
SHM_STAT 获取消息队列属性,buf为输出型参数
IPC_SET 设置消息队列属性,buf为输入型参数
IPC_RMID 删除消息队列,buf为NULL
buf:
struct msqid_ds {
struct ipc_perm msg_perm; // 属主信息
time_t msg_stime; // 最后发送时间
time_t msg_rtime; // 最后接收时间
time_t msg_ctime; // 最后修改时间
unsigned long __msg_cbytes; // 当前消息队列字节数
msgqnum_t msg_qnum; // 当前的消息数量
msglen_t msg_qbytes; // 消息的最大字节数
pid_t msg_lspid; // 最后发送者的PID
pid_t msg_lrpid; // 最后接收者的PID
};
编程模型:
进程A 进程B
创建消息队列 获取消息队列
发送消息 接收消息
接收消息 发送消息
删除消息队列
信号量:
基础概念:
由内核维护并共享的一个“全局变量”,用于记录共享资源的数量 ,限制进程对共享资源的访问
信号量是一种操作锁,本身不具备数据交换功能 ,而是通过控制其他的通信资源来实现进程间的协调通信
1.若信号量的值大于0,说明有可以使用的资源,需要将信号量-1再使用
2.若信号量的资源等于0,说明没有资源可以使用,此时进程会进入休眠,直到信号量的值大于0,程序会自动唤醒,再重复步骤1、2
3.当资源使用完毕,需要将信号量的值+1,唤醒其他正在休眠的进程
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建、获取一个信号量
key:IPC键值
nsems:信号量的数量,一般给1即可
msgflg:
IPC_CREAT 创建信号量
IPC_EXCL 如果信号量已存在,则返回错误
mode: 当创建信号量时需要提供权限
返回值:成功返回IPC标识符,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号量进行加减操作
semid:IPC标识符,代表了要操作的信号量
sops:
struct sembuf
{
unsigned short sem_num; /* 信号量下标 */
short sem_op;
/* 1 信号量+1
-1 若信号量值为0,不能-1,则阻塞
0 等待信号量的值为0 */
short sem_flg;
/* IPC_NOWAIT 不阻塞
SEM_UNDO 如果进程终止没有返还信号量,系统会自动返回 */
}
int semctl(int semid, int semnum, int cmd, ...);
功能:删除、控制信号量
semid:IPC标识符,代表了要操作的信号量
semnum:第几个信号量,下标从0开始
cmd:
SHM_STAT 获取信号量属性,buf为输出型参数
IPC_SET 设置信号量属性,buf为输入型参数
IPC_RMID 删除信号量,buf为NULL
GETALL 获取所有信号量的值
GETNCNT 获取等待减信号量的进程数量(休眠进程)
GETVAL 通过返回值获取某个信号量的值
GETZCNT 获取等待信号量为0的进程数量
SETALL 设置所有的信号量的值
SETVAL 设置某个信号量的值