当前位置: 首页 > backend >正文

文件IO之系统IO

系统IO

1.文件系统

文件系统是操作系统中用于明确存储设备(如硬盘、闪存、光盘等)或分区上文件的方法和数据结构即文件在存储设备上的组织和管理方式。具体来说,文件系统负责在存储设备上分配空间、记录文件位 置、保存文件属性和权限等信息,并提供对文件和目录的读、写、复制、删除等操作。

  • 定义

文件系统是操作系统用于明确存储设备或分区上的文件的方法和数据结构,是操作系统中负责管理和存 储文件信息的软件结构。

  • 功能
    • 管理和调度文件的存储空间。
    • 提供文件的逻辑结构、物理结构和存储方法。
    • 实现文件从标识到实际地址的映射。
    • 实现文件的控制操作和存取操作。
    • 实现文件信息的共享并提供可靠的文件保密和保护措施。
    • 提供文件的安全措施。

文件系统通常由三部分组成:
文件系统的接口:提供用户与文件系统交互的方式。
对对象操纵和管理的软件集合:包括负责文件创建、删除、读写等操作的软件。
对象及属性:即文件和目录等对象及其属性信息,如文件名、大小、创建时间等。

  • 工作原理

在操作系统中,每个文件和目录都被指定了一个文件名,用户按文件名存取文件。而实际上,文件和目录在磁盘中是按照柱面、磁道等物理位置存放的**。文件系统能够将操作系统对文件的按名存取转化成按磁盘的物理位置进行读写**。这种转换是通过文件系统内部的数据结构和管理机制实现的。

1.1 Linux文件系统

Linux文件系统是Linux操作系统中用于组织和管理文件的一种机制,它定义了文件和目录的结构,以及 文件在存储设备上的存储方式。

在linux下面,一切皆文件。在linux 下面操作任何东西,其实都是通过 操作文件来实现的,通过文件的接口实现。

  • Linux文件系统的结构
    • 树状结构:Linux文件系统采用树状结构,只有一个根目录(/),其中含有下级子目录或文件的信息。子目录中又可以包含更多的子目录或文件,这样一层一层地延伸下去,构成一棵倒置的树。
    • 目录和文件:在目录树中,根节点和中间节点都必须是目录,而文件只能作为叶子节点出现。目录文件用于管理和组织系统中的大量文件,存储一组相关文件的位置、大小等与文件有关的信息。
  • Linux下文件的处理过程

在Linux内核中,当一个文件或目录被系统识别或访问时,内核会为其创建一个或多个相关的数据结构来管理该文件或目录的元数据(metadata)和访问权限等信息。这些数据结构中最核心的就是inode(索引节点)。inode 包括文件的类型(普通文件、目录、字符设备、块设备等)、大小、创建和修改时间、链接数(有多少文件名指向这inode)、数据块位置等。文件名存储在目录的条目(dentry)中,这些条目通过inode号来链接到对应的inode结构。

读取inode:当打开一个文件或执行文件操作时,内核会根据文件名通过目录项找到对应的inode号,然后使用inode号从inode缓存或磁盘上读取inode信息。

修改inode:当文件的状态被修改(如文件大小、权限、时间戳等)时,内核会更新内存中的inode结 构,并在适当的时候将这些更改写回到磁盘上。

删除inode:当文件被删除时,文件名会从目录项中移除,**但inode本身不会被立即删除。只有当文件的链接数(即有多少文件名指向这个inode)减少到0时,inode才会被标记为可回收,**并在适当的时候被 物理删除(同时释放占用的磁盘空间)。

2 系统IO

系统IO(Input/Output)是计算机操作系统提供给应用程序的一种输入和输出方式,它涉及计算机系统与外部设备(如磁盘驱动器、网络接口、串口等)以及其他程序之间的数据交换

  • 定义

系统IO是操作系统内核提供的一种机制,允许应用程序通过系统调用来实现数据的读取和写入。

功能

通过系统IO,应用程序可以与各种硬件设备、文件和其他进程进行高效的数据传输。

