4.1 概述
所有制形式I/O操作的系统调用都以文件描述符,一个非负整数,来指代打开的文件。文件描述符用于表示所有类型的已打开文件,包括管道、FIFO、socket、终端、设备和普通文件。针对每个进程,文件描述符都自成一套。
大在程序开始运行之前,shell代表程序打开这3个文件描述符;实质上是程序继承了shell文件描述符的副本:
可以用freopen
进行重定向流。
打开文件:**open()**
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, .../* mode_t mode */);
//flags是八进制数
如果调用成功,返回文件描述符,用于在后续函数调用中指代该文件。若发生错误,则返回-1并将errno置为相应的错误标志。
open()
成功调用时返回值必须为进程未使用的文件描述符中数值最小的那一个。比如下列代码就会确保使用文件描述符0打开文件:
读取文件内容:**read()**
#include <unistd.h>
ssize_t read(int fd, void *buffer, size_t count);
count
参数指定最多能读取的字节数;buffer
参数提供用来存放输入数据的内存缓冲区地址。缓冲区至少应有count
个字节。
系统调用不会分配内存缓冲区用以返回信息给调用者,所以必须预先分配大小合适的缓冲区并将缓冲区指针传递给系统调用。
如果read()
调用成功,返回实际读取的字节数,遇到文件结束(EOF)则返回0,出现错误返回-1。此外,read()
读取到换行符\n
就会结束调用。
考虑如下情况:
输出结果可能很奇怪,因为**read()**
调用没有在pirntf()
函数打印的字符串尾部添加表示终止的空字符'\0'
。因为**read()**
可以从文件中读取任意序列的字节。输入信息可能是期望的文本数据,也有可能是二进制证书或者二进制形式的代码或者其他东西。**read()**
只能读取,但无法区分这些数据,所以无法遵从C语言对字符串处理的约定,在字符串尾部追加标识字符串结束的空字符。所以需要显式追加一个表示终止的空字符:
根据这种情况,缓冲区的大小也至少要比预计读取的最大字符串长度多出1字节。
数据写入文件:**write()**
#include <unistd.h>
ssize_t write(int fd, void *buffer, size_t count);
如果write()
调用成功,返回实际写入文件的字节数,该返回值可能小宇count
。这被称为部分写,可能原因是磁盘已满,或者是进程资源对文件大小的限制。
对磁盘文件执行I/O操作时,write()
调用成功并不能保证数据已经写入磁盘。因为为了减少磁盘活动量和加快write()
系统调用,内核会缓存磁盘的I/O操作。
关闭文件: **close()**
#include <unistd.h>
int clost(int fd);
Returns 0 on success, or -1 on error
文件描述符属于有限资源,因此文件描述符关闭失败可能会导致一个进程将文件描述符资源耗尽。
改变文件偏移量: **lseek()**
对于每个打开的文件,系统内核都会记录其文件偏移量。文件偏移量是指执行下一个**read()**
或**write()**
操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
offset
指定了一个以字节为单位的数值,whence
则是基点,应为下列之一:
SEEK_SET:文件头部起始点
SEEK_CUR:文件当前偏移量
SEEK_END:文件尾部(offset参数应该从文件最后一个字节之后的下一个字节算起)
lseek()
调用成功会返回新的文件偏移量。
文件空洞
如果程序的文件偏移量已经跨越了文件结尾,再执行I/O操作的话,read()
会返回0
表示文件结尾;而**write()**
函数可以在文件结尾后的任意位置写入数据。
从文件结尾后到新写入数据间的这段空间被称为文件空洞。虽然从编程角度看文件空洞中存在字节,读取空洞将返回以0
填充的缓冲区。但是文件空洞不占用任何磁盘空间。直到后续在文件空洞中写入了数据,文件系统才会位置分配磁盘块。