当前位置:首页>Linux>Linux笔记1.3---文件描述符fd

Linux笔记1.3---文件描述符fd

  • 2026-10-11 06:59:06
Linux笔记1.3---文件描述符fd

一句话:文件描述符(fd)就是一个整数——进程私有「接线表」的编号。程序只认编号、不认设备,这正是重定向能成立的前提。

怎么区分文件

Linux笔记1.2---一切皆文件(下)中, 程序同时打开了一堆「文件」,怎么区分「我现在操作的是哪一个」?

答案就是 文件描述符(fd)——一个编号,指向进程那张私有的接线表。

fd 到底是什么

事实 1:fd 是一个整数

它是一个小整数(0、1、2、3…),是进程用来引用「一个打开的文件」的编号。fd 不是文件本身,是索引。例如fd=0,表示的就是

事实 2:每个进程有一张自己的 fd 表

内核里每个进程都挂着一张私有的 fd 表(task_struct->files)。表里每一项指向一个「打开的文件」;fd 就是这张表的下标。所以fd=1永远是指「我这个进程的表里第2号位置file[1]」——你的 fd 1 和我的 fd 1 可以指向完全不同的东西。

事实 3:「打开的文件」是广义的

普通文件、目录、设备(/dev/null、/dev/tty)、管道、socket、/proc/...——全都是「文件」,都能用 fd 引用、用 read/write 读写。这就是「一切皆文件」的具体落点:不是它们长得像,是它们共用同一套 fd + read/write 接口。

0 / 1 / 2 只是约定

内核没有规定 0/1/2 必须是什么。它只是一个长期约定(POSIX):

fd
名字
含义
0
stdin
标准输入
1
stdout
标准输出
2
stderr
标准错误

关键在于:程序把这个约定写死在代码里——C 库的 printf 往 fd 1 写、scanf 读 fd 0、perror 往 fd 2 写。程序只认编号,不认设备。fd 1 接的是屏幕、文件还是管道,程序既不知道、也不关心。

正是「程序认编号不认设备」这一点,让重定向成为可能:只要把 fd 1 改接到别处,程序不用改一行代码,输出就换了去向。

一个刚启动、接终端的进程,它的 fd 表长这样:

进程的 fd 表(默认接终端 /dev/pts/0)┌──────┬──────────────────────────┐│ fd 0 │ → /dev/pts/0  (stdin  键盘) ││ fd 1 │ → /dev/pts/0  (stdout 屏幕) ││ fd 2 │ → /dev/pts/0  (stderr 屏幕) │└──────┴──────────────────────────┘

实测杀手锏:/proc/PID/fd 肉眼看 fd 表

fd 最大的好处:fd 表是看得见的,不用猜。Linux 把每个进程的 fd 表暴露在 /proc/PID/fd:

ls -l /proc/$$/fd          # $$ 是当前 shell 的 PID

典型输出(0/1/2 都接终端,255 是 bash 自用的):

lrwx------  0 -> /dev/pts/0lrwx------  1 -> /dev/pts/0lrwx------  2 -> /dev/pts/0lrwx------ 255 -> /dev/pts/0

改接线,再回头看(自己跑这一段,最能体会「fd 表是一张真实存在的、可操作的表」):

exec 3>/tmp/y            # 当前 shell 手动开一个 fd 3,指向 /tmp/yls -l /proc/$$/fd        # 会多出一行:3 -> /tmp/yexec 3>&-                # 关掉 fd 3(3>&- = 关闭),别留垃圾

看到 3 -> /tmp/y 出现的那一刻,你就理解了 fd 表是真实存在的、可操作的接线表。重定向、管道干的也是这件事——只不过由 Shell 在子进程里自动做完。

fork / exec 时 fd 怎么办

fd 表不是孤立的,它在进程创建和程序替换时有确定的行为——这两条是 Shell 能「先接好线再启动命令」的根基。

fork:子进程复制 fd 表,且共享 offset

fork() 复制父进程,连 fd 表一起复制。但「复制」要理解准:父子进程的 fd 表项指向同一个「打开的文件」(内核里的 open file description),所以它们共享同一个文件偏移量(offset)。

父进程 fd 表                 子进程 fd 表fd 3 → ┐                    fd 3 → ┐       └──► 同一个打开文件(含 offset)◄──┘           (父子共享偏移量,互相能看到对方的读写位置)

这就是为什么 Shell 在父进程里接好线,fork 出的子进程能直接继承这套接好的 fd 表——子进程不用重新接线。

exec:默认保留所有 fd

exec() 换了程序代码,但fd 表默认原样保留。

例外(一句带过):某个 fd 若标记了 close-on-exec(FD_CLOEXEC),exec 时会被自动关掉。这是给「不想被子进程继承」的 fd 用的,日常重定向碰不到。

小结

fd 三定律:

  1. fd 是一个整数,是进程私有 fd 表的索引。
  2. 0/1/2 是约定;程序认编号,不认设备。
  3. 正因如此,改 fd 表的接线就能改变程序输出去向——程序代码不用改,行为就变。

最新文章

随机文章