Day8 进程间通信
基本概念
进程间的通信(IPC)InterProcess Communication
两个或多个进程之间的交换数据的过程
当多个进程协同工作高效率完成任务时,因为每个进程都是一个独立的个体(资源单位),进程之间就需要通信
进程之间通信方式
- 简单进程通信:命令行参数,环境变量表,信号,文件
- 传统进程通信:管道
- XSI进程间通信:共享内存,消息队列,信号量
- 网络进程通信:socket
传统的进程间通信-管道
管道是UNIX系统最古老的进程间通信方式(基本不再使用),历史上的管道通常是半双工的(只允许单向的数据流动),现在的大部分系统都可以全双工,数据可以双向流动
- 有名管道(创建实体文件)
-
命令:mkfifo
-
函数:
int mkfifo(const char *pathname, mode_t mode);
- 创建管道文件
- pathname 文件路径
- mode 权限
- 返回值 0成功 -1失败
- 编程模型
进程A 进程B 创建管道 mkfifo … 打开管道 open 打开管道 读写数据 read/write 读写数据 关闭管道 close 关闭管道 删除管道 unlink …
-
a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/type.h>
#include <fcntl.h>
#include <string.h>
int main(){
//1.创建管道,一般在/temp目录下创建
if(mkdifo("/tmp/fifo",0644)){
perror("mkfifo");
return -1;
}
//2.打开管道
int fd = open("/tmp/fifo",O_WRONLY);//对方不打开管道就不会返回,程序停在这里
if(0 > fd){
perror("open");
return -1;
}
//3.读写数据
char buf[1024] = {};
for(;;){
printf(">");
gets(buf);
write(fd,buf,strlen(buf)+1);
if(0 == strcmp("quit",buf)){
printf("通信完成!\n");
break;
}
}
//4.关闭管道
close(fd);
//5.删除管道
unlink("/tmp/fifo");
}
b.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/type.h>
#include <fcntl.h>
#
int main(){
//1.打开管道
int fd = open("/tmp/fifo",O_RDONLY);
if(0 > fd){
preror("open");
return -1;
}
//2.读写数据
char buf[1024] = {};
for(;;){
read(fd,buf,sizeof(buf));
printf("read:%s\n",buf);
if(0 == strcmp(buf,"quit")){
printf("通信结束\n");
break;
}
}
//3.关闭管道
close(fd);
}
- 无名管道(用于通过fork创建的父子进程之间的通信)
int pipe(int pipefd[2]);
- 创建无名管道
- pipefd 用来存储内核返回的文件描述符
- pipefd[0] 用于读操作
- pipefd[1] 用于写操作
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
int pipefd[2] = {};
//1.打开无名管道
if(pipe(pipefd)){
perror("pipe");
return -1;
}
//2.创建进程
pid_t id = fork();
//3.父进程写
if(id){
//关闭读
close(pipefd[0]);
char buf[1024] = {};
printf("我是父进程%u,我要和子进程%u通信了\n",getpid(),id);
for(;;){
printf(">");
gets(buf);
write(pipefd[1],buf,strlen(buf)+1);
if(0 == strcmp("quit",buf)){
printf("父进程通信结束!");
close(pipefd[1]);
return 0;
}
}
}else{//子进程读
//关闭写
close(pipefd[1]);
char buf[1024] = {};
printf("我是子进程%u,我要和父进程%u通信了\n",getpid(),getppid());
for(;;){
read(piprfd[0],buf,sizeof(buf));
printf("read:%s\n",buf);
if(0 == strcmp("quit",buf)){
printf("子进程通信完成!\n");
close(pipefd[0]);
return 0;
}
}
}
}
练习1 使用有名管道进程通信,管道创建者读,对方写
练习2 使用无名管道进行通信,父进程读,子进程写
XSI进程间通信
X/open组织为UNIX系统设计的一套进程间通信机制,有共享内存,消息队列,信号量
-
IPC标识
- 内核会为每个进程间通信对象维护一个IPC对象(XSI对象)
- 该对象通过一个非负整数来引用(类似于文件描述符)
- 与文件描述符不同的是,每用一个IPC对象,标识符持续+1,达到最大时再从零开始
- IPC标识需要程序员自己创建(类似于创建文件)
-
IPC键值
- 创建IPC标识的依据(类似创建文件的文件名),也是一个非负整数
- 自定义(不建议,可能会冲突)
- 自动生成(项目的路径和项目的编号)
- 注意 项目路径一定要有效路径
key_t ftok(const char *pathname, int proj_id);
-
IPC对象的创建
- IPC_PRIVATE 创建IPC对象时永远创建成功
- IPC_CRETA 对象存在则获取,不存在则创建
- IPC_EXCL 如果对象已经创建,则创建失败
-
IPC对象销毁/控制用到的宏
- IPC_STAT 获取IPC对象的属性
- IPC_SET 设置IPC对象的属性
- IPC_RMID 删除IPC对象
共享内存
内核中开辟的一块由IPC对象管理的内存,进程A和进程B都可以用自己的虚拟地址与它进程映射,这样进程A与进程B就共享同一块内核中的内存
特点
- 不需要复制信息,是最快的一种进程间通信
- 需要考虑同步问题(必须借助其他的机制,如信号)
编程模型
进程A | 进程B | ||
---|---|---|---|
生成IPC键值 | ftok | 生成IPC键值 | ftok |
创建IPC对象(共享内存) | shmget | 创建IPC对象(共享内存) | shmget |
映射共享内存 | shmat | 映射共享内存 | shmat |
使用共享内存 | *ptr | 使用共享内存 | *ptr |
取消映射 | shmdt | 取消映射 | shmdt |
删除共享内存 | shmctl | … |
-
int shmget(key_t key, size_t size, int shmflg);
- 创建/获取共享内存
- key IPC键,由ftok函数生成
- size 共享内存大小,最好是4096的整数倍,获取共享内存时,此值无效
- shmflg
- 0 获取共享内存
- IPC_CREAT 创建
- IPC_EXCL 如果存在则创建失败
- 返回值 成功返回共享内存标识(IPC标识),失败 -1
-
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 映射共享内存
- shmid 共享内存标识符,shmget函数的返回值
- shmaddr 进程提供的虚拟地址,与内核中的内存映射用,也可以是NULL(内核会自动选择一个地址映射)
- shmflg
- 0 自动分配
- SHM_RDONLY 只读权限
- SHM_RND 当shmaddr为空时shmaddr向下取整页
- 返回值 映射成功后的虚拟地址
-
int shmdt(const void *shmaddr);
- 取消虚拟地址与共享内存的映射
- shmaddr倍映射过的虚拟地址
-
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 删除共享内存,获取/设置共享内存的属性
- shmid 共享内存标识符shmget的返回值
- cmd
- IPC_STAT 获取IPC对象的属性
- IPC_SET 设置IPC对象的属性
- IPC_RMID 删除IPC对象
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches 映射次数*/
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
a.c
#include <stdio.h>
#include <unistd,h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys.shm.h>
#include <string.h>
#include <signal.h>
int main(){
//创建IPC键
key_t key = ftok(".",110);
//创建共享内存
int shmid = shmget(key,4096,IPC_CREAT);
if(0 > shmid){
perror("shmget");
retunr -1;
}
//映射
char* str = shmat(shmid,NULL,SHM_RND);
if((void)*-1 == str){
perror("shmat");
return -1;
}
pid_t pid = 0;
printf("请输入要通信的进程号:");
scanf("%u",&pid);
//使用
for(;;){
printf(">");
gets(str);
kill(pid,SIGINT);
if(0 == strcmp("quit",str)){
printf("通信结束\n");
break;
}
}
//取消映射
if(0 > shmdt(str)){
perror("shmdt");
return -1;
}
//删除
if( 0 > shmctl(shmid,IPC_RMID,NULL)){
perror("shmctl");
return -1;
}
}
b.c
#include <stdio.h>
#include <unistd,h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys.shm.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
int main(){
printf("我是进程%u\n",getpid());
//创建IPC键
key_t key = ftok(".",110);
//获取共享内存
int shmid = shmget(key,0,0);
if(0 > shmid){
perror("shmget");
return -1;
}
//映射
char* str = shmat(shmid,NULL,SHM_RND);
if((void*)-1 == str){
perror("shmat");
return -1;
}
//使用
void sigint(int sig){
printf("read:%s\n",str);
if(0 == strcmp("quit",str)){
printf("通信结束\n");
//取消映射
if(0 > shmdt(str)){
perror("shmdt");
exit(0);
}
}
}
signal(SIGINT,sigint);
pause();
}
消息队列
由内核管理的管道,可以按顺序发送消息包(消息类型+消息内容),可以全双工工作
-
int msgget(key_t key, int msgflg);
- 创建/获取消息队列
- key IPC键值,由ftok函数生成
- msgflg
- 0 获取消息队列
- IPC_CREAT 创建队列
- IPC_EXCL 如果存在则创建失败
- 返回值 消息队列标识符
-
int msgsnd(int msqid, const void *msgp, size_t msgs_z, int msgflg);
- 向消息队列发送消息
- msqid 消息队列标识符msgget函数的返回值
- msgp 结构指针
struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1]; /* message data */ };
- 消息的长度,不包括消息类型,
sizeof(msgbuf)-4
- msgflg
- 0 阻塞,当消息队列满时等待
- IPC_NOWAIT 不阻塞,不等待
- 成功0 失败-1
-
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- 从消息队列获取消息
- msqid 消息队列标识符msgget函数的返回值
- msgp 结构指针
- msgsz 要接收消息的长度,可以长一些
- msgtyp 要接收消息的类型
- 0 接收任意类型的消息(第一个)
- > 0 只接收msgtyp类型的消息
- < 0 接收消息队列中小于等于msgtyp绝对值的消息,取最小的哪个
- msgflg
- 0 阻塞,消息队列中是否有对应类型的消息,没有则等待
- 1 不阻塞,消息队列中没有对用类型的消息,则返回
- 当消息类型正确,而消息的实际长度大于msgsz,则不接收消息且返回-1
- MSG_NOERROR 把多余的消息截去,成功接收
- IPC_NOWAIT 如果消息队列没有要接收的数据,则不等待直接返回
- MSG_EXCEPT 接收消息队列中第一个不是msgtyp的消息,需要编译时加-D_GNU_SUORCE 参数
-
int msgctl(int msqid, int cmd, struct msqid_ds *buf));
-
删除消息队列,设置或获取消息队列的属性
-
msqid 消息队列标识符msgget函数的返回值
-
cmd
- IPC_STAT 获取消息队列的属性
- IPC_SET 设置消息队列的属性
- IPC_RMID 删除消息队列
-
buf
struct msqid_ds { struct ipc_perm msg_perm; /* Ownership and permissions */ time_t msg_stime; /* Time of last msgsnd(2) */ time_t msg_rtime; /* Time of last msgrcv(2) */ time_t msg_ctime; /* Time of last change */ unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */ msgqnum_t msg_qnum; /* Current number of messages in queue */ msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */ pid_t msg_lspid; /* PID of last msgsnd(2) */ pid_t msg_lrpid; /* PID of last msgrcv(2) */ };
-
返回值 成功0 失败-1
-
a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sreing.h>
#include <sts/msg.h>
#include "struct.h"
int main(){
//创建消息队列
int msgid = msgget(ftok(".",119),IPC_CREAT|IPC_EXCL|0644);
if(0 > msgid){
perror(msgget);
return -1;
}
Msg msg = {666};
for(;;){
printf(">");
gets(msg.data);
msgsnd(msgid,&msg,sizeof(Msg)-sizeof(msg.type),0)
if(0 == strcmp("quit",msg.data)){
printf("通信结束\n");
break;
}
}
if(0 > msgctl(msgid,IPC_RMID,NULL)){
perror("msgctl");
return -1;
}
Msg msg = {};
for(;;){
msgrcv(msgid,&msg,sizeof(Msg),555,0);
printf("read:%s\n",msg.data);
if(0 == strcmp("quit",msg.data)){
printf("通信结束\n");
break;
}
}
}
b.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sreing.h>
#include <sts/msg.h>
#include "struct.h"
int main(){
//获取消息队列
int msgid = msgget(ftok(".",119),0);
if(0 > msgid){
perror("msgget");
return -1;
}
//
}
struct.h
typedef struct Msg{
long type;
char data[256];
}Msg;
-
msqid 消息队列标识符msgget函数的返回值
-
cmd
- IPC_STAT 获取消息队列的属性
- IPC_SET 设置消息队列的属性
- IPC_RMID 删除消息队列
-
buf
struct msqid_ds { struct ipc_perm msg_perm; /* Ownership and permissions */ time_t msg_stime; /* Time of last msgsnd(2) */ time_t msg_rtime; /* Time of last msgrcv(2) */ time_t msg_ctime; /* Time of last change */ unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */ msgqnum_t msg_qnum; /* Current number of messages in queue */ msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */ pid_t msg_lspid; /* PID of last msgsnd(2) */ pid_t msg_lrpid; /* PID of last msgrcv(2) */ };
-
返回值 成功0 失败-1
a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sreing.h>
#include <sts/msg.h>
#include "struct.h"
int main(){
//创建消息队列
int msgid = msgget(ftok(".",119),IPC_CREAT|IPC_EXCL|0644);
if(0 > msgid){
perror(msgget);
return -1;
}
Msg msg = {666};
for(;;){
printf(">");
gets(msg.data);
msgsnd(msgid,&msg,sizeof(Msg)-sizeof(msg.type),0)
if(0 == strcmp("quit",msg.data)){
printf("通信结束\n");
break;
}
}
if(0 > msgctl(msgid,IPC_RMID,NULL)){
perror("msgctl");
return -1;
}
Msg msg = {};
for(;;){
msgrcv(msgid,&msg,sizeof(Msg),555,0);
printf("read:%s\n",msg.data);
if(0 == strcmp("quit",msg.data)){
printf("通信结束\n");
break;
}
}
}
b.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sreing.h>
#include <sts/msg.h>
#include "struct.h"
int main(){
//获取消息队列
int msgid = msgget(ftok(".",119),0);
if(0 > msgid){
perror("msgget");
return -1;
}
//
}
struct.h
typedef struct Msg{
long type;
char data[256];
}Msg;