Linux进程间通信——管道

简介:

Linux进程间通信机制:

1.同一主机进程间通信机制:

  Unix方式:有名管道FIFO、无名管道PIPE、信号Signal

  SystemV方式:信号量、消息队列、共享内存

2.网络通信:RPC(Remote Procedure Call)、Socket

管道

管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者可用于具有亲缘关系进程间的通信,即可用于父进程和子进程间的通信,后者额克服了管道没有名字的限制,因此,除具有前者所具有的功能外,它还允许无亲缘关系进程间的通信,即可用于运行于同一台机器上的任意两个进程间的通信。 

无名管道由pipe()函数创建:

#i nclude <unistd.h>
        int pipe(int filedis[2]);

参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。 无名管道占用两个文件描述符,  不能被非血缘关系的进程所共享,  一般应用在父子进程中. 

特点:

    半双工单向、父子进程间、一端写一段读、没有名字、缓冲区有限、数据无格式

    无名管道常用于父子进程中,  可简单分为单向管道流模型和双向管道流模型.  其中,  单向管道流根据流向分为从父进程流向子进程的管道和从子进程流向父进程的管道. 

    下面设计一个实例,  数据从父进程流向子进程:父进程向管道写入一行字符,  子进程读取数据并打印到

屏幕上. 

[bill@billstone Unix_study]$ cat pipe1.c 

#include <unistd.h> 

#include <stdio.h> 

#include <sys/types.h> 

#include <assert.h> 

int main() 

                int fildes[2]; 

                pid_t pid; 

                int i,j; 

                char buf[256]; 

                assert(pipe(fildes) == 0);                          //  创建管道 

                assert((pid = fork()) >= 0);                      //  创建子进程 

                if(pid == 0){                                        //  子进程 

                                close(fildes[1]);                                //  子进程关闭管道输出 

                                memset(buf, 0, sizeof(buf)); 

                                j = read(fildes[0], buf, sizeof(buf)); 

                                fprintf(stderr, "[child] buf=[%s] len[%d]\n", buf, j); 

                                return; 

                } 

                close(fildes[0]);                                              //  父进程关闭管道输入 

                write(fildes[1], "hello!", strlen("hello!")); 

                write(fildes[1], "world!", strlen("world!")); 

                return 0; 

[bill@billstone Unix_study]$ make pipe1 

cc          pipe1.c      -o pipe1 

[bill@billstone Unix_study]$ ./pipe1 

[child] buf=[hello!world!] len[12]                              //  子进程一次就可以读出两次父进程写入的数据 

[bill@billstone Unix_study]$ 

    从上面可以看出,  在父进程中关闭 fildes[0],  向 fildes[1]写入数据;  在子进程中关闭 filedes[1],  从

fildes[0]中读取数据可实现从父进程流向子进程的管道. 

    在进程的通信中,  我们无法判断每次通信中报文的字节数,  即无法对数据流进行  自行拆分,  侧耳发生了上例中子进程一次性读取父进程两次通信的报文情况. 

管道是进程之间的一种单向交流方法,  要实现进程间的双向交流,  就必须通过两个管道来完成.  双向管道流的创立过程如下: 

    (1)  创建管道,  返回两个无名管道文件描述符 fildes1 和 fildes2: 

    (2)  创建子进程,  子进程中继承管道 fildes1 和 fildes2. 

    (3)  父进程关闭只读文件描述符 fildes1[0],  只写描述符 fildes2[1] 

    (4)  子进程关闭只写文件描述符 fildes1[1],  只读描述符 fildes2[0] 

    创建的结果如下: 

              父进程  --写--> fildes1[1] --管道--> fildes1[0] --读-->  子进程 

              父进程  <--读-- fildes2[0] <--管道-- fildes2[1] <--写--  子进程 

    这里实现一个父子进程间双向通信的实例:  父进程先向子进程发送两次数据,  再接收子进程传送刚来

的两次数据.

  为了正确拆分时间留从父进程流向子进程的管道采用'固定长度'方法传送数据;  从子进程流向

父进程的管道采用'显式长度'方法传回数据. 

    (1)  固定长度方式 

char bufG[255]; 