实现方式

  • 系统调用:系统IO的操作是通过系统调用来完成的。系统调用是一种特殊的函数调用,用于向操作系统请求特定的操作,如打开文件、读写文件、关闭文件等。

  • 内核管理:在系统IO中,操作系统内核负责管理和执行实际的IO操作。内核会维护一个缓冲区池(如分页缓冲区),用于存放数据页,并通过替换算法(如LRU)来决定哪些数据页需要被写回磁盘,以及哪些数据页可以被替换出缓冲区。

  • 特点和优势

    • 直接性:系统IO允许应用程序直接通过系统调用来操作硬件设备和文件,而无需通过其他中间层。

    • 高效性:通过内核管理的缓冲区池和替换算法,系统IO能够优化数据传输过程,提高IO操作的效率。

    • 安全性:系统IO通过内核提供的权限检查和隔离机制,确保了数据传输的安全性和稳定性。

2.1 打开/创建文件

在Linux系统中,open 函数是一个系统调用,用于打开或创建一个文件。这个函数定义在fcntl.h 件中,并且它的原型通常如下所示:

#include <fcntl.h>  
#include <unistd.h>  //Unix 和类 Unix 系统(如 Linux)中提供对操作系统 API 访问的头文件之一int open(const char *pathname, int flags);  
int open(const char *pathname, int flags, mode_t mode);
  • 参数

    • pathname :要打开或创建的文件的路径名。

    • flags :指定文件如何被打开或创建,这些标志可以是以下值的组合(使用按位或运算 符|):

      • 打开文件

        • O_RDONLY :以只读方式打开文件。

        • O_WRONLY:以只写方式打开文件。

        • O_RDWR :以读写方式打开文件。

      • 创建文件

        • O_CREAT:如果文件不存在,则创建文件。使用此选项时,需要第三个参数mode 来指定新文件的权限。

        • O_TRUNC :如果文件已存在且以写方式打开,则将其长度截断为零。(如果文件已存在,而且要修改文件,将之前的文件全部覆盖

        • O_APPEND :以追加方式打开文件(数据会被写入原来文件的末尾,而不是文件的开头)。

        • 其他标志等。

    • mode (仅在 O_CREAT 被指定时使用):指定新文件的权限。这个参数使用八进制数(最高权限777 最低权限000)表示,并且会受到实际能设置的权限的影响->比如管理员只给了普通用户666的权限,那么即使当前用户修改到777的权限,系统也只会给其666方法权限。

  • 返回值

    • F成功

    文件描述符:文件描述符是一个非负整数,用于标识进程打开的文件或套接字。它是内核为了高效管理已被打开的文件所创建的索引**,所有的I/O操作都会通过文件描述符进行。**

    • 失败

​ 出错时,返回-1,并设置errno 以指示错误原因。(errno 是错误码,错误码是一个数字,一般伴随错误信息,错误信息就是对错误码的解释)

示例

 	const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR|  O_CREAT, 0777);

perror 是一个标准库函数,用于输出前一个系统调用的错误描述。当你调用一个系统函数(如 open 、 write 等)并且该函数因为某种原因失败时,它会设置一个全局变量errno误的原因。 read 、 errno 是一个整型值,对应于一个特定的错误代码。

void perror(const char *s);

这里,s 是一个指向的字符串的指针,是自己手动设置的,用来区分多个系统调用的错误

紧接着这个字符串, perror 会输出一个冒号(:)、一个空格,以及对应于 errno 当前值的错误描述字符串。这个错误描述字符串是由系统提供的,通常是一个 简短的、人类可读的错误消息,比如 "No such file or directory"(没有找到文件或者目录) 或 "Permission denied"(权限不够) 。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR|  O_CREAT, 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;//关闭文件,参数是int类型close(fd);return 0;
}

2.2 关闭文件

在Linux系统中, close 函数是一个系统调用,用于关闭一个打开的文件描述符。当文件描述符不再需要时,,应该使用close函数来关闭它,以释放系统资源。

#include <unistd.h>  
int close(int fd);
  • 参数

fd :是要关闭的文件描述符。

  • 返回值

成功时,close 函数返回0。
出错时,返回-1,并设置全局变量errno 以指示错误类型。

