第十章 I/O重定向和管道

11 篇文章 0 订阅

0.摘要

概念与技巧
-I/O重定向:概念与原因
-标准输入,输出和标准错误的定义
-重定向标准I/O到文件
-使用fork来为其他程序重定向
-管道(Pipe)
-创建管道后调用fork
相关的系统调用与函数
-dup,dup2
-pipe

1.shell编程

首先将介绍编写shell脚本时的I/O重定向和管道起的作用.然后,本章将介绍操作系统中对I/O重定向的支持.最后,写一个程序来改变进程的输入和输出流..

utmp函数列出用户列表

2.标准I/O与重定向的若干概念

shell中的三种流分别位如下三种:
1.标准输入–需要处理的数据流
2.标准输出–结果数据流
3.标准错误输出–错误消息流

2.1 3个标准文件描述符

stdin:0
stdout:1
stderr:2

2.2默认的连接:tty

在unix和linux中,shell默认链接着stdin,stdout,stderr三个文件。很多程序没有输入,只是把正确的输出写到文件描述符位1,并且把错误消息写到文件描述符2中.

2.3重定向I/O的是shell来操作而不是程序

当使用输出重定向标志,命令cmd>filename是由shell将文件描述符1定位到文件.把原有的链接打断.
本章的三个主要的工作:
1.who>userlist 将stdout连接到一个文件
2.sort

3.如何将stdin定向到文件

进程是从文件描述符读取数据的.并且由将标准输入重定向到可以从文件读入数据.

3.1.方法1:close then open

开始的时候,0连接输入,1连接输出,2连接出错.三种标准流是被连接到终端设备上的.通过文件close可以指定标准的输入输出关闭。如果调用close来关闭程序的stdin(0),之后打开open一个指定文件,则它会按照最低可用文件描述符来匹配,得到打开新文件对应到stdin位置

close(0);
fd = open(<file path>,O_RDONLY);
3.2.方法2:open..close..dup..close

执行过程
1.open打开一个文件,fd不为0,
2.close(0)关闭文件描述符为0。
3.dup(fd)来将文件描述符fd做一个复制。把复制得到的文件fd根据最低可用文件描述符原则与0相连。
4.close(fd)关闭最先打开的文件。
其中在这个方法中,close(0)和dup(fd)可以结合在一起作为一个单独的系统调用dup2

fd=open(<file path>)
close(0)
dup(fd)
close(fd)
3.3.方法3:open..dup2..close
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
//#define DUP2
#define CLOSE_DUP

void main()
{
    int fd;
    int newfd;  //the new file descriptor
    char line[100];
    fgets(line,100,stdin);
    printf("%s",line);

    fd = open("./data",O_RDONLY); //read data from file which creates by myself
    #ifdef CLOSE_DUP
        close(0);
        dup(fd);
    #else
        newfd = dup2(fd,0);
    #endif
    if(newfd !=0)
    {
        perror("gouge cuole");
        exit(1);
    }
    close(fd);  //every time you wouldn't use the file descriptor, you can close it 
    fgets(line,100,stdin);
    printf("%s",line);
}

4.为其他程序重定向I/O:who>userlist

伪代码:

pid=fork()
if(pif ==0 )
{
    close(1);
    fd=creat(<file>,0644);//open a newho file
    execlp("who","who",NULL);
    perror("execlp");
    exit(1);
}
if(pid!=0)
{
    wait(NULL);
}

重定向到文件的小结:
1.标准输入、输出以及错误输出分别对应于文件描述符0、1、 2
2.内核总是使用最低可用文件描述符
3.文件描述符集合通过exec调用传递,且不会被改变。

5.管道编程

管道是内核中的一个单向的数据通道。管道有一个读取端和一个写入端。实现who|sort的两个技巧:如何创建管道,如何使标准输入和输出通过管道联系起来。

5.1创建管道

