管道的定义:
管道是一种进程间通信机制,也是Linux操作系统中的一种文件形式。一个进程写入管道的数据可以被另一个进程读取。数据按先进先出顺序处理。Linux有两种形式的管道文件,管道和FIFO。
管道提供一个单向的数据流【半双工管道】, 示意图:
管道和FIFO的异同:
管道没有名字,只能由“亲缘关系”的进程间进行通信时使用,例如父子进程间的通信。
FIFO被称为已命名管道(named pipe), 进程需要按照名称打开 FIFO。
管道或FIFO都可以使用read/write函数访问,且读写操作都是按顺序发生的,从文件的开头读取并在末尾写入(先进先出机制)。管道或 FIFO 必须同时在读写的两端打开。
对管道或FIFO,由于是半双工模式,write()函数总是往末尾添加数据,read()函数则总是从开头读出数据。如果对管道或FIFO调用lseek(), 会返回ESPIPE错误。
管道的创建:
管道由pipe函数创建
#include <unistd.h>
int pipe(int fd[2])
--创建一个管道并将管道读写端的文件描述符(分别)放入fd[0]和fd[1]
--管道成功创建时返回0
*有些版本的操作系统可以创建全双工管道,使用socketpair函数创建
管道创建的经典场景:
一个进程在它派生一个或多个子进程之前创建一个管道, 然后将管道用于父进程和子进程之间或两个兄弟进程之间的通信。
*单个进程使用管道与自己对话的场景,没有实现的意义
创建子进程的函数fork()
#include <sys/types.h>
#include <unistd.h>
pid_t fork( void )
--成功运行后,向子进程返回0,并向父进程返回子进程的进程ID
Demo1: 父进程关闭管道的读端,只往写端写入数据;子进程关闭管道的写端,只从读端读出数据
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
/*Read characters from pipe and echo them to stdout*/
void read_from_pipe(int file)
{
FILE *stream; /*流式文件结构体*/
int c;
stream = fdopen(file, "r");
while ((c = fgetc(stream)) != EOF)
putchar(c);
fclose(stream);
}
/*Write some random text to the pipe*/
void write_to_pipe(int file)
{
FILE *stream;
stream = fdopen(file, "w");
fprintf(stream, "hello, world!\n");
fprintf(stream, "goodbye, world!\n");
fclose(stream);
}
int main(void)
{
pid_t pid;
int mypipe[2];
/* Create the pipe. */
if(pipe(mypipe))
{
fprintf(stderr, "Pipe failed.\n");
return EXIT_FAILURE;
}
/* Create the child process. */
pid = fork();
/*在子进程中,fork返回0*/
if(pid == (pid_t)0)
{
/* This is the child process.
Close other end first. */
close(mypipe[1]);
read_from_pipe(mypipe[0]);
return EXIT_SUCCESS;
}
/*如果出现错误,fork返回一个负值*/
else if(pid < (pid_t)0)
{
/* The fork failed. */
fprintf(stderr, "Fork failed.\n");
return EXIT_FAILURE;
}
/*在父进程中,fork返回新创建子进程的进程ID*/
else
{
/* This is the parent process.
Close other end first. */
close(mypipe[0]);
write_to_pipe(mypipe[1]);
return EXIT_SUCCESS;
}
}
管道和标准输入/输出的交互:POPEN/PCLOSE
popen()的功能是 启动另外一个进程去执行一个shell命令行,调用popen的进程为父进程,由popen启动的进程称为子进程。popen函数还创建一个管道用于父子进程间通信
#include <stdio.h>
FILE *popen(const char *command, const char *type);
--运行成功时返回新文件流,没有正常调用fork()或pipe()时返回 NULL
--popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令
--参数type可使用"r"代表读取,"w"代表写入
--popen()会建立管道连到子进程的标准输入/输出设备,然后返回一个文件指针
int pclose(FILE *stream);
--运行成功时返回0,失败时返回-1
--pclose()用来关闭由popen()所建立的管道及文件指针,参数stream为先前由popen()所返回的文件指针
Demo2:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
FILE *fpr=NULL,*fpw=NULL;
char buf[256];
int ret;
fpr=popen("cat /etc/group","r");
fpw=popen("grep root","w");
while((ret=fread(buf,1,sizeof(buf),fpr))> 0)
{
fwrite(buf,1,ret,fpw);
}
pclose(fpr);
pclose(fpw);
return 0;
}
Demo3:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE *read_fp = NULL;
FILE *write_fp = NULL;
char buffer[BUFSIZ + 1];
int chars_read = 0;
//Initialize the buffer
memset(buffer,'\0', sizeof(buffer));
//Open ls and grep process
read_fp = popen("cd /opt; ls -l", "r");
write_fp = popen("grep rwxrwxr-x", "w");
//Both processes are opened successfully
if (read_fp && write_fp)
{
//read a data block
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while (chars_read> 0)
{
buffer[chars_read] ='\0';
//Write data to the grep process
fwrite(buffer, sizeof(char), chars_read, write_fp);
//There is still data to read, read the data cyclically, until all the data is read
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
}
//Close the file stream
pclose(read_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
FIFO(named pipe)的创建
FIFO与pipe不同的是,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。
FIFO可以由mkfifo()函数或者mknod函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *file_path, mode_t mode);
--创建成功返回0,失败返回-1
--file_path,是路径名,也是该FIFO的名字
--mode参数,定义在了<sys/stat.h>中, 指定了FIFO的权限
--mkfifo函数已隐含了 O_CREAT | O_EXCL
创建并打开一个管道只需要调用pipe(), 创建并打开一个FIFO,需要调用mkfifo()后再调用open()
管道在所有相关进程关闭它以后自动消失。FIFO的name则需要调用unlink()才能从文件系统中删除。
Demo4:
#define _POSIX_SOURCE
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
main() {
char fn[]="temp.fifo";
char out[20]="FIFO's are fun!", in[20];
int rfd, wfd;
if (mkfifo(fn, S_IRWXU) != 0)
perror("mkfifo() error");
else {
if ((rfd = open(fn, O_RDONLY|O_NONBLOCK)) < 0)
perror("open() error for read end");
else {
if ((wfd = open(fn, O_WRONLY)) < 0)
perror("open() error for write end");
else {
if (write(wfd, out, strlen(out)+1) == -1)
perror("write() error");
else if (read(rfd, in, sizeof(in)) == -1)
perror("read() error");
else printf("read '%s' from the FIFO\n", in);
close(wfd);
}
close(rfd);
}
unlink(fn);
}
}
*为了保证进程访问管道和FIFO的原子性,需要对管道和FIFO加以限制:
OPEN_MAX: 一个进程在任意时刻打开的最大描述符数
PIPE_BUF:可原子地写往一个管道或FIFO的最大数据量
shell脚本中的管道指令:
参考阅读:
https://www.ibm.com/docs/en/
https://www.gnu.org/software/libc/manual/html_node/Pipes-and-FIFOs.html
《UNIX网络编程 卷2 进程间通信 第2版》