目录
前言
各类语言的文件操作其实是对系统调用的封装
我们经常说,创建一个文件,或者打开一个文件,其实并不是我们用户本身创建,而是进程创建文件。文件是存储在磁盘上的,是硬件,像文件写入其实就是向硬件写入,而我们用户这一角色是没有权力直接写入的,操作系统(OS)是硬件的管理者,必须要通过OS写入;
但是,OS不相信任何人,只是提供了系统调用接口对外,想访问文件,就需要使用系统调用;而C/C++..语言都提供了这些系统调用的封装,为何要封装呢?跨平台性。
系统调用
open
打开一个文件,返回一个文件描述符,可以以flags形式打开文件
//2号手册 是系统调用(System Calls) 查找系统调用里面的open函数 man 2 open
pathname:表示文件的路径名,也可以是一个文件名;如果没有路径名,默认在当前文件下;
flags:表示打开文件的方式选项,参见选项有:
- O_WRONLY:只写打开
- O_CREAT:文件不存在时创建文件
- O_RDONLY:只读打开
- O_RDWR:读写方打开
- O_APPEND:追加方式打开
- O_TRUNC:清空文件,重新写入
mode:如果这个文件不存在,那么以写的方式打开的时候就会创建这个文件,在创建文件的时候需要给这个文件设定权限(使用八进制数)(会被umask影响);
如果这个文件存在的话,那么就不用传第三个参数了,因为文件的权限已经确定了
参数flags
在open函数定义中,flags是以整形定义的,有32比特位,满足参数个数不固定的情况,以二进制形式传参;
实际上是一个32位二进制的位图,位图(Bitmap)是一种基于位操作的数据结构,用于表示一组元素的集合信息。位图中的每个二进制位都表示着某个元素是否在集合中。
比如宏O_WRONLY假设表示的是2即10(二进制),O_CREAT表示4,即100(二进制)。那么O_WDONLY|O_CREAT=110(二进制)。在open函数内部就会有相应的机制监测flags的二进制位哪些是1,再分别对应其代表的功能。
参数mode
表示一个四位八进制的数,取后三位来表示各个角色的权限;
例:0666,取666用二进制表示的就是110 110 110分别对应文件的拥有者、所属组、其他人的(other)权限。
如果当文件不存在时,就要设置该参数,不然就会出现权限处乱码
当新建一个文件时,一定要加上mode参数,来设置权限
umask默认为002,也就是过滤掉了“其他人”的w权限,所以得到的最终文件权限编码是664。
文件权限和权限掩码的关系:文件权限& (~umask权限掩码)
write
用于向文件中写入数据。通过指定文件描述符、数据来源的缓冲区地址和要写入的字节数;可以将数据写入到文件中。如果写入失败则返回-1,否则返回写入的字节数;
- fd表示的是文件标识符
- buf表示的是数据的地址;
- 对于系统调用来说,它并不在意写入的数据是什么类型的,它接收到的数据都是二进制的数字,然后按照字节为单位写入。
- count表示的是字节数
- 返回值类型: ssize_t 类型表示有符号整型,输出格式为%ud.
追加方式
以追加方式写入,只需要在open(...)第二个参数中 把O_TRUNC换成O_APPEND
read
用于从文件中读取数据。通过指定文件描述符、缓冲区地址和读取的字节数;
可以将文件中的数据读取到缓冲区中。
读取成功返回读取的字节数,否则返回-1.
int fd:打开文件时返回的文件描述符。
void* buf:从文件中读取的数据放在这个数组中,同样系统不管文件中的数据类型是什么,都是按字节放入这个数组中。
size_t count:要读取的字节个数。
ssize_t:读取了多少个返回多少。
使用只读方式打开(目标文件已经存在,open(...)中mode参数可以不用加入设置);
将读取的内容放在ch_arr数组中。
close
用于关闭一个文件描述符,释放系统资源。(也就是令struct file* fd_array[]对应下标fd指向空。)
在文件操作完成后,应该及时关闭文件描述符,以防资源泄漏。
关闭成功返回0,否则返回-1.
文件描述符
操作系统要管理文件,必定要让文件先加载到内存,然后先描述,再组织。内核中要有描述对应文件的结构体——也就是struct file;
struct file中最核心的数据可以分为3大类,属性,方法集,缓冲区。而文件描述符fd就存在属性当中。
文件是由进程发起创建的,而一个进程实际上是一个PCB(task_struct),里面有一个结构体指针struct file_struct* files,指向了一个结构体。这个结构体中又有一个指针数组struct file* fd_array[N];该指针数组存放了指向进程所打开文件的结构体下标,也就是文件描述符fd;
- 内核会返回一个小的非负整数。这个非负整数就叫做描述符,也叫文件描述符。文件描述符是用于唯一标识文件的号码。
- 进程实际上并不记录文件本身,而只需要记录一个为一个文件ID。所有被打开的文件的信息都被集中在一起被内核管理,内核向进程提供文件的接口。
打开多个文件并观察其文件描述符
创建的每一个进程开始的时候都有三个打开的文件:标准输入流(fd=0),标准输出流(fd=1),标准错误流(fd=2)。
这三个文件分别对应的硬件设备是键盘、显示器、显示器。
而这三个都是默认自动打开的
当进程打开文件时,会在struct file*数组中找到当前没有被使用的最小的下标,作为新的文件标识符。
如果在打开文件之前,把这个三个文件流关闭,那么就会自动分配到当前的最小下标。
C语言文件操作
C语言文件操作默认打开三个输入输出流,分别是stdin,stdout,stderr。
之前提到过,不同语言的文件操作不过是对系统调用的封装,这里发现C语言的返回值和系统调用open..返回值不一样,我们大胆猜测FILE是一种封装
打印输入输出流
可以发现,FILE结构体中是有文件描述符的。
文件描述符fd的分别规则是:从小到大,按顺序查找,将没有被占用的数组下标作为被打开文件的文件描述符fd值。
理解一切皆文件
这个文件可以理解成结构体
- 每一个硬件,操作系统都会维护一个struct file类型的结构体,硬件的各种信息都在这个结构体中,并且还有对应读写函数指针(对硬件的操作主要就是读写)。
- 每个硬件的具体读写函数的实现方式都在驱动层中,使用到相应的硬件时,操作系统会通过维护的结构体中的函数指针调用相应的读写函数。
- 站在操作系统的角度来看下层,无论驱动层和硬件层中有什么,在它看来都是struct file结构体,都是通过维护这个结构体来控制各种硬件。
- 站在操作系统的角度来看上层,无论用户层以及系统调用有什么,在它看来都是一个个进程,都是一个个的task_struct结构体,都是通过维护这个结构体来调度各个进程的。
- 真正的文件在操作系统中的体现也是结构体,操作系统维护的同样是被打开文件的结构体而不是文件本身。
理解open操作
- 创建struct file(包括fd)
- 开辟文件缓存区,加载文件中的数据(延后)
- 查进程的文件描述符表(fd_array数组)
- 将file的内存地址填入到fd_array[fd]中
- 返回fd.