4388|2

241

帖子

4

TA的资源

纯净的硅(初级)

楼主
 

Helper2416-33——Linux_Programing——管道 [复制链接]

本帖最后由 yuanlai2010 于 2014-8-22 18:16 编辑

管道
参与Helper2416开发板助学计划心得
这些天由于生活中的一些琐事耽搁了学习的进度,现在慢慢补上来!
么是管道?
当从一个进程连接数据流到另一个进程时,我们使用术语管道.通常我们使用管道把一个进程的输出连接到另一个进程的输入.

“|”(命令管道字符)
对于shell来说,命令的连接是通过管道字符来完成的,如下所示:
       Cmd1 | cmd2
Shell负责安排两个命令的标准输入和输出。
Cmd1的标准输入来自终端键盘。
Cmd1的标准输出传递给cmd2,作为他的标准输入。
Cmd2的标准输出连接到终端屏幕。

Pipe(无名管道):
那么在自己的程序中要获得这样的效果,可以通过无名管道pipe来实现。

pipe()创建无名管道pipe。
函数原型:int pipe(int filedes[2]);
函数功能:pipe()会建立管道,并将文件描述词由参数filedes数组返回。
                    filedes[0]为管道里的读取端
                    filedes[1]则为管道的写入端。
函数返回:若成功则返回零,否则返回-1,错误原因存于errno中。
            错误代码:
                         EMFILE进程已用完文件描述词最大量
                         ENFILE系统已无文件描述词可用。
                         EFAULT参数filedes 数组地址不合法。
参数说明:filedes指定创建了的管道的文件描述符的存放地址。
所属文件:

两个返回的文件描述符以一种特殊的方式连接起来,写到filedes[1]的所有数据都可以从filedes[0]读取回来。数据基于先进先出的原则进行处理,这就意味着如果把字节1、2、3写到filedes[1],从filedes[0]读取到的数据也会是1、2、3。

使用技巧:
* 程序中通过pipe调用来创建管道后,便可以自往一端写入数据,然后在另一端读取数据,但这样使用未免也太屈才pipe了,pipe的真正优势体现在父子进程的数据传递,当程序中用fork调用创建新进程的时候,原来打开的文件描述符仍将保持打开状态,所以我们只需要在fork之前调用pipe创建管道,便可以在两个进程之间通过管道传递数据了,但是当需要在新的进程中运行与父进程完全不相同的程序的时候,就需要通过exec调用来替换原先的进程,虽然exec不会关闭调用ecex之前已经打开的文件描述符,但是会失去对非标准输入输出文件描述符的索引,所以就需要通过参数告诉新替换的进程管道的文件描述符是几号,或者在exec之前把管道重定向到标准输入输出上去。

* 往往管道只用于单向传输数据,如果是父进程向子进程发送数据的时候,在父进程中只向filedes[1]写入数据,而关闭filedes[0],而在子进程中从filedes[0]读取数据,同时关闭filedes[1],需要从子进程向父进程中发送数据时,子进程使用filedes[1],关闭filedes[0],父进程中使用filedes[0],关闭filedes[1]。这是一种准双向的传输方式。

* 通过read调用从filedes[0]读取数据,当没有数据可读时,read调用通常会阻塞,即它将暂停进程来等待直到有数据到达为止。如果管道的另一端已被关闭,也就是说没有进程打开这个管道并向他写入数据了,这是read调用就会返回0,这就相当于检测文件结束一样。

* 如果跨越fork调用使用管道,就会有两个不同的文件描述符可以用于向管道写数据,一个在父进程中,一个在子进程中。只有把父子进程中的针对管道的写文件描述符都关闭,管道才会被认为是关闭了,对管道的read调用才会返回0.