注意:
如果尝试对同一个文件描述符进行多次 close操作,则除了第一次调用之外,后续的调用都会失败,并 设置errno EBADF (错误的文件描述符)。然而,这种行为在不同的系统和库实现中可能有所不同, 因此最好的做法是只关闭每个文件描述符一次。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR|  O_CREAT, 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;//关闭文件,参数是int类型close(fd);return 0;
}

2.3 文件写入

在Linux系统中, if (fd == -1) write 函数是一个系统调用,用于向打开的文件或设备写入数据。

#include <unistd.h>  
ssize_t write(int fd, const void *buf, size_t count);
  • 参数:

    • fd :文件描述符。

    • buf :指向要写入数据的缓冲区的指针。

    • count :指定要写入的最大字节数。

  • 返回值:

成功时,write 函数返回写入的字节数。这个值可能小于请求写入的字节数(count),特别是在非阻塞模式下或当磁盘空间不足时。

出错时,返回-1,并设置errno 以指示错误原因。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR | O_CREAT | O_APPEND , 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;const char* test_write ="Hello, World!\nThis is a test file.\nAnother line of text.\n"; // 要写入的数据if(write(fd, test_write, strlen(test_write)) == -1){perror("文件写入失败");return -1;close(fd); // 写入失败后关闭文件}//暂停一下getchar();//关闭文件,参数是int类型close(fd);return 0;
}
  • 问题:为什么不用显示的写成功的if语句?

  • 因为write 函数调用时,写入操作就已经开始并完成(成功或失败),因此不用再显示的写入成功的if语句,通过检查 write 的返回值,可以判断写入是否成功

​ 在不需要额外处理的情况下,直接在 if 语句中检查 write 的返回值是一种合理且 常见的做法

2.4 文件读取

在Linux系统中, read 函数是一个系统调用,用于从打开的文件或设备中读取数据。

#include <unistd.h>  
ssize_t read(int fd, void *buf, size_t count);
  • 参数:

fd :文件描述符。
buf :指向一个缓冲区的指针,该缓冲区用于存储从文件或设备中读取的数据。
count :指定要读取的最大字节数。

  • 返回值:

成功时,read 函数返回实际读取的字节数。这个值可能小于请求读取的字节数(count
特别是在文件末尾(EOF,end of file)。

出错时,返回-1,并设置 errno 以指示错误原因。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR | O_CREAT | O_APPEND , 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;// 读取数据到缓冲区,-1是为了留空间给'\0'char buffer[1024] = {0};if(read(fd, buffer, sizeof(buffer) - 1) == -1){perror("文件写入失败");close(fd);// 读取失败后关闭文件return -1;}// 输出读取到的内容printf("Read from file: \n%s", buffer);//关闭文件,参数是int类型close(fd);return 0;
}

示例:完整代码

.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;const char* test_write ="Hello, World\n"; // 要写入的数据if(write(fd, test_write, strlen(test_write)) == -1){perror("文件写入失败");return -1; // 写入失败后关闭文件close(fd);}// 关闭文件(对于写入操作,这一步是可选的,但出于好习惯应关闭它)close(fd);// //暂停一下getchar();// 重新打开文件以读取fd = open(filename, O_RDONLY);// 读取数据到缓冲区,-1是为了留空间给'\0'char buffer[1024] = {0};if(read(fd, buffer, sizeof(buffer) - 1) == -1){perror("文件写入失败");close(fd);// 读取失败后关闭文件return -1;}// 输出读取到的内容printf("Read from file: \n%s", buffer);//关闭文件,参数是int类型close(fd);return 0;
}

2.5 移动文件指针

lseek 函数是 Linux 系统中用于移动文件描述符的读写位置的一个系统调用。这个函数允许你指定一个偏移量,相对于文件的开头、当前位置或文件末尾来移动文件的读写指针。这对于需要随机访问文件内容的程序非常有用。

#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
  • 参数

fd 是你想要移动读写位置的文件描述符。

offset 是你想要移动的字节数。这个值可以是正数、负数或零。正数表示向前移动,负数表示向后移动,零表示不移动但可以用来更新文件的状态(如文件长度)。

