文件共享
UNIX系统支持在不同进程中共享打开的文件,首先先用一幅apue的图来介绍一下内核用于I/O文件的数据结构:
如图所见,一个进程都会有一个记录项,记录项中包含有一张打开文件描述符表,每个描述符占用一项。
描述符包括:(a)文件描述符标志(fd标志),(b)指向一个文件表项的指针(文件指针)。
而文件表包括:(a)文件状态标志,(b)当前文件偏移量,(c)v节点指针
v节点包括:文件类型和对此文件进行各种操作的函数的指针,大部分v节点还包括年i节点(索引节点,第四章详细介绍)
如果两个进程各自打开同一个文件的话,文件表是的分开的,但v节点是共享的:
文件表分开是因为:使每个进程都有它自己的对该文件的当前文件偏移量。
这样,当多个进程写同一个文件时,就有可能出问题。Let me tell you why.
早期的UNIX没有O_APPEND追尾选项,所以写到文件的结尾是这样做的:
if (lseek(fd, 0L, 2) < 0) /* 定位到文件的末端 */
err_sys("lseek error");
if (write(fd, buf, 100) != 100) /* 追尾写100字节 */
err_sys("write error");
这样对单个进程来说是没有问题的。但是对于多个进程对同一个文件同时使用这种方法就会出错了。
比如A和B进程同时打开一个文件,假定A调用了lseek,它将进程A的该文件当前偏移量设置为1500字节(文件末尾处),然后内核切换B进程运行,B也是调用lseek也将该文件的当前偏移量设置为1500字节(文件末尾处,记住,每个进程并不是共享同一个当前文件偏移量的),然后B调用了write函数,将当前偏移量曾至1600字节。因为该文件的长度已经增加了,所以内核对v节点中的当前文件长度更新为1600。然后内核切回A进程,当A调用write时,就从当前文件偏移量(1500字节)处将数据写到文件中去,这样也就代换了进程B
刚写到该文件中的数据。
问题出在逻辑操作“先定位到文件尾端处,然后写“上,它使用了两个分开的函数调用。解决的方法就是使这两个操作变成一个操作(这也就叫做原子操作)。任何一个需要多个函数的操作都不可能是原子操作,因为在两个函数调用之间,内核都有可能临时挂起该进程(如上面所加假定的)。
UNIX提供了一种方法使这种操作成为原子操作,该方法就是在打开文件时设置O_APPEND标志。这就使内核每次对这种文件进行写之前,都将进程的当前文件偏移量设置到该文件的尾端处,于是每次写之前就不用再调用lseek。
一般而言,原子操作(atomic operation)指的时由多步组成的操作。如果该原子操作执行,则要么时执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。(只要理解原子是不可分的就行了)
第三章笔记待续。