下的实验就是跨fork使用管道,并在子进程中把管道的输出端重定向为标准输入,然后通过exec使用新的程序。完成 echo “yuanlai” | od –c 这条命令的效果:
代码实践
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <sys/types.h>

  6. int main()
  7. {
  8.         int data;
  9.         int file_pipes[2];
  10.         pid_t fork_result;
  11.         const char some_data[]= "yuanlai\n";
  12.         
  13.         if(pipe(file_pipes) == 0){
  14.                 fork_result = fork();
  15.                 if(fork_result == -1){
  16.                         fprintf(stderr, "Fork failure");
  17.                         exit(EXIT_FAILURE);
  18.                 }

  19.                 if(fork_result == 0){
  20.                         dup2(file_pipes[0], 0);
  21.                         close(file_pipes[0]);
  22.                         close(file_pipes[1]);

  23.                         execlp("od", "od", "-c", NULL);
  24.                         exit(EXIT_FAILURE);
  25.                 }else{
  26.                         close(file_pipes[0]);
  27.                         data = write(file_pipes[1], some_data,
  28.                                         strlen(some_data));
  29.                         close(file_pipes[1]);
  30.                         printf("%d - wrote %d bytes\n", getpid(), data);
  31.                         wait();
  32.                 }
  33.         }
  34.         exit(EXIT_SUCCESS);
  35. }
复制代码
运行结果
  1. [jyxtec@localhost pipe]$ vim pipe0.c
  2. [jyxtec@localhost pipe]$ gcc pipe0.c -o pipe0
  3. [jyxtec@localhost pipe]$ ./pipe0
  4. 2656 - wrote 8 bytes
  5. 0000000   y   u   a   n   l   a   i  \n
  6. 0000010
  7. [jyxtec@localhost pipe]$
  8. [jyxtec@localhost pipe]$ echo "yuanlai"|od -c
  9. 0000000   y   u   a   n   l   a   i  \n
  10. 0000010
  11. [jyxtec@localhost pipe]$
复制代码
通过代码运行结果和命令的结果,管道实践成功!


FIFO(命名管道)
前面提到的pipe只能用与父子进程间的数据传递,如果是非父子进程间的数据传递就可以使用FIFO(命名管道 named pipe)文件来完成这项工作。命名管道是一种特殊的文件,它在文件系统中以命名文件的形式存在,也用open和close函数打开和关闭,但它的行为却和pipe类似。

创建FIFO的方式:
1:命令行创建FIFO
       $ mkfifo filename

2:在C程序中创建
mkfifo()创建命名管道FIFO。
函数原型:int mkfifo(const char * filename, mode_tmode);
函数功能:创建一个命名管道。
函数返回:若成功则返回零,否则返回-1,错误原因存于errno中。
参数说明:filename指定要被创建的FIFO的文件名;
                mode指定FIFO的访问权限。
表头文件:#include
                #include

通过ls –l查看创建好的FIFO的属性时可以看到第一个字符是p,表示这是一个管道。


FIFO文件的访问

1:使用open打开FIFO文件
和pipe的使用方式一样,FIFO也是一种准双向的传输方式,程序不能以O_RDWR模式打开FIFO文件进行操作。
打开FIFO可能会用到open_flag的O_NONBLOCK选项,它与O_RDONLY 、O_WRONLY共有以下四种组合方式:
(1):open(const char *path, O_RDONLY);
在这种情况下,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不会返回。
(2):open(const char *path, O_RDONLY | O_NONBLOCK);
即使没有其他进程以写方式打开FIFO,这个open调用也将成功并立即返回。
(3):open(const char *path, O_WRONLY);
在这种情况下,open调用将阻塞,直到有一个进程以读方式打开同一个FIFO为止。
(4):open(const char *path, O_WRONLY |NONBLOCK);
这个函数调用总是立刻返回,如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1并且FIFO也不会被打开。

