UNIX系统中的大多数文件I/O只需要用到5个函数:open、read、write、lseek以及close。本章说明的函数经常称为“不带缓冲的I/0”,术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。这些不带缓冲的I/O函数不是ISO C的组成部分,但是它们是POSIX.1和Single UNIX Specification的组成部分。
文件描述符:
对内核而言,所有打开的文件都通过文件描述符引用。文件描述符是个非负整数。按照惯例,UNIX系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错相关联。这是各种shell以及许多应用程序使用的惯例,而与UNIX内核无关。在依从POSIX的应用程序中,幻数0、1、2应当替换成符号常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,这些常量都定义在<unistd.h>中。
文件描述符的变化范围是0-OPEN_MAX。
open函数:
调用open函数可以打开或创建一个文件:
#include <fcntl.h>
int open(const char *pathname, int flag, .../* mode_t mode */);
返回值,若成功则返回文件描述符,若出错则返回-1
pathname:是要打开或创建文件的名字,flag参数可以说明此函数的多个选项,用下列一个或多个常量进行或运算,构成flag参数。
open函数返回的文件描述符一定是最小的未用描述符数值。这一点被某些应用程序用来在标准输入、标准输出或标准错误上打开新的文件。
文件名与路径名截短:
在POSIX.1中,常量_POSIX_NO_TRRUNC决定了是否要截短过长的文件名或路径名,还是返回一个出错。根据文件系统类型,此值可变。若_POSIX_NO_TRUNC有效,则在整个路径名超过PATH_MAX,或路径名中的任意文件名超过NAME_MAX时,返回出错状态,并将errno设置为ENAMETOOLONG。
creat函数:
也可以调用creat函数创建一个新文件:
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
返回值:若成功,则返回只写打开的文件描述符,若出错则返回-1。
此函数等效于:open(pathname,O_WRONLY | O_CREAT | O_TRUNC, mode)。creat函数的一个不足之处是它以只写方式打开所创建的文件。
close函数:
可调用close函数关闭一个打开的文件:
#include <unistd.h>
int close(int fieldes)
返回值:若成功则返回0,若出错则返回-1。
关闭一个文件时,还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用这一功能而不显示地用close关闭打开文件。
lseek函数:
每个打开的文件都有一个与其相关联的当前文件偏移量。它通常是个非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
按照系统默认情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量设置为0。可调用lseek函数显式地为一个打开的文件设置其偏移量:
#include <unistd.h>
off_t lseek(int fieldes, off_t offset, int whence);
返回值:若成功,则返回新的文件偏移量,若出错则返回-1。
对参数offset的解释与参数whence的值有关:
因此可用如下方式确定打开文件的当前偏移量:
offset currpos
currpos = lseek(fd, 0, SEEK_CUR);
用上述方法还可以确定所涉及的文件是否可以设置偏移量。如果文件描述符引用的是一个管道,FIFO或网络套接字,则leek返回-1,并将errno设置为ESPIPE。
下列程序用于测试能否对其标准输入设置偏移值:
/* * Copyright (C) [email protected] */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(void) { if (lseek(STDIN_FILENO, 0, SEEK_CUR) < 0) { printf("can't seek\n"); } else { printf("seek ok\n"); } exit(0); }
通常文件的当前偏移量应当是个非负整数,但是某些设备也可能允许负的偏移量。但是对于普通文件,其偏移量必须是非负值。lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。
文件偏移量可以大于文件的当前长度,这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被读为0。文件中的空洞并不要求在磁盘上占用存储区,具体处理方式与文件系统的实现有关。
下列程序用于创建一个具有空洞的文件:
/* * Copyright (C) [email protected] */ #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #define FILE_NAME "file.hole" int main(void) { char buf_one[] = "abcdefghij"; char buf_two[] = "ABCDEFGHIJ"; int fd; if ( (fd = creat(FILE_NAME, S_IRUSR | S_IWUSR)) < 0) { printf("create file %s error: %s\n", FILE_NAME, strerror(errno)); exit(1); } if (write(fd, buf_one, 10) != 10) { printf("write error: %s\n", strerror(errno)); exit(2); } if (lseek(fd, 16384, SEEK_SET) == -1) { printf("lseek error: %s\n", strerror(errno)); exit(3); } if (write(fd, buf_two, 10) != 10) { printf("write error: %s\n", strerror(errno)); exit(4); } exit(0); }
read函数:
调用read函数从打开文件中读取数据:
#include <unistd.h>
ssize_t read(int fields, void *buf, size_t nbytes);
返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错返回-1。
ssize_t和size_t都是基本系统数据类型,ssize_t为带符号的整数,size_t为不带符号的整数。
有多种情况可使实际读到的字节数少于要求读的字节数:
write函数:
调用write函数向打开的文件写数据:
#include <unistd.h>
sszie_t write(int fields,const void *buf, size_t nbytes)
若成功则返回已写的字节数,若出错则返回-1。
其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见原因是:磁盘已写满,或者超过了一个给定进程的文件长度限制。
I/O的效率:
下列程序使用read和write函数复制一个文件:
/* * Copyright (C) [email protected] */ #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #define BUFSIZE 4096 int main(void) { int n; char buf[BUFSIZE]; while ( (n = read(STDIN_FILENO, buf, BUFSIZE)) > 0) { if (write(STDOUT_FILENO, buf, n) != n) { printf("write error: %s\n", strerror(errno)); exit(1); } } if (n < 0) { printf("read error: %s\n", strerror(errno)); exit(2); } exit(0); }
不同的BUFSIZE对程序的运行时间有非常大的影响。系统CPU时间的最小值出现在BUFFSIZE为4096处(一个block的大小),继续增大缓冲区长度对此时间几乎没有影响。