对OS实验中的“管道”的一点儿理解

mac2024-07-05  54

管道通信机制 管道 pipe 是进程间通信最基本的一种机制。在内存中建立的管道称为无名管道, 在磁盘上建立的管道称为有名管道。无名管道随着进程的撤消而消失,有名管道则可 以长久保存,shell 命令符| 建立的就是无名管道,而 shell 命令 mkfifo 建立的是有名 管道。两个进程可以通过管道一个在管道一端向管道发送其输出,给另一进程可以在 管道的另一端从管道得到其输入.管道以半双工方式工作,即它的数据流是单方向的. 因此使用一个管道一般的规则是读管道数据的进程关闭管道写入端,而写管道进程关 闭其读出端。管道既可以采用同步方式工作也可以采用异步方式工作。

对“读管道数据的进程关闭管道写入端,而写管道进程关闭其读出端”的理解: 用反证法的思想,假如写管道的进程关闭管道写入端,那么,读管道进程已经不想读管道了,写管道进程还在源源不断地写入数据,那么管道是不是就会太慢而导致“炸裂”?,所以是读管道进程关闭管道的写入端。而写管道的进程关闭读出端,也很好理解,写管道的进程不想写了,它就关闭读出端,读管道进程读完管道中剩余的数据后自然也读不到数据了。 上面这句话的意思是,读管道数据的进程在读数据之前要关闭管道写入端,写管道进程在写数据之前关闭其读出端,在读管道数据的进程读完数据之后,关闭管道的读出端(0端),写管道进程在写完数据之后关闭其写入端(1端)。

匿名管道的局限性主要有两点:一是由于管道建立在内存中,所以它的容量不可能很大;二是管道所传送的是无格式字节流,这就要求使用管道的双方实现必须对传输的数据格式进行约定。

例子:在父子进程之间利用匿名管道通信。

#include <unist.h> #include <string.h> #include <wait.h> #include <stdio.h>

#define MAX_LINE 80

int main() { int testPipe[2], ret; char buf[MAX_LINE + 1]; const char * testbuf = “主程序发送的数据”;

if (pipe(testbuf) == 0) { if (fork() == 0) { ret = read(testPipe[0], buf, MAX_LINE); buf[ret] = 0; printf("子程序读到的数据为:%s", buf); close(testPipe[0]); }else { ret = write(testPipe[1], testbuf, strlen(testbuf)); ret = wait(NULL); close(testPipe[1]); } } return 0;

}

———————————————— 版权声明:本文为博主「Yngz_Miao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_38410730/article/details/81569852

pipe 系统调用的语法为: #include <unistd.h> int pipe(int pipe_id[2]); pipe 建立一个无名管道,pipe_id[0]中和 pipe_id[1]将放入管道两端的描述符,如果 pipe 执行成功返回 0。.出错返回-1. 管道读写的系统调用语法为: #include <unistd.h> ssize_t read(int pipe_id,const void *buf,size_t count); ssize_t write(int pipe_id,const void *buf,size_t count);

read 和 write 分别在管道的两端进行读和写。 pipe_id 是 pipe 系统调用返回的管道描述符。 Buf 是数据缓冲区首地址, count 说明数据缓冲区以 size_t 为单位的长度。 read 和 write 的返回值为它们实际读写的数据单位。

注意1.管道的读写默认的通信方式为同步读写方式,即如果管道读端无数据则读者阻塞直到数据到达,反之如果管道写端有数据则写者阻塞直到数据被读走。 注意2.上面说“Buf 是数据缓冲区首地址”,这里的数据缓冲区不要误以为是管道对应一片数据缓冲区,而是,往管道里写入数据时,可以把已有的一个数据缓冲区作为写入数据的数据来源,例如:这时的数据缓冲区可以是已经定义好内容的一个字符数组,那么这是一个“输入数据缓冲区”或者“写入数据缓冲区”,而从管道中读出数据时,把读出来的数据放在哪里呢?可以事先定义一个字符数组,然后把此数组名作为参数传入read()函数中,以便把读出的数据存放在这个数组中,那么,这个数组就相当于一个"输出数据缓冲区”/“读出数据缓冲区”,第三个参数可以传入数据缓冲区的大小(可以是你此前定义好的数组的大小)

疑问: 1.有管道的容量这个概念吗?每次最多可以写入或者读出多少个数据呢? 答:好像没有限制,读多少都可以?是面向字节流的。 命令 ulimit -a可以查看管道的大小,这是内核设定的为8*512byte=4k bit

管道的创建

管道是一种最基本的进程间通信机制。管道由pipe函数来创建:

SYNOPSIS #include <unistd.h> int pipe(int pipefd[2]); 调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。

pipe函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,需要注意pipefd [0]指向管道的读端, pipefd [1]指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过read(pipefd [0]);或者write(pipefd [1])进行操作。pipe函数调用成功返回0,否则返回-1。

pipe的特点:

只能单向通信

只能血缘关系的进程进行通信

依赖于文件系统

4、生命周期随进程

面向字节流的服务

管道内部提供了同步机制

说明:因为管道通信是单向的,在上面的例子中我们是通过子进程写父进程来读,如果想要同时父进程写而子进程来读,就需要再打开另外的管道;

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道的件描述符。 上面的例子是父进程把文件描述符传给子进程之后父子进程之 间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信, 总之 需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。(自己在实验过程中发现,程序中不一定要有fork()才能使用管道,同一个进程的多个线程也可以使用管道通信)

四个特殊情况:

如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样

如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

如果所有指向管道读端的文件描述符都关闭了,这时有进程指向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。

如果有指向管道读端的文件描述符没关闭,而持有管道写端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再write会阻塞,直到管道中有空位置了才写入数据并返回。

此段的参考来源:https://blog.csdn.net/qq_36829091/article/details/80138836 ·································································································

LINUX 管道实现的机制

从本质上说,管道也是一种文件,但他又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题

限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中该换冲区的大小为一页,4k

使得他的大小不像文件那样不加检验的增长。使用固定缓冲区也会带来问题,比如再写管道时可能变满

当这种情况发生时,随后对管道的write()调用被阻塞,等待某些数据被读取,以便腾出足够的空间供

write()调用。

读取工作也可能比写的进程快。当所有进程的数据被读取完时,一个随后的read()调用将默认的被阻塞、

管道变空。这种情况发生时,一个随后的read()调用将被默认的阻塞,等待某些数据被写入,这样就解决了read()

调用将被默认的阻塞,等待某些数据将被写入,这解决了read()调用返回文件结束的问题。 ———————————————— 版权声明:上面一段为博主「魏尔肖」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_35116353/article/details/60963287

最新回复(0)