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,如需转载请自行联系原作者


相关文章
|
12天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
40 4
linux进程管理万字详解!!!
|
3天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
36 8
|
12天前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
11天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
42 4
|
12天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
14天前
|
消息中间件 存储 Linux
|
20天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
21 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
20 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
29 0
Linux c/c++之IPC进程间通信
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能