文章

"echo "Hello, World!" > output.txt"背后重定向的系统调用分析

Linux下打开终端, 运行命令 echo "Hello, World!" > output.txt, 你会发现当前目录下多了一个名为 output.txt 的文件, 其内容就是 Hello, World! . 今天,我们就来研究一下他背后的原理.

strace 查看命令的系统调用

我们不能直接strace echo "Hello, World!" > output.txt,这样打印的系统调用是不完整的. 我们先创建一个包含 echo "Hello, World!" > output.txt 的脚本 test.sh

#!/bin/bash
echo "Hello, World!" > output.txt

接着我们执行脚本并使用strace进行追踪

chmod +x test.sh
strace -o strace_output.txt ./test.sh

解析统计的系统调用

现在,我们开始对strace_output.txt进行分析,我可能会省略部分系统调用 第一步:执行脚本

execve("./test.sh", ["./test.sh"], 0x7ffc7fe04830 /* 19 vars */) = 0

第二步:打开输出文件

#打开或创建 output.txt 文件,并返回文件描述符 3。
openat(AT_FDCWD, "output.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

第三步:保存原标准输出的文件描述符

fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10 #将文件描述符 1(标准输出)复制到 10,以保存原始的标准输出。
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0

第四步:重定向标准输出到 output.txt 文件

dup2(3, 1) = 1	#将文件描述符 3(output.txt 文件)复制到 1,即将标准输出重定向到 output.txt 文件。
close(3) = 0
newfstatat(1, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0

第五步:写入数据到重定向的标准输出

write(1, "Hello, World!\n", 14) = 14 #将字符串 "Hello, World!\n" 写入到文件描述符 1,此时 1 被重定向到 output.txt 文件。

第六步:恢复原标准输出的文件描述符

dup2(10, 1)                             = 1		#将保存的文件描述符 10 复制回 1,恢复原始的标准输出。
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0

文件描述符

我们都知道, linux系统下, 有一个系统级的打开文件表 (Open File Table).而每个进程的 PCB (Process Control Block) 中都有一个文件描述符表 (File Descriptor Table), 进程就是通过文件描述符表中的文件描述符对文件进行操作的. 接下来,我们再来看看这些系统调用执行后,OFT和FDT的变化: 初始状态 进程的 FDT:包含标准输入(fd=0),标准输出(fd=1),标准错误(fd=2)。 系统的 OFT:初始状态为空。

第一步:执行脚本

execve("./test.sh", ["./test.sh"], 0x7ffc7fe04830 /* 19 vars */) = 0

第二步:打开输出文件

#打开或创建 output.txt 文件,并返回文件描述符 3。
openat(AT_FDCWD, "output.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
  • 系统的 OFT:添加一项指向 output.txt 的文件条目,记录文件状态、文件偏移量等信息。
  • 进程的 FDT:增加一项 fd=3,指向 OFT 中的 output.txt 条目。

第三步:保存原标准输出的文件描述符

fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10 #将文件描述符 1(标准输出)复制到 10,以保存原始的标准输出。
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
  • 系统的 OFT:无变化。
  • 进程的 FDT:增加一项 fd=10,指向 OFT 中原标准输出的条目。

第四步:重定向标准输出到 output.txt 文件

dup2(3, 1) = 1	#将文件描述符 3(output.txt 文件)复制到 1,即将标准输出重定向到 output.txt 文件。
close(3) = 0
newfstatat(1, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0
  • 系统的 OFT:无变化。
  • 进程的 FDT:fd=1 现在指向 OFT 中 output.txt 的条目,而不再指向原标准输出的条目。

第五步:写入数据到重定向的标准输出

write(1, "Hello, World!\n", 14) = 14 #将字符串 "Hello, World!\n" 写入到文件描述符 1,此时 1 被重定向到 output.txt 文件。
  • 系统的 OFT:更新 output.txt 条目的文件偏移量和文件内容。
  • 进程的 FDT:无变化。

第六步:恢复原标准输出的文件描述符

dup2(10, 1)                             = 1		#将保存的文件描述符 10 复制回 1,恢复原始的标准输出。
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
  • 系统的 OFT:无变化。
  • 进程的 FDT:fd=1 恢复指向 OFT 中原标准输出的条目,fd=10 被关闭。
License:  CC BY 4.0