管道

管道

管道是Linux中进程通信的一种方式。这里所说的管道指无名管道,它具有以下特点:

  • 它只能用于具有亲缘关系的进程之间通信
  • 它是一个半双工的通信模式,具有固定的读端和写端
  • 管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。但它不是普通文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中

管道的创建与关闭

管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读,fds[1]固定用于写,这样就构成了一个半双工的通道。管道的关闭只需逐个关闭各个文件描述符即可

无名管道

管道的创建可以通过pipe()实现:

1
2
#include <unistd.h>
int pipe(int fd[2]);

成功返回0,出错返回-1

管道的读写说明

用pipe()创建的管道两端出于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。实际上,通常是先创建一个管道,再通过fork()函数创建一子进程,这时子进程会继承父进程所创建的管道,这是父子进程管道的对应关系如下图:

父子进程管道关系

这时父子进程都有了自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端关闭即可,例如将父进程的写端和子进程的读端关闭,这时就建立了一条“子进程写入,父进程读取”的通道:

子写父读管道

同样也可以关闭父进程的读端,子进程的写端,这样就可以建立一条“子进程读取,父进程写入”的通道

pipe使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*pipe*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_DATA_LEN 256
#define DELAY_TIME 1

int main ()
{
pid_t pid;
int fds[2];
char buf[MAX_DATA_LEN] = {0};
const char data[] = "Pipe Test Program";
int real_read, real_write;

if (pipe (fds) < 0)
{
perror ("pipe");
exit (1);
}

printf ("Test start!\n");

if ((pid = fork ()) == 0)
{
close (fds[1]);
sleep (DELAY_TIME * 3);

if ((real_read = read (fds[0], buf, MAX_DATA_LEN)) > 0)
{
printf ("%d bytes read from the pipe is '%s'\n", real_read, buf);
}
else
{
perror ("read");
}

close (fds[0]);
exit (0);
}
else if (pid > 0)
{
close (fds[0]);
sleep (DELAY_TIME);

if ((real_write = write (fds[1], data, strlen (data))) != -1)
{
printf ("Parent wrote %d bytes : '%s'\n", real_write, data);
}
else
{
perror ("write");
}

close (fds[1]);

waitpid(pid, NULL, 0);
}

printf ("Test end!\n");
return 0;
}

标准流管道

与Linux的文件操作中有基于文件流的标准I/O一样,管道的操作也支持基于文件流的模式。标准流管道将一系列的创建过程合并到一个函数popen()中完成,它完成工作有以下几步:

  • 创建一个管道
  • fork()一个子进程
  • 在父子进程中关闭不需要的文件描述符
  • 执行exec函数族调用
  • 执行函数中所指定的命令

这个函数可以大大减少代码的编写量,但同时也有一些不利之处,它不如管道创建函数那样灵活,并且用popen()创建的管道必须使用标准I/O函数进行操作,但不能使用read(),write()一类不带缓冲的I/O函数

关闭用popen()创建的管道,必须使用函数pclose()来关闭,该函数关闭标准I/O流,并等待命令执行结束

1
2
3
#includ <stdio.h>
FILE *popen (const char *command, const char *type);
int pclose (FILE *stream);

command:指向一个以null结尾的字符串,这个字符串包含一个shell命令

type:“r”,文件指针连接到command的标准输出;“w”,文件指针连接到command的标准输入

成功返回文件流指针,失败返回-1

popen在Linux嵌入式软件中常用方式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define POPEN_READ_BUFF 1024

int popen_read(const char* cmd, char* res, int len)
{
FILE* fp;
char buf[POPEN_READ_BUFF];
int l, nread = 0;

if (cmd == NULL || res == NULL)
{
printf("Invalid parameter\n");
return -1;
}
if(len <= 0)
{
printf("Invalid len\n");
return -1;
}
len--; //Reserve a bit to save '\0'

fp = popen(cmd, "r");
if(fp == NULL)
{
printf("popen % failed!\n", cmd);
return -1;
}

while (fgets(buf, POPEN_READ_BUFF, fp) != NULL)
{
l = strlen(buf);
if (l >= len - nread)
{
memcpy(res + nread, buf, len - nread);
res[len + 1] = '\0';
pclose(fp);
return len;
}
else
{
memcpy(res + nread, buf, l + 1);
}
nread += l;
}
pclose(fp);
return nread;
}

传入命令、接收执行结果的空间以及空间大小,返回读取的字节数


管道
https://carl-5535.github.io/2021/05/31/Linux系统编程/管道/
作者
Carl Chen
发布于
2021年5月31日
许可协议