result = pipe(int array[2]);其中管道的输出端是array[0]中的文件描述符表示,输入端是array[1]中的文件描述符表示.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
void main()
{
    int len,i,apipe[2]; /*two file descriptor*/
    char buf[BUFSIZ];

    if(pipe(apipe)==-1)
    {   
        perror("could not make pipe");
        exit(1);
    }   
    printf("Got a pipe! It is file descriptors: {%d %d \n",apipe[0],apipe[1]);
    while(fgets(buf,BUFSIZ,stdin))
    {   
        len = strlen(buf);
        if(write(apipe[1],buf,len)!=len)
        {
            perror("writing to pipe");
            break;
        }

        for(i=0;i<len;i++)  /*wipe*/
            buf[i]='X';         //nothing will be changed
        if((len = read(apipe[0],buf,BUFSIZ))==-1)
        {
            perror("reading from price");
            break;
        }
        if(write(1,buf,len)!=len){ /*send*/
            perror("writing to stdout"); /*to*/
            break;                      /*pipe*/
        }
    }   
}

可以看到上述代码创建一个管道,默认使用3和4文件描述符.执行过程如图
这里写图片描述
当然很少有自己拿个管道和自己玩的,一般用在进程间通信的比较多,要把pipe和fork结合起来玩.

5.2使用fork来共享管道

父进程创建子进程后,他们之间共享管道,两个进程都访问管道的两端。而进程创建时复制了文件描述符表等其他内容。因此管道可以用作进程间的数据交流
这里写图片描述

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PARENTS_MESS "I am the parents\n"
#define opps(m,x)  {perror(m);exit(x);}

void main()
{
    int apipe[2];
    int len;
    int read_len;
    char buf[BUFSIZ];
    if(pipe(apipe)==-1)
    {   
        opps("pipe",1);
    }   

    switch(fork())
    {   
        case -1: 
            opps("fork ",1);
        case 0:
            len = strlen(CHILD_MESS);

            while(1){
                if(write(apipe[1],CHILD_MESS,len)!=len) //write the CHILD_MESS to parents
                    opps("child wirte",3);
                sleep(5);
            }
            /*parent reads from pipe and write to pipe*/
        default:
            len = strlen(PARENTS_MESS);
            while(1){
                if(write(apipe[1],PARENTS_MESS,len)!=len) //write the PARENTS_MESS to apipe[0]
                    opps("parents write",4);
                sleep(1);
                read_len = read(apipe[0],buf,BUFSIZ);
                if(read_len <=0)
                    break;
               write(1,buf,read_len);
            }
    }
}

当然这个管道存在大家都可以往里面写,可以在里面读,所以就容易造成混乱.所以要让子进程写,让父进程读,在不同的进程中关闭无用的文件描述符.

5.3技术细节:管道并非文件

管道像文件一样,是一种不带有任何结构的字节序列,另一方面,管道又与文件不同.
1.从管道中读数据
(1)管道读取 阻塞
当进程试图从管道中读取数据时,进程被挂起直到读取完成,随之而来的如何避免这个问题?(flush写完成之后flush一下?)
(2)管道的读取结束标志
当所有写者关闭了管道的写数据端时,试图从管道读取数据的调用返回0,意味着文件的结束
(3)多个读者可能引起麻烦
管道是一个队列。当进程从管道中读取数据之后,数据已经不存在了。所以多个读者可能会出现读取数据不完整的情况.
2.向管道中写数据
(1)写入数据阻塞直到管道有空间去接纳新的数据
如果一次写入量超过管道大小,则等管道传输完前一部分,再导入一部分。
(2)写入必须保证一个最小的块大小:POSIX保证内核不会被拆分成小于512字节的块,而linux保证的是4096字节连续.
(3 )若无读者在读取数据,则写操作执行失败
如果所有的读者关闭了管道的端口,那么首先,内核发送SIGPIPE消息给进程。若进程被终止,则无任何事情发横。否则,write返回-1报错,errno设置位EPIPE

以上已经介绍了了如何在一个进程中输入,输出数据.那么数据是怎么在两个进程直接传递,切是双向传递呢?还有上述说的管道只是在父子进程之间能够起到传递数据的作用,且是单项传递的,如何做到双向传递?如何在两个距离很远的计算机之间传递信息?如何做到可靠传输?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值