54-System V IPC 内核对象

来源:互联网 发布:2017网络最流行的歌曲 编辑:程序博客网 时间:2024/05/30 05:28

在你基本掌握了共享内存的使用方法后,接下来我们需要继续深入 System V IPC。

1. IPC 内核对象

每个 IPC 内核对象都是位于内核空间中的一个结构体。它长什么样现在我们还不需要关心,所以现在你只需要知道这样的结构体存在就够了。

具体的对于共享内存、消息队列和信号量,他们在内核空间中都有对应的结构体来描述。当你使用 get 后缀创建内核对象时,内核中就会为它开辟一块内存保存它。只要你不显式删除该内核对象,它就永远位于内核空间中,除非你关机重启。

图1 展示了 IPC 内核空间的内核对象。

1.1 内核对象的 id 号(标识符)


这里写图片描述
图1 IPC 内核对象示意图

图1 只是帮助你理解,内核代码具体实现方式并不一定和图1相同。

进程空间的高 1G 空间(3GB-4GB)是内核空间,该空间中保存了所有的 IPC 内核对象。图1 中给出不同的 IPC 内核对象在内存中的布局(以数组的方式),实际操作系统的实现并不一定是数组,也可能是链表或者其它数据结构等等。每个内核对象都有自己的 id 号(数组的索引)。

此 id 号可以被用户空间使用。所以只要用户空间知道了内核对象的 id 号,就可以操控内核对象了。

1.2 获取内核对象的 id 号

为了能够得到内核对象的 id 号,用户程序需要提供键值——key,它的类型是 key_t (int 整型)。系统调用函数(shmget, msgget 和 semget)根据 key,就可以查找到你需要的内核 id 号。在内核创建完成后,就已经有一个唯一的 key 值和它绑定起来了,也就是说 key 和内核对象是一一对应的关系。(key = 0 为特殊的键,它不能用来查找内核对象,后面会说明)


这里写图片描述
图2 根据 key 获取内核对象 id 号

有些同学可能会产生疑惑,相同的 key,系统函数怎么知道你是需要共享内存内核对象的 id,还是消息队列的或者信号量的内核对象 id ? 实际上,你使用不同的 get 后缀函数就可以了。

比如使用 shmget(0x8888, 0, 0) 得到的 id 号就是 0,如果使用 msgget(0x8888, 0),得到的 id 号就是1;如果使用 semget(0x8888, 0, 0) 得到的 id 号就是 4 了。

int id = shmget(0x8888, 0, 0); // 返回 0int id = msgget(0x8888, 0); // 返回 1int id = semget(0x8888, 0, 0); // 返回 4

如果用户用 key = 0 的键调用 get 后缀函数,将导致创建一个匿名内核对象而不是获取内核对象,这样的内核对象不绑定任何键值,这意味着你将无法通过 get 后缀函数来获取其 id。

2. 创建 IPC 内核对象

在创建 IPC 内核对象时,用户程序一定需要提供 key 值才行。实际上,创建 IPC 内核对象的函数和获取内核对象 id 的函数是一样的,都是使用 get 后缀函数。比如在键值 0x8888 上创建 ipc 内核对象,并获取其 id,应该像下面这样:

// 在 0x8888 这个键上创建内核对象,权限为 0644,如果已经存在就返回错误。int id = shmget(0x8888, 4096, IPC_CREAT | IPC_EXCL | 0644);int id = msgget(0x8888, IPC_CREAT | IPC_EXCL | 0644);int id = semget(0x8888, 1, IPC_CREAT | IPC_EXCL | 0644); // 第二个参数表示创建几个信号量

所以,你想创建哪种类型的 ipc 内核对象时,只需要调用相应的 get 后缀函数就可以了。

这里详细讲解一下 shmget 函数,msgget 和 semget 函数后面再说明。

3. shmget 函数

掌握了 shmget 函数,msgget 和 semget 函数的用法都是类似的,大家融会贯通就行了(不过后面还会详细介绍)。

  • 函数原型
int shmget(key_t key, size_t size, int flags);
  • 参数 key:用户约定好的键值。
    • 如果该值为 IPC_PRIVATE(这个宏被定义为 0),则表示创建一个新的内核对象并返回其 id 号。
    • 如果该值不等于 0,表示创建或者获取 IPC 内核对象的 id 号(具体是创建还是获取需要依据 shmflg 参数)。
  • 参数 size:只对创建内核对象时该值才有效,表示创建共享内存的大小(一般设置为一页内存大小的整数倍比较好,页面内存大小通过 getpagesize() 函数获取)。
  • 参数 flags:可选项
    • IPC_CREAT:创建内核对象。如果内核对象已存在且未指定 IPC_EXCL,就返回该内核对象的 id 号。
    • IPC_EXCL:总是搭配 IPC_CREAT 一起使用。如果设定该选项,当内核对象已存在就返回错误,同时 errno 设定为 EEXIST。
    • 权限位:如果是创建新的内核对象,flags 还需要位或内核对象的权限位,比如 0664.

