环境信息
OS:debian11
kernel:linux kernel 5.15
linux 环境测试时附录源代码修改
#ifndef MSG_R
#define SVMSG_MODE (FILE_MODE)
#else
#define SVMSG_MODE (MSG_R | MSG_W | MSG_R>>3 | MSG_R>>6)
#endif
linux 上 System V IPC 实现的一些特点
查看 System V IPC 占用率
可以使用 lspic 来查询 System V IPC 状态信息,示例如下:
RESOURCE DESCRIPTION LIMIT USED USE%
MSGMNI Number of message queues 32000 10 0.03%
MSGMAX Max size of message (bytes) 8192 - -
MSGMNB Default max size of queue (bytes) 16384 - -
..................................................................................
缺省的限制为 32000,lspic 能够显示消息队列的使用情况,在上面的实例中,有 10 个消息队列被使用,占用率约为 0.03%。
System V IPC 的释放
创建 System V IPC 的进程退出时内核不会回收 ipc,除非主动调用释放函数。ipc id 泄露完了后,再次尝试创建会报如下错误:
msgget error: No space left on device
lsipc 能够看到如下内容:
[longyu@debian:12:36:21] svmsg $ lsipc
RESOURCE DESCRIPTION LIMIT USED USE%
MSGMNI Number of message queues 32000 32000 100.00%
.....................................................................................
32000 个 id 已经被分配,占用率为 100%。
man msgget 得到如下描述信息:
MSGMNI System-wide limit on the number of message queues. Before Linux 3.19, the default value for this limit was calculated using a formula
based on available system memory. Since Linux 3.19, the default value is 32,000. On Linux, this limit can be read and modified via
/proc/sys/kernel/msgmni.
msgmni 可以动态修改,允许修改的值有限定,比 32000 小可以正常配置,比 32000 大,需要符合特定的规律才能设定。
[longyu@debian:12:54:17] kernel $ sudo su -c 'echo 32600 > msgmni '
[longyu@debian:12:54:32] kernel $ cat ./msgmni
32600
921271566
1102226159
linux 内核代码中对 System V IPC id 的描述信息
18 /*
19 * The IPC ID contains 2 separate numbers - index and sequence number.
20 * By default,
21 * bits 0-14: index (32k, 15 bits)
22 * bits 15-30: sequence number (64k, 16 bits)
23 *
24 * When IPCMNI extension mode is turned on, the composition changes:
25 * bits 0-23: index (16M, 24 bits)
26 * bits 24-30: sequence number (128, 7 bits)
27 */
linux 内核的 IPC ID 由 index 与 sequence number 两部分数值拼接而成。默认配置下这两部分数值关系如下:
- 低 0-14 位,共计 15 位为 index 的值
- 高 15-30 位,共计 16 位为 sequence number 的值
当 IPCMNI 扩展模式使能时,IPC id 值的生成规则如下:
- 低 0-23 位,共计 24 位为 index 的值
- 高 24-30 位为 sequence number 的值
Exercises 3.1:获取 ipc_perm 与 seq number 的值
示例代码:
int
main(int argc, char **argv)
{
int i, msqid;
struct msqid_ds info;
for (i = 0; i < 20; i++) {
msqid = Msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
printf("msqid = %d\n", msqid);
Msgctl(msqid, IPC_STAT, &info);
printf("ipc_perm seq number is %d\n", info.msg_perm.__seq);
Msgctl(msqid, IPC_RMID, NULL);
}
exit(0);
}
运行结果如下:
msqid = 921370624
ipc_perm seq number is 28118
msqid = 921370625
............................
ipc_perm seq number is 28119
msqid = 921403393
每 64 个 msqid seq 递增一次。数字上的关系为 seq * 512 * 64 + 1 为下一组 64 个 msgid 的起始值。
10 进制与 2 进制对比:
110110111010110 28118
110110111010110000000000000001 921370625
能够发现 28118 是 921370625 的高 15 位值。
Exercises 3.2:msq 每次获取返回的 id 值的变化特点
测试记录
0~63
32768-32831
65536- 65599
98304-98367
每 64 个 id 值为一组连续空间,超过 64 后起始值按照某种规律重新生成。
Exercises3.3:创建 fifo 与 msgq 时对 umask 的使用
umask 的值:0022
示例代码:
int i, msqid;
#define TEST_FIFOPATH "/tmp/test_fifo"
struct msqid_ds info;
struct stat statbuf;
mkfifo(TEST_FIFOPATH, 0666);
stat(TEST_FIFOPATH, &statbuf);
unlink(TEST_FIFOPATH);
printf("fifo perm is %o\n", statbuf.st_mode);
msqid = Msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
Msgctl(msqid, IPC_STAT, &info);
Msgctl(msqid, IPC_RMID, NULL);
printf("msgq perm is %o\n", info.msg_perm.mode);
测试结果:
fifo perm is 10644
msgq perm is 666
system V ipc 消息队列创建时不会使用 umask 来去除必要的权限,fifo 在创建时会使用 umask 去掉必要的权限。
man mkfifo 得到的信息:
DESCRIPTION
mkfifo() makes a FIFO special file with name pathname. mode specifies the FIFO's permissions. It is modified by the process's umask
in the usual way: the permissions of the created file are (mode & ~umask).q
man msgget 未搜索到任何与 umask 相关的内容与测试结果一致。
Exercises3.4:服务端为客户端创建唯一消息队列时那种方式更适用?
1. 使用程序名作为固定的路径名作为参数用 ftok 生成 key 来创建
在这种方式下,编码约定、引入三方通信框架将路径名代表的消息队列通知客户端,客户端使用相同的路径名用 ftok 生成 key 获取 server 创建的消息队列。当需要创建多个消息队列时,需要指定多个路径以区分不同的实例。
2. 使用 IPC_PRIVATE 标志让内核自行分配 id 创建
在这种方式下,服务端与客户端之间需要依赖第三方的通信框架以将消息队列的标识符通知客户端,当有多个客户端时,使用这种方式避免了第一种方式中提供多个文件路径的行为,然而此种方式却不能简单的通过编码约定来关联同一个消息队列。
Exercises3.5:使用 find 批量化查找文件并制作参数使用 ftok 生成 key
示例命令:
find / -print | xargs -i ./svipc/ftok '{}' > output
初步观察未找到相同的 key。