[温故而知新] 《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这种比较啰嗦的语言,实现起来估计就会是一个类,然后里面各种方法重载,然后各种参数。当然两种方式各有优缺点。

思考的一些问题:

  1. 对于 open 函数,返回的是一个文件描述符(file descriptor) , 是一个int型结果,为什么不是返回一个具体的结构体呢?
    首先如果让我自己来实现这个系统api,必须有个结构体来记录打开的文件的相关信息,比如当前读取到哪个位置了,文件的路径等。从使用者角度来讲,大多数情况关心的只是如何对文件进行IO,屏蔽掉底层的实现显然是比较合理的。
    既然返回的是一个int型的,那么fd可以认为就是个索引而已,内核中必须有个结构来维护进程打开的文件列表。

  2. 对于 read函数我们关心的是读了多少数据,这些数据读完放哪,而函数只能有一个返回值,所以buffer作为函数参数传递了。
    有个问题待确认,在汇编层面,系统调用中传递的数组参数是如何进行的?//TODO
    目前简单的猜测,传递数组实际传的只是个指针,然后在系统调用时切到内核态后,把进程的虚拟地址进行转换为物理地址然后进行IO,而这一步转换是如何进行的?

  3. open调用成功,其返回值为进程未用文件描述符中数值最小者。
    原因猜测,一个进程的文件描述符是有限的,所以已经关闭的描述符可以重复利用。

  4. 关于 O_CLOEXEC 的flag
    //TODO

  5. open函数的O_CREAT 标识,用来在打开文件不存在的时候也进行创建,但是这里有个问题,如果open的时候没有传mode,也就是权限没有传的话,亲测,创建出来的文件的权限是个随机值(书中说的是栈中的随机值,没有具体去考证如何从栈中取值的)。 然而这里为什么不直接返回失败呢?//TODO

  6. open函数的O_CREAT标识,可以用来创建文件,那么为什么不用creat函数呢?
    好吧,O_CREAT可以和另一个参数 O_EXCL 配合,达到的效果是,判断文件是否存在,如果存在则调用失败。也就是检查文件存在和创建文件是一个原子操作。
    实际上 creat() 等价于
    open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode)

  7. 系统调用的read(),write() 实际上只是在传递的参数buffer和内核的缓冲区进行数据拷贝而已,并不是实际的通过磁盘IO然后拷贝到buffer中。那么,触发磁盘IO的时机是什么?
    对于写操作,如果没有手动刷,内核有个专门的线程干这个事情,检查是否为脏缓冲(超过一定时间,比如30s)是的话就刷缓冲。对于写操作,如果是内核缓冲区满了是不是也刷缓冲?内核的策略是如何的?//TODO

  8. open 的几个参数
    O_NOATIME //不修改访问时间,对于一些读操作可以优化IO,因为可以少一次把文件的元数据刷到磁盘的操作
    O_NOFOLLOW //对于一些有特权的进程非常有用,不跟随符号链接,保证安全性。
    O_ASYNC //TODO
    O_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类似,缓冲模式为全缓冲,缓冲区大小自己配置。*/

思考的一些问题:

  1. 对于文件I/O的内核缓存,对于写缓冲,内核把缓冲刷到磁盘上的策略是什么?
    如果程序没有手动调用flush,那么系统内核会有个线程在周期性执行检查然后flush。脏缓冲区能被刷的条件是达到规定的“年龄”(在/proc/sys/vm/dirty_expire_centisecs ,单位为1%秒,一般是30秒),也就是30秒内没有手动刷,系统的一条长期运行的内核线程下次检查到了就会把它刷到磁盘去。

  2. stdio有setvbuf之类的设置缓冲策略的东西,内核呢?如何控制缓冲策略?//TODO

