Unix 文件 IO
1 系统 IO
本章所说明的函数经常被称为不带缓冲的 I/O ,术语不带缓冲指的是每个 read 和 write
都调用内核中的一个系统调用。这些不带缓冲的 IO 不是 ISO C 的组成部分,但是,它们是
POSIX.1 和 Single UNIX Specification 的组成部分。
1.1 文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。
按照惯例,UNIX 系统 shell 使用文件描述符 0 与进程的标准输入相关联,文件描述符 1-
与标准输出相关联,文件描述符 2 与标准出错输出相关联。这是各种 shell 以及很多应用程
序使用的惯例,而与 UNIX 内核无关。尽管如此,如果不遵照这种惯例,那么很多 UNIX 系
统应用程序就不能正常工作。
1.2 函数 open
pathname 是要打开或创建文件的名字。大多数的现代文件系统支持文件名的最大长度
为 255;如果超过了该长度,根据 POSIX.1 标准,若 POSIX_NO_TRUNC 有效,则报错;否则,
将文件名截短。
oflag 参数可用来说明此函数的多个选项。用下列一个或多个常量进行“或”运算构成
of lag 参数(这些常量定义在头文件中):
O_RDONLY: 只读打开。
O_WRONLY: 只写打开。
O_RDWR: 读、写打开。
O_EXEC:只执行打开
O_SEARCH:只搜索打开,对应于目录
在这五个常量中必须指定一个且只能指定一个。下列常量则是可选择的:
O_APPEND:每次写时都追加到文件的尾端。
FD_ CLOEXEC:自进程中调用 exec 时,关闭文件描述符
O_CREAT 若此文件不存在,则创建它。使用此选项时,需要第三个参数 mode,用
其指定该新文件的访问权限位
O_DIRECTORY:如果 pathname 不是目录,则出错
O_ EXCL 如果同时指定了 O_CREAT,而文件已经存在,则会出错。用此可以测试
一个文件是否存在,如果不存在,则创建此文件,这使测试和创建两者成为一个原
子操作。
O_ TRUNC:如果此文件存在,而且为只写或读写成功打开,则将其长度截短为 0
O_NOCTTY 如果 pathname。指的是终端设备,则不将该设备分配作为此进程的控
制终端。
O_NONBLOCK 如果 pathname 指的是一个 FIFO、一个块特殊文件或一个字符特殊文
件,则此选项为文件的本次打开操作和后续的 I/O 操作设置非阻塞模式。
O_ DSYNC:使每次 write 等待物理 I/O 操作完成,但是如果写操作并不影响读取刚
写入的数据,则不等待文件属性被更新。
O_RSYNC: 使每一个以文件描述符作为参数的 read 操作等待,直至任何对文件同一
部分挂起的写操作都完成。
O_ SYNC:使每次 write 都等到物理 1/O 操作完成,包括由 write 操作引起的文件属
性更新所需的 IO。
mode:只在创建时使用,指定文件权限位
1.3 函数 creat
等价于:
open (pathname,O_WRONLY|O _CREAT|O_ TRUNC,mode);
1.4 函数 close
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用了这一功能而不
显式地用 close 关闭打开文件。
1.5 函数 lseek
每个打开的文件都有一个与其相关联的“当前文件偏移量”(current file offset)。它通常
是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏
移量处开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除
非指定。O_APPEND 选项,否则该偏移量被设置为 0。
若 whence 是 SEEK_SET,则将该文件的偏移量设置为距文件开始处 offset 个字节。
若 whence 是 SEEK_CUR,则将该文件的偏移量设置为其当前值加 offset, offset 可为正或负。
若 whence 是 SEEK_END,则将该文件的偏移量设置为文件长度加 offset, offset 可为正或负。
1.6 函数 read
读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之
前还有 30 个字节,而要求读 100 个字节,则 read 返回 30,下一次再调用 read 时,它
将返回 0。
当从终端设备读时,通常一次最多读一行。
当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
当从管道或 FIFO 读时,如若管道包含的字节少于所需的数量,那么 read 将只返回实际
可用的字节数。
当从某些面向记录的设备(例如磁带)读时,一次最多返回一个记录。
1.7 函数 write
其返回值通常与参数 nbytes 的值相同,否则表示出错。write 出错的一个常见原因是:
磁盘已写满,或者超过了一个给定进程的文件长度限制
1.8 IO 效率
标准输出重定向到/dev/null 上,测试用的文件系统为 linux ext4,其磁盘块长度为 4096
字节。
1.9 文件共享
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进
程对另一个进程可能产生的影响。
(1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其
视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
a) 文件描述符标志
b) 指向一个文件表项的指针。
(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
a) 文件状态标志(读、写、添写、同步和非阻塞等)。
b) 当前文件偏移量。
c) 指向该文件 v 节点表项的指针。
(3) 每个打开文件(或设备)都有一个 v 节点(v-node)结构。v 节点包含了文件类型和对此文件
进行各种操作的函数的指针。对于大多数文件,v 节点还包含了该文件的 i 节点(i-node ,
索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息
都是快速可供使用的。例如,i 节点包含了文件的所有者、文件长度、文件所在的设备、
指向文件实际数据块在磁盘上所在位置的指针等等
如果两个独立进程各自打开了同一个文件,则有如下图的关系:
1.10 原子操作
调用 pread 相当于顺序调用 lseek 和 read,但是调用 pread 时,无法中断其定位和读操
作。
1.11 函数 dup 和 dup2
由 dup 返回的新文件描述符一定是当前可用文件描述符中的最小数值。用 dup2 则可以
用 filedes2 参数指定新描述符的数值。如果 filedes2 已经打开,则先将其关闭。如若 filedes
等于 filedes2,则 dup2 返回 filedes2,而不关闭它。
1.12 函数 sync、fsync、fdatasync
sync 函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁
盘操作结束。通常称为 update 的系统守护进程会周期性地(一般每隔 30 秒)调用 sync 函
数。这就保证了定期冲洗内核的块缓冲区。
fsync 函数只对由文件描述符 filedes 指定的单一文件起作用,并且等待写磁盘操作结束,
然后返回。fsync 可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即
写到磁盘上。
fdatasync 函数类似干 fsync,但它只影响文件的数据部分。而除数据外,f sync 还会同步
更新文件的属性。
1.13 函数 fcntl
fcntl 函数可以用来修改已经打开的文件的属性
在本节的各实例中,第二个参数总是一个整数,与上面所示函数原型中的注释部分相对
应。但是在使用记录锁时,第三个参数则是指向一个结构的指针。
fcntl 函数有 5 种功能:
复制一个现有的描述符(cmd=F_ DUPFD 或 F_ DUPFD_CLOEXEC)
获得/设置文件描述符标记(( cmd=F_ GETFD 或 F_ SETFD)
获得/设置文件状态标志(cmd=F_GETFL 或 F_SETFL)
获得/设置异步 IO 所有权(cmd = F_GETOWN 或 F _SETOWN)
获得/设置记录锁(cmd=F _GETLK, F_ SETLK 或 F_ SETLKW)
(1) F_DUPFD:复制文件描述符 filedes。新文件描述符作为函数值返回。它是尚未打开的各
描述符中大于或等于第三个参数值(取为整型值)中各值的最小值。新描述符与 filedes 共
享同一文件表项(见图 3-3)。但是,新描述符有它自己的一套文件描述符标志,其 FD_
CLOEXEC 文件描述符标志被清除。
(2) F_ DUPFD_CLOEXEC:复制文件描述符 filedes,设置与新描述符关联的 FD_ CLOEXEC,返
回新描述符
(3) F_GETFD:对应于 filedes 的文件描述符标志作为函数值返回。当前只定义了一个文件描
述符标志 FD_CLOEXEC
(4) F_SETFD:对于 filedes 设置文件描述符标志。新标志值按第 3 个参数(取为整型值)设置
(5) F_GETFL:对应于 filedes 的文件状态标志作为函数值返回。在说明 open 函数时,已说
明了文件状态标志。不幸的是,五个访问方式标志并不各占 1 位(前三种标志的值分别
是 0, 1 和 2,由于历史原因。这 5 个值互斥,一个文件只能有这 5 种值之一)。因此首
先必须用屏蔽字 O_ACCMODE 取得访问模式位,然后将结果与这三种值中的任一种作比
较。
(6) F_SETFL:将文件状态标志设置为第三个参数的值(取为整型值)。可以更改的几个标志
是:O_ APPEND, O_ NONBLOCK、O_SYNC, O_DSYNC、O_RSYNC、O_ FSYNC 和 O_ASYNC。
(7) F_GETOWN:获取当前接收 SIGIO 和 SIGURG 信号的进程 ID 或进程组 ID
(8) F_SETOWN:设置接收 SIGIO 和 SIGURG 信号的进程 ID 或进程组 ID。正的 arg 指定一个
进程 ID,负的 arg 表示等于 arg 绝对值的一个进程组 ID
2 标准库 IO
本章说明标淮 vo 库。因为不仅在 UNIX 上,而且在很多操作系统上都实现了此库,所以
它由 ISOC 标淮说明。Single UNIX Specification 对 ISO C 标淮进行了扩展,定义了另外一些接
口。标堆 Io 库处理很多细节,例如缓冲区分配,以优化长度执行 IO 等。这些处理使用户不
必担心如何选择使用正确的块长度(如 1.8 节中所述)。
2.1 流和 FILE 对象
系统 IO 中,所有函数都是针对文件描述符的。当打开一个文件时,即返回一个文件描
述符,然后该文件描述符就用于后续的 I/O 操作。而对于标淮 I/O 库,它们的操作则是围绕
流( stream)进行的,当用标淮库 IO 打开或创建一个文件时,我们已使一个流与一个文件相
关联。
对于 ASCII 字符集,一个字符用一个字节表示。对于国际字符集,一个字符可用多个字
节表示。标淮 IO 文件流可用于单字节或多字节(“宽”)字符集。流的定向(streram's orientation)
决定了所读、写的字符是单字节还是多字节的,freopen 可以清除流的定向,fwide 可以设置
流的定向。
mode 为负,设置流为字节定向;mode 为正,设置流为宽定向;mode 为 0,返回当前
定向。
2.2 标准输入、标准输出、标准错误
对一个进程预定义了三个流,并且这三个流可以自动地被进程使用,它们是:标准输入、
标准输出和标准出错。这些流引用的文件与系统 IO 中提到的文件描述符定义的标准 IO 引用
的文件相同。
这三个标准 I/O 流通过预定义文件指针 stdin, stdout 和 stderr 加以引用。这三个文件
指针同样定义在头文件中。
2.3 缓冲
标准 IO 库提供缓冲的目的是尽可能减少使用 read 和 write 调用的次数(见表 3-2,其中
显示了在不同缓冲区长度情况下,为执行 IO 所需的 CPU 时间量)。它也对每个 IO 流自动地
进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。不幸的是,标准 IO 库
最令人迷惑的也是它的缓冲。标准 IO 提供了三种类型的缓冲:
(1) 全缓冲:这种情况下,在填满标准 I/O 缓冲区后才进行实际 I/O 操作。缓冲区可由标准