whence 是一个指示偏移量起始位置的参数,它决定了 offset 是从哪里开始计算的。它可以是以下三个常量之一:
SEEK_SET :文件的开头。
SEEK_CUR :当前读写位置。
SEEK_END :文件的末尾。

  • 返回值

如果调用成功, lseek 返回新的读写位置相对于文件开头的偏移量(以字节为单位)。如果发生错误, 返回(off_t)-1 ,并设置``errno以指示错误的原因。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
using namespace std;
int main()
{// int open(const char *pathname, int flags, mode_t mode);const char* filename = "/home/wanfeng/code/test.txt"; // 文件名// 打开文件以写入。如果文件不存在,则创建它。int fd = open(filename, O_RDWR | O_CREAT, 0777);// 失败if(fd == -1){perror("打开文件失败");return -1;}// 成功cout << "文件打开成功" << endl;// const char* test_write ="Hello, World\n"; // 要写入的数据// if(write(fd, test_write, strlen(test_write)) == -1)// {  //     perror("文件写入失败");//     return -1; // 写入失败后关闭文件//     close(fd);// }// // 关闭文件(对于写入操作,这一步是可选的,但出于好习惯应关闭它)// close(fd);// // //暂停一下// getchar();off_t fileSize = lseek(fd, 3, SEEK_SET);if(fileSize == -1){perror("文件偏移失败");close(fd);// 读取失败后关闭文件return -1;}// // 重新打开文件以读取// fd = open(filename, O_RDONLY);// 读取数据到缓冲区,-1是为了留空间给'\0'char buffer[1024] = {0};if(read(fd, buffer, sizeof(buffer) - 1) == -1){perror("文件写入失败");close(fd);// 读取失败后关闭文件return -1;}// 输出读取到的内容printf("Read from file: \n%s", buffer);//关闭文件,参数是int类型close(fd);return 0;
}
结果
文件打开成功
Read from file: 
456789

2.6文件标识符的类型

  • 文件描述符(File Descriptor):在 Unix-like 系统(如 Linux)中,文件描述符是一个整数,用于标识打开的文件。它是内核用来跟踪文件打开状态的索引。例如,通过 open 系统调用返回的文件描述符是一个整数。
  • 文件指针(File Pointer):在 C 语言中,FILE 类型的指针(如 FILE*)用于文件操作。它是标准库提供的文件抽象,用于更高级的文件输入输出操作
http://www.xdnf.cn/news/5299.html

相关文章:

  • dockerfile编写入门
  • 十六、统一建模语言 UML
  • 16前端项目----交易页
  • QT6 源(90):阅读与注释 LCD显示类 QLCDNumber ,源代码以及属性测试。该类继承于容器框架 QFrame
  • Windows报错:OSError: [WinError 1455] 页面文件太小,无法完成操作的问题
  • Redis能保证数据不丢失吗之RDB
  • 【Web】使用Vue3开发鸿蒙的HelloWorld!
  • 模拟太阳系(C#编写的maui跨平台项目源码)
  • Autoware message_filters::Synchronizer链接错误问题
  • Axure疑难杂症:统计分析页面引入Echarts示例动态效果
  • 目录粘滞位的使用
  • JDBC链接数据库
  • 【typenum】 0 配置文件(Cargo.toml)
  • 【MySQL】事务(重点)
  • 酒店洗护用品那些事儿:品牌选择及扬州卓韵用品介绍
  • 6. 存储池配置与CephFS创建 ceph version 14.2.22
  • muduo源码解析
  • 【第33节 数据库基础概念】
  • 游戏引擎学习第269天:清理菜单绘制
  • [模型选择与调优]机器学习-part4
  • PyTorch API 6 - 编译、fft、fx、函数转换、调试、符号追踪
  • HTTP 请求中 Content-Type 头部
  • 使用谱聚类将相似度矩阵分为2类
  • 2025年RAG技术有哪些创新点?
  • 海市蜃楼的形成原理
  • M0的基础篇之PWM学习
  • adb命令查询不到设备?
  • 第二个简单的SpringBoot和Vue前后端全栈的todoapp案例
  • 告别“感觉良好”:深入RAG评估,从方法、工具到指标的全方位指南
  • Telnetlib三种异常处理方案