分类:
2008-10-28 18:19:28
3 file i/o
重点关注unbuffered i/o functions:
open, read, write, lseek, close
3.2 file descriptors
file descriptor是kernel级的概念,由unbuffered i/o 函数open获得
0,1,2习惯上对应一个process的标准输入,输出,错误,但是这对于unix并不是定死的。
建议不使用0,1,2这样的数字编程,而是使用宏STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO这样的posix.1制定的宏
3.3 open function
#include
int open(const char *pathname, int oflag, ... /*
mode_t mode */ );
1.有趣的几个mode:
O_EXCL: 当建立新文件的时候,如果该文件已被建立,那么就失败,否则就建立新的文件,这个判断和建立的过程是原子操作。(可用于进程之间或线程之间的互斥)
O_SYNC: write操作要等数据写到磁盘上后才返回。如果没有O_SYNC有可能write操作只是将要写的数据交给了kernel的buffer。
O_APPEND:
当以这个打开文件,如果调用write操作,那么系统先移到文件末尾,然后再启动写入操作。这个移动和写入的调用是原子操作。不会移动完后被别的抢先。使用场合:多个进程或者线程向一个文件不断添加数据的场合,如log文件。一个会产生竞争条件的例子(不使用O_APPEND):
if (lseek(fd,
err_sys("lseek error");
if (write(fd, buf, 100) != 100) /* and write */
err_sys("write error");
当有多个上述程序的进程同时运行时,就会出现数据互相覆盖的情况。这就是O_APPEND的好处。
多进程之间文件操作的原理:
process table entry, file table entry, v-node(unix里面的)/generic i-node(linux里面的)之间的关系:
process table entry里记载了一个process打开的各个文件的descriptors。每个descriptor会对应一个file table entry. file table是kernel维护的,并不是每个process都有的。是所有process 共同使用的。 每个file table entry里含有current file offset, 且每个file table entry都指向v-node或者generic i-node。其中,v-node是unix里的结构,而generic i-node是linux里的于系统不相关的i-node. v-node table或者generic i-nodetable 都是kernel维护的,并且他们与系统打开的文件是1:1对应的。
当多个进程打开一个文件的时候,就成了系统中有一个v-node entry,被多个file table entry指向。每个file table entry里记录了各个不同的process使用该文件的不同的偏移。
注意,还有一种情形:即多个不同的file descriptor对应同一个file table entry,如:dup function,以及使用了fork后的parent和child process.
使用O_APPEND能保证多进程写操作正确的原因分析:
每个进程都有对应file table中的一个entry对应该文件,例如有A,B两个进程,它们对应的file table entry是ftA和ftB。他们都指向了系统唯一的v-node。当A,B进程打开文件时设置了O_APPEND的descriptor flag后,会同时修改ftA和ftB各自的file status flag。这样当A要写文件时,他首先从v-node获取文件当前size,刷新ftA内部的current offset域,然后执行写操作,写完后v-node的current file size就被更新了。B要写的时候也一样,首先要读v-node中的current file size….这样就不会冲突。如果没有O_APPEND flag。A调用seek,将ftA中的offset刷新,然后B同样lseek刷新,接着,A写入数据,B再写入数据。就出事了。B写入的位置不正确,A的数据被覆盖了。
O_APPEND保证了获取最新的offset和启动写操作是一个原子操作。
2.filename和pathname长度的控制
如果filename长度超过了NAME_MAX,或者pathname长度超过了PATH_MAX:
有些系统,或者有些文件系统,会自动将filename或者pathname按照其limit直接将数据截取(truncate),而且并不返回错误。这是不好的。
有些系统,或者文件系统,则会返回错误,将errno置位ENAMETOOLONG。如linux和BSD-derived systems。
If _POSIX_NO_TRUNC is in effect, errno is set to ENAMETOOLONG, and an error status is returned if the entire pathname exceeds PATH_MAX or
any filename component of the pathname exceeds NAME_MAX
3.11 原子操作
在以O_APPEND flag打开的文件上执行写入操作,O_CREATE和O_EXCL同时制定时执行open操作时,判断文件存在与否以及创建新文件也是原子操作,pread和pwrite先执行了seek操作然后再执行read和write,也是原子操作。
所谓原子操作:In general, the term atomic operation
refers to an operation that might be composed of multiple steps. If the
operation is performed atomically, either all the steps are performed, or none
are performed. It must not be possible for a subset of the steps to be
performed.
3.12 dup和dup2
Dup和dup2可以在一个进程内部使多个descriptor对应一个file table entry,这样他们共享了同一个文件的offset等信息。 File table entry的数量不变。此外还有的系统使用fcntl函数的F_DUPFD操作来达到同样的功能。Posix.1要求两者都要支持。
3.5 close
一个process退出的时候,其打开的file都会被自动close. 所以有些应用程序可以不关闭已经打开的文件。
3.6 lseek function
Lseek可用来检测一个文件是否是普通文件,因为pipe, fifo, socket不能被seek,方法:在当前位置调用lseek,偏移值设置为0。
File是可以有hole的,即hole里面没有数据,不占据磁盘空间,但是占据文件大小。方法是:在一段文件数据后,lseek一段距离,然后再次写入数据。就产生了hole。因此同样大小的文件,如果一个有hole,一个没有,他们最终占据的磁盘block数目可能是不同的。
如下代码就产生了一个有hole的文件:
#include "apue.h"
#include
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int
main(void)
{
int fd;
if ((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");
if (write(fd, buf1, 10) != 10)
err_sys("buf1 write error");
/* offset now = 10 */
if (lseek(fd, 16384, SEEK_SET) == -1)
err_sys("lseek error");
/* offset now = 16384 */
if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
/* offset now = 16394 */
exit(0);
}