网站上传用什么软件做视频,wordpress多个page,网站每天更新的内容是内链吗,凡科做的网站提示证书错误IPC 是 Linux 编程中一个重要的概念#xff0c;IPC 有多种方式#xff0c;本文主要介绍消息队列(Message Queues)#xff0c;消息队列可以完成同一台计算机上的进程之间的通信#xff0c;相比较管道#xff0c;消息队列要复杂一些#xff0c;但使用起来更加灵活和方便IPC 有多种方式本文主要介绍消息队列(Message Queues)消息队列可以完成同一台计算机上的进程之间的通信相比较管道消息队列要复杂一些但使用起来更加灵活和方便本文针对 System V 消息队列并给出了多个具体的实例每个实例均附有完整的源代码本文所有实例在 Ubuntu 20.04 上编译测试通过gcc版本号为9.4.0本文适合 Linux 编程的初学者阅读。 1 消息队列的基本概念 消息队列是 Linux 下多种 IPC 方法之一最早源自 UNIX System V后来 POSIX 也制定了消息队列的标准 所以实际上有两种不同类型的消息队列System V 消息队列和 POSIX 消息队列两种消息队列的功能几乎是一样的但调用方法不同 Linux 既支持 Sytem V 消息队列也支持 POSIX 消息队列本文针对 System V 消息队列 消息队列本质上是存放在内核空间中的一个消息链表由内核负责维护这个链表显然是要占用内核资源的所以对消息队列是有一定限制的以避免过多地占用内核资源这些限制定义在头文件 linux/msg.h 中 #define MSGMNI 32000 /* IPCMNI */ /* max # of msg queue identifiers */
#define MSGMAX 8192 /* INT_MAX */ /* max size of message (bytes) */
#define MSGMNB 16384 /* INT_MAX */ /* default max size of a message queue */头文件中对这三个宏定义有比较详细的说明MSGMNI 是系统中消息队列数量的上限MSGMAX 是单条消息的最大字节数这个值设置为 MSGMNB 的 1/2以保证在消息队列中至少可以有两条消息MSGMNB 是每个消息队列所占用的最大字节数也就是一个队列的所有消息的长度之和不能大于这个数 上述三个对消息队列做出限制的当前值可以简单地使用 ipcs -q -l 命令查看 上述三个对消息队列做出限制的当前值也可以在 proc 文件系统中找到 使用 sysctl 命令也可以查看消息队列的限制值 IPC 标识符 System V 定义了三种 IPC 方法消息队列(Message queues)、信号量(Semaphores)和共享内存(Shared Memory)这也是 Linux 下的三种重要的 IPC 方法我们把这些统称为IPC 对象(IPC Object)每个 IPC 对象都有一个与之关联的唯一的标识符(IPC Identifier)这个标识符在内核中用于唯一地标识一个 IPC 对象例如要访问特定的一个消息队列只需要知道这个消息队列的标识符(ID)即可在 Ubuntu 上这个 ID 的唯一性与 IPC 对象的类型是无关的比如一个消息队列的 ID 是 1234那么在共享内存中就不可能有 ID 为 1234 的 IPC 对象在下面的描述中 IPC 标识符 ID 将被简称为 ID消息队列的 ID在 Linux 下其实就是一个 32 位整数的序列号从 0 开始也就是说系统建立的第一个 IPC 对象的 ID 是 0第二个 IPC 对象的 ID 是 1…以此类推即便一个 IPC 对象被删除其对应的 ID 号空闲出来新建立的 IPC 对象也不会用这个空闲出来的 ID 号而是按照序列号继续延续也就是说系统建立的第一个 IPC 对象的 ID 是 0…第三个 IPC 对象的 ID 是 2现在删除 ID 号为 1 的 IPC 对象再建立一个 IPC 对象时其 ID 号是 3 而不会是 1。 IPC Key Unix 有一个所谓“一切皆文件”的原则但是 System V IPC 的设计上似乎与这一原则有些偏差内核使用 “IPC 标识符 ID” 来标识一个 IPC 对象但是我们并不能通过打开一个文件来获得与这个 “IPC 标识符 ID” 的关联IPC 需要使用一个“IPC Key”来创建或者获取 “IPC 标识符 ID”理论上说这个 IPC Key 可以使用任意方法生成同时 System V IPC 提供了一个可以生成 IPC Key 的函数调用key_t ftok(const char *pathname, int proj_id);该调用实际上是将 pathname 在内核中的 inode 节点号、设备号以及 proj_id 三者组合到一起生成 IPC Key这使得 IPC Key 和一个文件路径产生了联系似乎又有点回到“一切皆文件”的原则上来总之System V IPC 的逻辑是 用 路径 项目 ID(一个整数) 生成一个 IPC Key通过 Key 生成或者获取到 IPC 标识符 ID用 IPC 标识符 ID 对 IPC 对象进行操作 举例key_t key ftok(./tmp/, 1234);
2 创建/获取消息队列 ID 消息队列 ID 是操作一个消息队列的唯一标识 创建一个新的消息队列会为这个消息队列生成一个 ID要操作一个已经存在的消息队列也必须首先获得该消息队列的 ID 下面两个函数用于创建/获取消息队列 ID 函数ftok() - 将路径名和项目 ID 组合转换为 IPC Key此值将用于创建/获取一个消息队列 ID #include sys/ipc.hkey_t ftok(const char *pathname, int proj_id);这个函数在前面已经提到过这里稍微详细地介绍一下pathname 既可以是一个目录的路径比如/tmp/也可以是一个文件的路径比如/tmp/msgqueue.txt既可以是一个绝对路径也可以是一个相对路径proj_id 称为项目 ID实际上就是个数字在这个函数中仅使用这个数字的低 8 位通常认为同一个项目的程序总是放在同一个目录下这个目录可以指定为 pathname但同一个目录下可能有不同的几个项目那么可以用项目 ID 来区分这样就可以生成不同的 Key从而创建/获取不同的消息队列的 ID该函数会取出 pathname 的 inode 节点号和设备号(dev_no)然后与 proj_id 组合生成一个 key同样的 pathname 和 proj_id 将获得相同的 key生成 key 的组合方式为(inode 0xffff) | ((dev_no 0xff) 16) | ((proj_id 0xff) 24)源程序ipc-key.c(点击文件名下载源程序)验证了这种组合方式的正确性。 函数msgget() - 创建新消息队列或者获取已有消息队列的 ID #include sys/ipc.h
#include sys/msg.hint msgget(key_t key, int msgflg);该系统调用用于建立一个新的消息队列或者获取一个已经存在的消息队列的 ID该系统调用返回的 ID 号将成为对一个消息队列操作的唯一标识该系统调用返回与 key 参数值相关联的消息队列标识符 ID它既可以用于获取已经存在的消息队列的标识符也可以用于创建一个新的消息队列msgget() 中的 key 通常使用 ftok() 生成(也可以自定义生成)也可以设置为 IPC_PRIVATEmsgflag 的有效值有 IPC_CREAT 和 IPC_EXCL 当 IPC_CREAT 时如果 key 对应的消息队列存在则返回其消息队列的 ID如果 key 对应的消息队列不存在则建立与 key 关联的消息队列并返回消息队列的 ID当 IPC_CREAT | IPC_EXCL 时如果 key 对应的消息队列存在则报错返回 -1errno EEXIST(File exists)如果 key 对应的消息队列不存在则建立与 key 关联的消息队列并返回消息队列的 ID当 IPC_EXEL 时如果 key 对应的消息队列存在则返回消息队列的 ID(这点和 IPC_CREAT 一样)如果 key 对应的消息队列不存在则返回 -1errno ENOENT(No such file or directory)另外msgflag 还可以加上所创建的消息队列的读写权限比如0666msgflag 举例IPC_CREAT | IPC_EXEL | 0666 当 key IPC_PRIVATE 时msgget() 将创建一个新的消息队列并返回该消息队列的 ID 这样生成的消息队列只有 ID没有 key(key 为 0)所以其它进程并不能方便地使用这个消息队列通常只能在子进程之间使用实际上IPC_PRIVATE 的值是 0所以我们自己生成的 key 不能是 0否则相当于将 key 设置为 IPC_PRIVATE 再次强调消息队列的生成逻辑通过“一个路径 项目 ID”生成 key通过 key 与一个消息队列 ID 相对应通过消息队列 ID 操作消息队列。 创建/获取消息队列 ID 的示例代码 #include sys/ipc.h
#include sys/msg.h
...
key_t key ftok(/tmp/, 1234);
// check if the key exists
msqid msgget(ipc_key, IPC_EXCL);
if (msqid -1) {// key doesnt exist, create itmsqid msgget(ipc_key, IPC_CREAT | 0666);if (msqid -1) {perror(msgget());exit(EXIT_FAILURE);}
}
...特别要注意的是 msgflag 参数中的读/写权限的设置如果不显式标明那么默认的读写权限将变成 0000这样的一个消息队列是没有办法进行读写的当然还可以使用 msgctl() 修改消息队列的读写权限但需要更高的权限(比如root)才能做到。
3 向消息队列中发送消息 函数msgsnd() - 向消息队列中发送一条消息 #include sys/ipc.h
#include sys/msg.hint msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);通过调用 getget() 获取了消息队列的 ID 后就可以向这个消息队列中发送消息了 该系统调用向 ID 为 msgid 的消息队列中发送一条消息调用该函数的进程必须要有该消息队列的写权限 该函数在调用成功时返回 0调用失败时返回 -1errno 中为错误代码 msgp 指向要发送的消息其消息结构的具体格式如下 struct msgbuf {long mtype; /* message type, must be 0 */char mtext[1]; /* message data */
};mtype 字段必须是正整数值接收进程可以根据这个字段对收到的消息进行选择mtext 字段是一个数组(或其他结构)其大小由 msgsnd() 调用中的 msgsz 指定 msgsz 不能是负数但可以为 0也就是所谓零长度的消息(即没有 mtext 字段) msgflag 可以填 0 或者 IPC_NOWAIT msgsnd() 默认是阻塞的当用于消息队列的内存空间不够用时msgsnd() 会阻塞直到有足够的空间可用当 msgflag 设置为 IPC_NOWAIT 时msgsnd() 在消息队列的空间不够用时也不会阻塞会立即返回失败errno EAGAIN 还有以下两种情况会导致 msgsnd() 调用失败 msgid 指向的消息队列不存在或者已经被删除返回失败errno EIDRM调用该函数时被信号处理程序中断此时msgsnd() 不会自动重启返回失败errno EINTR 源程序msg-send.c(点击文件名下载源程序)演示了如何使用 msgsnd() 向消息队列中发送消息 这个程序以当前目录的相对路径 “./” 建立一个 key然后用这个 key 建立/获取一个消息队列的 ID然后向这个消息队列中发送了三条消息 编译gcc -Wall msg-send.c -o msg-send 运行./msg-send 使用命令 ipcs -q 可以查看消息队列情况 使用命令 ipcrm -q [ID] 可以删除指定详细队列中的消息其中 [ID] 可以通过 ipcs -q 命令查到 运行截图从截图中可以看出消息队列中有三条消息 4 从消息队列中读取消息 函数msgrcv() - 从消息队列中接收一条消息 #include sys/ipc.h
#include sys/msg.hssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);该系统调用从 ID 为 msgid 的消息队列中接收一条消息调用该函数的进程必须要有该消息队列的读权限 该系统调用从消息队列中取出一条消息放到 msgp 参数指定的缓冲区内同时会从消息队列中删除这条消息 该系统调用在调用成功时返回收到消息的长度调用失败时返回 -1errno 中为错误代码 与 msgsnd() 类似msgp 指向接收消息的缓冲区其结构的具体格式同 msgsnd() 中介绍的 struct msgbuf这里再重复一遍 struct msgbuf {long mtype; /* message type, must be 0 */char mtext[1]; /* message data */
};msgsz 参数为 msgp 指向的结构中 mtext 字段的长度 当收到消息的长度大于 msgsz 时 如果 msgflag 中设置了 MSG_NOERROR则该消息将被截断被截断的消息部分将丢失调用返回成功如果 msgflag 中没有设置 MSG_NOERROR则调用失败errno E2BIG msgtyp 用于选择收到的消息类别收到的信息格式符合 struct msgbuf其结构中的 mtype 表示该消息类别msgtype 可以选择接收哪种类别的消息 当 msgtyp 0 时从消息队列中读取出第一条消息当 msgtyp 0 时从消息队列中读取出 mtype msgtyp 的第一条消息当 msgtyp 0 时从消息队列中读取出 mtype |msgtyp| 且 mtype 为最小的第一条消息 举个例子消息队列中有 4 条消息第 1 条消息的 mtype 3第 2 条消息的 mtype 1第 3 条消息的 mtype 4第 4 条消息的 mtype 2当使用 msgrcv(msqid, (void *)msgp, msgsz, -3, ...) 从消息队列中读取消息时msgtyp -3|msgtyp| 3那么第 1、2、4 条消息都满足 mtype |msgtyp| 的条件但是收到的消息为第 2 条因为第 2 条的 mtype 最小。 msgflag 可以有下面的值组合而成 IPC_NOWAIT如果消息队列中没有请求类型(msgtyp)的消息则立即返回。系统调用失败errno ENOMSGMSG_COPY从消息队列中读取消息但是不会破坏原消息队列即取出消息后并不把这条消息从消息队列中删除 该标志必须和 IPC_NOWAIT 一起使用当消息队列中没有符合条件的消息时会立即返回调用失败errno ENOMSG该标志不能与 MSG_EXCEPT 一起使用 MSG_EXCEPT当 msgtyp 0 时从消息队列中读取 mtype ! msgtyp 的第一条消息MSG_NOERROR如果收到消息的长度大于参数 msgsz则将消息截断 如果消息队列中没有符合条件的消息同时msgflag 中没有设置 IPC_NOWAIT则该调用将产生阻塞直到 一条所需类型(mtype)的消息进入到消息队列中该消息队列被删除此时返回调用失败errno EIDRM当前进程捕获了一个信号此时返回调用失败errno EINTR 源程序msg-recv.c(点击文件名下载源程序)演示了如何使用 msgrcv() 从消息队列中读取消息 编译gcc -Wall msg-recv.c -o msg-recv 与上一个示例 msg-send 一样该程序以当前目录的相对路径 “./” 建立一个 key然后用这个 key 获取一个消息队列的 ID如果这个消息队列不存在程序将退出 运行./msg-recv [msgtype] 当 [msgtype] 存在时从消息队列中读取其消息类型为 [msgtype] 的第一条消息当 [msgtyoe] 不存在时默认 [msgtyoe] 为 0 这个程序可以和上一个示例 msg-send 一起使用这时msg-send 和 msg-recv 两个程序应该放在用一个工作目录下并从这个目录下运行 msg-send 程序建立消息队列并向这个队列中发送了三条消息消息类型分别是 1、2 和 3运行 msg-recv 时可以指定只读取其中一种类型的消息比如./msg-recv 2 运行截图 5 消息队列的控制和操作
函数msgctl() - 对消息队列进行控制操作struct ipc_perm {__key_t __key; /* Key. */__uid_t uid; /* Owners user ID. */__gid_t gid; /* Owners group ID. */__uid_t cuid; /* Creators user ID. */__gid_t cgid; /* Creators group ID. */__mode_t mode; /* Read/write permission. */unsigned short int __seq; /* Sequence number. */unsigned short int __pad2;__syscall_ulong_t __glibc_reserved1;__syscall_ulong_t __glibc_reserved2;
};struct msqid_ds {struct ipc_perm msg_perm; /* structure describing operation permission */__MSQ_PAD_TIME (msg_stime, 1); /* time of last msgsnd command */__MSQ_PAD_TIME (msg_rtime, 2); /* time of last msgrcv command */__MSQ_PAD_TIME (msg_ctime, 3); /* time of last change */__syscall_ulong_t __msg_cbytes; /* current number of bytes on queue */msgqnum_t msg_qnum; /* number of messages currently on queue */msglen_t msg_qbytes; /* max number of bytes allowed on queue */__pid_t msg_lspid; /* pid of last msgsnd() */__pid_t msg_lrpid; /* pid of last msgrcv() */__syscall_ulong_t __glibc_reserved4;__syscall_ulong_t __glibc_reserved5;
};#include sys/ipc.h
#include sys/msg.hint msgctl(int msqid, int cmd, struct msqid_ds *buf);该系统调用对 msqid 指定的消息队列执行一个由 cmd 指定的命令包括获取/设置消息队列的属性删除消息队列等Linux 下允许的 cmd 值如下 IPC_RMID - 删除一个消息队列IPC_SET - 设置消息队列的权限结构IPC_STAT - 获取消息队列的权限结构 当 cmd 为 IPC_STAT 时该调用将消息队列在内核中的属性拷贝到 buf 指向的 struct msqid_ds 中当 cmd 为 IPC_SET 时该调用将根据 buf 指向的 struct msqid_ds 的信息更新该消息队列在内核中的属性 其实可以更新的属性有限已知可更新的属性有msg_qbytes、msg_perm.uid、msg_perm.gid 和 msg_perm.mode 的最后九位这个命令的主要用途就是改变一个消息队列的读写权限也就是修改 msg_perm.mode 的最后九位 下面代码可以删除 ID 为 msqid 的消息队列msgctl(msqid, IPC_RMID, 0);下面的代码段将 ID 为 msqid 的消息队列的读写权限改为 0660 struct msqid_ds queue_ds;msgctl(msqid, IPC_STAT, queue_ds);
queue_ds.msg_perm.mode 0660;
msgctl(msqid, IPC_SET, queue_ds);6 实例消息队列的小工具 最后这个实例可以作为消息队列的一个小工具使用这个小工具有如下功能 向消息队列中发送一条新消息从消息队列中读取一条消息删除一个消息队列修改消息队列的读写权限。 源程序msgtool.c(点击文件名下载源程序)可以看作是一个消息队列的小实用工具 以当前路径 ‘m’ 的 ASCII 码组合成 key该实用工具仅针对该 key 关联的消息队列进行操作在发送消息时如果不存在与 key 关联的消息队列则建立一个新的消息队列从消息队列中接收消息时如果与 key 关联的消息队列不存在则直接退出删除消息队列时如果与 key 关联的消息队列不存在则直接退出修改消息队列的读/写权限时如果与 key 关联的消息队列不存在则直接退出该程序使用了上面介绍过的与消息队列相关的所有函数。 编译gcc -Wall msgtool.c -o msgtool 向消息队列中发送消息./msgtool s 2 Hello world! 从消息队列中接收消息./msgtool r 删除消息队列./msgtool d 修改消息队列的读写权限./msgtool m 0666 运行截屏 7 操作消息队列的命令行命令 ipcs -q -l - 显示消息队列的限制值 ipcs -q - 显示现有消息队列的 key、ID 等部分属性 ipcs -q -i ID - 显示指定 ID 的消息队列的属性(比 ipcs -q 显示的属性要多些) ipcrm -Q key - 删除指定 key 的消息队列 ipcrm -q ID - 删除指定 ID 的消息队列 ipcrm --allmsg - 删除所有的消息队列 ipcmk -Q - 创建一个新的消息队列其读写权限为默认的 0644 ipcmk -Q -p perm - 创建一个新的消息队列其读写权限为指定的 。
8 其它 前面介绍过消息队列的实现在内核中是使用的链表理论上链表并不是一个先进先出(FIFO)的结构但是消息队列的链表并没有向用户空间开放同时内核也没有给出完全操作消息队列链表的接口从操作上看消息队列仍然具有先进先出的特性 内核并没有提供一个列出当前系统中所有消息队列的接口只能依靠 proc 文件系统中的 /proc/sysvipc/msg 文件来查看当前所有消息队列的基本属性和权限可以使用 cat /proc/sysvipc/msg 来查看消息队列的状态 源程序listmsq.c(点击文件名下载源程序)演示了如何通过读取 /proc/sysvipc/msg 文件列出系统中所有的消息队列还有一个办法可以列出所有的消息队列因为消息队列的 ID 号的总量是有限制的(MSGMNI)所有可以采用遍历所有消息队列的 ID 号的方法找到系统中所有的消息队列源程序traversemsq.c(点击文件名下载源程序)演示了如何通过遍历所有可能的消息队列 ID 号找到系统中所有的消息队列