进程通信——管道消息队列共享内存信号量
Posted 南笺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程通信——管道消息队列共享内存信号量相关的知识,希望对你有一定的参考价值。
一、进程间通信(IPC)
简单的进程间通信:
命令行:父进程通过exec函数创建子进程时可以附加一些数据。
环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表。
信号:父子进程之间可以根据进程号相互发送信号,进程简单通信。
文件:一个进程向文件中写入数据,另一个进程从文件中读取出来。
命令行、环境变量只能单身传递,信号太过于简单,文件通信不能实时
XSI通信方式:X/open 计算机制造商组织。共享内存、消息队列、信号量
网络进程间通信方式:网络通信就是不同机器的进程间通信方式。
传统的进程间通信方式:管道
二、管道
1、管道是一种古老的通信的方式(基本上不再使用)
2、早期的管道是一种半双工,现在大多数是全双工。
3、有名管道(这种管道是以文件方式存在的)。
mkfifo ()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。
管道通信的编程模式:
进程A 进程B
创建管道mkfifo
打开管道open 打开管道
写/读数据read/write 读/写数据
关闭管道close 关闭管道
4、无名管道:由内核帮助创建,只返回管道的文件描述符,看不到管道文件,这种管道只能用在fork创建的父子进程之间。
pipefd[0] 用来读数据
pipefd[1] 用来写数据
#include <stdio.h> #include <unistd.h> int main() { int pipefd[2] = {}; if(pipe(pipefd) < 0 ) { perror("pipe"); return -1; } if(0 == fork()) { int num = 10; write(pipefd[1],&num,4); close(pipefd[1]); return 0; } int num2; read(pipefd[0],&num2,4); printf("%d\\n",num2); close(pipefd[0]); }
三、XSI IPC进程间通信
XSI通信是靠内核的IPC对象进程通信。每一个IPC对象都有一个IPC标识(类似文件描述符),IPC标识它是一个非的整数。
创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据。
产生键值的方法:
固定的字面值:1980014
使用函数计算:键值=ftok(项目路径,项目id)
使用宏让操作系统随机分配:IPC_PRIVTE,但其必须把获取到IPC对象标识符记录下来,告诉其它进程
XSI可以创建的IPC对象有:共享内存,消息队列,信号量
四、共享内存
1、由内存维护一个共享的内存区域,其它进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存了。
2、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
3、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号。
共享内存的编程模式:
进程之间要约定一个键值
进程A 进程B
创建共享内存 获取共享内存
加载共享内存 加载共享内存
卸载共享内存 卸载共享内存
销毁共享内存
编程相关函数:
功能:创建共享内存
key:键值key,通常由ftok函数获取
size:共享的大小,尽量是4096的倍数(一页=4096byte)
shmflg:设置IPC_CREAT标志,则共享内存存在就打开,不存在则创建。设置IPC_CREAT | IPC_EXCL则不存在创建,存在则返回一个错误。
返回值:IPC对象标识符(类似文件描述符)
功能:加载共享内存(进程的虚拟地址与共享的内存映射)
shmid:shmget的返回值
shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射。
shmflg:
SHM_RDONLY:限制内存的权限为只读
SHM_REMAP:映射已经存的共享内存。
SHM_RND:当shmaddr为空时自动分配
SHMLBA:shmaddr的值不能为空,否则出错
返回值:映射后的虚拟内存地址
功能:卸载共享内存(进程的虚拟地址与共享的内存取消映射关系)
addr参数是以前调用shmat时的返回值
功能:控制/销毁共享内存
cmd:
IPC_STAT:获取共享内存的属性
IPC_SET:设置共享内存的属性
IPC_RMID:删除共享内存
buf:记录共享内存属性的对象
示例:shm_a,shm_b
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <unistd.h> char* buf = NULL; void sigint(int num) { printf("\\r接收到数据:%s\\n",buf); printf(">"); fflush(stdout); } int main() { signal(SIGINT,sigint); key_t key = ftok("shm",6); int pid = 0; printf("我是进程%d\\n",getpid()); printf("与我通信的进程是:"); scanf("%d",&pid); getchar(); // 创建共享内存 int shmid = shmget(key,4096,IPC_CREAT|0744); if(0 > shmid) { perror("shmget"); return -1; } // 加载共享内存 buf = shmat(shmid,NULL,SHM_RND); while(1) { printf(">"); gets(buf); kill(pid,SIGINT); } if(shmdt(buf)) { perror("shmdt"); } }
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <unistd.h> char* buf = NULL; void sigint(int num) { printf("\\r接收到数据:%s\\n",buf); printf(">"); fflush(stdout); } int main() { signal(SIGINT,sigint); key_t key = ftok("shm",6); int pid = 0; printf("我是进程%d\\n",getpid()); printf("与我通信的进程是:"); scanf("%d",&pid); getchar(); // 创建共享内存 int shmid = shmget(key,4096,0); if(0 > shmid) { perror("shmget"); return -1; } // 加载共享内存 buf = shmat(shmid,NULL,SHM_RND); while(1) { printf(">"); gets(buf); kill(pid,SIGINT); } if(shmdt(buf)) { perror("shmdt"); } }
五、消息队列
1、消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表。
2、消息队列可以设定接收特定的消息类型,由此可以处理不同的消息类型的消息
3、消息队列类似于队列,先进先出的排队机制
功能:创建或获取消息队列
msgflg:IPC_CREAT|IPC_EXEC,为0则表示获取消息队列
功能:向消息队列发送消息
msqid:msgget的返回值
msgp:消息(消息类型+消息内容)的首地址,通常为结构体地址
msgsz:消息内存的长度(不包括消息类型)
msgflg:
0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
MSG_NOERROR:当发送消息的实际长比msgsz还要长的话,则按照msgsz长度截取再发送,且不通知发送进程。
功能:从消息队列接收消息
msqid:msgget的返回值
msgp:存储消息的缓冲区
msgsz:要接收的消息长度
msgtyp:消息的的类型(它包含消息的前4个字节)
msgflg:
0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待
IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息
IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃
功能:控制/销毁消息队列
cmd:
IPC_STAT:获取消息队的属性
IPC_SET:设置消息队列的属性
IPC_RMID:删除消息队列
buf:通常填0
示例:
msg_a.c
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> // 定义消息 typedef struct Msg { long type; char buf[255]; }Msg; int main() { // 获取键值 key_t key = ftok(".",6); // 创建消息队列 int msgid = msgget(key,0777|IPC_CREAT); if(0 > msgid) { perror("msgget"); return -1; } while(1) { Msg msg = {}; msg.type = 1; printf(">"); gets(msg.buf); msgsnd(msgid,&msg,sizeof(msg.buf),0); if(\'q\' == msg.buf[0]) break; msgrcv(msgid,&msg,sizeof(msg.buf),2,0); perror("msgrcv"); if(\'q\' == msg.buf[0]) break; printf("接收到:%s\\n",msg.buf); } msgctl(msgid,IPC_RMID,NULL); }
msg_b.c
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> // 定义消息 typedef struct Msg { long type; char buf[255]; }Msg; int main() { key_t key = ftok(".",6); int msgid = msgget(key,0); if(0 > msgid) { perror("msgid"); return -1; } while(1) { Msg msg = {}; msgrcv(msgid,&msg,sizeof(msg.buf),1,0); printf("接收到:%s\\n",msg.buf); if(\'q\' == msg.buf[0]) break; printf(">"); gets(msg.buf); msg.type = 2; msgsnd(msgid,&msg,sizeof(msg.buf),0); if(\'q\' == msg.buf[0]) break; } }
六、信号量
1、信号量是一种特殊的变量,访问具有原子性。
2、我们使用信号量,来解决进程或线程间共享资源引发的同步问题。
3、可以将信号量理解为两个进程共享的一个变量
功能:创建一个新信号量或取得一个已有信号量
key:键值,通常由ftok函数获取
nsems:指定需要的信号量数目,它的值几乎总是1
semflg:IPC_CREAT | IPC_EXCL,为0则表示获取一个已有的信号量
功能:对信号量指定的变量进行操作,是选择发送(+1)还是等待(-1);
sem_id:是由semget返回的信号量标识符;
sops:表示要对信号量进行什么操作;
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0 ,从0开始,表示要操作的信号量是第几个;
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, 对第一个变量选择的信号量进行操作;一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号, 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
nsops:是表示操作的信号量个数。
功能:用来控制或销毁信号量
semid:信号量的标志码(ID),也就是semget()函数的返回值;
semnum:操作信号在信号集中的编号,从0开始;
cmd:命令,表示要进行的操作。
参数cmd中可以使用的命令如下:
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID将信号量集从内存中删除。
GETALL用于读取信号量集中的所有信号量的值。
GETNCNT返回正在等待资源的进程数目。
GETPID返回最后一个执行semop操作的进程的PID。
GETVAL返回信号量集中的一个单个的信号量的值。
GETZCNT返回这在等待完全空闲的资源的进程数目。
SETALL设置信号量集中的所有的信号量的值。
SETVAL设置信号量集中的一个单独的信号量的值。
示例:图书馆借阅书籍小程序
lib.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/types.h> #include <sys/sem.h> #include <signal.h> #include <unistd.h> int semid = 0; void sigint(int signum) { //删除信号量 if(!semctl(semid,0,IPC_RMID)) { printf("图书馆关闭了...\\n"); } } int main() { signal(SIGINT,sigint); key_t key = ftok(".",6); semid = semget(key,1,0644|IPC_CREAT); if(0 > semid) { perror("semget"); return -1; } //设置信号量初始值为10 semctl(semid,0,SETVAL,10); printf("图书馆开放了...\\n"); pause(); }
customer.c
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main() { key_t key = ftok(".",6); int semid = semget(key,0,0); if(semid < 0 ) { perror("semget"); return -1; } struct sembuf buf = {0,-2,0}; if(!semop(semid,&buf,1)) { printf("借到两本书...\\n"); printf("还剩%d\\n",semctl(semid,0,GETVAL)); } getchar(); buf.sem_op = 2; if(!semop(semid,&buf,1)) { printf("还回去两本书...\\n"); printf("还剩%d\\n",semctl(semid,0,GETVAL)); } }
以上是关于进程通信——管道消息队列共享内存信号量的主要内容,如果未能解决你的问题,请参考以下文章