在Unix系统中,每个进程都有STDIN、STDOUT和STDERR这3种标准I/O,它们是程序最通用的输入输出方式。几乎所有语言都有相应的标准I/O函数,比如,C语言可以通过scanf从终端输入字符,通过printf向终端输出字符。熟悉Shell的朋友都知道,我们可以方便地对Shell命令进行I/O重定向,比如 find -name "*.java" >testfile.txt 把当前目录下的Java文件列表重定向到testfile.txt。多数情况下,我们只需要了解I/O重定向的使用就够了,但是如果要编程实现类似Shell的I/O重定向以及管道功能,那么就需要清楚它的原理和实现。

下面本文就以Linux系统为具体例子,介绍I/O重定向的原理和实现(文中实验环境为Ubuntu 12.04,内核版本3.2.0-59)。

文件描述符表

理解I/O重定向的原理需要从Linux内核为进程所维护的关键数据结构入手。对Linux进程来讲,每个打开的文件都是通过文件描述符(File Descriptor)来标识的,内核为每个进程维护了一个文件描述符表,这个表以FD为索引,再进一步指向文件的详细信息。在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR,如下图所示意:

所谓的I/O重定向也就是让已创建的FD指向其他文件。比如,下面是对STDOUT重定向到testfile.txt前后内核文件描述符表变化的示意图

重定向前:

重定向后:

在I/O重定向的过程中,不变的是FD 0/1/2代表STDIN/STDOUT/STDERR,变化的是文件描述符表中FD 0/1/2对应的具体文件,应用程序只关心前者。本质上这和接口的原理是相通的,通过一个间接层把功能的使用者和提供者解耦。

下面我们通过strace命令跟踪一下echo命令的系统调用:

dagang@ubuntu12:~$ strace echo hello 2>&1 >/dev/null | grep write
write(1, "hello\n", 6) = 6

我们可以看到 write(1, "hello\n", 6) 这样一个系统调用,它的第一个参数1就是代表的STDOUT的FD,这说明对于echo程序,它只管(通过标准I/O函数从STDOUT)向FD 1写入,而不关心它们FD 1到底对应的是哪个文件。

Shell正是通过I/O重定向和管道这种特殊的文件把多个程序的STDIN和STDOUT串联在一起组成更复杂功能的,下面是Shell中通过管道的示意图:

下面我们用一个实际的例子来体验一下:

dagang@ubuntu12:~$ sleep 30 | sleep 40 &
[1] 5584
dagang@ubuntu12:~$ pgrep -l sleep
5583 sleep
5584 sleep
dagang@ubuntu12:~$ ll /proc/5583/fd
total 0
lrwx------ 1 dagang dagang 64 Feb 27 13:41 0 -> /dev/pts/3
l-wx------ 1 dagang dagang 64 Feb 27 13:41 1 -> pipe:[246469]
lrwx------ 1 dagang dagang 64 Feb 27 13:41 2 -> /dev/pts/3
dagang@ubuntu12:~$ ll /proc/5584/fd
total 0
lr-x------ 1 dagang dagang 64 Feb 27 13:41 0 -> pipe:[246469]
lrwx------ 1 dagang dagang 64 Feb 27 13:41 1 -> /dev/pts/3
lrwx------ 1 dagang dagang 64 Feb 27 13:41 2 -> /dev/pts/3

上面我们启动了两个进程5583和5584,通过查看/proc//fd,我们看到进程5583的STDOUT和5584的STDIN被重定向到了pipe:[246469],这样就达到了连接两个进程标准I/O的目的。

dup2()系统调用

上面介绍了文件描述符表和I/O重定向的原理,那么在Linux系统中如何通过C程序实现I/O重定向呢?主要用到了dup2()这个系统调用,man中关于dup2是这样说的:

int dup2(int oldfd, int newfd);

dup2() create a copy of the file descriptor oldfd. After a successful return from dup() or dup2(), the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.

这里我们通过一个实际的问题来说明它的使用方法:

编写一个C程序,通过调用sort这个Shell命令进行排序,要求把in.txt和out.txt分别重定向到sort的STDIN,STDOUT。

参考实现:

int main() {
int pid = 0;
// fork a worker process
if (pid = fork()) {
// wait for completion of the child process
int status;
waitpid(pid, &status, 0);
}
else {
// open input and output files
int fd_in = open("in.txt", O_RDONLY);
int fd_out = open("out.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd_in > 0 && fd_out > 0) {
// redirect STDIN/STDOUT for this process
dup2(fd_in, 0);
dup2(fd_out, 1);
// call shell command
system("sort");
close(fd_in);
close(fd_out);
}
else {
// ... error handling
}
}
return 0;
}

上面的主要步骤包括:

1. 首先fork一个子进程,后续步骤都在子进程中完成,父进程通过waitpid()系统调用等待子进程结束;
2. 打开open()系统调用打开in.txt和out.txt,得到它们的描述符(在我的测试中,这两个值通常为3和4);
3. 通过dup2()系统调用把STDIN重定向到fd_in,把STDOUT重定向到fd_out(注意,重定向的影响范围是整个子进程);
4. 通过system()系统调用运行shell命令sort

通过上面的例子我们就了解最基本的I/O重定向的实现方法,接下来,你能否根据这些知识进一步实现出Shell的管道特性呢?

