内核用于I/O的数据结构

来源:互联网 发布:java循环结构 编辑:程序博客网 时间:2024/06/04 19:30

这一篇中将介绍UNIX系统中I/O有关的数据结构,并针对数据结构而解释各个I/O操作的实现方法,另外介绍文件共享和原子操作。

(1)每一个进程在进程表中都有一个记录项,每个记录项中有一个打开的文件的描述符表,可将此表视为一个矢量,每个描述符占用一项,与每个描述符相关联的是:

(a)文件描述符标志

(b)指向一个文件表项的指针

(2)内核为每一个打开的文件维持一张文件表,每个文件表项包含

(a)文件状态标志(在open时指定,只读,只写,追加,同步,阻塞等)

(b)文件当前偏移量

(c)指向文件V节点表项的指针

(3)每个打开的文件或者设备,都有一个V节点结构,V节点包含了文件类型和对此文件进行各种操作函数的指针信息。

对于大多数文件,V节点还包含了该文件的 i 节点(索引节点)。这些信息都是打开从磁盘读到内存的,所以关于文件信息都是快速可用的,i 节点包含了文件的所有者,文件长度,文件所在的设备,指向文件在盘上所使用的实际数据块指针。

示意图如下:


两个独立进程打开同一个文件,则有如下安排:



打开此文件的进程都获得一个文件表项,但对于一个给定文件只有一个V节点表项,每个进程都有一个文件表项的理由是:这种安排使每个进程来说都有一个他自己对该文件访问的文件当前偏移量。

下面对各个文件I/O操作作进一步说明:

- 每个write后,在文件项表中的“当前文件偏移量”即增加所写字节数,如果使当前文件字节数大于文件的当前文件长度,那么则在 i 节点中设置当前文件长度为当前文件偏移量,文件加长。

- 如果用O_APPEND选项打开文件,则相应标志也被设置到文件表项的“文件状态标志”中,每次对这种具有添加标志文件执行写操作时,在文件表项中的“当前文件偏移量”设置为 i 节点中的“当前文件长度”,这就使得每次写的数据都添加到文件当前尾端。

- lseek函数只是修改了文件表项中的“当前文件偏移量”,没有进行任何I/O操作

文件描述符标志和文件状态标志的作用范围方面的区别:

前者只用于一个进程的一个描述符而已,而后者适用于指向该文件表项的所有进程的所有文件描述符。

原子操作

原子操作(atomic operation)指的是由多步操作组成的,如果操作原子地执行,则执行完所有这些操作或者一步也不执行,不可能执行所有步的一个子集。

追加方式与原子操作

早期UNIX版本并不支持open的O_APPEND选项,所以程序被编写为如下方式:

if( lseek(fd,0L,SEEK_END) ==-1){err_sys("lseek error .");}if( write(fd,buff,buffsize) !=buffsize){err_sys("write error .");}

对于单进程而言,这段代码能正确工作,而多进程则可能出现问题。

假定有两个独立的进程A,B,都对同一个文件进行追写操作,每个进程都打开了文件,但是都未使用O_APPEND选项,这时两个进程都有他自己的文件表项,但是共享一个V节点表项,假定A调用了lseek,将自己的文件表项的当前文件偏移量设置为1500字节,然后切换到进程B,进程B也调用了lseek,也同样将自己的文件表项的当前文件偏移量为1500字节,然后进程B调用write函数写入了100字节,进程B将自己文件项的当前文件偏移量增至为1600,然后恢复执行A,当A调用write时,就从当前文件位移量1500处将数据写到文件中去,这样也替换了B刚刚写入文件的内容

创建文件与原子操作

在对open的O_CREAT和O_EXCL选项时,当同时指定O_CREAT | O_EXCL 而文件已存在,则open失败,此时检查该文件是否存在和创建文件是原子的,如果没有这样一个原子操作,可能编写如下代码来测试文件存在并创建文件

if( (fd=open(pathname,O_RDONLY)) ==-1){if(errno==ENOENT){//要打开的文件不存在这个时候创建文件if( (fd=creat(pathname,mode))==-1){err_sys("Create error.");}}else{err_sys("Open error.");}}

如果在打开(open调用)和创建(creat调用)之间,另外一个进程创建了这个文件,那么就会发生问题,如果另一个进程不仅创建了文件还写入了数据,那么在执行creat时,写上去的数据就被擦除了,将这两个操作合并为一个原子操作就不会出问题

原创粉丝点击