文章目录:
- 1. 文件描述符
-
- 1.1 文件描述符的实质:
- 1.2 文件描述符的分配规则:
- 1.3 一个进程最大可以开多少个文件描述符?
- 2. 文件流指针(FILE)
-
- 2.1 前言
- 2.2 文件流指针的本质
- 2.3 深究FILE
- 3. 文件流指针和文件描述符的区别
- 4. 重定向
-
- 4.1 重定向的命令符号
- 4.2 重定向的原理
- 4.3 使用dup2系统调用
1. 文件描述符
文件描述符就是内核当中 fd_array 数组的下标,是一个正整数
我们写出如下代码,并在代码最后加上一个循环,让代码不要退出
运行程序,后查看当前进程
如下图所示:
文件描述符:
- 0–>标准输入
- 1–>标准输出
- 2–>标准错误
1.1 文件描述符的实质:
在进程中有一个结构体指针(struct file_struct* files),指向了一个结构体(struct files_struct),而在这个结构体(struct files_struct)中有一个数组,这个数组的下标就是文件描述符,数组的每个元素的类型是struct file*,struct file* 这个结构体指针指向 struct file这个结构体(保存了文件的元信息),而最终和磁盘打交道的就是struct file这个结构体
1.2 文件描述符的分配规则:
最小占用原则:在数组中找到当前没有被使用的最小的一个下标,作为新的文件描述符
如下代码所示,我们关闭了2,会发现fd不是3而是2
1.3 一个进程最大可以开多少个文件描述符?
进程最大打开文件描述符个数是由操作系统中一个参数限制的,可以通过命令:
ulimit -a
查看到一个open files
,open files后面的值就是进程可以打开的最大文件描述符个数
最大文件描述符个数更改:
ulimit -n
2. 文件流指针(FILE)
2.1 前言
我们可以先cd到 /usr/include目录下,在此目录下我们可以看到所有的头文件
我们在用vim打开stdio.h
此时我们就可以找到FILE
2.2 文件流指针的本质
- FILE文件流指针是一个typedef之后的值,本质是一个结构体
- 在“/usr/include/stdio.h”目录下可以看到
2.3 深究FILE
而我们怎么才能查看FILE这个结构体中的内容呢?
我们可以通过grep "struct _IO_FILE {" . -R
命令进行搜索,此时我们会看到,这个结构体的位置在 ./libio.h
此时我们只需vim进入libio.h,在进行搜索即可找到
在struct _IO_FILE这个结构体中,我们可以看到读缓冲区
和写缓冲区
int _fileno;当中保存的内容就是文件描述符
在此我们可以更清楚的了解文件流指针的工作原理,如下图所示:
之前在进程终止的时候说的刷新缓冲区:指的是C库当中维护的读写缓冲区
借助上图我们可以理解:_exit()函数之所以不会刷新缓冲区,是因为_exit()函数是内核代码,直接操作内核结束结束了进程,而此时并不会通知上层的C库,所以,C库维护的缓冲区完全在无感知的情况下进程就终结了
3. 文件流指针和文件描述符的区别
- 文件流指针是一个结构体,在结构体内部保存了文件描述符
- 文件描述符是一个正整数,其含义为fd_array数组的下标
- 文件流指针维护了读写缓冲区
4. 重定向
4.1 重定向的命令符号
> :清空重定向,将文件内容清空之后在重定向
测试如下:
我们vim一个abc文件,可以看到abc文件中的内容如下图所示:
然后我们使用 ls > abc 命令之后可以看到abc中的内容已经被清空重定向了
>> :追加重定向,直接在文件的尾部进行重定向
测试如下:
我们对abc文件进行追加重定向,可以看到,直接在文件的尾巴进行了重定向
4.2 重定向的原理
将 fd_array 数组当中的元素struct file* 指针的指向关系进行修改,改变成为其它的struct file结构体的地址
4.3 使用dup2系统调用
int dup2(int oldfd, int newfd);
通过man手册我们可以对dup2函数进行一些了解,我们会看到这样一段话
- newfd的值是拷贝于oldfd的值,及newfd的值来源于oldfd
eg:上文重定向原理图解中的过程可以写为:dup2(3,0);
(1) 如果必要,使用dup2进行重定向的时候,会先关闭newfd
- 必要:oldfd是正常的文件描述符
- 如果oldfd不是一个有效的文件描述符,则不关闭newfd,重定向失败 eg:dup2(10,0) 没有文件描述符10
- 如果oldfd是一个有效的文件描述符并且和newfd是相同的文件描述符数值,则dup2什么事情都不有干 eg:dup2(0,0);
(2) 将oldfd的值拷贝给newfd
注意:
一般C库函数写入文件时是全缓冲,而写入显示器是行缓存
eg:“n”==》行缓冲