内核用于控制文件IO缓冲的系统调用:#include <unistd.h>int fsync(int fd);int fdatasync(int fd);
  1. fsync和fdatasync的区别?
    参考:http://blog.csdn.net/zbszhangbosen/article/details/7956558
    简单来说,它们的共同点都是同步操作,需要等磁盘的IO,对于fsync会确保文件的数据和文件的元数据(例如文件的最近访问时间、修改时间等)都同步写完才返回(两次磁盘操作),而fadatasync只保证文件的数据同步写,并不保证文件的元数据同步写(一次磁盘操作)。

  2. 针对stdio,强制刷新写缓冲到系统内核的函数: fflush(FILE *stream)

  3. 打开一个流同时用于输入和输出,C99两项要求:

    • 一个输出操作不能紧跟一个输入操作,必须在两者之间调用fflush() 函数或者一个文件定位函数(fseek(),fsetpos(),rewind() )。 这里是不是意味着这些定位函数会调用一次fflush()?//TODO
    • 一个输入操作不能紧跟着一个输出操作,必须在二者之间调用一个文件定位函数,除非输入操作已经到了文件结尾。
      这两项要求的目的是什么?各种系统的实现如何?(试了下输入后立即输出和 输出后立即输入操作,暂时没发现问题,猜测跟同步问题相关)//TODO
  4. open函数对于缓冲的控制flag
    O_SYNC和 O_DSYNC 作用于写操作。
    O_SYNC flag, 相当于后续的输出操作,会类似fsync一样,同步写文件的元数据和文件的数据,对性能影响非常大。
    O_DSYNC flag, 这个与O_SYNC类似,不过它的语义跟fdatasync类似。
    O_RSYNC flag, 作用于读操作,是与O_SYNCO_DSYNC相结合使用的。具体语义是,如果O_RSYNC|O_DSYNC ,那么在读操作之前,会执行像O_DSYNC一样所有待处理的写操作。
    这个标志的使用场景呢?//TODO

  5. I/O缓冲小结,画张图出来//TODO

    • 缓冲有多处,stdio缓冲,内核缓冲,磁盘高速缓冲
    • 对于stdio缓冲,任意时刻可以调用fflush()刷缓冲; 或者在输出之前,通过调用setbuf(stream,NULL)禁用掉stdio的缓冲,然后直接走系统调用。
    • read,write系统调用,并不是直接进行磁盘IO,而是在读写内核的缓冲区。任意时刻可以调用fsync之类的函数强刷内核缓冲到磁盘。也可以在open的时候设置O_SYNC之类的标志来强刷缓冲。
    • 磁盘的缓冲控制
      禁用:hdparam -W0
      启用://TODO
  6. 裸 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大小整数倍。
  7. 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是带符号的,也就是可以从文件最后往前读。

思考的几个问题

  1. 为什么是lseek() 而不是 seek()?
    返回值是long型。

  2. 文件空洞//TODO

  3. lseek() 到文件最后开始写,和open的时候带上O_APPEND的区别?
    区别在于O_APPEND能保证原子性的语义,也就是说保证每次写都是从文件的最后开始写。而如果有两个进程同时lseek()到文件最后然后写,有可能导致写覆盖。

  4. int ioctl() //TODO 百宝箱
    像这种百宝箱类的函数,参数一般都是一个资源、一个cmd、变长的其它参数。

  5. 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
    • 两个进程修改状态标志会相互影响吗?参考下面的文件描述符与文件的关系。
  6. 文件描述符与文件的关系
    书中一张神图解决所有问题//TODO

  7. 如何读写大文件,在32位的机器上,off_t最大是2G
    一种推荐做法是定义一个宏,_FILE_OFFSET_BITS 64
    然后,之前的IO函数都会变为64位的版本,比如open()->open64()
    所以那些都是宏定义。

  8. 创建临时文件的几种种方法:

    • int mkstemp(char* template)
      该调用会加上O_EXCL标志,模版参数类似”/tmp/abcXXXXXX”,内核会替换最后6个X并且保证文件名唯一,如何做到?//TODO
    • tmpnam(),tempnam(),mktemp() 能用于生成唯一文件名,区别是什么?又为什么说会有安全漏洞?有安全漏洞那么哪些场景下可用?//TODO
    • FILE* tmpfile(void)
      文件流关闭后自动删除该文件,如何做到?//TODO
      内部调用unlink()来删除文件名??//TODO
      进程退出后自动关闭所有打开的文件描述符,然后就删除临时文件?
  9. 一些好用的api //TODO

readv    //read vectorwritev   //write vectorpread    //position readpwrite   //position write
0 0
原创粉丝点击