1 分钟阅读 次阅读

概要:以 shell 执行 cat < input.txt 为例,讲解 IO 重定向的实现原理:子进程先关闭标准输入,再打开目标文件使其占用最小文件描述符 0,从而让 cat 从文件读取。

1 代码示例

  • shell 执行 cat<input.txt 的简化代码
char *argv[2];
argv[0] = "cat";
### argv[1] = 0;
if(fork() == 0) {
    close(0);
    open("input.txt", O_RDONLY);
    exec("cat", argv);
}

shell 的实际行为是:

shell
 └── fork()
      └── child
           close(0)
           open("input.txt")
           exec("cat")

1.1 执行过程:

  • step 1:fork
child:
FD0 -> terminal
FD1 -> terminal
FD2 -> terminal
  • step 2:close(0)
child:
FD0 -> 空
FD1 -> terminal
FD2 -> terminal
  • step 3:open
open("input.txt") -> 返回 0

现在:

FD0 -> input.txt
FD1 -> terminal
  • step 4:exec
exec("cat")

cat 启动时看到:

stdin = input.txt
stdout = terminal

所以 cat 自动从文件读取,而无需任何额外代码。

2 文件描述符的分配规则

在 Unix 中:

FD 含义
0 stdin
1 stdout
2 stderr

系统保证:

新分配的文件描述符一定是当前进程中最小的未使用整数

例如:

当前FD: 0 1 2 3 4
close(0)
现在空闲: 0
open() -> 返回 0

这就是代码里:

close(0);
open("input.txt", O_RDONLY);

的核心原理: 通过关闭 0,让 open 占用它,从而替换 stdin。


3 fork 会复制文件描述符表

执行:

pid = fork();

之后:

父进程FD表: 0 1 2 -> tty
子进程FD表: 0 1 2 -> tty   (完全一样)

也就是说:

fork 会复制文件描述符表,但不会重新打开文件

父子进程:

  • 共享同一个打开文件(内核对象)
  • 但各自拥有独立的 FD 表

4 exec 不会清空文件描述符

exec 的行为是:

内容 是否保留
内存 ❌ 替换
代码 ❌ 替换
文件描述符 ✅ 保留

因此:

fork -> 修改FD -> exec

是 shell 重定向的标准流程。

5 xv6实际代码解析

7930

case REDIR:
    rcmd = (struct redircmd*)cmd;
    close(rcmd->fd);
    if(open(rcmd->file, rcmd->mode) < 0){
        printf(2, "open %s failed\n", rcmd->file);
        exit();
    }
    runcmd(rcmd->cmd);
    break;
  • 在 xv6 中,命令被解析成一棵结构体树,例如:
struct redircmd {
  int type;
  struct cmd *cmd;   // 实际要执行的命令
  char *file;        // 重定向文件
  int mode;          // 打开方式
  int fd;            // 要替换的文件描述符
};


- 当前命令被解析为 **重定向命令**,强制转换为 `redircmd` 结构。
```c
rcmd = (struct redircmd*)cmd;
  • 关闭要被替换的文件描述符:
close(rcmd->fd);
重定向类型 fd
< 0
> 1
2> 2

例如:

cat < input.txt

这里:

rcmd->fd = 0
  • 打开文件,返回 最小可用 fd
open(rcmd->file, rcmd->mode)

因为 fd 已经被关闭,所以:

open 会返回 最小可用 fd,也就是刚刚关闭的 fd。

这就完成了:

stdin -> input.txt
  • 递归执行真正的命令
runcmd(rcmd->cmd);

xv6 shell 是递归执行命令树的,例如:

grep x < in.txt | sort > out.txt

解析结构:

        pipe
       /    \
   redir   redir
    |        |
   grep     sort

留下评论