一.文件基本操作
二.文件基本操作及其对应的系统调用介绍
2.1.OS的管理框架
文件存在于磁盘(硬件设备)上,我们通过C/C++读取到文件,并不是我们想象中的直接读
问:能否直接操作磁盘读取文件吗??
答:不可以!如果经由C/C++直接操作,可能会擅自修改磁盘中的其他内容
操作系统不相信任何人
2.2. C/C++读取文件的过程
C语言提供的 fopen函数,C++提供的 ofstream,都属于上层接口
open函数,属于系统接口,上层接口会去 调用系统接口 进入OS
我们这里仅关注 上层接口的调用 以及 系统接口的调用
2.2. 上层接口的调用
2.2.1.打开文件的功能实现,以open()和fopen()函数的调用为例
系统接口是open函数,首先看看调用系统接口需要哪些参数:
第一个参数:需要读取的文件所在路径
第二个参数:标志位(以读/写的方式打开文件)
第三个参数:权限设置
参数说明:
这里主要说明第二个和第三个参数
第二个参数是标志位,告诉open函数是要以读还是以写的方式打开文件,第二个参数必须包含O_RDONLY, O_WRONLY, 以及 O_RDWR 中的一个。
O_WRONLY:以写的方式打开文件
O_RDONLY:以读的方式打开文件
O_WRONLY:以读写的方式打开文件
在此基础上可以附加其他参数O_CREAT:当文件不存在时,创建文件
O_APPEND:在文件末尾追加第三个参数是文件权限设置
Linux中每一个文件被创建出来都自带权限,分别表示用户权限、组权限、其他权限
第三个参数就是设置权限,示例给的 0644 是 8进制 (6 表示011,对应上面的 -rw)
(3)返回值:文件描述符
打开/创建不同的文件,文件描述符不同,设置对应的文件描述符方便OS管理各种文件
下面打开了五个不同的文件,所以会被分配五个不同的文件描述符
补充:
文件描述符从3开始,是因为C在运行程序之前,会自动打开三个输入输出流,分别是stdin、stdout、stderr
stdin:标准输入,对应的外设是键盘 fd:0
stdout:标准输出,对应的外设是显示器 fd:1
stderr:标准错误,对应的外设是显示器 fd:2
函数原型
FILE *fopen(const char *filename, const char *mode);
参数
filename-- 这是 C 字符串,包含了要打开的文件名称。
mode-- 这是 C 字符串,包含了文件访问模式。
功能
使用给定的模式mode打开filename所指向的文件。
返回值
文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回 NULL,并把错误代码存在error中。
一般而言,打开文件后会做一些文件读取或写入的动作,若打开文件失败,接下来的读写动作也无法顺利进行,所以一般在 fopen() 后作错误判断及处理。
参数说明
参数mode字符串包含了文件访问模式,欲打开的文件路径及文件名,参数 mode 字符串则代表着流形态。
mode 有下列几种形态字符串
字符串
说明
r
以只读方式打开文件,该文件必须存在。
r+
以读/写方式打开文件,该文件必须存在。
rb+
以读/写方式打开一个二进制文件,只允许读/写数据。
rt+
以读/写方式打开一个文本文件,允许读和写。
w
打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+
打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a
以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
a+
以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
wb
以只写方式打开或新建一个二进制文件,只允许写数据。
wb+
以读/写方式打开或新建一个二进制文件,允许读和写。
wt+
以读/写方式打开或新建一个文本文件,允许读和写。
at+
以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+
以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
fopen()系统调用背后的过程如下:
2.2.2.读文件功能的实现,以read()调用为例
系统调用提供给应用程序的 调用请求接口,调用请求中执行了 软中断的指令,应用程序使用调用请求后, 处理器会产生一个中断,中断服务得到执行,中断服务根据 调用号执行特定的功能实现函数。
注:图很重要,要多对照看
系统调用是昂贵的,因为这个过程,涉及到用户态到内核态的切换,而这个切换的过程是通过中断实现的,从用户态切换到内核态需要保护现场,从内核态到用户态时需要恢复现场,也就是上下文切换了,所以一次系统调用的开销,还是蛮大的。
系统调用是操作和理解 操作系统内核的关键切入点,系统调用只是入口,真正的工作都是内核完成。内核中会有很多机制来完成这些工作,比如内核的驱动模块、内核的中断系统等,这些都是做内核开发时会经常设计的模块。
read系统调用的原型:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
read系统调用,是从与文件描述符flides相关联的文件中读取前nbytes字节的内容,并且写入到数据区buf中。read系统调用返回的是实际读入的字节数
参数
fd是文件描述符,count是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。注意这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。
返回值
read函数返回时,成功则返回读取的字节数,出错返回-1并设置errno返回值,说明了buf中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count,例如:读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字节而请求读100个字节,则read返回30,下次read将返回0。
2.2.3.写文件功能的实现,以write()调用为例
头文件#include <unistd.h>
三个参数
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
// write(int fd, const void *buf, size_t count);
第一个参数 文件描述符fd
第二个参数 无类型的指针buf,可以存放要写的内容
第三个参数 写多少字节数
2.2.4.关闭文件功能的实现,以close()调用为例
终止文件描述符flides与其对应的文件间的联系,文件描述符被释放,可重新使用。
#include <unistd.h>
int close(int flides);