Linux文件描述符、重定向

1 UNIX 的特点:大道至简

Linux文件描述符、重定向

         Ken Thompson(左:肯·汤普森)和Dennis Ritchie (右:与丹尼斯·里奇)

        上个世纪六十年代,贝尔实验室Ken Thompson发明了UNIX,Dennis Ritchie参与了开发。在吸取了远古 Multics 操作系统研发中的失败经验后,里奇将UNIX的设计原则定为Keep it simple & stupid(大道至简)。

        UNIX 的特点:

  • 所有的事物(甚至硬件本身)都是一个的文件,Everything (including hardware) is a file 
  • 以文本形式储存配置数据,Configuration data stored in text 
  • 程序尽量朝向小而单一的目标设计,Small, single-purpose program
  • 量避免令人困惑的用户接口, Avoid captive user interfaces
  • 将几个程序连接起来,处理大而复杂的工作,Ability to chain program together to perform complex tasks

这几条设计思路,可以说每一条都是伟大的创举。尤其是第一条“所有的事物(甚至硬件本身)都是一个的文件”,实在是强大无比、妙不可言。(所以说,如果提到UNIX,仅仅说Ken Thompson的功劳,也是对Dennis Ritchie的不公。)

2 在Linux中,一切皆是文件

在 Linux/UNIX 操作系统中,所有事物都被当作文件来处理:硬件设备(包括键盘和终端)、目录、命令本身,当然还有文件。这个奇怪的惯例实际上是 Linux/UNIX 的能力和灵活性的基础。

在windows中,文件是一份txt文档、一个WORD文档、或者一个exe程序。文件仅是能存在硬盘上的实体记录,其它都不是文件。其它叫什么呢?WINDOWS采用一种面向对象的叫法,比如说文件对象、目录对象、屏幕对象、打印机对象,等等。说句搞笑的,在windows中操作、编程,其实都是在“搞对象“!

但是,在LInux中,文件的概念被大大扩展了。硬盘上存储的文档,本身就是文件。目录也是文件,屏幕、键盘、打印机、串口等等,所有的软硬件资源,都是文件。在linux上,你听到的文件、目录文件、屏幕文件、键盘文件、打印机文件、串口文件等,甚至还有"空"文件(/dev/null)、“零文件”(/dev/zero)等,千万不要奇怪。当你不奇怪之后,就明白在LInux中文件系统的强大和精妙之处了。

当然,如果要汇总一下,主要有以下6种分类。

1) 普通文件

类似 mp4、pdf、html 这样,可直接拿来使用的文件都属于普通文件,Linux 用户根据访问权限的不同可以对这些文件进行查看、删除以及更改操作。

2) 目录文件

对于用惯 Windows 系统的用户来说,目录是文件可能不太好理解。

Linux 系统中,目录文件包含了此目录中各个文件的文件名以及指向这些文件的指针,打开目录等同于打开目录文件,只要你有权限,可以随意访问目录中的任何文件。

注意,目录文件的访问权限,同普通文件的执行权限,意思一样。

3) 字符设备文件和块设备文件

这些文件通常隐藏在 /dev/ 目录下,当进行设备读取或外设交互时才会被使用。

例如,磁盘光驱属于块设备文件,串口设备则属于字符设备文件。

Linux 系统中的所有设备,要么是块设备文件,要么是字符设备文件。

4) 套接字文件(socket)

套接字文件一般隐藏在 /var/run/ 目录下,用于进程间的网络通信。

5) 符号链接文件(symbolic link)

类似于 Windows 中的快捷方式,是指向另一文件的简介指针(也就是软链接)。

6) 管道文件(pipe)

主要用于进程间通信。例如,使用 mkfifo 命令创建一个 FIFO 文件,与此同时,启用进程 A 从 FIFO文件读数据,启用进程 B 从 FIFO文件中写数据,随写随读。

总之,记住一句话就好:在Linux中,一切皆是文件。

3 Linux文件描述符

Linux文件描述符、重定向

/dev目录下面的部分设备文件

在Linux系统中,硬盘上有成千上万的文档,有终端、屏幕、键盘等设备,还有数不清的虚拟资源。怎么区分?那还不简单,用名字不就能区分了吗!

