UNIX系统中大多数文件I/O只需要用到5个函数:open,read,write,lessk,close
这些函数是不带缓冲的I/O,read和write都调用内核的一个系统调用
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。
当打开/创建一个文件,内核向进程返回一个文件描述符
惯例:
在#include <unistd.h>中定义
文件描述符0 与进程的标准输入关联 STDIN_FILENO
文件描述符1 与标准输出关联 STDOUT_FILENO
文件描述符2 与标准错误关联 STDERR_FILENO
文件描述符的变化范围是0~OPEN_MAX -1
函数open和openat
#include <fcntl.h>
path参数是要打开或者创建文件的名字
oflag参数可以用来说明此函数的多个选项
例如 O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
O_CREAT 若不存在则创建。使用时需要同时说明第3个参数mode,用mode指定该新文件的访问权限位
由open和openat函数返回的文件描述符一定是最小的未用描述符数值。
例如,一个程序可以先关闭标准输出,然后打开另一个文件,执行打开操作返回的文件描述符一定是1
例子:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main()
{
close(1);
int fd = open("./test61.txt",O_RDWR|O_CREAT,777);
cout<<fd<<endl;
char* buf={"1234567"};
write(fd,buf,7);
}
首先关闭了标准输出,则目前可用最小文件描述符则是1,在打开文件时赋给的文件描述符就是1,cout向标准输出输出,因为现在的文件描述1属于test61.txt文件,则输出的内容进了test61.txt文件,而不是屏幕上显示。
结果:
fd参数把open和openat函数区分开:
(1) path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,就是open函数
(2)path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取的
(3)path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。这时,路径名在当前工作目录中获取。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main()
{
int fd1 = openat(AT_FDCWD,"../../../test.txt",O_RDWR,777);
int fd2 = openat(fd1,"Users/test/test.txt",O_RDWR,777);
char buf[]="1234567";
write(fd2,buf,7);
return 0;
}
openat函数解决两个问题
(1)可以让线程使用相对路径打开目录中的文件,而不再只能打开当前的工作目录。
(2)避免time-of-check-to-time-of-use(TOCTTOU)错误,这种错误的基本思想是:如果有两个基于文件的函数调用,其中第二个调用依赖于第一个调用的结果,那么程序是脆弱的。因为两个调用并不是原子操作。
函数create
等效于:
int open(path,O_CREAT|O_WRONLY|O_TRUNC,mode) == int openat(AT_FDCWD,path,.... , .....);
create的不足方式就在于它以只写的方式打开创建的文件。
函数close:
函数lseek
每次打开一个文件都有一个与之关联的“当前文件偏移量”。它通常是一个非负整数,用以度量从文件开始处计算的字节数。
通常,读写操作都是从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
当打开一个文件时,如果没有指定O_APPEND选项,则该偏移量被设置为0;
如果whence 是SEEK_SET,则将该文件的偏移量设置为距离文件开始处offset字节
如果whence 是SEEK_CUR,则将文件偏移量设置为当前值加offset,offset可为正负
如果whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正负
若lseek成功执行,则返回新的文件偏移量,但若文件描述符指向的是一个管道,FIFO,或者网络套接字,则lseek返回-1,可以以此来判断是否可以设置偏移量。lseek将当前的文件偏移量记录在内核中,并不引起任何I/O操作。然后用该偏移量用于下一个读或写操作。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main()
{
int fd = openat(AT_FDCWD,"test2333.txt",O_RDONLY);
off_t currpos = -10;
currpos = lseek(fd,currpos,SEEK_END);
char buf[4];
read(fd,buf,4);
//cout<<buf<<endl;
write(1,buf,4);
cout<<endl<<currpos;
}
文件偏移量可以大于文件的当前长度,在这种情况下,对文件的下一次写将加长该文件。并在文件中构成一个位于文件中但没有写过的读为0的字节,称为空洞。文件的空洞并不要求在磁盘上占用存储区。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
char buf1[] = "1234567";
int main()
{
int fd = open("test65.txt",O_CREAT,777);
lseek(fd,16384,SEEK_SET);
write(fd,buf1,7);
close(fd);
}
例子:用write和read函数复制一个文件
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{
int n;
char buf[4096];
while((n=read(STDIN_FILENO,buf,4096))>0)
write(STDOUT_FILENO,buf,n);
}
函数dup和dup2
(成功返回新的文件描述符,失败返回-1)
#include <unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
由dup返回的新文件描述符一定是当前可用文件描述符的最小数值。
dup2 可用用fd2制定新描述符的值,如果fd2已经打开则将其关闭。如果fd==fd2,则dup2返回fd2,而不关闭它。
这些函数返回的新文件描述符和参数fd共享一个文件表项。
延迟写:(大多数磁盘I/O都通过缓冲区进行,当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,之后再写入磁盘。)当内核需要重用缓冲区时来存放其他磁盘块数据时,会把所有延迟写数据块写入磁盘。