文件小注意
目录
0 前言
1 标识 O_CREAT O_APPEND
2 ftruncate与truncate
3 O_DIRECT与O_DSYNC、O_SYNC
4 open与fopen
5 关于mmap
0 前言
文件操作在软件开发中是很常见的一件事。虽然与它相关的工作看起来不怎么起眼,无非就是通过通过open、read、write、close几个调用搞定的事情,但也正是这种看似简单的工作,在稍加变化需求的场景中,会给你带来意想不到的麻烦。这里就记录本人最近关注到的几个点,做为小例子,分享给大家。
多说一句,往往简单的事情会给人带来大麻烦,就是因为人的大脑会对简单或者认为简单的事情过于麻痹大意,先入为主,不加重视,导致往往只看到冰山一角。该怎么做,不用多说了,直接看实例。(本文都是基于Ubuntu系统)
1 标识 O_CREAT O_APPEND
对于linux下的open调用,你敢说man里面的这些flags你都清楚了?
如果路径下未有文件,就需要O_CREAT标识,否则open就会失败。如果想让文件处于追加写的状态,则需要O_APPEND标识。这还是简单的标识,还有许多复杂标识等着我们深入了解。
man手册对于系统调用都有详尽的说明,很多人其实是比较抗拒看man手册的,往往通过百度了解一下接口的使用,就开始调用了。这主要是man手册为英文写成,很多时候搞清楚一个接口需要花比较多的时间,另外个别语句还可能比较绕口,有时候琢半天也不明白意思,这个时候不如直接百度来的称手。但是这些都是初期的阵痛与障碍,时间长了,就会习惯,而且效率也会变高不少。
等等,有人可能会说,man open的时候,看不到关于open接口的说明。这个时候,需要安装一下,apt install manpages-dev
关于man的使用,可以参考博主之前的一篇文章:https://blog.csdn.net/wwwyue1985/article/details/123929554
2 ftruncate与truncate
使用open调用创建文件后,文件大小为零。这个时候,如果通过mmap建立文件映射,那么写文件内存会失败(会报Bus error的错误)。同样,如果文件大小不够,写范围超过文件大小时,也会失败。也就是说,文件大小不会随着写内容的增加而增大。貌似window倒是支持这一点,神奇吧。此时,我们可以通过ftruncate来扩展文件大小。如果查看man手册,我们会发现另外一个接口truncate。二者有什么区别呢?
通过手册,可以看到,使用ftruncate时,文件必须打开,且支持写入;使用truncate接口,文件必须是可写的。
3 O_DIRECT与O_DSYNC、O_SYNC
操作系统为了提高文件读写性能,特别是像磁盘这种不支持随机读写的设备(需要旋转盘面和移动磁头),往往会在内存中开辟一片缓冲区。写入文件的内容不会立即写入硬件设备,而是先写到文件的缓冲区,当攒够一定量或者满足某些特征(也可能是内核自己排序,使得对硬件设备的访问尽可能有序)之后,才会触发写设备,以此来提高落盘性能。但有时候,我们又想让写入文件的内容立即同步到磁盘等硬件设备(比如一些关键的日志或者元数据),以免意外事件(比如断电)导致关键信息丢失或文件不可用。此时,O_DIRECT标识就需要登场了。使用O_DIRECT标识意味着写文件的数据会同步传输到硬件设备:
但如果我们看man手册,会发现有相似功能的其他标识,比如O_DSYNC、O_SYNC:
这几个标识有什么区别呢?根据man手册说明,上面两个sync标识跟direct标识的差异主要在是不是同步文件元数据。什么是文件元数据?内核为了管理文件,需要一些辅助数据,比如文件的名称、大小、创建时间、磁盘位置等等,这些都不是文件内容本身的数据,而是为了管理文件所需的数据,这类数据就是元数据。当我们向文件写入内容的时候,文件的元数据可能就会发生变化,使用这里的带sync的两个标识,则操作系统不仅帮我们同步文件本身的内容,也同步文件相关的元数据。
那有人可能就又问了,同样是支持同步元数据,O_DSYNC和O_SYNC又有什么区别呢?根据man手册说明,O_DSYNC仅同步与输出数据相关的元数据,而O_SYNC同步文件相关的元数据。所以从集合的角度来看,应该是O_SYNC包含O_DSYNC,O_DSYNC包含O_DIRECT的关系。
4 open与fopen
我们平时操作文件时,会发现有两套类似的接口,open与fopen。它们的使用上有一些差别,很多人往往是通过网络复制一份代码,简单改改就使用,可能没有细究过open与fopen有什么区别。其实,fopen是C库提供的接口,而open是系统调用。从可移植性的角度来看,fopen的通用性更强(一些不支持操作系统的嵌入式系统中也支持,比如xilinx就自己提供了一套文件库,可以在裸机程序中使用上述fopen类接口)。但是在Linux下,fopen最终也是通过open这个系统调用实现的。Open类接口不仅支持普通文件,还支持被系统抽象为文件的所有东西,比如各种各样的设备。这样描述,大家是不是就清楚了。
5 关于mmap
额外补充一点,关于文件mmap映射的。用户空间看到的是(接口返回值)进程虚拟地址空间的一个虚拟地址,内核帮我们建立了页表并完成了物理地址到虚拟地址的映射。当我们将数据写入这个虚拟地址时,就相当于写入文件在内存的缓冲区。同步后,数据就体现到硬件上最终的文件中。基于这一点,我们可以在用户空间,对文件访问进行专门的优化。