三个 System V IPC(shmget, msgget, semget) 都有参数 key 和 flags,用法都是一样的。

  • 返回值:成功返回内核对象的 id 号,否则返回 -1.

如果要获取已存在的内核对象 id,除了 key 以外,其它参数都指定为 0 即可。

4. 实例

4.1 创建内核对象

程序 ipccreate 用于在指定的键值上创建 ipc 内核对象。使用格式为 ./ipccreate <ipc type> <key>,比如 ./ipccreate 0 0x8888 表示在键值 0x8888 上创建共享内存。

  • 代码
#include <unistd.h>#include <sys/ipc.h>#include <sys/shm.h>#include <sys/msg.h>#include <sys/sem.h>#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc, char* argv[]) {  if (argc < 3) {    printf("%s <ipc type> <key>\n", argv[0]);    return -1;   }  key_t key = strtoll(argv[2], NULL, 16);  char type = argv[1][0];  char buf[64];  int id;   if (type == '0') {    id = shmget(key, getpagesize(), IPC_CREAT | IPC_EXCL | 0644);    strcpy(buf, "share memory");  }  else if (type == '1') {    id = msgget(key, IPC_CREAT | IPC_EXCL | 0644);    strcpy(buf, "message queue");  }  else if (type == '2') {    id = semget(key, 5, IPC_CREAT | IPC_EXCL | 0644);    strcpy(buf, "semaphore");  }  else {    printf("type must be 0, 1, or 2\n");    return -1;   }  if (id < 0) {    perror("get error");    return -1;   }  printf("create %s at 0x%x, id = %d\n", buf, key, id);  return 0;}
  • 编译和运行
$ gcc ipccreate.c -o ipccreate
$ ./ipccreate 0 0x1234// 创建共享内存$ ./ipccreate 1 0x1234 // 创建消息队列$ ./ipccreate 2 0x1234 // 创建信号量
  • 运行结果


这里写图片描述
图3 创建 ipc 内核对象,并使用 ipcs 命令查看

注:使用命令 ipcs 可以查看 ipc 内核对象

4.2 获取 ipc 内核对象

程序 ipccreate 用于在指定的键值上获取 ipc 内核对象的 id 号。使用格式为 ./ipcget <ipc type> <key>,比如 ./ipcget 0 0x8888 表示获取键值 0x8888 上的共享内存 id 号。

  • 代码
#include <unistd.h>#include <sys/ipc.h>#include <sys/shm.h>#include <sys/msg.h>#include <sys/sem.h>#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc, char* argv[]) {  if (argc < 3) {    printf("%s <ipc type> <key>\n", argv[0]);    return -1;   }  key_t key = strtoll(argv[2], NULL, 16);  char type = argv[1][0];  char buf[64];  int id;   if (type == '0') {    id = shmget(key, 0, 0);     strcpy(buf, "share memory");  }  else if (type == '1') {    id = msgget(key, 0);     strcpy(buf, "message queue");  }  else if (type == '2') {    id = semget(key, 0, 0);     strcpy(buf, "semaphore");  }  else {    printf("type must be 0, 1, or 2\n");    return -1;   }  if (id < 0) {    perror("get error");    return -1;   }  printf("get %s at 0x%x, id = %d\n", buf, key, id);  return 0;}
  • 编译和运行
$ gcc ipcget.c -o ipcget
$ ./ipcget0 0x1234// 创建共享内存$ ./ipcget1 0x1234 // 创建消息队列$ ./ipcget2 0x1234 // 创建信号量
  • 运行结果


这里写图片描述
图4 获取 ipc 内核对象 id

5. 总结

学会自由的创建和获取 ipc 内核对象后,我们就可以利用它来做进程间通信了。

本篇你需要掌握:

  • ipc 内核对象
  • ipc 内核对象如何创建和获取

练习:
1. 多次执行 ./ipccreate 0 0x1234 会有什么结果?
2. 查阅命令 ipcrm 的作用和用法,解决第一题中的现象。

0 0
原创粉丝点击