进程间通信:
基本概念:
什么是进程间通信:
是指两个或者多个进程之间交互数据的过程,因为进程之间是相互独立的,为了能够让多个进程协同工作必须交互数据
进程间通信的分类:
简单的进程间通信:信号、文件、环境变量、命令行参数
传统的进程间通信:管道文件
XSI进程间通信: 共享内存、消息队列、信号量
网络进程间通信: Socket套接字
传统进程间通信:
管道是UNIX最古老的进程间通信方式,古老意味着所有系统都支持,早期的管道都是半双工,现在有些系统的管道是全双工(但是也要假定管道是半双工来实现)
管道就是一种特殊的文件,它的数据在文件中是流动的,读取之后就会消失,如果文件中没有数据可以读取则会阻塞
有名管道: 基于有文件名的管道文件的通信
编程模型
进程A 进程B
创建管道 ...
打开管道 打开管道
写数据 读数据
关闭管道 关闭管道
删除管道 ...
创建管道文件:
1、命令 mkfifo file
2、函数:
int mkfifo(const char *pathname, mode_t mode);
功能:创建管道文件
pathname:管道文件路径
mode:管道文件权限
3、标准库函数
FILE* popen(const char *command, const char *type);
功能:打开或创建管道文件
command:可执行文件名
type:
r 文件指针链接到可执行文件的标准输出
w 文件指针链接到可执行文件的标准输入
int pclose(FILE *stream);
功能:关闭管道文件
注意:关闭管道文件的专用函数,一定不能与fclose混用
匿名管道:
注意:只适合通过fork函数创建的父子进程之间使用
int pipe(int pipefd[2]);
功能:创建一个匿名管道文件,获取管道文件的读权限fd、写权限fd
pipefd:用于存储管道文件的fd的数组
pipefd[0]:用于读
pipefd[1]:用于写
编程模型
父进程 子进程
创建获取一对fd
创建子进程 拷贝一对fd
关闭读 关闭写
写数据 读数据
关闭写 关闭读
XSI进程间通信:
X/open 公司执行的用于进程间通信的系统接口
XSI进程间通信都需要借助系统内核,需要创建内核对象,内核对象以整数形式返回给用户,相当于文件描述符,也叫做IPC标识符
文件的创建、打开需要文件名,IPC内核对象的创建也需要借助IPC键值(整数),必须要确保IPC键值独一无二
key_t ftok(const char *pathname, int proj_id);
功能:计算出一个独一无二的IPC键值
pathname:项目路径,不是依赖字符计算的,而是依赖路径的位置,如果提供的是假的路径,可能会计算出相同的IPC键值
proj_id:项目编号
返回值:计算出来的
共享内存:
基本特点:
两个或者多个进程之间共享一块由内核负责管理维护的内存,该内存可以与进程的虚拟内存空间进行映射
优点:不需要复制信息,是最快的一种IPC机制
缺点:需要考虑同步访问问题
int shmget(key_t key, size_t size, int shmflg);
功能:创建\获取共享内存
key:由进程提供的独一无二的IPC键值
size:共享内存的大小,获取共享内存时此参数无意义,一般设置为0
shmflg:
IPC_CREAT 创建共享内存
IPC_EXCL 共享内存已经存在时返回错误
获取时赋值 0
mode_flags:创建共享内存时需要提供权限
返回值:IPC标识符,错误返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:虚拟内存与共享内存进行映射
shmid:IPC标识符,shmget的返回值
shmaddr:想要映射的虚拟内存地址,给NULL系统会自动操作
shmflg:
SHM_RDONLY:以只读方式访问共享内存
返回值:与共享内存映射后的虚拟内存的首地址,失败返回 (void *)-1
int shmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射过的虚拟内存首地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除/控制共享内存
shmid:IPC标识符
cmd:
IPC_STAT 获取共享内存属性 则buf为输出型参数
IPC_SET 设置共享内存属性 则buf为输入型参数
IPC_RMID 删除共享内存 NULL
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; // 创建共享内存的PIC兼职
uid_t uid; //当前使用共享内存的用户ID
gid_t gid; //当前使用共享内存的用户组ID
uid_t cuid; //创建共享内存的用户ID
gid_t cgid; //创建共享内存的用户组ID
unsigned short mode; //共享内存权限
unsigned short __seq; //共享内存的序列号
};
编程模型
进程A 进程B
创建共享内存 获取共享内存
映射共享内存 映射共享内存
写数据后通知读 接受读通知后读数据
接受读通知后读数据 写数据后通知读
取消映射 取消映射
删除共享内存
消息队列:
基本特点:是由内核管理维护的数据链表,通过消息类型来收发数据
int msgget(key_t key, int msgflg);
功能:创建/获取消息队列
key:IPC键值
msgflg:
IPC_CREAT 创建消息队列
IPC_EXCL 如果消息队列已存在则会返回错误
如果获取消息队列 直接给0
如果是创建消息队列,记得提供权限mode
返回值:成功返回IPC标识符,错误返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgfl);
功能:向消息队列发送数据
msqid:IPC标识符
msgp:要发送的消息的首地址
struct msgbuf {
long mtype; // 消息类型
char mtext[n]; // 数据
};
msgsz: 数据的字节数,不包含消息类型
msgfl:
阻塞一般写0
IPC_NOWAIT 当消息队列满时,不等待立即返回
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中读取数据
msqid:IPC标识符
msgp:存储数据的首地址
msgsz:结构体中数据的最大字节数
msgtyp:消息类型
0 读取消息队列第一条消息
>0 读取消息队列中消息类型等于msgtyp的消息
<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:
IPC_STAT 获取消息队列的属性
IPC_SET 设置消息队列的属性
IPC_RMID 删除消息队列
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
3、当资源使用完毕时,变量的值+1,正在休眠的进程可以被唤醒了
int semget(key_t key, int nsems, int semflg);
功能:创建/获取信号量
key:IPC键值
nsems:信号量的数量 一般写1即可
semflg:
IPC_CREAT 创建
IPC_EXCL 存在则返回-1
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
0 等待信号量的值为0
-1 信号量-1,如果不能-1,则阻塞
short sem_flg;
IPC_NOWAIT 不阻塞
SEM_UNDO 如果进程终止还没有还原信号量,系统将自动还原
}
nsops:表示sops指针指向多少个结构体,一般写1
int semctl(int semid, int semnum, int cmd, ...);
功能:删除/控制信号量
semid:IPC标识符
semnum:信号量的下标
cmd:
IPC_STAT 获取信号量属性
IPC_SET 设置信号量属性
IPC_RMID 删除信号量
GETALL 获取所有信号量的值
GETVAL 获取某个信号量的值
GETNCNT 获取所有等待减信号的进程数量
SETALL 设置所有信号量的值
SETVAL 设置某个信号量的值
union semun {
int val; // 用于设置信号量的值
struct semid_ds *buf; // 信号量的属性
unsigned short *array; // 批量获取/设置信号量的值
};