是的,我们看到的文件,无论是磁盘上的文件,或者是各种设备文件,甚至空"文件(/dev/null)和“零文件”(/dev/zero),都有名字。但是,这些文件名字是静态的。一但真正操作起来,则必须打开文件。为了表示和区分已经打开的文件,Linux 会给每个文件分配一个 ID,这个 ID 就是一个整数,被称为文件描述符(File Descriptor)。在LInux系统上,无论是操作,还是编程,则主要借助文件描述符来进行。同时,这也说明,文件描述符主要是个动态的,是在内存中与进程息息相关的概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在windows中,内核记录应用打开的对象,就是handle句柄,也是一个整数值,两者有相似之处。

4 Linux命令操作进阶-重定向

在Linux操作系统上,理解文件和文件描述符,才能深入理解shell命令的重定向操作。

在Linux中,在编写脚本的时候会频繁使用标准输入( stdin)、标准输出( stdout)和标准错误( stderr)。通过内容过滤将输出重定向到文件是我们平日里的基本任务之一。文件描述符是与某个打开的文件或数据流相关联的整数。文件描述符0、 1以及2是系统预留的,相当于每个进程的全局变量。

我们每打开一个应用程序,比如打开命令终端,系统就给我们打开了3个文件描述符。

Linux文件描述符、重定向

我们能在命令行上输入命令字符串:echo "This is a sample test",因为内核为命令终端进程打开了这三个文件描述符。我们能用">、>>、|"进行重定向操作,内部完全借助了文件和文件描述符机制。

这方面的操作,请参阅我之前发表的《Linux Shell脚本攻略-重定向理解与补充》,本文不再重复。

5 进程文件描述符

不论是开启shell终端,或者是运行一个程序,Linux就开创建一个进程。每个进程创建时,内核为进程创建了一个文件描述符表,该表的每一条都记录了进程打开的文件,这个表格的索引序号就是文件描述。

Linux文件描述符、重定向

前面提到过:文件描述符0(stdin)、 1(stdout)以及2(stderr)是系统预留的,相当于每个进程的全局变量。所以,一般情况下,在进程中打开一个文件,序号起始与3。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
    int fd, fd_new_file;
    ssize_t write_len=0 ;
    fd = open("/dev/stdout",O_WRONLY);
    printf("fd is :%d\n",fd); //实测输出文件描述符 3

    if (fd < 0) {
        perror(" file open error ");
        exit(1);
    }

    write_len = write(fd,"0123456789ABC\n",14);

    if (write_len != LEN_OF_WRITE_STR) {
        printf("File : /dev/stdout write error\n") ;
        exit(1);
    }
    printf("Write %d bytes to /dev/stdout \n",(int)write_len) ;

    fd_new_file = open(“abc.txt”,O_WRONLY);
    printf("fd_new_file is :%d\n",fd_new_file); //实测输出文件描述符 4

    if (fd_new_file < 0) {
        perror(" new file open error "); exit(1);
    }
    //完整的代码,fd、fd_new_file的close动作,此处略过,可自行补充。
    return 0;
}

上面代码运行时文件描述符状况如下图。

Linux文件描述符、重定向

(1)新打开的文件,描述符从3开始。

(2)如果关闭了文件,就立刻释放了文件描述符。

如果把“close(fd)”放在“fd_new_file = open(“abc.txt”,O_WRONLY);”前面,那么fd_new_file的值将还是3。因为fd=3,并且提前释放了,所以再打开文件,还得从3开始计文件描述符。为什么?别忘记,文件描述符是进程文件打开表的素引。

(3)多个文件描述符可以对应一个文件。

上面代码中的3和1都对应/dev/stdout。还可以用fcntl函数,比如“fd_new = fcntl(fd, F_DUPFD, 0);”来复制文件描述符,复制N多个(进程限制1024个)都可能。这种“文件/描述符”一对多的情况是允许的。但要注意同步的问題(以后的文章会记录在使用open、lseek、read、write等系统api函数中遇到的同步、原子操作问題)。

6 文件描述符表和物理文件之间的关系

Linux 内核用三个相关的数据结构来表示打开的文件。

(1)descriptor table:描述符表 。每个进程都有自己独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。

(2)file table:文件表 。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成包括当前文件位置,引用计数(即当前指向该表项的描述符表项数),以及一个指向 i-node 表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。

(3)i-node 表,也称 v-node 表。同文件表一样,所有的进程共享这张 i-node 表。每个表项包含 stat 结构中的大多数信息,包括 st_mode 和 st_size 成员。

Linux文件描述符、重定向

上一篇:Android常用的IPC通信


下一篇:CPP服务器07--服务器层次分析及其设计