Linux编程之pipe与fifo

本文详细介绍了Linux编程中的管道(pipe)和命名管道(fifo)的使用,包括创建、读写操作及阻塞模式。同时,讲解了popen函数如何在进程间建立管道,实现命令执行的输出重定向。通过实例代码演示了各种用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pipe的使用

管道(pipe)是POSIX标准中一种可用于进程间通信的单向数据通道。一个管道有一个写端和一个读端,数据从写端写入,从读端读取,因此读端和写端分别有一个文件描述符。

管道的读写模式可以是阻塞模式,也可以是非阻塞模式(O_NONBLOCK)。对于阻塞模式,如果管道中没有数据,那么读操作将阻塞,直到写入足够数据;如果管道已满,那么写操作将阻塞,直到读出数据留出足够空间。

管道有一个最大容量,可用pathconf(_PC_PIPE_BUF)获取。当写入的数据小于最大容量时,写入操作是原子的,内核会把数据全部写入管道;而当写入的数据大于最大容量时,则写操作不再是原子的,多个进程写的数据可能交织在一起。

用于创建管道的函数如下。

       #include <unistd.h>

       int pipe(int pipefd[2]);

该函数所带的参数实际上是输出参数。如果该函数成功,则返回两个文件描述符,pipefd[0]代表管道读端的文件描述符,pipefd[1]则代表管道写端的文件描述符。如果要修改管道的阻塞模式,可以用fcntl函数来实现。Linux还提供一个特有的函数pipe2,允许在创建管道时直接指定阻塞模式。该函数如下:

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int pipe2(int pipefd[2], int flags);

pipe函数创建的管道是匿名管道,因此只能在共享文件描述符的父子进程之间使用。Linux的man手册给出了一个例子:

       #include <sys/types.h>
       #include <sys/wait.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <string.h>

       int
       main(int argc, char *argv[])
       {
           int pipefd[2];
           pid_t cpid;
           char buf;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s <string>\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           if (pipe(pipefd) == -1) {
               perror("pipe");
               exit(EXIT_FAILURE);
           }

           cpid = fork();
           if (cpid == -1) {
               perror("fork");
               exit(EXIT_FAILURE);
           }

           if (cpid == 0) {    /* Child reads from pipe */
               close(pipefd[1]);          /* Close unused write end */

               while (read(pipefd[0], &buf, 1) > 0)
                   write(STDOUT_FILENO, &buf, 1);

               write(STDOUT_FILENO, "\n", 1);
               close(pipefd[0]);
               _exit(EXIT_SUCCESS);

           } else {            /* Parent writes argv[1] to pipe */
               close(pipefd[0]);          /* Close unused read end */
               write(pipefd[1], argv[1], strlen(argv[1]));
               close(pipefd[1]);          /* Reader will see EOF */
               wait(NULL);                /* Wait for child */
               exit(EXIT_SUCCESS);
           }
       }

编译并运行该程序可得到如期结果。

gcc -o test_pipe test_pipe.c
./test_pipe "Test pipe message"
Test pipe message

fifo的使用

FIFO则是一种命名的管道,但其编程使用方式与管道有所不同。首先应调用mkfifo函数来创建一个FIFO,该函数会在文件系统中创建一个FIFO特殊文件。然后进程可以分别调用open来打开这个FIFO特殊文件进行读和写。mkfifo函数原型如下。

       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

该函数有两个参数,第一个参数是一个文件路径,代表要创建FIFO特殊文件;第二个参数是访问模式。如果创建成功,返回值为0。

创建好FIFO特殊文件后,任何进程都可以打开FIFO进行读写操作。但是跟普通文件不同的是,阻塞模式下open操作成对进行才能继续进行读写操作。比如一个进程如果要打开FIFO进行读,那么这个open操作会阻塞,直到有某个进程打开它进行写操作;反之亦然。如果要使用非阻塞模式,那么在open是应在flags参数中指定O_NONBLOCK。