总结

本文介绍了Linux系统I/O重定向的原理和实现方式。原理方法最重要的是理解文件描述符和文件描述符表的概念,以及标准I/O所对应的特殊FD;实现方面主要是了解dup2()系统调用的功能和用法。最后需要注意的是dup2()不仅可以用来对标准I/O重定向,对任何FD都是可以的,这是习惯使用Shell进行标准I/O重定向的朋友容易忽略的。

图片引用

http://cs.ucla.edu/classes/fall08/cs111/scribe/4/FDT_diagram.JPG
http://academic.udayton.edu/SaverioPerugini/courses/cps346/lecture_notes/images/beforeredir.png
http://academic.udayton.edu/SaverioPerugini/courses/cps346/lecture_notes/images/afterredir.png
http://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Pipeline.svg/280px-Pipeline.svg.png

I/O重定向的原理和实现的更多相关文章

  1. 请求转发和URL重定向的原理和区别

    一.请求转发和重定向是在java后台servlet中,由一个servlet跳转到另一个servlet/jsp要使用的技术 使用方法 请求转发  req.getResquestDispatcher(se ...

  2. SpringMVC中重定向底层原理

      只要将数据放入model中, 也能取到值,原因是model临时放入session域中,当从定向到另一个url时,底层把数据拼接在url地址后面(重定向一定是get请求方式),同时将session域 ...

  3. http协议 301和302的原理及实现

    一.来看看官方的说法: 301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于:  301 redirect: 301 代表永久性转移(Permanently Moved) ...

  4. (转)Windows管道(Pipe)重定向stdout,stderr,stdin

    参考: http://qiusuoge.com/11496.html http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html st ...

  5. 请求转发 和 URL 重定向

    五 请求转发 和 URL 重定向 1 请求转发和重定向 干什么用? 是我们在java后台servlet中 由一个servlet跳转到 另一个 servlet/jsp 要使用的技术 前端发送请求到后台 ...

  6. shell编程 之 输入输出重定向

    1 输入输出重定向 标准输入:从终端得到命令,对于计算机来说,是从终端获得了命令,执行完了以后,结果和执行状态或者错误提示又会发回终端,这叫标准输出. 输入输出重定向就是从终端以外的别的地方得到输入, ...

  7. SpringMVC 重定向和请求转发(转载)

    本文系转载,原文地址:https://blog.csdn.net/m0_37450089/article/details/78703366   servlet的请求转发(forward)和重定向(se ...

  8. Servlet-转发和重定向的区别

    实际发生位置不同,地址栏不同 转发是发生在服务器上的 转发是由服务器进行跳转的,细心的朋友会发现,在转发的时候,浏览器的地址栏是没有发生变化的,在我访问Servlet111的时候,即使跳转到了Serv ...

  9. springMVC之一(页面<--->控制器 互相传值,转发和重定向)

    #页面--->控制器1.request:不建议使用2.使用属性传值(建议使用)@RequestParam("name") String username3.使用Bean对象传 ...

随机推荐

  1. [翻译]Telnet简单介绍及在windows 7中开启Telnet客户端

    文章翻译自 http://social.technet.microsoft.com/wiki/contents/articles/910.windows-7-enabling-telnet-clien ...

  2. 设计模式 之 原型模式(ProtoType)

    什么时原型模式   或   原型模式的定义: 用原型实例来指定创建对象的种类,并通过拷贝这些原型创建新的对象. 原型模式的特点: 1),它是面向接口编程, 2),原型模式的新对象是对原型实例的一个克隆 ...

  3. 深入理解ConcurrentMap.putIfAbsent(key,value) 用法

    转自:http://blog.csdn.net/exceptional_derek/article/details/40384659 先看一段代码: public class Locale { pri ...

  4. Quartz定时调度框架

    Quartz定时调度框架CronTrigger时间配置格式说明 CronTrigger时间格式配置说明 CronTrigger配置格式: 格式: [秒] [分] [小时] [日] [月] [周] [年 ...

  5. Java操作属性文件之工具类

    最近空闲时间整理一下平时常用的一下工具类,重复造轮子实在是浪费时间,如果不正确或者有待改善的地方,欢迎指教... package com.hsuchan.business.utils; import ...

  6. C#启用管理员权限运行程序

    方法一:关闭程序重新请求打开 static class Program { [STAThread] static void Main() { Application.EnableVisualStyle ...

  7. Editbox之三个框框

    自重装系统后,电脑中两个版本的eclipse都驾崩了,起个VS也要花费半年的时间(观赏收费),所以就运用已有的工具STS编了代码,不能用JavaFX很是遗憾,只能在网上找了代码,自己修改后完成了测试. ...

  8. niginx 负载均衡

    下面是Nginx安装 直接yum install nginx不行,要先处理下源,下面是安装完整流程,十分简单: 1.CentOS 6,先执行:rpm -ivh http://nginx.org/pac ...

  9. Erlang error handling

    Erlang error handling Contents Preface try-catch Process link Erlang-way error handling OTP supervis ...

  10. 使用epel源安装软件

    问题:centos提供的官方base源可能无法提供某些软件的安装,可以通过epel源 系统:centos6.5 x86_64 解决:安装epel源 #wget http://dl.fedoraproj ...