文件描述符和标准输入输出的那些事儿
一、标准输入、输出、错误输出
$ ll /dev/std* lrwxrwxrwx. 1 root root 15 Apr 19 12:08 /dev/stderr -> /proc/self/fd/2 # 标准错误输出 lrwxrwxrwx. 1 root root 15 Apr 19 12:08 /dev/stdin -> /proc/self/fd/0 # 标准输入 lrwxrwxrwx. 1 root root 15 Apr 19 12:08 /dev/stdout -> /proc/self/fd/1 # 标准输出
# 这里面的0、1、2就是所谓的文件描述符
1.1 文件描述符示例
#include <stdio.h> #include <fcntl.h> int main(void) { int fd1 = open("open.txt", O_RDWR | O_CREAT | O_TRUNC, 0666); if (-1 == fd1) { perror("open"); return -1; } printf("fd1 = %d\n", fd1); close(fd1); return 0; }
# 执行过程
$ ./open fd1 = 3 # 文件描述符是3
1.2 文件描述符
- 首先打开一个文件背后是在系统内核中建立了一套数据结构,用来访问文件,然后进程表项里面会有一段"文件描述符表",这里面就包括了"文件描述符标志和文件表项指针";大致逻辑如下:
进程表项 ... 文件描述符表 |文件描述符标志 | 文件表项指针 | 0 -> 标准输入 |文件描述符标志 | 文件表项指针 | 1 -> 标准输出 |文件描述符标志 | 文件表项指针 | 2 -> 标准错误输出 +--------------------------+ \__这个下标我们称为文件描述符 文件表项 文件状态标志 文件读写位置 v节点指针指向v节点 v节点指针 i节点内容 ...
- 那查找一个文件是如何进行的呢?
拿到文件描述符 -> 文件表项指针 -> 文件表项 -> V节点指针 -> V节点 -> i节点内存 -> 找到数据
- 所以前面输出fd1 = 3,是因为前面0、1、2被标准输入、输出给占用了;作为文件描述符表项在文件描述符表中的下标,合法的文件描述符一定是大于或等于0的整数,产生新的文件描述符表项,系统总是从下标0开始在文件描述符表中寻找最小的未使用项。同时也说明文件描述符是用户空间的程序到内核空间关于文件的唯一联系方式。
1.3 关于重定向
#include <stdio.h> #include <stdlib.h> int main(void) { int x = 0; int y = 0; // 读 标准输入 scanf("%d%d", &x, &y); // 写 标准输出 printf("%d + %d = %d\n", x, y, x + y); // 写 标准错误输出 malloc(0xFFFFFFFFF); perror("malloc"); return 0; }
# 执行结果
$ ./test_file 10 20 # 这里等待用户从键盘输入 10 + 20 = 30 # 标准输出 malloc: Cannot allocate memory # 标准错误输出
1.4 终端shell里面测试标准输入输出
$ echo '10 20' > in.txt # 制作输入文件 $ cat in.txt # 查看文件内容 10 20 $ ./test_file 0< in.txt 1> out.txt 2> err.txt # 测试标准输入、输出、错误输出 $ cat out.txt # 检查标准输出 10 + 20 = 30 $ cat err.txt # 检查标准错误输出 malloc: Cannot allocate memory
1.5 代码没改,它是如何实现重定向的?
直接总结:
参考代码相见下文:
第一步:关闭0的文件描述符,紧接着打开一个in.txt文件,那这个文件的描述符就是0,scanf读取的是文件描述为0的文件,所以标准输入表示是文件描述符为0的文件。
第二步:关闭1的文件描述符,紧接着打开out.txt文件,那这个文件的描述符就是1,printf往文件描述符为1的里面写就写到out.txt里面了。
第三步:关闭2的文件描述符,紧接着打开err.txt文件,那这个文件的描述符就是2,perror往文件描述符为2的里面写就写到err.txt里面了。
第四步:然后启动进程,子进程从父进程中继承了文件描述表,在子进程里面的0、1、2同样代表了前面 的几个txt文本,所以代码里面的scanf和printf就自然把对应的内容给关联起来了。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> void test_close_std(void) { // 关闭标准输入0 close(STDIN_FILENO); // 打开文件后这个文件会占用0这个文件描述符 open("in.txt", O_RDONLY); // 关闭标准输出1 close(STDOUT_FILENO); // 打开文件后这个文件会占用1这个文件描述符 creat("out.txt", 0666); // 关闭标准错误输出2 close(STDERR_FILENO); // 打开文件后这个文件会占用2这个文件描述符 creat("err.txt", 0666); } void test_io(void) { int x = 0; int y = 0; // 读 标准输入 scanf("%d%d", &x, &y); // 写 标准输出 printf("%d + %d = %d\n", x, y, x + y); malloc(0xFFFFFFFF); // 写 标准错误输出 perror("malloc"); } int main(void) { test_close_std(); test_io(); return 0; }
$ rm out.txt err.txt # 删除这两个保存标准输出结果的文件 $ cat in.txt # 检查标准输入的内容 10 20 $ gcc test_std.c -o test_std # 编译 $ ./test_std # 测试 $ cat out.txt # 检查标准输出 10 + 20 = 30 $ cat err.txt # 检查标准错误输出 malloc: Cannot allocate memory