2:对FIFO进行读写操作
(引用http://www.cnblogs.com/xmphoenix/archive/2011/12/16/2290456.html比书上写的详细)

读操作:
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。

* 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。

* 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。

* 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。

* 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

写操作:
对于设置了阻塞标志的写操作(没有使用O_NONBLOCK标志打开):

* 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。

* 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

对于没有设置阻塞标志的写操作(使用O_NONBLOCK标志打开):

* 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

* 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

在limits.h中有对PIPE_BUF的定义。

面是阻塞模式的FIFO实践代码:在两个不相关的进程间通过FIFO传递数据。
代码实践:
fifo0.c
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <fcntl.h>
  6. #include <limits.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>

  9. #define FIFO_NAME "/tmp/my_fifo"
  10. #define BUFFER_SIZE PIPE_BUF
  11. #define TEN_MEG (1024 * 1024 * 100)

  12. int main()
  13. {
  14.         int pipe_fd;
  15.         int res;
  16.         int open_mode = O_WRONLY;
  17.         int bytes_send  =  0;
  18.         char buffer[BUFFER_SIZE + 1];

  19.         if(access(FIFO_NAME,F_OK) == -1){
  20.                 res = mkfifo(FIFO_NAME, 0777);
  21.                 if(res != 0){
  22.                         fprintf(stderr, "could not create fifo %s\n", FIFO_NAME);
  23.                         exit(EXIT_FAILURE);
  24.                 }
  25.         }
  26.         
  27.         printf("Process %d opening FIFO O_WRONLY\n", getpid());
  28.         pipe_fd = open(FIFO_NAME, open_mode);
  29.         printf("Process %d result %d\n", getpid(), pipe_fd);

  30.         if(pipe_fd != -1){
  31.                 while(bytes_send < TEN_MEG){
  32.                         res = write(pipe_fd, buffer, BUFFER_SIZE);
  33.                         if(res == -1){
  34.                                 fprintf(stderr, "Write error on pipe\n");
  35.                                 exit(EXIT_FAILURE);
  36.                         }
  37.                         bytes_send += res;
  38.                 }
  39.                 close(pipe_fd);        
  40.         }else{
  41.                 exit(EXIT_FAILURE);        
  42.         }
  43.         
  44.         printf("Process %d finished\n",getpid());
  45.         exit(EXIT_SUCCESS);
  46. }
复制代码
fifo1.c
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <fcntl.h>
  6. #include <limits.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>

  9. #define FIFO_NAME "/tmp/my_fifo"
  10. #define BUFFER_SIZE PIPE_BUF

  11. int main()
  12. {
  13.         int pipe_fd;
  14.         int res;
  15.         int open_mode = O_RDONLY;
  16.         int bytes_read  =  0;
  17.         char buffer[BUFFER_SIZE + 1];

  18.         memset(buffer, '\0', sizeof(buffer));
  19.         
  20.         printf("Process %d opening FIFO O_RDONLY\n", getpid());
  21.         pipe_fd = open(FIFO_NAME, open_mode);
  22.         printf("Process %d result %d\n", getpid(), pipe_fd);

  23.         if(pipe_fd != -1){
  24.                 do{
  25.                         res = read(pipe_fd, buffer, BUFFER_SIZE);
  26.                         bytes_read += res;
  27.                 }while(res > 0);
  28.                 close(pipe_fd);        
  29.         }else{
  30.                 exit(EXIT_FAILURE);        
  31.         }
  32.         
  33.         printf("Process %d finished, %d bytes read\n",getpid(), bytes_read);
  34.         exit(EXIT_SUCCESS);
  35. }
复制代码
运行结果:
  1. [jyxtec@localhost fifo]$ ./fifo0 &
  2. [1] 2420
  3. [jyxtec@localhost fifo]$ Process 2420 opening FIFO O_WRONLY
  4. time ./fifo1
  5. Process 2422 opening FIFO O_RDONLY
  6. Process 2420 result 3
  7. Process 2422 result 3
  8. Process 2420 finished
  9. Process 2422 finished, 104857600 bytes read
  10. [1]+  完成                  ./fifo0

  11. real        0m0.084s
  12. user        0m0.009s
  13. sys        0m0.063s
  14. [jyxtec@localhost fifo]$
复制代码
两个进程都是阻塞模式的FIFO,先启动fifo0(写进程),它将阻塞以等待读进程打开这个FIFO。Fifo1(读进程)启动以后,写进程接触阻塞并开始向管道写数据。同时,读进程也开始从管道中读取数据。(写进程在管道满时阻塞,读进程在管道空是阻塞

论坛ID:yuanlai2010
发表时间:2014-08-22

最新回复

这些基础知识学起来挺枯燥的,但是用起来的确很好用!  详情 回复 发表于 2014-8-22 19:59
点赞 关注

回复
举报

554

帖子

0

TA的资源

版主

沙发
 
这些基础知识学起来挺枯燥的,但是用起来的确很好用!

点评

BOSS说的是,学习这个确实比较枯燥,但也希望这样能让基础牢固一点!  详情 回复 发表于 2014-8-22 20:20
 
个人签名My dreams will go on...
http://www.jyxtec.com
 

回复

241

帖子

4

TA的资源

纯净的硅(初级)

板凳
 
spacexplorer 发表于 2014-8-22 19:59
这些基础知识学起来挺枯燥的,但是用起来的确很好用!

BOSS说的是,学习这个确实比较枯燥,但也希望这样能让基础牢固一点!
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/6 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表