Linux文件操作解析

来源:互联网 发布:dracut命令安装linux 编辑:程序博客网 时间:2024/06/06 03:47

最近重读APUE,之前很多一知半解的概念变得清晰了许多。其中就有关于Linux文件操作的一些系列api及其背后的概念。刚好就在博客里总结一下。

本文仅针对一般文件的操作,由于Linux“一切皆文件”的设计哲学,如果要谈广义上的文件操作,那么socket编程相关的内容也要牵扯进来了。

首先,Linux对进程提供文件描述符(file descriptor,以下简称fd)作为所有文件的身份标示。对于每个进程而言,在进程用户空间维护一张文件描述符表。表的每一行包含两项--文件描述符及文件指针。文件指针指向打开文件表(open file table)的一行.打开文件表是一张系统级的表。每一行包括了文件偏移量,文件状态(status flag),以及一个指向inode表项的指针,inode表同样是一张系统级的表,它储存了真正的被打开文件的文件信息,文件锁等数据,其每一项与实际打开文件是一对一的关系。

打开文件表和实际打开文件的关系是多对一的关系。这是由于同一个文件会被以不同的形式(r/w/rw)打开多次,也会被多个进程打开多次。每一次open操作都会创建一个文件描述符表项和打开文件表项。同时,由于子进程对父进程文件表的继承,以及dup操作导致的文件别名现象,文件描述符项与打开文件表项也是多对一关系。

文件描述符表,打开文件表,inode表关系如下图所示



针对文件和fd,Linux提供一套统一的api操作接口。分别为:文件打开及创建(create(..)已过时):open(...),文件关闭:close(...),文件偏移量设置:lseek(...),写文件:write(...),读文件:read(...).(具体函数参数和flag宏请自行查阅)。这五大函数在被调用时都会直接触发sys_call导致程序陷入内核,由此引发的context switch不可避免地会造成性能开销。在陷入内核后,write()和read()函数并不一定会直接于磁盘交互。Linux kernel 设置了内核缓冲区以减少I/O操作。对使用者,Linux提供了至少四种方式来帮助我们控制内核缓冲区的行为。

1.open(...)函数的O_SYNC标志,用该标志打开的文件在进行write操作时会阻塞至物理I/O操作完成为止。即保证本次写操作是写入磁盘的。

2.void sync()函数:将所有被修改过的块缓冲区排入写队列等待内核进行物理I/O操作同步,然后返回,update守护进程会定时调用该函数,从而保证缓冲区和磁盘数据的一致性。

3.int fsync(int fd)函数:仅对指定fd对应的文件有效,但会等到写磁盘结束后再返回。

4.int fdatasync(int fd)函数:同fsync,但不会更新文件属性。

以上介绍了直接依据fd进行的文件操作,在上文中也提到,这些基于fd的函数会直接触发sys_call,而sys_call是有性能开销的,参照内核缓冲区的设计思想,既然文件的读写与文件的实际内容未必要是时时刻刻都同步的,为什么不能在用户态设置一个缓冲区,从而减少sys_call的发生的?

本着这一原则,Linux提供基于流的拥有用户空间缓存的文件操作api。这一套API不再用fd作为文件标识,而是使用FILE结构体。程序中预定义的三个文件指针stdout,stdin和stderr便是基于FILE结构体的指针。我们常用的printf函数便是一个针对stdout的操作。FILE对应的文件缓冲区有三种文件缓冲模式:

1.全缓冲:顾名思义,仅在缓冲区满时进行sys_call同步数据,流被打开时的默认缓冲方式。

2.行缓冲:只有遇到换行符或缓冲区满时执行I/O操作(还有一种情况是当流被用作输入时,其输出缓冲会被冲洗)。通常涉及终端的流使用此缓冲方式,如stdout,stdin。

3.不缓冲:相当于没有用户空间缓冲区。stderr用此缓冲方式以及时打印错误信息。

另:

Linux同样提供一系列函数来控制缓冲区的属性及行为:

1.fflush(...):冲洗缓冲区,强制I/O操作。

2.setbuf与setvbuf:两者都可以设置特定流的缓冲区大小,后者可以设置缓冲方式。

至此,我们可以发现,如果使用基于流的文件读写api,整个文件读写过程大致如下图所示:



最后简单列出基于流的文件操作api:

打开文件:fopen(标准打开函数),fdopen(将fd结合到一个新流,用于包裹无法被fopen打开的文件,如socket),freopen(将打开文件定向到已有流)

关闭文件:fclose(文件关闭时,输出缓冲被冲洗,输入缓冲被丢弃)。

读写类函数太多,在此不一一列出。


原创粉丝点击