说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
《Linux系统编程》、《Linux网络编程》、《Unix环境高级编程》
linux中管道的概念,浅谈Linux管道、【Linux】Linux的管道
目录:
管道(pipe)
零、概念:
管道是UNIX族中进程通信的最古老的方式,本质也是一种文件,但与一般文件又有所不同,本身并不占用磁盘或其他外部存储空间,实现上占用内存空间,故管道实际上是一种:操作方式为文件的内存缓冲区。
利用内核在两个进程之间建立通道,将两进程之间的标准输入与标准输出连接起来的机制,利用读写的方式在进程之间传递数据。
一、匿名管道:
又可称作:无名管道、半双工管道,在shell中使用“|
”表示管道。
是具有公共祖先进程之间通信的一种方式:
父进程在产生子进程前创建管道文件,这样子进程通过拷贝父进程地址空间可获取到该文件描述符,该文件描述符的文件在父子进程双方所共享并设置只有一方可写,另一方可读,则该文件被称为管道,父子进程通过该文件(管道)以达到通信的目的。
总结:由于该文件无文件名,故非亲进程不能打开,只可用于亲属进程通信,又由于无文件名故被称作匿名管道。
(1)pipe()——创建一个管道
函数功能:
创建一个管道,一个单向的数据通道,可用于进程间通信。
数组pipefd
用于返回指向管道末端的两个文件描述符。pipefd[0]
表示管道的读取端。pipefd[1]
表示管道的写端。写入管道的写端数据被内核缓冲,直到从管道的读端读取数据为止。
项目 | 说明 |
---|---|
函数原型 | int pipe(int pipefd[2]); |
头文件 | unistd.h |
参数说明 | pipefd[2]:文件描述符 |
返回值 | 若成功则返回0 若失败则返回-1并设置errno,并保持pipefd不变。 |
注意 | ①fildes[0]是一个具有“只读”属性的文件描述符 ②fildes[1]是一个具有“只写”属性的文件描述符 |
pipe()的示例实践:
<1>源代码:pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main (int argc,char *argv[])
{
/* Step 1 初始化 */
pid_t pid; // PID值
int result = 1; //管道创建结果
int fd[2]; //文件描述符
int nbytes; //字符个数
char string[]="你好,管道";
char readbuffer[80];
/*文件描述符0用于读,文件描述符1用于写*/
int *read_fd=&fd[0]; //读文件描述符
int *write_fd= &fd[1]; //写文件描述符
/* Step 2 建立管道 */
result = pipe(fd);
if(result == -1) //管道创建失败
{
printf("建立管道失败\n");
return -1;
}
/* Step 3 新建进程 */
pid = fork();
if( pid == -1) //新建进程失败
{
printf("fork 进程失败\n");
return -1;
}
/*Step 4 父子进程间的通讯*/
if(pid == 0) /* 子进程 */
{
close(*read_fd); //关闭读端
/*向管道端写入字符*/
result = write(*write_fd,string,strlen(string));
return 0;
}
else /* 父进程 */
{
close(*write_fd); //关闭写端
/*从管道读取数值*/
nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
printf("接收到%d个数据,内容为:%s\n",nbytes,readbuffer);
}
return 0;
}
<2>编译运行及输出结果:
①编译:gcc pipe.c -o pipe
②运行:./pipe
③输出结果:接收到13个数据,内容为:你好,管道
管道阻塞和管道的原子性的描述与示例实践
1️⃣当管道的写端没有关闭时:
①若写请求的字节数 大于 阈值PIPE_BUF
时,
写操作返回值是 管道中 目前的 数据字节数
②若写请求的字节数 不大于 阈值PIPE_BUF
时,
写操作返回值是 管道中现有的 数据字节数(管道中数据量 小于 请求的数据量)
写操作返回值是 请求的字节数 数据字节数(管道中数据量 不小于 请求的数据量)
2️⃣当管道进行写入操作时:
①若写入数据的数目 小于128K 时,写入是非原子的。
②若父进程中两次写入字节数都改为128K(写入管道数据量 大于128K 时),缓冲区的数据将被连续的写入管道,知道数据全部写完为止,若没有进程读数据,则一直阻塞。
PS:
PIPE_BUF
在include/Linux/limits.h
中定义,不同内核版本可能有区别。
3️⃣示例实践:
①源文件:test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define K 1024
#define WRITELEN (128*K)
int main (int argc,char *argv[])
{
/* Step 1 初始化 */
pid_t pid; // PID值
int result = 1; //管道创建结果
int fd[2]; //文件描述符
int nbytes; //字符个数
char string[WRITELEN]="你好,管道";
char readbuffer[10*K]={0}; //读缓冲区
/*文件描述符0用于读,文件描述符1用于写*/
int *read_fd=&fd[0]; //读文件描述符
int *write_fd= &fd[1]; //写文件描述符
/* Step 2 建立管道 */
result = pipe(fd);
if(result == -1) //管道创建失败
{
printf("建立管道失败\n");
return -1;
}
/* Step 3 新建进程 */
pid = fork();
if( pid == -1) //新建进程失败
{
printf("fork 进程失败\n");
return -1;
}
/*Step 4 父子进程间的通讯*/
if(pid == 0) /* 子进程 */
{
int write_size = WRITELEN; //写入的长度
result = 0; //结果
close(*read_fd); //关闭读端
/*向管道端写入字符*/
while (write_size >= 0)
{
result = write(*write_fd,string,write_size);
if (result > 0) /* 写入成功 */
{
write_size -= result; //写入的长度
printf("写入%d个数据,剩余%d个数据\n",result,write_size);
}
else /* 写入失败 */
{
sleep(10); //等待10s,读端将数据读出
}
}
return 0;
}
else /* 父进程 */
{
close(*write_fd); //关闭写端
/*从管道读取数值*/
while (1) //一直读取数据
{
nbytes = read(*read_fd,readbuffer,sizeof(readbuffer));
if (nbytes <= 0) //读取数据失败
{
printf("没有数据写入了\n");
break; //退出循环
}
printf("接收到%d个数据,内容为:%s\n",nbytes,readbuffer);
}
}
return 0;
}
②编译运行及结果:
<1>编译:gcc test.c -o test
<2>运行:./test
<3>结果:
接收到10240个数据,内容为:你好,管道
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到10240个数据,内容为:
接收到8192个数据,内容为:
写入131072个数据,剩余0个数据
③分析:
可发现,父进程每次读取10K字节数据,读13次将全部数据读完,最后一次读数据由于缓冲区只剩下8K数据,故仅读取8K。
字进程一次性写入128K字节数据,父进程将全部数据读取完毕时,子进程的write()
函数才打印出对应信息。
(2)popen()——创建一个shell进程的管道流
1.函数功能:
通过创建管道、fork()
和调用shell来打开进程。由于管道的定义是单向的,type参数可以只指定读或写,而不能同时指定读或写;相应的,生成的流是只读或只写的。
项目 | 说明 |
---|---|
函数原型 | FILE *popen(const char *command, const char *type); |
头文件 | stdio.h |
参数说明 | command:命令字符串 指向一个包含shell命令行的以空结束的字符串 |
type:类型字符串 指向以空字符结束的字符串的指针,该字符串必须包含用于读取的字母'r'或用于写入的字母'w' | |
返回值 | 成功返回一个指向打开流的指针,该指针可用于读取或写入管道; 失败返回NULL并将errno设置为适当的值 |
注意 | ①必须用pclose()而不是fclose(3)关闭,②默认情况下是块缓冲的。 |
(3)pclose()——关闭一个shell进程的管道流
1.函数功能:
等待关联的进程终止,并返回wait4(2)
返回的命令退出状态。
项目 | 说明 |
---|---|
函数原型 | int pclose(FILE *stream); |
头文件 | stdio.h |
参数说明 | stream:文件流 |
返回值 | 成功返回命令的退出状态; 失败返回-1并将errno设置为适当的值 |
注意 |
popen()和pclose()示例实践
1.项目描述:
调用myuclc执行程序,实现终端输入大写字符过滤为小写字符。
2.具体实现:
STEP 1: 先编译生成“将大写字符转化为小写字符的过滤程序”myuclc并测试功能。
源文件:myuclc.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
/* 将大写字符转化为小写字符的过滤程序 */
int main(int argc, char *argv[])
{
int c;
while( (c = getchar()) != EOF )
{
if(isupper(c))
c = tolower(c);
if(putchar(c) == EOF)
fprintf(stderr,"output error");
if( c == '\n')
fflush(stdout);
}
return 0;
}
bash过程:
xsndz@computer:$ gcc myuclc.c -o myuclc
xsndz@computer:$ ./myuclc
HELLO WORLD
hello world
^C
STEP 2: 编译popen.c
文件
源文件:popen.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAXLINE 1024
int main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin;
if ((fpin = popen("./myuclc", "r")) == NULL)
fprintf(stderr, "popen error");
for (;;)
{
fputs("prompt> ", stdout);
fflush(stdout);
if (fgets(line, MAXLINE, fpin) == NULL)
break;
if (fputs(line, stdout) == EOF)
fprintf(stderr, "fputs error to pipe");
}
if (pclose(fpin) == -1)
fprintf(stderr, "pclose error");
putchar('\n');
return 0;
}
bash过程:
xsndz@computer:$ gcc popen.c -o popen
xsndz@computer:$ ./popen
prompt> HELLO WORLD
hello world
prompt> ^C
二、命名管道(FIFO):
由于匿名管道没有文件名,无法实现不同进程的通信,故便产生 命名管道。
由于命名管道不支持诸如lseek()
等文件定位操作,严格遵守先进先出的原则进行传输数据,即对管道的读总是从开始处返回数据,对它的写总是把数据添加到末尾,所以这种管道也叫做 FIFO文件
命名管道提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,在文件系统中产生一个物理文件,其他进程只要访问该文件路径,就能彼此通过管道通信。在读数据端以只读方式打开管道文件,在写数据端以只写方式打开管道文件.
命名管道与普通的管道区别。
①在文件系统中命名管道是以设备特殊文件的形式存在的。
②不同的进程可以通过命名管道共享数据。
FIFO文件(命名管道)与普通文件的区别:
①普通文件无法实现字节流方式管理,而且多进程之间访问共享资源会造成意想不到的问题;
②FIFO文件采用字节流方式管理,遵循先入先出原则,不涉及共享资源访问。
(4)mkfifo()——创建一个FIFO文件
① shell创建,指令:mkfifo
目录/ipc下建立一个名为namedfifo的命名管道
mkfifo /ipc/namedfifo
ls -l /ipc/namedfifo
② C语言创建
函数介绍:创建一个命名管道。【详细可查看:man 3 mkpipe
】
项目 | 说明 |
---|---|
函数原型 | int mkfifo(const char* pathname,mode_t mode); |
头文件 | sys/types.h、sys/stat.h |
参数说明 | pathname:命名管道文件名 |
mode:操作权限(与open相同) | |
返回值 | 若成功则返回0 若失败则返回-1 |
注意 |
FIFO操作
对命名管道FIFO来说,IO操作与普通的管道IO操作基本上是一样的,二者之间存在着一个主要的区别。
在 FIFO中,必须使用一个open()
函数来显式地建立联接到管道的通道。
一般来说FIFO总是处于阻塞状态。也就是说,如果命名管道FIFO打开时设置了读权限,则读进程将一直“阻塞”,一直到其他进程打开该FIFO并且向管道中写入数据。这个阻塞动作反过来也是成立的,如果一个进程打开一个管道写入数据,当没有进程冲管道中读取数据的时候,写管道的操作也是阻塞的,直到已经写入的数据被读出后,才能进行写入操作。如果不希望在进行命名管道操作的时候发生阻塞,可以在 open()
调用中使用O_NONBLOCK
标志,以关闭默认的阻塞动作。