[温故而知新] 《Linux/Unix系统编程手册》——文件I/O
来源:互联网 发布:数控铣编程 编辑:程序博客网 时间:2024/06/11 20:56
本文对文件IO这一块做一些梳理,记录思考的一些问题和一些待解决的问题,后续会继续更新。
I hear and I forget,I see and I remember,I do and I understand.
Part 1 :通用IO
/**相关头文件:<fcntl.h><unistd.h>文件IO的几个系统调用fd = open(pathname, flags, mode)numRead = read(fd, buffer, count)numWritten = write(fd, buffer, count)status = close(fd)*/
C标准库的函数真是简洁,跟OOP 形成鲜明的对比就是从参数的传递方式,比如open函数,对于flag是通过位运算来进行各种参数的判断,如果是像Java这种比较啰嗦的语言,实现起来估计就会是一个类,然后里面各种方法重载,然后各种参数。当然两种方式各有优缺点。
思考的一些问题:
对于 open 函数,返回的是一个文件描述符(file descriptor) , 是一个int型结果,为什么不是返回一个具体的结构体呢?
首先如果让我自己来实现这个系统api,必须有个结构体来记录打开的文件的相关信息,比如当前读取到哪个位置了,文件的路径等。从使用者角度来讲,大多数情况关心的只是如何对文件进行IO,屏蔽掉底层的实现显然是比较合理的。
既然返回的是一个int型的,那么fd可以认为就是个索引而已,内核中必须有个结构来维护进程打开的文件列表。对于 read函数我们关心的是读了多少数据,这些数据读完放哪,而函数只能有一个返回值,所以buffer作为函数参数传递了。
有个问题待确认,在汇编层面,系统调用中传递的数组参数是如何进行的?//TODO
目前简单的猜测,传递数组实际传的只是个指针,然后在系统调用时切到内核态后,把进程的虚拟地址进行转换为物理地址然后进行IO,而这一步转换是如何进行的?open调用成功,其返回值为进程未用文件描述符中数值最小者。
原因猜测,一个进程的文件描述符是有限的,所以已经关闭的描述符可以重复利用。关于 O_CLOEXEC 的flag
//TODOopen函数的O_CREAT 标识,用来在打开文件不存在的时候也进行创建,但是这里有个问题,如果open的时候没有传mode,也就是权限没有传的话,亲测,创建出来的文件的权限是个随机值(书中说的是栈中的随机值,没有具体去考证如何从栈中取值的)。 然而这里为什么不直接返回失败呢?//TODO
open函数的O_CREAT标识,可以用来创建文件,那么为什么不用creat函数呢?
好吧,O_CREAT可以和另一个参数 O_EXCL 配合,达到的效果是,判断文件是否存在,如果存在则调用失败。也就是检查文件存在和创建文件是一个原子操作。
实际上 creat() 等价于open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode)
系统调用的read(),write() 实际上只是在传递的参数buffer和内核的缓冲区进行数据拷贝而已,并不是实际的通过磁盘IO然后拷贝到buffer中。那么,触发磁盘IO的时机是什么?
对于写操作,如果没有手动刷,内核有个专门的线程干这个事情,检查是否为脏缓冲(超过一定时间,比如30s)是的话就刷缓冲。对于写操作,如果是内核缓冲区满了是不是也刷缓冲?内核的策略是如何的?//TODOopen 的几个参数
O_NOATIME
//不修改访问时间,对于一些读操作可以优化IO,因为可以少一次把文件的元数据刷到磁盘的操作O_NOFOLLOW
//对于一些有特权的进程非常有用,不跟随符号链接,保证安全性。O_ASYNC
//TODOO_NONBLOCK
//非阻塞IO,有些类型的文件,open后者后续的读写会造成阻塞,加入这个标志会变为非阻塞,open可能会直接失败返回,而对于读,可能只读了部分数据,对于写呢?//TODO
Part 2 : 文件I/O缓冲
/**对于标准库的缓冲(stdio的缓冲)相关的函数有:fprintf(), fscanf(), fgets(), fputs(), fgetc(), fputc()这些最终都是通过系统调用read()和write() 进行IO。设置标准库的缓冲策略相关函数:<stdio.h>int setvbuf(FILE *stream, char *buf, int mode, size_t size)缓冲策略有三种:1. 不缓冲 _IONBF io no buffer2. 行缓冲 _IOLBF io line buffer 遇到换行符或者缓冲区满就调系统调用3. 全缓冲 _IOFBF io full buffer 缓冲区满再调用系统调用setvbuf两个兄弟void setbuf(FILE *stream, char *buf) => setvbuf(fp,buf, ( buf!=NULL) ? _IOFBF:_IONBF, BUFSIZ )也就是缓冲区大小采用stdio.h中定义的默认缓冲区大小,缓冲模式默认为全缓冲#defnie _BSD_SOURCEvoid setbuffer(FILE *stream, char *buf,size_t size);跟setbuf类似,缓冲模式为全缓冲,缓冲区大小自己配置。*/
思考的一些问题:
对于文件I/O的内核缓存,对于写缓冲,内核把缓冲刷到磁盘上的策略是什么?
如果程序没有手动调用flush,那么系统内核会有个线程在周期性执行检查然后flush。脏缓冲区能被刷的条件是达到规定的“年龄”(在/proc/sys/vm/dirty_expire_centisecs ,单位为1%秒,一般是30秒),也就是30秒内没有手动刷,系统的一条长期运行的内核线程下次检查到了就会把它刷到磁盘去。stdio有setvbuf之类的设置缓冲策略的东西,内核呢?如何控制缓冲策略?//TODO
内核用于控制文件IO缓冲的系统调用:#include <unistd.h>int fsync(int fd);int fdatasync(int fd);
fsync和fdatasync的区别?
参考:http://blog.csdn.net/zbszhangbosen/article/details/7956558
简单来说,它们的共同点都是同步操作,需要等磁盘的IO,对于fsync会确保文件的数据和文件的元数据(例如文件的最近访问时间、修改时间等)都同步写完才返回(两次磁盘操作),而fadatasync只保证文件的数据同步写,并不保证文件的元数据同步写(一次磁盘操作)。针对stdio,强制刷新写缓冲到系统内核的函数: fflush(FILE *stream)
打开一个流同时用于输入和输出,C99两项要求:
- 一个输出操作不能紧跟一个输入操作,必须在两者之间调用fflush() 函数或者一个文件定位函数(fseek(),fsetpos(),rewind() )。 这里是不是意味着这些定位函数会调用一次fflush()?//TODO
- 一个输入操作不能紧跟着一个输出操作,必须在二者之间调用一个文件定位函数,除非输入操作已经到了文件结尾。
这两项要求的目的是什么?各种系统的实现如何?(试了下输入后立即输出和 输出后立即输入操作,暂时没发现问题,猜测跟同步问题相关)//TODO
open函数对于缓冲的控制flag
O_SYNC和 O_DSYNC 作用于写操作。O_SYNC
flag, 相当于后续的输出操作,会类似fsync一样,同步写文件的元数据和文件的数据,对性能影响非常大。O_DSYNC
flag, 这个与O_SYNC
类似,不过它的语义跟fdatasync类似。O_RSYNC
flag, 作用于读操作,是与O_SYNC
和O_DSYNC
相结合使用的。具体语义是,如果O_RSYNC|O_DSYNC
,那么在读操作之前,会执行像O_DSYNC
一样所有待处理的写操作。
这个标志的使用场景呢?//TODOI/O缓冲小结,画张图出来//TODO
- 缓冲有多处,stdio缓冲,内核缓冲,磁盘高速缓冲
- 对于stdio缓冲,任意时刻可以调用fflush()刷缓冲; 或者在输出之前,通过调用setbuf(stream,NULL)禁用掉stdio的缓冲,然后直接走系统调用。
- read,write系统调用,并不是直接进行磁盘IO,而是在读写内核的缓冲区。任意时刻可以调用fsync之类的函数强刷内核缓冲到磁盘。也可以在open的时候设置
O_SYNC
之类的标志来强刷缓冲。 - 磁盘的缓冲控制
禁用:hdparam -W0
启用://TODO
裸 I/O:
O_DIRECT
O_DIRECT
需定义_GNU_SOURCE
裸I/O看起来好麻烦的样子,看裸I/O的语义,就是可以不经过内核缓冲区,直接经过磁盘DMA进行IO,所以速度应该很慢。但是O_DIRECT和 O_SYNC有什么区别呢?//TODO
参考:http://stackoverflow.com/questions/5055859/how-are-the-o-sync-and-o-direct-flags-in-open2-different-alike
目前还没验证答案是否正确,大概的意思是说O_DIRECT
并没有保证数据刷到磁盘上函数才返回,而O_SYNC
有这个保证(虽然刷到磁盘上还可能有缓冲)。而O_SYNC
是会经过内核缓冲区的,O_DIRECT
没有经过内存缓冲区,所以O_DIRECT
的使用,需要设置缓冲区,并且有各种内存对齐的要求:- 用于数据传递的缓冲区,内存边界必须为block大小(不同环境的block大小不一样)的整数倍。
- 数据传输的起点,必须是block大小的整数倍。
- 待传递的数据的长度,必须是block大小整数倍。
posix_fadvise() //TODO
给内核提供建议,优化性能。
Part 3 库函数和系统调用混用
/**有的函数对于文件传递的是 FILE* 指针,有的是一个int型的文件描述符#include <stdio.h>int fileno(FILE *stream)FILE *fdopen(int fd, const char* mode);两个函数的作用相反。*/
Part 4 文件操作控制
lseek()函数off_t lseek(int fd, off_t offset, int whence)用来定位文件的读写位置,并不是所有类型的文件都支持,比如像 socket,终端就不支持lseek。lseek() 只是调整内核中与文件相关的文件描述符结构,并没有物理设备访问。lseek() 的 offset是带符号的,也就是可以从文件最后往前读。
思考的几个问题
为什么是lseek() 而不是 seek()?
返回值是long型。文件空洞//TODO
lseek() 到文件最后开始写,和open的时候带上O_APPEND的区别?
区别在于O_APPEND能保证原子性的语义,也就是说保证每次写都是从文件的最后开始写。而如果有两个进程同时lseek()到文件最后然后写,有可能导致写覆盖。int ioctl() //TODO 百宝箱
像这种百宝箱类的函数,参数一般都是一个资源、一个cmd、变长的其它参数。int fcntl(int fd, int cmd, ...)
//TODO 又是百宝箱…- 读取和修改文件状态标志
能读取的状态标志:
O_SYNC
O_RDONLY
O_WRONLY
O_RDWR
//TODO 还有哪些 - O_RDONLY,O_WRONLY,O_RDWR为什么没有与文件状态标志的比特位一一对应,原因很简单,它们有交叉关系……
能修改的标志:
O_APPEND
O_NONBLOCK
O_NOACTIME
O_ASYNC
O_DIRECT - 两个进程修改状态标志会相互影响吗?参考下面的文件描述符与文件的关系。
- 读取和修改文件状态标志
文件描述符与文件的关系
书中一张神图解决所有问题//TODO如何读写大文件,在32位的机器上,off_t最大是2G
一种推荐做法是定义一个宏,_FILE_OFFSET_BITS 64
然后,之前的IO函数都会变为64位的版本,比如open()
->open64()
…
所以那些都是宏定义。创建临时文件的几种种方法:
int mkstemp(char* template)
该调用会加上O_EXCL标志,模版参数类似”/tmp/abcXXXXXX”,内核会替换最后6个X并且保证文件名唯一,如何做到?//TODOtmpnam(),tempnam(),mktemp()
能用于生成唯一文件名,区别是什么?又为什么说会有安全漏洞?有安全漏洞那么哪些场景下可用?//TODOFILE* tmpfile(void)
文件流关闭后自动删除该文件,如何做到?//TODO
内部调用unlink()来删除文件名??//TODO
进程退出后自动关闭所有打开的文件描述符,然后就删除临时文件?
一些好用的api //TODO
readv //read vectorwritev //write vectorpread //position readpwrite //position write
- [温故而知新] 《Linux/Unix系统编程手册》——文件I/O
- Linux系统编程手册 文件I/O缓冲
- UNIX环境编程—文件I/O
- linux系统编程手册 I/O复用
- Unix/Linux编程-系统调用I/O
- 《Linux/UNIX系统编程手册》 英文版读书笔记 Alternative I/O Models63.2
- 《Linux/UNIX系统编程手册》 英文版读书笔记 Alternative I/O Models63.4
- 其他备选的I/O模型--《linux/unix系统编程手册》
- Linux 系统应用编程——文件I/O
- Linux 系统应用编程——文件I/O
- UNIX环境高级编程——文件I/O
- Unix环境高级编程——文件I/O
- 《UNIX环境高级编程》读书笔记 —— 文件 I/O
- unix环境高级编程——文件i/o
- UNIX环境高级编程—文件I/O
- Linux系统文件I/O编程(三)---I/O多路复用
- Linux系统文件I/O编程(三)---I/O多路复用
- linux系统编程之文件I/O
- 第八周oj2
- 利用ligerUI实现类似数据列表过滤展示,类似JQuery datatable和某些框架的dataGrid
- 《数据结构导论之遍历》
- luogu解题报告:P1262间谍网络【代码量惊人】【图论/强连通缩点】
- hduoj1492
- [温故而知新] 《Linux/Unix系统编程手册》——文件I/O
- 什么是SOA
- CSS设置列表的符号
- Redis初识
- 同花顺面试
- 树莓派用Java实现Max7219 LED点阵输出字符常用汉字和任意字符......
- ffmpeg的使用
- Java异常学习总结
- 五种开源协议简介