《Unix环境高级编程》——文件I/O(1)

引言

本章先说明可用的文件I/O函数——打开文件、读文件、写文件等。UNIX系统种的大多数文件I/O只需要用到5个函数:open、read、write、lseek以及close。然后说明不同缓冲区长度对read和write函数的影响。

本章描述的函数被称为不带缓冲的I/O(unbuffered I/O)。术语不带缓冲指的是每个read和write都调用内核的一个系统调用。

只要涉及多个进程资源共享资源,原子操作的概念就非常重要。

文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。读写文件时,使用open返回的文件描述符标识该文件,将其作为参数传递给read或write。

UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。

函数open和openat

调用open和openat函数可以打开或创建一个文件。

#include <fcntl.h>

int open(const char *path, int oflag, ... /* mode_t mode */);

int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);

对于open函数,仅当创建新文件时才使用最后的可变参数。

path参数时要打开或创建的文件的名字。

oflag参数用下列一个或多个常量进行或运算。

  • O_RDONLY     只读打开
  • O_WRONLY    只写打开
  • O_RDWR    读、写打开
  • O_EXEC   只执行打开

这几个常量必须指定一个且只能指定一个。

下面是可选的。

  • O_APPEND   每次写都追加到文件的尾端。
  • O_CREAT   若文件不存在则创建,使用此选项,需要同时说明mode参数,用mode指定新文件的访问权限位。
  • O_EXCL   如果同时指定了O_CREAT,而文件已存在则出错。
  • O_NONBLOCK   如果path是一个FIFO、一个块特殊文件,则此选项为文件的本次打开操作和后续I/O操作设置为非阻塞方式。
  • O_SYNC    使每次write等待物理I/O操作完成。
  • O_TRUNC   如果此文件存在,而且为只写或读写方式打开,则将其长度截断为0

由open和openat返回的文件描述符一定是最小的未用描述符值。

fd参数将open和openat函数区分开,由3种情况:

1)path参数指定绝对路径名,此时,fd参数被忽略。openat函数和open函数相同。

2)path参数指定相对路径名,fd参数指定了相对路径名在文件系统中的开始地址。

3)path参数指定相对路径名,fd参数具有特殊值AT_FDCWD,此时路径名在当前工作目录中获取。

openat函数是POSIX.1新增的,希望解决两个问题:

1. 让线程可以使用相对路径名打开目录中的文件,不再只能打开当前工作目录。

2. 避免time-of-check-to-time-of-use(TOCTTOU)错误。

函数creat

可以调用creat函数创建一个新文件。

#include <fcntl.h>

int creat(const char *path, mode_t mode);

返回值:如成功,返回为只写打开的文件描述符;如出错,返回-1.

此函数等效于:

open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);

函数close

调用close函数关闭一个打开文件。

#include <unistd.h>

int close(int fd);

返回值:若成功,返回0;若出错,返回-1

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。

当一个进程终止时,内核自动关闭它所有的打开文件。

函数lseek

每个打开文件都有一个与其相关联的“当前文件偏移量”。它通常是一个非负整数,度量从文件开始处计算的字节数。通常,读写文件都是从当前文件偏移量开始,并使偏移量增加所读写的字节数。

按系统默认情况,当打开一个文件时,除非指定O_APPEND,否则该偏移量设置为0.

可以调用lseek显示地为一个打开文件设置偏移量。

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

返回值:若成功,返回新的文件偏移量;若出错,返回-1

参数offset的解释与参数whence有关:

  • 若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。
  • 若whence是SEEK_CUR, 则该文件的偏移量设置为当前值加offset,offset可为正可负。
  • 若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负。

若lseek成功执行,返回新的文件偏移量。可以用如下方式确定打开文件的当前偏移量。

off_t currpos;

currpos = lseek(fd, 0, SEEK_CUR);

这种方式也可以用来确定文件是否可以设置偏移量,如果文件描述符指向管道、FIFOO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。

某些设置的偏移量可能为负,所以比较lseek的返回值时,不要测试它是否小于0,而要而是它是否等于-1

文件偏移量可以大于文件当前长度。在这种情况下,对文件的下一次操作将加长该文件,并在文件中构成一个空洞,文件中没写过的字节都读为0.

函数read

调用read函数从打开文件中读数据。

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t nbytes);

返回值:读取到得字节数,若已到文件尾,返回0;若出错,返回-1

多数情况下,实际读到得字节数少于要求读得字节数;

  • 读普通文件时,在读到要求字节数之前到达了文件尾。
  • 从终端设备读时,通常一次最多读一行。
  • 当从网络读时,网络中的缓冲机制可能造成返回值效于所要求读的字节数。
  • 从管道或FIFO读时,若管道包含的字节数效于要求的数量。
  • 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录。
  • 当一信号造成中断,而已经读了部分数据量时。

函数write

调用write函数向打开文件写数据。

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t nbytes);

返回值:若成功,返回已写的字节数;若出错,返回-1

若其返回值与参数nbytes相同则表示成功,否则表示出错。

write出错的一个常见原因时磁盘写满,或超出了一个给定进程的文件长度限制。

对于普通文件,写操作从文件当前偏移量开始。如果在打开文件时,指定了O_APPEND选型,则在每次写操作之前,将文件偏移量设置在当前文件结尾处。

I/O的效率

对于UNIX系统而言,文件文件和二进制代码文件并无区别。

大多数文件系统为改善系统性能都采取某种预读技术。当检查到正在进行顺序读取时,系统就试图读入比应用要求的更多数据。

发布了23 篇原创文章 · 获赞 0 · 访问量 977

猜你喜欢

转载自blog.csdn.net/u014635079/article/details/104320339