本帖最后由 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 这条命令的效果:
代码实践:
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/types.h>
- int main()
- {
- int data;
- int file_pipes[2];
- pid_t fork_result;
- const char some_data[]= "yuanlai\n";
-
- if(pipe(file_pipes) == 0){
- fork_result = fork();
- if(fork_result == -1){
- fprintf(stderr, "Fork failure");
- exit(EXIT_FAILURE);
- }
- if(fork_result == 0){
- dup2(file_pipes[0], 0);
- close(file_pipes[0]);
- close(file_pipes[1]);
- execlp("od", "od", "-c", NULL);
- exit(EXIT_FAILURE);
- }else{
- close(file_pipes[0]);
- data = write(file_pipes[1], some_data,
- strlen(some_data));
- close(file_pipes[1]);
- printf("%d - wrote %d bytes\n", getpid(), data);
- wait();
- }
- }
- exit(EXIT_SUCCESS);
- }
复制代码
运行结果:
- [jyxtec@localhost pipe]$ vim pipe0.c
- [jyxtec@localhost pipe]$ gcc pipe0.c -o pipe0
- [jyxtec@localhost pipe]$ ./pipe0
- 2656 - wrote 8 bytes
- 0000000 y u a n l a i \n
- 0000010
- [jyxtec@localhost pipe]$
- [jyxtec@localhost pipe]$ echo "yuanlai"|od -c
- 0000000 y u a n l a i \n
- 0000010
- [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
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #define FIFO_NAME "/tmp/my_fifo"
- #define BUFFER_SIZE PIPE_BUF
- #define TEN_MEG (1024 * 1024 * 100)
- int main()
- {
- int pipe_fd;
- int res;
- int open_mode = O_WRONLY;
- int bytes_send = 0;
- char buffer[BUFFER_SIZE + 1];
- if(access(FIFO_NAME,F_OK) == -1){
- res = mkfifo(FIFO_NAME, 0777);
- if(res != 0){
- fprintf(stderr, "could not create fifo %s\n", FIFO_NAME);
- exit(EXIT_FAILURE);
- }
- }
-
- printf("Process %d opening FIFO O_WRONLY\n", getpid());
- pipe_fd = open(FIFO_NAME, open_mode);
- printf("Process %d result %d\n", getpid(), pipe_fd);
- if(pipe_fd != -1){
- while(bytes_send < TEN_MEG){
- res = write(pipe_fd, buffer, BUFFER_SIZE);
- if(res == -1){
- fprintf(stderr, "Write error on pipe\n");
- exit(EXIT_FAILURE);
- }
- bytes_send += res;
- }
- close(pipe_fd);
- }else{
- exit(EXIT_FAILURE);
- }
-
- printf("Process %d finished\n",getpid());
- exit(EXIT_SUCCESS);
- }
复制代码 fifo1.c- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #define FIFO_NAME "/tmp/my_fifo"
- #define BUFFER_SIZE PIPE_BUF
- int main()
- {
- int pipe_fd;
- int res;
- int open_mode = O_RDONLY;
- int bytes_read = 0;
- char buffer[BUFFER_SIZE + 1];
- memset(buffer, '\0', sizeof(buffer));
-
- printf("Process %d opening FIFO O_RDONLY\n", getpid());
- pipe_fd = open(FIFO_NAME, open_mode);
- printf("Process %d result %d\n", getpid(), pipe_fd);
- if(pipe_fd != -1){
- do{
- res = read(pipe_fd, buffer, BUFFER_SIZE);
- bytes_read += res;
- }while(res > 0);
- close(pipe_fd);
- }else{
- exit(EXIT_FAILURE);
- }
-
- printf("Process %d finished, %d bytes read\n",getpid(), bytes_read);
- exit(EXIT_SUCCESS);
- }
复制代码
运行结果:
- [jyxtec@localhost fifo]$ ./fifo0 &
- [1] 2420
- [jyxtec@localhost fifo]$ Process 2420 opening FIFO O_WRONLY
- time ./fifo1
- Process 2422 opening FIFO O_RDONLY
- Process 2420 result 3
- Process 2422 result 3
- Process 2420 finished
- Process 2422 finished, 104857600 bytes read
- [1]+ 完成 ./fifo0
- real 0m0.084s
- user 0m0.009s
- sys 0m0.063s
- [jyxtec@localhost fifo]$
复制代码
两个进程都是阻塞模式的FIFO,先启动fifo0(写进程),它将阻塞以等待读进程打开这个FIFO。Fifo1(读进程)启动以后,写进程接触阻塞并开始向管道写数据。同时,读进程也开始从管道中读取数据。(写进程在管道满时阻塞,读进程在管道空是阻塞)
论坛ID:yuanlai2010
发表时间:2014-08-22