前言
Linux 中一切皆文件,比如 C++ 源文件、视频文件、Shell 脚本、可执行文件等,就连键盘、显示器、鼠标等硬件设备也都是文件。
一个 Linux 进程可以打开成百上千个文件,为了表示和区分已经打开的文件,Linux 会给每个文件分配一个编号(一个 ID),这个编号就是一个整数,被称为文件描述符(File Descriptor)。
文件描述符是什么?
一个 Linux 进程启动后,会在内核空间中创建一个 PCB
控制块,PCB
内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。
除了文件描述符表,系统还需要维护另外两张表:
- 打开文件表(Open file table)
i-node
表(i-node table)
文件描述符表每个进程都有一个,打开文件表和 i-node
表整个系统只有一个,它们三者之间的关系如下图所示。
对上图的说明:
- 在进程
A
中,文件描述符1
和20
都指向了同一个打开文件表项,标号为23
(指向了打开文件表中下标为 23 的数组元素),这可能是通过调用dup()
、dup2()
、fcntl()
或者对同一个文件多次调用了open()
函数形成的。 - 进程
A
的文件描述符2
和进程B
的文件描述符2
都指向了同一个文件,这可能是在调用fork()
后出现的(即进程A
、B
是父子进程关系),或者是不同的进程独自去调用open()
函数打开了同一个文件,此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。 - 进程
A
的描述符0
和进程B
的描述符3
分别指向不同的打开文件表项,但这些表项均指向i-node
表的同一个条目(标号为 1976);换言之,它们指向了同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了open()
调用。同一个进程两次打开同一个文件,也会发生类似情况。
通过文件描述符,可以找到文件指针,从而进入打开文件表。该表存储了以下信息:
- 文件偏移量,也就是文件内部指针偏移量。调用
read()
或者write()
函数时,文件偏移量会自动更新,当然也可以使用lseek()
直接修改。 - 状态标志,比如只读模式、读写模式、追加模式、覆盖模式等。
i-node
表指针。
然而,要想真正读写文件,还得通过打开文件表的 i-node 指针进入
i-node
表,该表包含了诸如以下的信息:- 文件类型,例如常规文件、套接字或
FIFO
。 - 文件大小。
- 时间戳,比如创建时间、更新时间。
- 文件锁。
标准文件描述符
文件描述符 | 用途 | POSIX 名称 | stdio 流 |
---|---|---|---|
0 | 标准输入 | STDIN FILENO | stdin |
1 | 标准输出 | STDOUT FILENO | stdout |
2 | 标准错误 | STDERR FILENO | stderr |
标准文件描述符通常会和重定向符<,>,<<,>>
结合使用。箭头向左表示输入重定向,向右表示输出重定向。文件描述符中的0
通常省略。如0< ~ <
,0<< ~ <<
。
标准输入
使用wc
命令统计文档中有多少行字,命令格式如下,详细介绍参考这篇文章。
wc [选项] [文件名]
$ cat test.txt
This is a test file.
Hello world!
$ wc -l <test.txt
2
这里的重定向符号<
作用就是将test.txt
的内容作为标准输入,传递给wc
命令。
标准输出与标准错误
这个我们每天都在接触,但是可能没有留意。假设我当前目录下只有一个test.txt
文件,执行如下命令
$ cat text.txt
This is a test file.
Hello world! #标准输出1
$ cat text.md
cat: test.md: No such file or directory #标准错误2
$ ls text.txt text.md 1>file.out 2>file.err
# 执行后,没有任何返回值. 原因是, 返回值都重定向到相应的文件中了,而不再前端显示
$ cat file.out
text.txt
$ cat file.err
cat: test.md: No such file or directory
&
描述符
&
是一个描述符,如果1
或2
前不加&
,会被当成一个普通文件。
1>&2
意思是把标准输出重定向到标准错误。
2>&1
意思是把标准错误输出重定向到标准输出。
&>filename
意思是把标准输出和标准错误输出都重定向到文件 filename 中