void WriteG(int fd, char *str, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                sprintf(bufG, "%s", str); 

                write(fd, bufG, len); 

char *ReadG(int fd, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                read(fd, bufG, len); 

                return(bufG); 

    在此设计中,  父子程序需要约定好每次发送数据的长度;  且长度不能超过 255 个字符.

    (2)  显式长度方式 

char bufC[255]; 

void WriteC(int fd, char str[]){ 

                sprintf(bufC, "%04d%s", strlen(str), str); 

                write(fd, bufC, strlen(bufC)); 

char *ReadC(int fd){ 

                int i, j; 

                memset(bufC, 0, sizeof(bufC)); 

                j = read(fd, bufC, 4); 

                i = atoi(bufC); 

                j = read(fd, bufC, i); 

                return(bufC); 

    父子进程约定在发送消息前先指明消息的长度. 

    (3)  主程序 

#include <unistd.h> 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

int main() 

                int fildes1[2], fildes2[2]; 

                pid_t pid; 

                char buf[255]; 

                assert(pipe(fildes1) == 0); 

                assert(pipe(fildes2) == 0); 

                assert((pid = fork()) >= 0); 

                if(pid == 0){ 

                                close(fildes1[1]); 

                                close(fildes2[0]); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                return(0); 

                } 

                close(fildes1[0]); 

                close(fildes2[1]); 

                WriteG(fildes1[1], "hello!", 10); 

                WriteG(fildes1[1], "world!", 10); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                return 0; 

    执行结果如下: 

[bill@billstone Unix_study]$ make pipe2 

cc          pipe2.c      -o pipe2 

[bill@billstone Unix_study]$ ./pipe2 

[child] buf = [hello!] 

[child] buf = [world!] 

[father] buf = [hello!] 

[father] buf = [world!] 

[bill@billstone Unix_study]$   

  dup dup2复制文件描述符

   

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。

下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:

方式一:mkfifo("myfifo","rw");

方式二:mknod myfifo p

生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。

    管道是 UNIX 中最古老的进程间通信工具,  它提供了进程之间的一种单向通信的方法. 

  popen 模型 

    从前面的程序可以看出,  创建连接标准 I/O 的管道需要多个步骤,  这需要使用大量的代码, UNIX 为了

简化这个操作,  它提供了一组函数实现之.  原型如下: 

#include <stdio.h> 

FILE *popen(const char *command, char *type); 

int pclose(FILE *stream); 

    函数 popen 调用成功时返回一个标准的 I/O 的 FILE 文件流,  其读写属性由参数 type 决定. 

    这里看一个模拟 shell 命令'ps -ef | grep init'的实例. 

[bill@billstone Unix_study]$ cat pipe3.c 

#include <stdio.h> 

#include <assert.h> 

int main() 

                FILE *out, *in; 

                char buf[255]; 

                assert((out = popen("grep init", "w")) != NULL);                //    创建写管道流 

                assert((in = popen("ps -ef", "r")) != NULL);                      //  创建读管道流 

                while(fgets(buf, sizeof(buf), in))                //  读取 ps -ef 的结果 

                                fputs(buf, out);                            //  转发到 grep init 

                pclose(out); 

                pclose(in); 

                return 0; 

[bill@billstone Unix_study]$ make pipe3 

cc          pipe3.c      -o pipe3 

[bill@billstone Unix_study]$ ./pipe3 

root                  1          0    0 Apr15 ?                00:00:04 init 

bill            1392    1353    0 Apr15 ?                00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients 

bill          14204 14203    0 21:33 pts/0        00:00:00 grep init 

[bill@billstone Unix_study]$ ps -ef | grep init 

root                  1          0    0 Apr15 ?                00:00:04 init 

bill            1392    1353    0 Apr15 ?                00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients 

bill          14207    1441    0 21:35 pts/0        00:00:00 grep init 

[bill@billstone Unix_study]$ 

    读者可以从上面自行比较同 Shell 命令'ps -ef | grep init'的执行结果. 

有名管道 FIFO 

    FIFO 可以在整个系统中使用. 

    在 Shell 中可以使用 mknod 或者 mkfifo 命令创建管道;  而在 C 程序中,  可以使用 mkfifo 函数创建有名管道. 

    要使用有名管道,  需要下面几个步骤: 

    (1)  创建管道文件 

    (2)  在某个进程中以只写方式打开管道文件,  并写管道 

    (3)  在某个进程中以只读方式打开管道文件,  并读管道 

    (4)  关闭管道文件. 

    低级文件编程库和标准文件编程库都可以操作管道.  管道在执行读写操作之前,  两端必须同时打开,  否

则执行打开管道某端操作的进程将一直阻塞到某个进程以相反方向打开管道为止. 

C代码 

  1. /*fifoserver.c:向FIFO中写入信息*/
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #define FIFO_SERVER "FIFO4"
  7. main(int argc,char** argv)  
  8. {  
  9. int fd=0;  
  10. char w_buf[4096];  
  11. int real_wnum;  
  12.     memset(w_buf,0,4096);  
  13. if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|0666)<0)&&(errno!=EEXIST))  
  14.         printf("cannot create fifoserver\n");  
  15. /*此处存在着打开依赖,即若没有读端打开FIFO的话,写端就阻塞在写端*/
  16.     fd=open(FIFO_SERVER,O_WRONLY);  
  17. if(fd==-1)  
  18.         printf("open error; no reading process\n");  
  19.     printf("%d\n",fd);  
  20.     real_wnum=write(fd,w_buf,2048);  
  21. if(real_wnum==-1)  
  22.         printf("write to fifo error; try later\n");  
  23. else
  24.         printf("real write num is %d\n",real_wnum);  
  25. /*往FIFO写入的数据都是原子的,如果没有足够的空间,则会等待,而不是一点一点的写入。*/
  26.     real_wnum=write(fd,w_buf,4096);  
  27. if(real_wnum==-1)  
  28.         printf("write to fifo error; try later\n");  
  29. else
  30.         printf("real write num is %d\n",real_wnum);  

C代码 

  1. /*fifoclient.c:从FIFO中读出数据*/
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #define FIFO_SERVER "FIFO4"
  7. main(int argc,char** argv)  
  8. {  
  9. char r_buf[4096];  
  10. int  fd;  
  11. int  r_size;  
  12. int  ret_size;  
  13.     r_size=atoi(argv[1]);  
  14.     memset(r_buf,0,sizeof(r_buf));  
  15.     fd=open(FIFO_SERVER,O_RDONLY);  
  16. if(fd==-1)  
  17.     {  
  18.         printf("open %s for read error\n");  
  19.         exit(1);  
  20.     }  
  21.     printf("%d\n",fd);  
  22. while(1)  
  23.     {  
  24.         ret_size=read(fd,r_buf,r_size);  
  25. if(ret_size==-1)  
  26.             printf("no data avlaible\n");  
  27. else
  28.             printf("real read bytes %d\n",ret_size);  
  29.         sleep(1);  
  30.     }     
  31.     unlink(FIFO_SERVER);  

    下面是一个简单的实例. 

    首先是写进程:  创建 FIFO 文件,  再打开写端口,  然后读取标准输入并将输入信息发送到管道中,  当键

盘输入'exit'或'quit'时程序退出. 

[bill@billstone Unix_study]$ cat fifo1.c 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

#include <sys/errno.h> 

extern int errno; 

int main() 

                FILE *fp; 

                char buf[255]; 

                assert((mkfifo("myfifo", S_IFIFO|0666) > 0) || (errno == EEXIST)); 

                while(1){ 

                                assert((fp = fopen("myfifo", "w")) != NULL); 

                                printf("please input: "); 

                                fgets(buf, sizeof(buf), stdin); 

                                fputs(buf, fp); 

                                fclose(fp); 

                                if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0) 

                                                break; 

                } 

                return 0; 

[bill@billstone Unix_study]$ make fifo1 

cc          fifo1.c      -o fifo1 

[bill@billstone Unix_study]$   

    然后是读进程:  打开管道的读端口,  从管道中读取信息(以行为单位),  并将此信息打印到屏幕上.  当读

取到'exit'或者'quit'时程序退出. 

[bill@billstone Unix_study]$ cat fifo2.c 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

int main() 

                FILE *fp; 

                char buf[255]; 

                while(1){ 

                                assert((fp = fopen("myfifo", "r")) != NULL); 

                                fgets(buf, strlen(buf), fp); 

                                printf("gets: [%s]", buf); 

                                fclose(fp); 

                                if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0) 

                                                break; 

                } 

                return 0; 

[bill@billstone Unix_study]$ make fifo2 

cc          fifo2.c      -o fifo2 

[bill@billstone Unix_study]$   

    在一个终端上执行 fifo1,  而在另一个终端上执行 fifo2. 

    我们先输入'hello', 'world',  然后再输入'exit'退出: 

[bill@billstone Unix_study]$ ./fifo1 

please input: hello 

please input: world 

please input: exit 

[bill@billstone Unix_study]$   

    我们可以看到读出结果如下: 

[bill@billstone Unix_study]$ ./fifo2 

gets: [hello 

]gets: [world]gets: [exit][bill@billstone Unix_study]$ 

    看到上面的输出结果,  您可能认为是我写错了.  其实不是的,  读出结果正是如此,  其实按照我们的本意,

正确的输出结果应该是这样的: 

[bill@billstone Unix_study]$ ./fifo2 

gets: [hello 

]gets: [world 

]gets: [exit


本文转自feisky博客园博客,原文链接:http://www.cnblogs.com/feisky/archive/2010/03/24/1693484.html,如需转载请自行联系原作者


相关文章
|
15天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
39 1
|
3天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
35 13
|
10天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
18天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
137 4
linux进程管理万字详解!!!
|
23天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
75 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
65 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
下一篇
DataWorks