把前面pipe的例子稍微改一下,就可以得到fifo的示例。

       #include <sys/types.h>
       #include <sys/wait.h>
       #include <sys/stat.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <string.h>
       #include <fcntl.h>

       int
       main(int argc, char *argv[])
       {
	       int fd;
           pid_t cpid;
           char buf;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s <string>\n", argv[0]);
               exit(EXIT_FAILURE);
           }

	       if (mkfifo("/tmp/test_fifo", 0666) == -1) {
               perror("mkfifo");
               exit(EXIT_FAILURE);
           }

           cpid = fork();
           if (cpid == -1) {
               perror("fork");
               exit(EXIT_FAILURE);
           }

           if (cpid == 0) {    /* Child reads from fifo */
	           fd = open("/tmp/test_fifo", O_RDONLY);

               while (read(fd, &buf, 1) > 0)
                   write(STDOUT_FILENO, &buf, 1);

               write(STDOUT_FILENO, "\n", 1);
               close(fd);
               _exit(EXIT_SUCCESS);

           } else {            /* Parent writes argv[1] to fifo */
	       fd = open("/tmp/test_fifo", O_WRONLY);
               write(fd, argv[1], strlen(argv[1]));
               close(fd);          /* Reader will see EOF */
               wait(NULL);                /* Wait for child */
	           unlink("/tmp/test_fifo");
               exit(EXIT_SUCCESS);
           }
       }

编译并运行,可得到如期结果。

gcc -o test_fifo test_fifo.c
./test_fifo "Test fifo message"
Test fifo message

popen的使用

POSIX还提供了另外一个API以便更方便地使用管道,即popen函数。该函数原型如下。

       #include <stdio.h>

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

       int pclose(FILE *stream);

这个函数的作用是在调用popen的进程和执行command的进程之间建立一个管道。本质上,它组合pipe函数和fork函数的功能。第一个参数command指定要执行的命令,第二个参数则指定管道的类型(方向),可以为可读"r"或者可写"w",但不能为同时读写,因为管道是单向的。

对于可读"r"管道,管道的读端在调用进程,而管道的写端则在command进程的标准输出。而command进程的标准输入则继承自父进程也就是调用进程的标准输入,除非command进程自己做了重定向。默认情况下,管道是阻塞模式。也就是说command进程端写满时会等待调用进程读走才能继续;而调用进程读空时也会等待command进程写入才能继续。因此,这种管道的作用就是把command进程的标准输出重定向了。

对于可写"w"管道,管道的写端在调用进程,而管道的读端则在command进程的标准输入。而command进程的标准输出则继承自父进程也就是调用进程的标准输出,除非command进程自己做了重定向。同样,此管道默认也是阻塞模式,类似与"r"管道。因此,这种管道的作用就是把command进程的标准输入重定向了。

pclose函数用于关闭管道,它会等待关联的子进程退出后才返回。

下面这个例子演示了可读"r"管道的用法。从编译运行结果可以看到,子进程的printf输出都到了管道里,而父进程可从管道读取。

/*
 * test_popen.c: demostrates popen
 */
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

static void child_proc(void)
{
	printf("Hello from child\n");
	exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
	FILE *pipefp = NULL;
	char *self_exe;
	char child_cmd[1024];
	char buf;

	if (argc == 1) {
		self_exe = realpath(argv[0], NULL);
		snprintf(child_cmd, sizeof(child_cmd), "%s child", self_exe);
		free(self_exe);
		pipefp = popen(child_cmd, "r");
		if (!pipefp) {
			perror("popen");
			return -1;
		}
		while (fread(&buf, 1, 1, pipefp)) {
			printf("From pipe: %c\n", buf);
		}
		pclose(pipefp);
	} else {
		child_proc();
	}
	return 0;
}
gcc -o test_popen test_popen.c
./test_popen
From pipe: H
From pipe: e
From pipe: l
From pipe: l
From pipe: o
From pipe:
From pipe: f
From pipe: r
From pipe: o
From pipe: m
From pipe:
From pipe: c
From pipe: h
From pipe: i
From pipe: l
From pipe: d
From pipe:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值