IPC 概念(后续补充)
IPC 持续性
管道
pipe管道
需要一个父进程先使用 pipe 函数获得两个文件描述符 fd[0] 和 fd[1],fd[0] 用于读入,fd[1] 用于写入,然后 fork 一个子进程,然后通过fd 数组进行数据传输;
我们平时写的 shell 命令是典型的管道,如 who | sort | lp
半双工管道和全双工管道
pipe 是半双工的,即数据传输是单向的,只能使用 fd[0] 进行读入,使用 fd[1] 写入
理想上 使用单管道的全双工实现,如图
管道就是一个缓冲区,使用一个管道时,相当于使用了一个缓冲区实现,所有数据都写入到缓冲区的末尾,这可能导致一方在读取数据时,读取到自己之前写入的数据
下图为全双工管道的正确实现
使用一个管道只能实现半双工,实现全双工需要使用到两个半双工管道
缺点
使用 pipe 只能使用在有亲缘关系的进程中,使用也叫做匿名管道
FIFO
FIFO 和 pipe 管道类似,但是不同的是 FIFO 是有名字的,这意味着进程间不需要有亲缘关系,所以也叫有名管道
使用 mkfifo 函数来创建的
//mkfifo的函数声明
int mkfifo(const char *pathname, mode_t mode);//成功返回0,出错返回-1
1.pathname
一个 Unix 路径名,也是创建的 FIFO 的名字
2.mode
为权限位(读写权限,创建者和组用户等,总共 6 个常量)
使用完 mkfifo 必须使用 open 函数才能获取到文件描述符,代码如下
//open的函数声明
int open(const char*pathname, int flags, mode_t mode);
1.pathname
要打开或创建的目标文件
2.flags
可以传入多个参数选项,设置多个参数时进行"位或"运算
参数:O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR: 读,写打开
3.mode
(该参数设置值待考证,但以下说明应该可以确定)
O_CREAT: 若文件不存在,则创建它
创建一个 FIFO ,获取他的读写文件描述符,再关闭 FIFO 的代码
#define FIFO1 "/tmp/fifo.1"
if(mkfifo(FIFO1, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
printf("can't create %s", FIFO1);
}
int readfd = open(FIFO1, O_RDONLY, 0);
int writefd = open(FIFO1, O_WRONLY, 0);
unlink(FIFO1)
pipe 在关闭所有进程后管道就会自动消失,而 FIFO 需要使用 unlink 函数才能在文件系统中删除
IPC 持续性
pipe 和 FIFO 的 IPC 持续性都是随进程的
缺点与解决办法
pipe 和 FIFO 使用的是字节流 I/O 模型,是不记录边界的,我们不会对数据进行检查;
举个例子:当我们读取到100个字节时,我们不知道是一个进程一次写了100字节 还是 多个进程分多次写入总数是100字节
当我们希望数据可以具有某种数据结构 或者 在写入多个信息时可以正确的读出数据时,不记录边界的方式会出现错误
大致解决方法有三种
- 使用特殊的分隔符来分隔消息,需要数据中使用到该分隔符的地方进行转义
- 使用显式长度,在每一个消息前记录消息的长度
- 每次连接一个记录,(在网络应用中使用 TCP 连接,在 IPC 应用中使用 IPC 连接)
管道要求写入者进程在写入消息之前,是需要有读出者进程在等待读取消息;(此话有错误)
如果写入者在读入者等待之前写入数据是没意义的
消息队列
消息队列可以认为是一个消息链表,有写权限的线程可以向队列中写消息,有读权限的的线程可以向队列中取走消息(Unix网络编程卷2 中原话是有足够权限的线程,目前本人还不太理解足够的意思,我们姑且认为只有读写权限)
消息队列不要求读出者早于写入者存在
消息队列主要有 Posix 消息队列和 System V 队列,接下来内容我们只讨论 Posix 的消息队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ovnh9GUW-1619186943077)(D:\Desktop\note\linux与操作系统\消息队列示意图.png)]
队列的头节点放置两个队列的属性
- mq_maxmsg:队列所允许的最大队列数
- mq_msgsize:每个消息的最大大小
创建、关闭、删除队列
使用 mq_open 函数创建队列
#include <mqueue.h>
mqd_t mqd_open(const char *name, int oflag, ···);
oflag:和 open 函数的 flag 参数一致,可能按位或上O_CREAT、O_EXCL、O_NONBLOCK
返回值为消息队列描述符,作为其他7个消息队列的函数的第一个参数
使用 mq_close 函数关闭队列
#include <mqueue.h>
int mq_close(mqd_t mqdes);
和文件的 close 函数类似,调用后进程不再使用该描述符,但是消息队列不会删除,一个进程终止时是就相当于调用该函数,关闭所有打开的消息队列
使用 mq_unlink 函数输出
#include <mqueue.h>
int mq_unlink(const char *name);
每个消息队列会保存一个引用计数器,在计数值 >0 时就可以调用 mq_unlink 会删除该名字,但已经打开的进程还可以使用(待考证),等到所有进程都关闭该队列,即计数器为0,才会彻底删除
mq_close 也可以使计数器减一
获取消息队列的属性
mq_attr 结构:
struct mq_attr{
long mq_flags;
long mq_maxmsg;
long mq_msgsize;
long mq_curmsgs;
};
mq_getattr 函数 获取队列属性
mq_setattr 函数 设置队列属性
#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *oattr);
均成功返回0,出错则为-1
发送和接收消息
mq_send 函数发送消息
mq_receive 函数接收消息
#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);
ptr 为消息
prio 为消息的优先级
返回:成功返回0,出错返回-1
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *priop);
priop 如果是非空指针,就用于存放返回的优先级
len 注意不可以小于 mq_attr 结构中的 mq_msgsize 的大小,否则会失败,可以先用 mq_getattr 获取一下(血的教训);
返回:ssize_t 在32位计算机中为 int 型,在64位计算机为 long 型,成功返回的消息中的字节数,失败返回-1
mq_notify函数(待补充)