父子进程间 IPC 总结

来源:互联网 发布:电脑w8软件下载 编辑:程序博客网 时间:2024/06/11 11:38
目前本人熟悉的、特定适用于父子进程之间的 IPC 方式有三种:pipe、匿名 FIFO(Unix domain socket)和共享内存。现在将使用方法总结一下。

1、管道

Pipe 的特点:单向传递。在管道创建的时候,数据只能从读处读,从写处写,属于单向流动。相关文章已经太多了,小弟不多废话,贴一段代码提示记忆。

CODE:
[Copy to clipboard]
$ cat -n pipe.c
     1  #include <unistd.h>
     2  #include <assert.h>
     3
     4  int main ()
     5  {
     6    int fd[2];
     7    #define READER 0
     8    #define WRITER 1
     9
    10    int r = pipe( fd );
    11    if ( r != 0 ) {
    12      perror( "pipe()" );
    13      exit( 1 );
    14    }
    15
    16    if ( fork() ) {
    17      /* Parent, writer */
    18      int n = 0;
    19      close( fd[READER] );
    20      while ( 1 ) {
    21        ++n;
    22        write( fd[WRITER], &n, sizeof(n) );
    23        sleep( 1 );
    24      }
    25    }
    26    else {
    27      /* Child, reader */
    28      int n;
    29      close( fd[WRITER] );
    30      while ( 1 ) {
    31        read( fd[READER], &n, sizeof(n) );
    32        printf( "Got value: %d/n", n );
    33      }
    34    }
    35  }
因为 pipe 只能进行单向传递的特性,popen(3) 的第二个参数只能为读写之一种,就是这个道理了。

2、socketpair
基本类似 pipe,不过是基于 Unix domain socket,通信为双向通信。

CODE:
[Copy to clipboard]
$ cat -n socketpair.c
     1  #include <sys/types.h>
     2  #include <sys/socket.h>
     3
     4  #include <stdlib.h>
     5  #include <stdio.h>
     6
     7  int main ()
     8  {
     9    int fd[2];
    10
    11    int r = socketpair( AF_UNIX, SOCK_STREAM, 0, fd );
    12    if ( r < 0 ) {
    13      perror( "socketpair()" );
    14      exit( 1 );
    15    }
    16
    17    if ( fork() ) {
    18      /* Parent process: echo client */
    19      int val = 0;
    20      close( fd[1] );
    21      while ( 1 ) {
    22        sleep( 1 );
    23        ++val;
    24        printf( "Sending data: %d/n", val );
    25        write( fd[0], &val, sizeof(val) );
    26        read( fd[0], &val, sizeof(val) );
    27        printf( "Data received: %d/n", val );
    28      }
    29    }
    30    else {
    31      /* Child process: echo server */
    32      int val;
    33      close( fd[0] );
    34      while ( 1 ) {
    35        read( fd[1], &val, sizeof(val) );
    36        ++val;
    37        write( fd[1], &val, sizeof(val) );
    38      }
    39    }
    40  }
Linux 系统中,socketpair 的第一个参数只能是 AF_LOCAL / AF_UNIX。

3、共享内存
传统 SysV IPC 系列提供了 Shared Memory 实现,简称 sysv shm。它的优劣在 Stevens 前辈的 APUE 中都有了详细介绍,此处不多做赘述。Linux 2.4 及之后版本的内核提供了一种新的进程间共享内存方式:通过 mmap。指定 MAP_SHARED | MAP_ANONYMOUS,系统会创建一块可被子进程继承的共享匿名内存块。它的优点是:与其它 mmap 分配的内存具有相同属性:当附着进程执行 exec 或者退出的时候,内存会被系统自动收回,而不像 sysv shm 一样仍然被保留在系统中。而且,由于不需要 key 来进行标识,它的 API 也相对更简单清晰。直接看代码好了。

CODE:
[Copy to clipboard]
$ cat -n mmap.c
     1  #include <stdlib.h>
     2  #include <stdio.h>
     3
     4  #include <sys/mman.h>
     5
     6  int main ()
     7  {
     8    int *p = mmap( NULL, sizeof(int),
     9                   PROT_READ|PROT_WRITE,
    10                   MAP_SHARED|MAP_ANONYMOUS, // 关键在这里。
    11                   0, 0 );
    12    if ( p == MAP_FAILED ) {
    13      perror( "mmap()" );
    14      exit( 1 );
    15    }
    16
    17    if ( fork() ) {
    18      while ( 1 ) {
    19        sleep( 1 );
    20        printf( "Current val: %d/n", *p );
    21        fflush(NULL);
    22      }
    23    }
    24    else {
    25      while ( 1 ) {
    26        (*p)++;
    27        sleep( 1 );
    28      }
    29    }
    30  }
相比较之前两种方式,共享内存有许多不同。首先,它不是通过文件描述符进行标识,而是直接将虚拟页面映射到进程内存空间中;传递数据也不需要在用户空间 <-> 核心的三块 buf 之间来回传递;其次,当执行 exec 之后,进程空间被全部重新生成,mmap 会自动脱离被映射的地址而不会继续继承;第三,与 sysvshm 一样,使用过程需要其它的同步手段,而 pipe / socket 自身提供了同步机制。

以上是一点学习心得,欢迎指正。

ps. 忽然想起前一段时间看到的 Win32 API 中 CreateFileMapping 如果指定第一个参数(文件句柄) hFile 为 INVALID_HANDLE_VALUE,则会在系统内存交换文件中创建一块映射区域。这大约是 Win32 在放弃了 SysV shm 之后的取代方案吧。