进程间通信:System V消息队列,pipe,FIFO

System V消息队列

基本概念

  • 消息队列本质是在内核中存储的消息链表。和System V信号量/共享内存一样,首先需要使用msgget根据给定key获取消息队列标识符qid,在使用标识符链接到目标消息队列进行操作。
  • msgsnd用于向消息队列添加新消息到队列尾部msgrcv可以实现以非先入先出的顺序从消息中取消息,可以按照消息的类型字段来取。

msgget

创建/获取一个消息队列IPC结构的标识符

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
  • 通过给定key创建/获取消息队列成功,返回消息队列标识符qid,供后续msgctl/msgsnd/msgrcv
  • 类似于System V信号量,共享内存,一个消息队列IPC结构被创建后,内核会维护一下msqid_ds结构,保存对应消息队列的控制信息和权限,后续可以通过msgctl进行修改:
 struct msqid_ds {
     struct ipc_perm msg_perm;     /* 消息队列权限 */
     time_t          msg_stime;    /* 最后一次msgsnd时间 */
     time_t          msg_rtime;    /* 最后一次msgrcv时间 */
     time_t          msg_ctime;    /* 创建时间/最后一次msgctl()修改时间 */
     unsigned long   __msg_cbytes; /* 当前队列字节数 */
     msgqnum_t       msg_qnum;     /* 当前队列中消息数 */
     msglen_t        msg_qbytes;   /* 队列中允许的最大字节数 */
     pid_t           msg_lspid;    /* 最后一次msgsnd的进程pid */
     pid_t           msg_lrpid;    /* 最后一次msgrcv的进程pid */
 };

msgctl

执行消息队列的控制,修改操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • buf用于接收msgctl返回的控制结构,或者传入目标修改的控制结构,如果不需要设置置为NULL即可,根据具体cmd而定
  • cmd指定msgctl具体的操作,Linux还有一些自己专有的cmd,可以看man,这里只列出APUE上通用的:
    • IPC_STAT:拷贝当前消息队列的msqid_dsbuf
    • IPC_SET:修改msgqid中部分成员:msg_perm.uid, msg_perm.gid, msg_perm.mode。这些成员只有由当前的有效用户进程或者sudo进程设置。此外msg_qbytes只能由sudo用户才能增加超过系统参数MSGMNB
    • IPC_RMID:立刻删除该消息队列,以及消息队列中所有数据,唤醒所有读写等待的进程,错误返回并将errno设置为EIDRM和文件,共享内存相反,信号量相同,只有所有文件指针都关闭/共享内存链接都分离,文件/共享内存才会实际关闭/释放)。同样这个命令需要当前的有效用户进程为消息队列创建用户,或者sudo进程设置。

msgsnd

向消息队列写数据, msqid.msg_qnum会递增,当消息队列已满,flag没有设置的话默认阻塞写:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

对于msgsnd传递的消息格式,一般由三部分组成:

  • 正长整型(long)字段type,表示消息类型
  • 消息数据,如果msgsz指定为0,则没有消息数据
  • 消息数据长度,就是参数msgsz,注意这里不是整个消息结构的长度,只是消息数据长度

因此一般msgp指向的消息结构可以自定义如下:

struct mymesg {
	long mtype;  // 消息类型
	char mtext[msgsz];  // 消息数据
};

关于消息类型mtype需要注意,32位程序long詹4字节,64位程序long占8字节,32位向64位程序送数据没有问题,如果64位向32位程序送数据,如果mtype大于INT_MAX,会被截断。

关于flag一般可以为0,或者指定IPC_NOWAIT,这样发送队列如果已满,函数能够立即返回错误并设置errnoEAGAIN

msgrcv

从消息队列中读出一个消息数据,msqid_ds::msg_qnum递减,可以是非先入先出读,返回读取消息数据部分长度(但是我在测试程序的时候返回0。。。):

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

msgpmsgsz定义与msgsnd相同,msgtype指定读取哪一个消息:

  • msgtype == 0:读取队首消息
  • msgtype > 0:读取相同type下第一个消息
  • msgtype < 0:读取type小于等于msgtype绝对值的第一个消息,如果有多个则读取type最小的那个消息

关于flag同样可以设置为IPC_NOWAIT,读操作不阻塞,队列中没有消息可用时,msgrcv返回错误,errno设置为ENOMSG

pipe(匿名管道)

基本概念

#include <unistd.h>

int pipe(int fd[2]);
  • pipe是半双工的,提供的fd[2]中,fd[0]仅用于读,fd[1]仅用于写,fd[0]的输出就是fd[1]的输入。UNIX定义的系统调用IO可以正常对于pipefd操作
  • pipe只支持公共祖先的两个进程通信,比如父进程打开pipe后,fork子进程与其通信
  • 当读一个已关闭的管道时,所有数据被读取后,read返回0,表文件结束(和socket读取一致)
  • 当往一个已关闭的管道写时,产生SIGPIPE信号(默认终止动作),忽略该信号或调用信号处理函数返回后,write返回-1。可以看到写一个已关闭管道问题更严重,一般如果pipe在一个进程内通信(比如同一事件源),最好先关fd[1],再关fd[0]
  • 内核管道缓冲区大小由PIPE_BUF(系统限制值,默认一个页长4K)规定。通过fpathconf或者pathconf修改

popen / pclose

组合函数,将forkpipeexec,标准输入输出重定向组合在一起的函数:

#include <stdio.h>

FILE* popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);
  • popenfork子进程,然后根据cmdstring执行shell指令
  • type指定了cmdstring重定向的IO,
    • "r":指定了fd[0]重定向到子进程的STDOUT_FILENO(父进程fd[1]<–子进程stdout),子进程的输出直接作为父进程管道的输出
    • "w":制定了fd[1]重定向到子进程的STDIN_FILENO(父进程fd[0]–>子进程stdin),父进程管道的输入直接作为子进程的输入
  • 虽然popen会开启子进程,但是用户不需要在父进程中调用wait / waitpid,调用pclose中会内部调用waitpid回收子进程资源

FIFO(有名管道)

基本概念

FIFO解决了pipe通信双方必须是具有公共祖先这一限制。FIFO需要提供路径名进行标识,FIFO的路径名也是存在于UNIX文件系统中,创建一个FIFO函数如下:

#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
  • FIFO的创建类似于文件创建,mode的指定和open相同,在mkfifoatmode中指定了AT_FDCWDpath以当前路径开始
  • 创建的FIFO需要使用open打开,当所有引用FIFO路径的进程都关闭时,虽然FIFO的路径还会留在文件系统中,但是数据已经被删除
  • modeO_NONBLOCK的设置:
    • 没有设置O_NONBLOCK:只读FIFO打开会阻塞到有其他进程写打开,只写FIFO打开会阻塞到有其他进程读打开
    • 设置O_NONBLOCK:只读FIFO打开立即返回,只写FIFO打开在没有其他读进程打开的情况返回-1设置errnoENXIO
  • FIFO适合有多个进程向管道写,读进程聚合数据的情况(pipe一般用于单入单出),因此需要原子写保证多进程写数据不交叉,PIPE_BUF指定了原子写FIFO的最大数据量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值