栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【操作系统】管道

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【操作系统】管道

【操作系统】管道
  • 管道命令
  • 管道概述
    • 管道文件描述符
    • 为什么需要两个进程
  • 练习1 双向管道
  • 练习2 顺序通信的并发进程

管道命令

在Unix/Linux中,管道命令:cmd1 | cmd2包含一个管道符 “|”,cmd1是写入端,cmd2是读取端。
shell将通过一个进程运行cmd1,通过另一个进程运行cmd2,它们通过管道连接在一起,cmd1是cmd2的输入。

处理过程
(1)shell从标准输入获取命令行cmd1|cmd2时,会fork出一个子进程shell,并等待子进程shell终止。
(2)子进程shell:解析命令发现有|符号,于是将命令划分为head=cmd1,tail=cmd2。
(3)子进程执行以下代码段:

int pd[2];
pipe(pd); 			// 系统调用,创建管道
pid=fork();			// 创建子进程
if(pid){			// 父进程作为写入端
	close(pd[0]);   // 写入端必须关闭pd[0]
	close(1);		// 关闭标准输入
	dup(pd[1]);		// 复制pd[1]为标准输入
	close(pd[1]);   // 关闭pd[1]
	exec(head);		// 执行cmd1
}
else{				// 子进程作为读取端
	close(pd[1]);   // 读取端必须关闭pd[1]
	close(0);		// 关闭标准输出
	dup(pd[0]);		// 复制pd[0]为标准输出
	close(pd[0]);   // 关闭pd[0]
	exec(tail);     // 执行cmd2
}
管道概述

管道是用于进程交换数据的单向进程间通信通道。管道有一个读取端和一个写入端,可以从读取端读取写入端写入的数据,读取和写入通道通常是同步、阻塞的。

工作方式
(1)当读进程从管道上读取数据时,如果管道上有数据,读进程会根据需要读取并返回读取的字节数;如果管道没有数据,但仍然有写进程,读进程会阻塞等待数据;如果既没有数据也没有写进程,读进程返回0,并停止从管道中读取。
(2)当写进程将数据写入管道时,如果管道有空间,它会根据需要尽可能多地写入,直至管道写满,并唤醒阻塞的读进程,使它们继续读取;如果管道没有空间,但仍然有读进程,写进程会阻塞等待空间,直到读进程从管道读取数据来释放更多空间以此唤醒阻塞的写进程;但是如果管道不再有读进程,写进程将其视为管道中断错误,中止写入。

管道文件描述符

在内核中创建一个管道,并在pd[2]中返回两个文件描述符,pd[0]为读取端,pd[1]为写入端。

int pd[2];
int r = pipe(pd); // 返回1表示成功,-1表示失败
为什么需要两个进程

管道不是为单进程创建的,设计管道的意图是为两个进程提供通信的方式。在创建管道之后,必须fork一个子进程,父进程和子进程分别作为写入端和读取端(顺序不强求),否则会陷入死锁。例如:在创建管道后,如果进程试图读取数据,此时管道中若没有数据,但是有一个写进程,该进程会阻塞等待写入端写入数据;然而写入端是其本身,所以进程等待自己,把自己锁起来了。

因此,创建管道后,进程需要fork一个子进程共享管道,内核的机制保证了子进程继承父进程所有打开的文件描述符。
假设父进程作为写入端,子进程作为读取端,各自需要关闭不需要的管道描述符。在这种情况下,如果父进程没有数据要再写入,可以先终止,读进程可以继续读取,读完管道中的所有数据后,发现没有写进程,读取端也终止。

练习1 双向管道

使用UNIX的系统调用写一个叫“pingpong”的程序,在两个进程之间通过管道互相传递一个字节。父进程给子进程发送一个字节,子进程打印“pid: received ping”,pid是子进程的ID。子进程通过管道给父进程发送一个字节并退出,父进程从子进程读取该字节,打印“pid: received pong”并退出。

管道是单向的,因此需要创建两个管道。

 #include "kernel/types.h"
 #include "kernel/stat.h"
 #include "user/user.h"
 
 int main(int argc, char *argv[])
 {
     int pd_ptoc[2]; // pipe from parent to child process
     int pd_ctop[2]; // pipe from child to parent process 
     char buf[4];
 
     if(pipe(pd_ptoc)<0 || pipe(pd_ctop)<0){
         write(2, "create pipe failedn", 19);
         exit(1);
     }
 
     if(fork()){      // parent
         close(pd_ptoc[0]);
         close(pd_ctop[1]);
         write(pd_ptoc[1], "ping", 4);
         close(pd_ptoc[1]);
         read(pd_ctop[0], buf, 4);
         printf("%d: received pongn",getpid());
         close(pd_ctop[0]);
     }else{           // child
         close(pd_ptoc[1]);
         close(pd_ctop[0]);
         read(pd_ptoc[0], buf, 4);
         close(pd_ptoc[0]);
         printf("%d: received pingn",getpid());
         write(pd_ctop[1], "pong", 4);
         close(pd_ctop[1]);
     }
     exit(0);
 }

question:怎么实现read的阻塞机制?

练习2 顺序通信的并发进程

使用管道写一个并发版本的质数过滤器。
参考资料:https://swtch.com/~rsc/thread/

问题描述:使用pipe和fork开启一个管道。第一个进程将数字2~35输入管道。对于每个质数,您将安排创建一个进程,该进程通过一个管道从它的左邻居读取数据,通过另一个管道向它的右邻居写入数据。

输出例子:

$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
$

注意:
(1)及时关闭进程不需要的文件描述符,否则可能会很快耗尽资源。
(2)一旦第一个进程达到35,它应该等待,直到整个管道终止,包括所有的子进程、孙子进程。因此,主质数进程应该只在所有输出输出完成后退出,并且在所有其他质数进程退出后退出。可以用wait实现。
(3)每两个进程之间的管道是单向独立的,需要单独分配。
(4)管道左边的进程写完后要及时关闭写入端,避免管道右边的进程处于阻塞状态。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void prime(int *pd_in);

int main(int argc, char*argv[])
{
    int pd[2];
	
	if(pipe(pd)<0){
		write(2, "create pipe failn", 17);
		exit(1);
	}

    if(fork()){
        close(pd[0]);
        for(int i=2;i<=35;i++)
            write(pd[1], &i, 4);
        close(pd[1]);
        wait(0);
    }else{
        prime(pd);
    }
	exit(0);
}

void prime(int *pd_in){
    int pd_out[2];
    int num_in1,num_in2;
    close(pd_in[1]);

    if(pipe(pd_out)<0){
        write(2, "create pipe failn", 17);
		exit(1);
    }

    if(read(pd_in[0], &num_in1, 4)){
        printf("prime %dn",num_in1);
    }else exit(0);
    if(read(pd_in[0], &num_in2, 4)){
        if(fork()){
            close(pd_out[0]);
            write(pd_out[1], &num_in2, 4);
            while(read(pd_in[0], &num_in2, 4)){
                if(num_in2%num_in1){
                    write(pd_out[1], &num_in2, 4);
                }
            }
			close(pd_out[1]);
			close(pd_in[0]);
            wait(0);
        }else{
            close(pd_in[0]);
            prime(pd_out);
        }
    }else exit(0);
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/511250.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号