1.1文件描述符分配规则
在进程的task_struct找到指针files,通过指针找到files_struct结构体对象,接着访问nd_array,找到数组内没有被分配&&最小的数组下标fd,会为新打开的文件建立一个struct file结构体对象(直接或间接包含文件属性),用新打开文件的属性初始化这一对象,接着把对象的地址填入nd_array[fd]

测试



我们看到关闭文件描述符1指向的文件,接着printf的内容都打印到了在之后第一个打开的文件log.txt(也就是文件描述符1分配给了log.txt),因为printf打印到stdout,stdout的FILE只认文件描述符1

输出重定向的做法,1.打开并清空文件 (2.向文件写入)
下图测试是只打开并清空文件,log.txt被清空

上面👆的测试我们可以看到,把文件描述符0指向的文件关了,在之后打开新文件,文件描述符0被分配了新文件,有没有什么办法在文件打开之后,把别的文件描述符指定分配给文件呢?下面我们介绍dup2
1.2 dup2
系统调用会把task_struct中struct files_struct* files中的nd_array数组下标fd的数组元素拷贝到1下标的内容,所以nd_array[newfd]=nd_array[oldfd]
如果newfd非空,那dup2会首先关闭newfd指向的文件,释放指向的struct file




因为标准输入/输出/错误的FILE封装的fd分别是0,1,2,在调用库函数比如fgets从标准输入读,进行系统调用,只认文件描述符0,而nd_array[0]的内容是什么,是否发生改变,语言层是不知道的,重定向的本质就是把nd_array的内容改了,就可以实现重定向
1.3 自定义shell的重定向
自定义shell主要处理的还是字符串用户输入的字符串,处理干净之后,进行差错处理,让子进程来执行或者父进程本身来执行
而重定向,把输入的字符串比如"ls -a -l > log.txt"切割为三部分,1.命令 2.重定向方式 3.文件名filename 这样在子进程exec前把stdout或者stdin的文件描述符替换为文件filename的fd,输入/追加/输出的区别就是打开文件的选项不同



由上面操作可以看到,在进程替换后,之前进行的重定向依然有效,进程替换的是代码和数据,与打开的文件无关
理解一切皆文件


小试牛刀
- Linux下两个进程可以同时打开同一个文件,这时如下描述错误的是()
A.两个进程中分别产生生成两个独立的fd
B.两个进程可以任意对文件进行读写操作,操作系统并不保证写的原子性
C.进程可以通过系统调用对文件加锁,从而实现对文件内容的保护
D.任何一个进程删除该文件时,另外一个进程会立即出现读写失败
E.两个进程可以分别读取文件的不同部分而不会相互影响
F.一个进程对文件长度和内容的修改另外一个进程可以立即感知
D
- 以下关于标准输入输出错误的描述正确的是()
A.在文件描述符中0表示标准输出,1表示标准错误,2表示标准输入
B.在文件描述符中0表示标准错误,1表示标准输出,2表示标准输入
C.在文件描述符中0表示标准输入,1表示标准输出,2表示标准错误
D.在文件描述符中0表示标准输出,1表示标准输入,2表示标准错误
C

- 以下描述正确的是()
A.文件描述符和文件流指针没有任何关系
B.文件流指针结构中封装了文件描述符
C.通过open打开文件返回的FILE *fp可以直接使用read读取数据
D.通过open打开文件返回的FILE *fp可以直接使用fread读取数据
B
C/D linux的open系统调用返回的是文件描述符(是一个整数)
- 以下描述正确的是 [多选]()
A.程序中打开文件所返回的文件描述符, 本质上在PCB中是文件描述符表的下标
B.多个文件描述符可以通过dup2函数进行重定向后操作同一个文件
C.在进程中多次打开同一个文件返回的文件描述符是一致的
D.文件流指针就是struct _IO_FILE结构体, 该结构体当中的int _fileno 保存的文件描述符, 是一对一的关系
ABD
C选项测试如下

- 在bash中,在一条命令后加入"1>&2"意味着()
A.标准输出重定向到标准错误输出
B.标准输入重定向到标准错误输出
C.标准输出重定向到标准输入
D.标准错误输出重定向到标准输入
A
1>&2,其中&2指向数字对应的文件描述符,如果是1>2,2也就是单独的数字表示文件名,echo 3>log.txt如果log.txt不存在,会新建

如果是1>3,3会被当做文件名,echo "测试" 1>3如果3不存在,会新建

- bash中,需要将脚本demo.sh的标准输出和标准错误输出重定向至文件demo.log,以下哪些用法是正确的 [多选]()
A.bash demo.sh &>demo.log
B.bash demo.sh >&demo.log
C.bash demo.sh >demo.log 2>&1
D.bash demo.sh 2>demo.log 1>demo.log
ABC
A/B 文件1 &> 文件2 和 文件1 >& 文件2 都是把文件1的stdout和stderr重定向到文件2
C demo.sh > demo.log是把文件demo.sh的标准输出重定向到文件demo.log,接着2>&1把demo.sh的标准错误重定向到标准输出
D 分前后两部分理解,demo.sh 2>demo.log,打开并清空文件demo.log(输出重定向的前置操作),将demo.sh的标准错误绑定到文件demo.log,接着打开并清空demo.log,将demo.log的标准输出绑定到demo.log,首先第二次打开的时候导致demo.sh的标准错误信息丢失,其次,把一个文件的标准输出重定向到文件自身的意义何在
- 以下对int dup2(int oldfd, int newfd);接口描述错误的是: [多选]()
A.重定向后,oldfd和newfd都会操作oldfd所操作的文件
B.重定向后,oldfd和newfd都会操作newfd所操作的文件
C.重定向前,若newfd已经有打开的文件,则会关闭
D.重定向前,若oldfd已经有打开的文件,则会关闭
AC
- 以下代码的功结果是
A.将hello bit打印到终端显示
B.将hello bit 写入到tmp.txt中
C.将hello bit 打印到终端显示并且写入tmp.txt文件中
D.既不打印,也没有写入到文件中
c
void func() {
int fd = open("./tmp.txt", O_RDWR|O_CREAT, 0664);
if (fd < 0) {
return -1;
}
dup2(fd, 1);
printf("hello bit");
return 0;
}
B

系统调用会把task_struct中struct files_struct* files中的nd_array数组下标fd的数组元素拷贝到1下标的内容,而printf是往stdout写的,FILE内部封装了fd,stdout标准输出对应的文件描述符是1,所以往stdout写就是往文件描述符1指向的文件写,现在1下标写的是tmp.txt对应的struct file结构体对象的地址,所以printf写到了tmp.txt