上一节介绍了不带缓冲文件I/O中的 open 函数,这一节继续介绍其它重要的常用函数。
首先是可用来创建一个新文件的 creat() 函数:
#include <fcntl.h>
int creat(const char *path, mode_t mode);
/* 返回值:若成功,返回为只写打开的文件描述符;否则,返回 -1 */
此函数等价于: open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
接着是读写打开文件的 read 和 write 函数:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/* 返回值:读到的实际字节数,若已到文件尾,返回 0;若出错,返回 -1 */
ssize_t write(int fd, const void *buf, size_t nbytes);
/* 返回值:若成功,返回已写的字节数 nbytes ;若出错,返回 -1 */
在读取文件时,有多种情况可使实际读到的字节数少于要求读的字节数:
* 读普通文件时,在读到要求字节数之前已到达了文件尾端。
* 当从终端设备读时,通常一次最多读一行(可自行设置)。
* 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求的字节数。
* 当从管道或 FIFO 读时,管道包含的字节少于所需的数量。
* 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录。
* 当一信号造成中断,而已经读了部分数据量时。
write 函数出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。对于普通文件,读/写操作都从文件的当前偏移量处开始,如果在打开该文件时,指定了 O_APEND 选项,则在每次写操作时,都会将文件偏移量设置在文件的当前结尾处,在写成功后,该文件偏移量增加实际写的字节数。
然后是关闭一个打开文件的 close() 函数:
#include <unistd.h> // 注意头文件
int close(int fd); /* 返回值:若成功,返回 0 ;否则,返回 -1 */
注意,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核会自动关闭它所有的打开文件,所以有时可以不用显示地调用 close() 函数关闭打开文件。
每个打开文件都有一个与其相关联的“当前文件偏移量”,它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前偏移量处开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定 O_APPEND 选项,否则该偏移量都被设置为 0。
可以调用 lseek() 函数显示地为一个打开文件设置偏移量:
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/* 返回值:若成功,返回新的文件偏移量;否则,返回 -1 */
对参数 offset 的解释与参数 whence 的值有关:
1、若 whence 是 SEEK_SET/0,则将该文件的偏移量设置为距文件开始处offset个字节。
2、若 whence 是 SEEK_CUR / 1,则将该文件的偏移量设置为其当前值加 offset,offset 可为正或负。
3、若 whence 是 SEEK_END / 2,则将该文件的偏移量设置为文件长度加 offset,offset 可为正或负。
利用该函数的返回值可确定所涉及的文件是否可以设置偏移量:如果文件描述符指向的是一个管道、FIFO 或网络套接字,则 lseek 返回 -1,并将 errno 设置为 ESPIPE 。
通常,文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量(例如在 Intel X86处理器上运行的 FreeBSD 的设备 /dev/kmem)。所以在比较 lseek 的返回值时,不要测试它是否小于0,而要测试它是否等于 -1。
另外,lseek 仅将当前的文件偏移量记录在内核中,它并不引起任何 I/O 操作。然后该偏移量用于下一个读或写操作。文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,位于文件中但没有写过的字节都被读为 0。文件中的空洞并不要求在磁盘上占用存储区。具体的处理方式与文件系统的实现有关。当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。下面这段代码将用来创建一个带有空洞的文件:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
/* Default file access permissions for new files */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(void){
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int fd;
if((fd=creat("file.hole", FILE_MODE)) < 0){
printf("creat() error.");
exit(2);
}
if(write(fd, buf1, 10) != 10){
printf("buf1 write error");
exit(2);
} // offset now = 10
if(lseek(fd, 16384, SEEK_SET) == -1){
printf("lseek() error");
exit(2);
}
if(write(fd, buf2, 10) != 10){
printf("buf2 write error");
exit(2);
} // offset now = 16394
exit(0);
}
运行程序得到file.hole文件,并使用 od 命令查看文件的实际内容:
$ ./hole.out
$ ls -l file.hole
-rw-r--r--. 1 lei root 16394 5月 31 22:09 file.hole
$ od -c file.hole
0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000 A B C D E F G H I J
0040012
为了证明该文件中确实含有一个空洞,将其与具有同样长度但无空洞的文件比较:
$ ls -ls file.hole file.nohole
8 -rw-r--r--. 1 lei root 16394 5月 31 22:09 file.hole
20 -rw-r--r--. 1 lei root 16395 5月 31 22:14 file.nohole
虽然两个文件的长度一样,但具有空洞的文件只占用 8 个磁盘块,而无空洞的文件占用了 20 个。