I/O重定向的原理和實現


在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,如下圖所示意:

fdt

所謂的I/O重定向也就是讓已創建的FD指向其他文件。比如,下面是對STDOUT重定向到testfile.txt前后內核文件描述符表變化的示意圖

重定向前:

before-redir

重定向后:

after-redir

在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中通過管道的示意圖:

pipeline

下面我們用一個實際的例子來體驗一下:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM