这节开始学习System V IPC,书中后面会介绍POSIX IPC,两者之间的区别可以参考stackoverflow上的一篇回答。system-v-ipc-vs-posix-ipc
System V IPC包含消息队列、信号量和共享内存。

消息队列

消息队列用来在进程之间传递消息。消息队列与管道有点像,但存在两个重大差异:
第一消息队列是存在边界的,这样读者和写者之间以消息进行通信,而不是通过无分隔符的字节流进行通信。
第二每条消息包括一个整型的type字段,并且可以通过类型选择消息而无需以消息被写入的顺序来读取消息。

信号量

信号量允许多个进程同步它们的动作。一个信号量是一个由内核维护的整数值,它对所有具备相应权限的进程可见。一个进程通过对信号量的值进行相应的修改来通知其他进程它正在执行某个动作。

共享内存

共享内存使得多个进程能够共享内存(即同被映射到多个进程的虚拟内存的页帧)的同一块区域(称为一个端)。由于访问用户空间内存的操作是非常快的,因此共享内存是其中一种速度最快的IPC方法:一个进程一旦更新了共享内存,那么这个变更会立即对共享同一个内存段的其它进程可见。

IPC Key

使用ftok函数生成key值,习题45-2是实现ftok()函数,下面的代码只是ftok函数的使用例子。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Solution for Exercise 45-2 */

/* t_ftok.c

   Test the key values returned by ftok(3).

   Usage: t_ftok key-file key-char

   Simply calls ftok(), using the values supplied in the command-line arguments,
   and displays the resulting key.
*/
#include <sys/ipc.h>
#include <sys/stat.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    key_t key;
    struct stat sb;

    if (argc != 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s file-name keychar\n", argv[0]);

    printf("Size of key_t = %ld bytes\n", (long) sizeof(key_t));

    if (stat(argv[1], &sb) == -1)
        errExit("stat");

    key = ftok(argv[1], argv[2][0]);
    if (key == -1)
        errExit("ftok");

    printf("Key = %lx i-node = %lx st_dev = %lx proj = %x\n",
          (unsigned long) key, (unsigned long) sb.st_ino,
          (unsigned long) sb.st_dev, (unsigned int) argv[2][0]);
    if (key == -1)
        printf("File does not exist\n");

    exit(EXIT_SUCCESS);
}
/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 45-1 */

/* svmsg_demo_server.c

   Demonstration System V message queue-based server.
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include "tlpi_hdr.h"

#define KEY_FILE "/some-path/some-file"
                                /* Should be an existing file or one
                                   that this program creates */

int
main(int argc, char *argv[])
{
    int msqid;
    key_t key;
    const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP;   /* rw--w---- */

    /* Optional code here to check if another server process is
       already running */

    /* Generate the key for the message queue */

    key = ftok(KEY_FILE, 1);
    if (key == -1)
        errExit("ftok");

    /* While msgget() fails, try creating the queue exclusively */

    while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) ==
            -1) {
        if (errno == EEXIST) {          /* MQ with the same key already
                                           exists - remove it and try again */
            msqid = msgget(key, 0);
            if (msqid == -1)
                errExit("msgget() failed to retrieve old queue ID");
            if (msgctl(msqid, IPC_RMID, NULL) == -1)
                errExit("msgget() failed to delete old queue");
            printf("Removed old message queue (id=%d)\n", msqid);

        } else {                        /* Some other error --> give up */
            errExit("msgget() failed");
        }
    }

    /* Upon loop exit, we've successfully created the message queue,
       and we can then carry on to do other work... */

    printf("Queue created with id %d\n", msqid);

    exit(EXIT_SUCCESS);
}

ipcs和ipcrm命令

ipcs和ipcrm命令是System V IPC领域中类似于ls和rm文件命令的命令。使用ipcs能够获取系统上IPC对象的信息。在默认情况下,ipcs会显示出所有对象,如下面例子所示:

root@52coder:~/tlpi-dist/svipc# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x01021263 262144     root       620        0            0           

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

在Linux上,ipcs(1)只显示出拥有读权限的IPC对象的信息,而不管是否拥有这些对象。
ipcrm命令删除一个IPC对象,remove certain IPC resource。

OPTIONS
       -a, --all [shm] [msg] [sem]
              Remove all resources.  When an option argument is provided, the removal is performed only for the specified resource types.  Warning!  Do not
              use -a if you are unsure how the software using the resources might react to missing  objects.   Some  programs  create  these  resources  at
              startup and may not have any code to deal with an unexpected disappearance.

       -M, --shmem-key shmkey
              Remove the shared memory segment created with shmkey after the last detach is performed.

       -m, --shmem-id shmid
              Remove the shared memory segment identified by shmid after the last detach is performed.

       -Q, --queue-key msgkey
              Remove the message queue created with msgkey.

       -q, --queue-id msgid
              Remove the message queue identified by msgid.

       -S, --semaphore-key semkey
              Remove the semaphore created with semkey.

       -s, --semaphore-id semid
              Remove the semaphore identified by semid.

       -V, --version
              Display version information and exit.

       -h, --help
              Display help text and exit.

在/proc/sysvipc/目录下存在msg sem shm分别对应消息队列 信号量和共享内存,与IPC命令不同的是这些文件总是会显示出相应种类的所有对象,不管是否在这些对象上拥有读权限。

总结

System V IPC是首先在System V中被广泛使用的三种IPC机制的名称并且之后被移植到了大多数UNIX实现中以及被加入了各种标准中。这三种IPC机制是允许进程之间交换消息的消息队列,允许进程同步对共享资源的访问的信号量,以及允许两个或更多进程共享内存的同一个页的共享内存。

这三种IPC机制在API和语义上存在很多相似之处。对于每种IPC机制来讲,get系统调用会创建或打开一个对象。给定一个整数key,get调用返回一个整数标识符用来在后续的系统调用中引用对象。每种IPC机制还拥有一个对应的ctl调用用来删除一个对象以及获取和修改对象的关联数据结构中的各种特性。

用来为新IPC对象生成标识符的算法被设计成将(立即)复用同样的标识符的可能性降到最小,即使相应的对象已经被删除了,甚至是使用同样的key来创建新对象也一样。这样客户端服务器与应用程序就能够正常工作了--重新启动的服务器进程能够检测到并删除上一个服务器进程创建的IPC对象,并且这个动作会令上一个服务器进程的客户端所保存的标识符失效。

ipcs命令列出了当前位于系统上的所有System V IPC对象。ipcrm命令用来删除System IPC对象。

在Linux上,/proc/sysvipc目录中的文件可以用来获取系统上所有System V IPC对象的信息。
每种IPC机制都有一组相关的限制,他们通过阻止创建任意数量的IPC对象来避免系统资源的耗尽。/proc/sys/kernel目录中的各个文件可以用来查看和修改这些限制。