关于文件I/O之前在学习C语言时已经了解了一部分,最近在看《Linux-UNIX系统编程手册》这本书时发现自己的认识解太浅薄了,所以在此贴上我新的学习理解和感悟。
首先让我们了解下文件偏移量的定义:(定义为自博主伍佰是只喵处转载)
文件偏移量
1、每个打开的文件都有一个与其相关联的“当前文件偏移量”。它通常是一个非负整数,用以度量从文件开始处计算的字节数。
2、内核为所有打开的文件维持一张文件表。文件表项包含文件偏移量。
3、默认情况下,当打开一个文件时,除非指定O_APPEND选项,否则偏移量为0。
4、读、写操作都是从当前文件偏移量处开始,并使偏移量被设置为0。
5、两个进程打开同一个文件时,每个进程都会有自己的文件表项(只有一个V节点),都有自己对该文件的当前文件偏移量。
6、文件的定位可以大于文件的当前长度,此时,对文件的下一次写将加长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被读为0。文件中的空洞并不要求在磁盘上占用存储区。
7、文件的当前偏移量可能为负值。但是普通文件的当前偏移量必须为非负值。
8、管道型文件(比如fifo、socket、pipe)不可以设置文件位置偏移量。
在相当一段时间内我都认为文件偏移量作为系统直接记录在打开文件表(定义2)中的属性,是整个系统中唯一的,进程关于文件的I/O操作都是建立一个相同的偏移量对象上,现在学习到了Linux操作系统中I/O操作需要原子性时才猛地发现自己对于文件偏移量的理解一直都是错误的。
关于文件偏移量的保存,在Linux系统上一般保存到两个地方,一个是系统打开文件表file,由linux系统自动生成和维护,里面存放了读写指针(文件偏移量)、文件状态和当前使用该文件的进程数信息,一般用于进程间或者进程内部不同打开指针的协同操作;另一个是进程的打开文件表,在进程user中有一个数组,存放一组指针指向系统打开文件表中该进程打开的文件所对应的表项。(定义5)。
有了以上知识背景,发现以下代码的bug也就很容易了:
if( lseek( d, 0, SEEK_END ) == -1 )
errExit(" lseek ");
if( write( fd, buf, len ) != len )
fatal("Partial / failed write ");
如果第一个进程执行到 lseek()和 write()之间,被执行相同代码的第二个进程所中断,那么这两个进程会在写入数据前,将文件偏移量设为相同位置,而当第一个进程再次获得调度时,会覆盖第二个进程已写入的数据。此时再次出现了竞争状态,因为执行的结果依赖于内核对两个进程的调度顺序。要规避这一问题,需要将文件偏移量的移动与数据写操作纳入同一原子操作。在打开文件时加入 O_APPEND 标志就可以保证这一点。
(本人对Linux的学习尚处于初级阶段,如若发布内容有任何错漏,欢迎各位指正,感激不尽)