zoukankan      html  css  js  c++  java
  • 进程间通信IPC

    深刻理解Linux进程间通信(IPC)

    一个大型的应用系统,往往需要众多进程协作,进程(Linux进程概念见附1)间通信的重要性显而易见。本系列文章阐述了Linux环境下的几种主要进程间通信手段,并针对每个通信手段关键技术环节给出详细实例。为达到阐明问题的目的,本文还对某些通信手段的内部实现机制进行了分析。

    linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。Linux则把两者继承了下来,如图示:

    其中,最初Unix IPC包括:管道、FIFO、信号;System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;Posix IPC包括: Posix消息队列、Posix信号灯、Posix共享内存区。有两点需要简单说明一下:1)由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;2)BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。

    图一给出了linux 所支持的各种IPC手段,在本文接下来的讨论中,为了避免概念上的混淆,在尽可能少提及Unix的各个版本的情况下,所有问题的讨论最终都会归结到Linux环境下的进程间通信上来。并且,对于Linux所支持通信手段的不同实现版本(如对于共享内存来说,有Posix共享内存区以及System V共享内存区两个实现版本),将主要介绍Posix API。

    linux下进程间通信的几种主要手段简介:

    1. 管道(Pipe)及有名管道(named pipe - fifo):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
    2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
    3. 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
    5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
    6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

    下面将对上述通信机制做具体阐述。

    附1:参考文献[2]中对linux环境下的进程进行了概括说明:

    一般来说,linux下的进程包含以下几个关键要素:

    • 有一段可执行程序;
    • 有专用的系统堆栈空间;
    • 内核中有它的控制块(进程控制块),描述进程所占用的资源,这样,进程才能接受内核的调度;
    • 具有独立的存储空间

    进程和线程有时候并不完全区分,而往往根据上下文理解其含义。

    原文地址:http://www.ibm.com/developerworks/cn/linux/l-ipc/

    例1.1 PIPE

    //pipe.c

    1 #include <sys/wait.h>
    2 #include <stdio.h>
    3 #include <stdlib.h>
    4 #include <unistd.h>
    5 #include <string.h>
    6 #include <assert.h>
    7
    8 int main(int argc, char* argv[])
    9 {
    10 assert(argc==2);
    11
    12 pid_t cpid;
    13 int pfd[2];
    14 char ch;
    15
    16 if (pipe(pfd) == -1)
    17 {
    18 perror("pipe");
    19 exit(EXIT_FAILURE);
    20 }
    21
    22 cpid = fork();
    23 if (cpid == -1)
    24 {
    25 perror("fork");
    26 exit(EXIT_FAILURE);
    27 }
    28
    29 if (cpid == 0) /* child read from pipe */
    30 {
    31 close(pfd[1]);
    32 while (read(pfd[0], &ch, 1) > 0)
    33 write(STDOUT_FILENO, &ch, 1);
    34 write(STDOUT_FILENO, " ", 1);
    35 close(pfd[0]);
    36 exit(EXIT_SUCCESS);
    37 }
    38 else /* parent write to pipe */
    39 {
    40 close(pfd[0]);
    41 write(pfd[1], argv[1], strlen(argv[1]));
    42 close(pfd[1]);
    43 wait(NULL);
    44 exit(EXIT_SUCCESS);
    45 }
    46 exit(0);
    47 }

    例1.2 FIFO

    //mkfifo.c

    1 #include <stdlib.h>
    2 #include <stdio.h>
    3 #include <sys/types.h>
    4 #include <sys/stat.h>
    5 #include <fcntl.h>
    6 #include <string.h>
    7
    8 #define FIFOFILE "./my_fifo"
    9
    10 int main(int argc, char* argv[])
    11 {
    12 int res = mkfifo(FIFOFILE, 0777);
    13 if (res < 0)
    14 {
    15 perror("mkfifo");
    16 }
    17
    18 char buf[] = "write string";
    19 int fd = open(FIFOFILE, O_WRONLY);
    20 if (write(fd, buf, strlen(buf)) <= 0)
    21 {
    22 perror("write");
    23 }
    24 close(FIFOFILE);
    25
    26 return 0;
    27
    28 }


    //readfifo.c

    1 #include <sys/stat.h>
    2 #include <fcntl.h>
    3 #include <stdio.h>
    4 #include <stdlib.h>
    5 #include <string.h>
    6
    7 #define FIFOFILE "./my_fifo"
    8
    9 int main(int argc, char* argv[])
    10 {
    11 int fd = open(FIFOFILE, O_RDONLY);
    12 if (fd == -1)
    13 {
    14 perror("open fifo error");
    15 exit(EXIT_FAILURE);
    16 }
    17
    18 char buf[64];
    19 memset(buf, 0, sizeof(buf));
    20 if (read(fd, buf, sizeof(buf)) == -1)
    21 {
    22 perror("read error");
    23 }
    24
    25 printf("%s ", buf);
    26
    27 return 0;
    28 }

    例3 msg

    //msgrcv.c

    1 #include <unistd.h>
    2 #include <stdlib.h>
    3 #include <stdio.h>
    4 #include <string.h>
    5 #include <errno.h>
    6 #include <sys/msg.h>
    7
    8 struct msg_st
    9 {
    10 long int msg_type;
    11 char text[BUFSIZ];
    12 };
    13
    14 int main()
    15 {
    16 int running = 1;
    17 int msgid = -1;
    18 struct msg_st data;
    19 long int msgtype = 0; //注意1
    20
    21 //建立消息队列
    22 char* msgpath = "./my_msgqueue";
    23 key_t key = ftok(msgpath, 'a');
    24 int gflags = IPC_CREAT | IPC_EXCL;
    25 msgid = msgget(key, gflags | 0666);
    26 if(msgid == -1)
    27 {
    28 fprintf(stderr, "msgget failed with error: %d ", errno);
    29 exit(EXIT_FAILURE);
    30 }
    31 //从队列中获取消息,直到遇到end消息为止
    32 while(running)
    33 {
    34 if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
    35 {
    36 fprintf(stderr, "msgrcv failed with errno: %d ", errno);
    37 exit(EXIT_FAILURE);
    38 }
    39 printf("You wrote: %s ",data.text);
    40 //遇到end结束
    41 if(strncmp(data.text, "end", 3) == 0)
    42 running = 0;
    43 }
    44 //删除消息队列
    45 if(msgctl(msgid, IPC_RMID, 0) == -1)
    46 {
    47 fprintf(stderr, "msgctl(IPC_RMID) failed ");
    48 exit(EXIT_FAILURE);

    49 }
    50 exit(EXIT_SUCCESS);
    51 }

    //msgsnd.c

    1 #include <unistd.h>
    2 #include <stdlib.h>
    3 #include <stdio.h>
    4 #include <string.h>
    5 #include <sys/msg.h>
    6 #include <errno.h>
    7
    8 #define MAX_TEXT 512
    9 struct msg_st
    10 {
    11 long int msg_type;
    12 char text[MAX_TEXT];
    13 };
    14
    15 int main()
    16 {
    17 int running = 1;
    18 struct msg_st data;
    19 char buffer[BUFSIZ];
    20 int msgid = -1;
    21
    22 //建立消息队列
    23 char* msgpath = "./my_msgqueue";
    24 key_t key = ftok(msgpath, 'a');
    25 int gflags = IPC_CREAT;
    26 msgid = msgget(key, gflags | 0666);
    27 if(msgid == -1)
    28 {
    29 fprintf(stderr, "msgget failed with error: %d ", errno);
    30 exit(EXIT_FAILURE);
    31 }
    32
    33 //向消息队列中写消息,直到写入end
    34 while(running)
    35 {
    36 //输入数据
    37 printf("Enter some text: ");
    38 fgets(buffer, BUFSIZ, stdin);
    39 data.msg_type = 1; //注意2
    40 strcpy(data.text, buffer);
    41 //向队列发送数据
    42 if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
    43 {
    44 fprintf(stderr, "msgsnd failed ");
    45 exit(EXIT_FAILURE);
    46 }
    47 //输入end结束输入
    48 if(strncmp(buffer, "end", 3) == 0)

    49 running = 0;
    50 sleep(1);
    51 }
    52 exit(EXIT_SUCCESS);
    53 }

    运行结果如下:
     
     
    >>例子分析——消息类型
     
    这里主要说明一下消息类型是怎么一回事,注意msgreceive.c文件main函数中定义的变量msgtype(注释为注意1),它作为msgrcv函数的接收信息类型参数的值,其值为0,表示获取队列中第一个可用的消息。再来看看msgsend.c文件中while循环中的语句data.msg_type = 1(注释为注意2),它用来设置发送的信息的信息类型,即其发送的信息的类型为1。所以程序msgreceive能够接收到程序msgsend发送的信息。
     
    如果把注意1,即msgreceive.c文件main函数中的语句由long int msgtype = 0;改变为long int msgtype = 2;会发生什么情况,msgreceive将不能接收到程序msgsend发送的信息。因为在调用msgrcv函数时,如果msgtype(第四个参数)大于零,则将只获取具有相同消息类型的第一个消息,修改后获取的消息类型为2,而msgsend发送的消息类型为1,所以不能被msgreceive程序接收。重新编译msgreceive.c文件并再次执行,其结果如下:
     
     
    我们可以看到,msgreceive并没有接收到信息和输出,而且当msgsend输入end结束后,msgreceive也没有结束,通过jobs命令我们可以看到它还在后台运行着。
     
    >>消息队列与命名管道的比较
     
    消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。
     
    与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
  • 相关阅读:
    python文件操作,读取,修改,合并
    LWIP学习之流程架构
    嵌入式网络笔记
    AD17笔记
    STM32之VCP1/VCAP2引脚的处理
    AD中添加中文字符丝印的方法:
    磁珠与电感
    稳压二极管选型
    TVS选型
    光耦的使用
  • 原文地址:https://www.cnblogs.com/xuelei/p/4580019.html
Copyright © 2011-2022 走看看