回顾文件
空文件,也是要在磁盘中占据空间
文件=内容+属性
文件操作=内容+属性操作
标识一个文件:文件名+路径
一个文件要被访问必须先打开:先是通过进程调用接口,接着由操作系统去实现
磁盘中存在着打开的文件和未被打开的文件
文件的本质:进程和被打开文件的关系
回顾文件操作
C语言有文件操作接口,其他语言同样也有,并且都不一样
文件保存在磁盘上,磁盘属于硬件,只能通过操作系统进行访问;想要访问文件就绕不开操作系统;操作系统会提供一系列的系统调用接口进行文件访问
但是,操作系统只有一个,语言却存在很多种;所以无论语言是怎么变化的,系统调用的底层是不变化的
库函数调用接口
写文件-w
int fprintf(FILE *stream, const char *format, ...);
先介绍打印函数 fprintf,将内容写到输入流中也就是写到文件中
结果如下
结果如下
读文件-r
char *fgets(char *s, int size, FILE *stream);
介绍读取函数 fgets在文件中读取大小为 size的内容到字符串 s中
结果如下
系统调用
打开文件-open
文件存在时:
int open(const char *pathname, int flags);
文件不存在时:
int open(const char *pathname, int flags, mode_t mode);
返回值
open() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
pathname:文件名/文件路径
flags:标识符:O_RDONLY 只读, O_WRONLY只写,O_CREAT创建,O_APPEND追加,O_TRUNC每次写时把之前的内容清空;宏所定义的;每个宏对应的数值,只有一个比特位,彼此位置不重叠
mode:文件权限
返回值:成功,返回文件描述符;失败,返回-1
由于文件不存在,所以需要先创建O_CREAT;文件权限默认是0666
结果如下
由于掩码的存在,文件权限并不是0666
写文件-write
ssize_t write(int fd, const void *buf, size_t count);
将大小为 count的内容 buf写到文件中;注意这里的内容类型没有限制,无论是二进制类还是文本类,在操作系统看来都是二进制类;写入成功时返回写入的大小 ssize_t;注意在写入字符串时,这里不同于C语言的规定,不需要计算最后一个字符
结果如下
相比于库函数调用的写文件 w就相当于系统调用FILE_NAME,O_WRONLY|O_CREAT|O_TRUNC,0666;所以库函数调用本质是对系统调用的封装,追加,读取类似
文件操作本质
在上面已经说明文件操作的本质是进程和被打开文件的关系,接下来进行深入学习
首先一个进程可以打开多个文件,所以系统中就必然存在着大量被打开的文件,系统便需要对这些文件进行管理,管理方式:先描述再组织;为了管理这些文件,创建对应的内核结构体struct file{ },其中包含文件的大部分属性
在上面系统调用打开文件时,返回的文件描述符是数字3,为什么呢???
再观察下列代码
同时打开三个文件,返回的文件描述符是连续的额3,4,5;为什么是从3开始呢,而且还是连续的呢???
其原因是,每个文件中都存在着三个标准输出流
stdin 键盘 stdout 显示器 stderr 显示器
它们占据着文件描述符的前三个位置
验证结果如下
在系统调用中接受返回值(文件描述符)是使用 int;为什么在标准输出中也会存在着库函数调用接口中接受返回值的 FILE*呢???
其实 FILE本身是一个结构体,是对系统调用的封装,所以其中就有一部分是文件描述符
文件描述符fd
上面还剩一个问题没有解决,就是问什么为返回的文件描述符都是连续的呢???
下面进行图解
磁盘中的文件test.txt加载到内存中,系统创建结构体struct file对其进行管理;进程的结构体中存在着一个指向管理所有内存中文件的结构体,次结构体中存在这一个数组,也称进程的文件描述符表从下标为零的位置开始指向标准输出,输入,错误,紧接着就是管理文件的结构体;所以,文件描述符的本质就是数组的下标
文件描述符的分配规则
观察下列代码,当我们把文件描述符fd==0关闭时,结果会怎么样呢
如果关闭的是文件描述符fd==2(不是关闭fd==1,原因下面会解释,结果又是怎么样的呢?
由此便可以得出结论:文件描述符的分配规则是从小到大,按照顺序寻找最小的且没有被占用的文件描述符
如果关闭文件描述符fd==1,结果会怎样呢?
很奇怪!!!为什么是这个结果,不应该是打印数字1吗?
文件描述符fd==1是标准输出,也就是将结果打印到显示器上面,现在它被关闭了结果便是显示器并没有打印结果,很合理;那么本来应该被打印的结果去了哪里呢
下面进行图解
从图中可以看到,根据文件描述符的分配规则。当fd==1被关闭之后,管理文件的结构体就寻找到该描述符,并将其指向自己;所以原本应该打印到显示器中内容,现在都被打印到了文件中,接下来打开文件进行验证
结果正确,其实这里还存在着缓冲区的问题,在后面会进行学习的;本应该打印到显示器的内容通过关闭描述符,就可以改变其打印的方向,这种操作就称作重定向,下面我们就来学习有关重定向的内容吧
重定向
重定向的本质:上层调用不变的情况下,改变底层的输出方向
上面是通过关闭文件描述符的方式进行重定向,这种方式太低端;这里介绍一种新的凡是进行重定向
int dup2(int oldfd, int newfd);
将文件描述符表中指向旧文件结构体的指针改变方向,指向新的文件结构体中;若成功,返回新的文件描述符
重定向标准输出dup2(fd,2)
代码如下
图解
重定向追加,标准输入类似,这里就不加赘述
Linux下一切皆文件,这句话该如何理解呢?
在冯诺依曼体系中,操作系统是通过驱动进而控制硬件;每种硬件其实都是由相应的结构体进行控制的,包括其读写功能;上面学习的当文件加载到内存中时会被其对应的结构体进行管理,其实对应的结构体中包含了两个重要的属性读写函数指针;当我们向键盘输入内容时,相应的文件就会调用其函数指针指向键盘结构体对应的功能,由此便可对上面的文件结构体进行补充:每个文件打开时,都会有对应的结构体去进行管理它,同时会调用函数指针完成初始化,并且找到该文件在内核中的缓冲区;所以就在文件结构体的上层来看,所有的设备和文件,统一都是通过调用文件结构体去完成,便可以解释Linux下一切皆文件
图解如下
FILE
观察下列代码
由结果来看,直接打印到显示器上面时,代码正常;当重定向到文件中时,却有些不同,库函数调用接口都打印了两次,系统调用接口却只打印了一次,这又是为什么呢?
为了解决这个问题,先来学习缓存区的概念