文件描述符
文件描述符是计算机操作文件时所用的符号,上文中所提到的i节点是计算机查找文件时所用的符号。那么,既然已经有i节点号可以唯一确定文件,为什么呢还要用文件描述符呢?一个文件可以被同一个用户或者不同用户同时打开一次或多次,这就造成文件需要有一个结构能够分别记录每一次打开后的操作,比如光标位置。因此,linux系统使用了文件描述符来解决这个问题。当打开或者创建一个文件的时候,内核就会向进程返回一个文件描述符(非负整数),所有对文件的后续操作都通过该文件描述符来完成。一个i节点可以对应多个文件描述符,每一个文件描述符都代表了一次对该文件的打开操作。系统的文件描述符是有限的,打开文件后一定要记得关闭,及时将文件描述符释放。
文件的基础IO
文件的创建与打开
-
函数
#include <fcntl.h> /** * @param pathname 文件的路径 * @param flags 打开方式 * @param mode 创建文件时,设置文件的权限 * @return int 打开/创建成功时返回文件描述符,否则返回-1 */ int open(const char *pathname, int flags, mode_t mode); // 创建文件 int open(const char *pathname, int flags); // 打开文件
flags 意义 O_RDONLY 只读 O_WRONLY 只写 O_RDWR 读写 O_APPEND 追加 O_CREAT 如果文件不存在则创建 O_EXCL 如果使用O_CREAT时文件已经存在,则强制open失败 O_TRUNC 打开文件时清空文件内容 O_DSYNC 每次写入时等待数据写到磁盘上 O_RSYNC 每次读取时,等到相同部分先写到磁盘上 O_SYNC 以同步方式写入文件,强制刷新内核缓冲区到输出文件 mode 意义 S_IRUSR 文件所有者的读权限 S_IWUSR 文件所有者的写权限 S_IXUSR 文件所有者的执行权限 S_IRWXU 文件所有者的读写和执行权限 S_IRGRP 文件用户组的读权限 S_IWGRP 文件用户组的写权限 S_IXGRP 文件用户组的执行权限 S_IRWXG 文件用户组的读写执行权限 S_IROTH 文件其他用户组的读权限 S_IWOTH 文件其他用户组的写权限 S_IXOTH 文件其他用户组的执行权限 S_IRWXO 文件其他用户组的读写和执行权限 flags的选项可以通过 | 联合使用,例如:
#include<sys/stat.h> // mode宏的头文件 #include <fcntl.h> // flags宏的头文件 int flags = O_CREAT | O_RDONLY; // 如果文件不存在则创建文件,并以只读的方式打开文件
-
演示
-
创建文件时不配置mode权限会发生什么
/* * name: 20230918_1.cpp * funtion: 无权限文件创建 * Date: 2023-09-18 * Author: Torch-HXM */ #include <iostream> #include <fcntl.h> #include <sys/stat.h> using namespace std; int main(){ char filePath[] = "./20230918_1.txt"; int flags = O_CREAT | O_RDWR; int fd = open(filePath, flags); if(fd){ cout<< "create file successfully."<< endl; } else{ cout<< "file created failed."<< endl; return -1; } return 0; }
在上面的代码中,由于我们没有文件"./20230918_1.txt",故而flags的O_CREAT部分会让系统创建这个文件,O_RDWR让系统以读写的方式打开文件。我们将代码编译并执行
$ g++ 20230918_1.cpp $ ls 20230918_1.cpp a.out $ ./a.out create file successfully. $ ls 20230918_1.cpp 20230918_1.txt a.out
可以看到文件被成功的创建,接下来我们查看文件内容:
$ cat 20230918_1.txt cat: 20230918_1.txt: Permission denied $ ls -l total 16 -rw-r--r-- 1 torch torch 415 Sep 18 14:49 20230918_1.cpp ---x------ 1 torch torch 0 Sep 18 14:51 20230918_1.txt -rwxr-xr-x 1 torch torch 9024 Sep 18 14:51 a.out
可以看到,当我们使用cat命令输出文件内容时,系统提示我们没有查看文件的权限。我们使用ls -l查看文件的权限信息,发现,文件所有者仅仅拥有对文件的执行权限。经过测试这个执行权限,仅允许我们在执行代码时打开文件,而不能够读取和写入文件内容。
-
创建一个有权限的文件
接下来我们修改代码,删除生成的a.out和20230918_1.txt文件。
/* * name: 20230918_1.cpp * funtion: 有权限文件创建 * Date: 2023-09-18 * Author: Torch-HXM */ #include <iostream> #include <fcntl.h> #include <sys/stat.h> using namespace std; int main(){ char filePath[] = "./20230918_1.txt"; int flags = O_CREAT | O_RDWR; mode_t mode = S_IRWXU; // 给文件所有者读写和执行权限 int fd = open(filePath, flags, mode); if(fd){ cout<< "create file successfully."<< endl; } else{ cout<< "file created failed."<< endl; return -1; } return 0; }
编译并执行代码后,使用cat命令查看20230918_1.txt文件,不再出现权限问题,查看文件权限:
$ ls -l total 16 -rw-r--r-- 1 torch torch 521 Sep 18 15:06 20230918_1.cpp -rwx------ 1 torch torch 0 Sep 18 15:03 20230918_1.txt -rwxr-xr-x 1 torch torch 9024 Sep 18 15:03 a.out
20230918_1.txt文件的所有者已经具有读写和执行权限。所以,在创建文件时,一定要设置权限,不然,即使创建成功,即使flags中规定以读写的方式打开,即使程序不报错,也会因为权限问题无法读写成功。
-
打开文件
- 如果打开文件时使用了mode参数会发生什么?
我们在已经拥有文件"./20230918_1.txt"的情况下再次执行程序,发现程序没有报错。我们修改程序中赋予的权限mode为读权限,编译后执行程序,发现"./20230918_1.txt"文件的权限仍然是读写和执行,并没有变为读,因此判定当文件存在时,mode参数失去作用,加上该参数不会报错也不会对文件的权限进行修改。 - 使用不带mode参数的open函数打开文件和带mode参数的open函数打开文件的效果一致。
- 如果打开文件时使用了mode参数会发生什么?
-
文件的关闭
系统的文件描述符数量是有限的,在程序中打开文件后一定要记得关闭。我们在文件的创建与打开中的示例在打开文件后并没有关闭文件的步骤,这样会使得内核分配给我们的文件描述符无法被释放(重启后自动释放)。-
函数
#include <unistd.h> /** * @param fd 要关闭文件的文件描述符 * @return int 成功关闭时返回0,否则返回-1 */ int close(int fd);
-
演示
/* * name: 20230918_1.cpp * funtion: 关闭文件 * Date: 2023-09-18 * Author: Torch-HXM */ #include <iostream> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> using namespace std; int main(){ char filePath[] = "./20230918_1.txt"; int flags = O_CREAT | O_RDWR; int fd = open(filePath, flags); cout<< "文件描述符为:"<< fd<< endl; if(fd){ cout<< "open file successfully."<< endl; } else{ cout<< "open file failed."<< endl; return -1; } fd = close(fd); if(fd==0){ cout<< "close file successfully."<< endl; } else{ cout<< "close file failed."<<endl; return -1; } return 0; }
-
-
写入和读取文件
-
函数
#include <unistd.h> /** * @param fd 文件描述符 * @param buf 字符缓冲区,储存着即将写入到文件中的字符 * @param count 写入数量,从buf中写入count个字符到文件中 * @return int 执行成功则返回写入的字节数,否则返回-1 */ ssize_t write(int fd, const void* buf, size_t count); /** * @param fd 文件描述符 * @param buf 字符缓冲区,用来储存读取到的字符 * @param count 读取数量,从文件中读取count个字符到buf中 * @return int 执行成功时返回读取到的字节数,失败时返回-1 */ ssize_t read(int fd, void* buf, size_t count); /** * @param fd 文件描述符 * @param offset 相对于whence的偏移量 * @param whence 位置宏 */ off_t lseek(int fd, off_t offset, int whence);
lseek中的whence
宏 意义 SEEK_SET 移动光标到文件起始位置 SEEK_CUR 保持当前光标位置不变 SEEK_END 移动光标到文件结尾位置 lseek函数通过调整文件偏移量(光标位置)可以设置我们从文件的何处开始读取或写入。
-
演示
/* * name: 20230918_1.cpp * funtion: 关闭文件 * Date: 2023-09-18 * Author: Torch-HXM */ #include <iostream> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> using namespace std; int main(){ // 打开文件 char filePath[] = "./20230918_1.txt"; int flags = O_CREAT | O_RDWR; int fd = open(filePath, flags); if(fd){ cout<< "成功打开文件 "<< filePath<< " 文件描述符为:"<< fd<< endl; } else{ cout<< "open file failed."<< endl; return -1; } // 此时文件的内容为空 char writeBuf[] = "Tody is September 28th, 2023."; int writeCount = strlen(writeBuf); // 写入内容 int getSize = write(fd, writeBuf, writeCount); if(getSize==-1){ cout<< "写入内容失败!"<< endl; return -1; } else{ cout<< "'"; for(int i=0;i<writeCount;i++){ cout<< writeBuf[i]; } cout<< "'"<< "写入成功!"<<endl; } // 调整文件偏移量至文件开头 off_t offset = 0; int whence = SEEK_SET; int where = lseek(fd, offset, whence); if(where==-1){ cout<<"文件偏移量调整失败!"<<endl; return -1; } else{ cout<<"文件偏移量已调整至:"<< where<< endl; } // 读取文件内容 char readBuf[100]; int readCount = strlen(writeBuf); getSize = read(fd, readBuf, readCount); if(getSize == -1){ cout<< "读取文件失败!"<< endl; return -1; } else{ cout<< "已读取内容:"; for(int i=0;i<getSize;i++){ cout<< readBuf[i]; } cout<< endl; } // 关闭文件 fd = close(fd); if(fd==0){ cout<< "close file successfully."<< endl; } else{ cout<< "close file failed."<<endl; return -1; } return 0; }
执行结果:
成功打开文件 ./20230918_1.txt 文件描述符为:3
'Tody is September 28th, 2023.'写入成功!
文件偏移量已调整至:0
已读取内容:Tody is September 28th, 2023.
close file successfully.
-