持久化
I/O设备#
系统架构#
基本上可以分为三层,从上到下的速度依次变慢,最上层的内存总线是不对外暴露的,只用于内存和CPU间的传输,其速度也是最快的,其次是PCI类似的通用I/O总线,所有的外围设备都会通过这条总线与CPU进行信息传输,其中显卡直连在这条线路上,而其他设备还需要一条最慢的外设总线来作为中转,这条外设总线几乎可以支持所有常用的外设协议,包括SATA、USB等。
DMA#
DMA,全称为Direct Memory Access,是一个用于优化I/O设备读写的硬件设备。
通常在一个进程中,CPU在将数据从内存搬运到I/O设备的过程是需要占用CPU时间的,而这部分由于硬件架构的限制,往往较慢,这就拖慢了CPU的执行效率。
CPU可以将数据的内存地址和对应的I/O设备信息发送给DMA,由DMA来不经过CPU直接将数据搬入或搬出I/O设备,在执行这个操作的进程就可以放弃当前时间片,等待数据传输完成就好了,CPU可以去执行其他真正需要CPU参与运算的操作,这就提高了CPU的执行效率。
磁盘#
磁盘是持久化存储数据的I/O设备。磁盘上的最小单位通常是512字节,又称为一个扇区,磁盘就是由大量的扇区组成的。通常,单个扇区的写入是原子性的,只有全部成功或者全部失败两种情况,如果涉及多个扇区的写入,异常情况下,比如突然的断电,如果没有其他策略支持,不能保证全部都能正确写入。
文件与系统#
操作 | 系统接口 |
---|---|
创建和打开文件 | open |
读取文件 | read |
偏移读 | lseek ,有时并不想顺序读取文件的全部内容,就可以用这个接口,通过传入文件描述符、偏移量和相对偏移方式,可以从任意位置开始读取文件。 |
写入文件 | write |
同步写 | fsync ,在调用write 接口时,系统为了保证效率,通常都不会立即与I/O设备交互,而是先将数据存到缓冲区中,等待一段时间或数据满了再一次性写入设备,但在一些特殊场景下,例如数据库的存储管理,需要频繁的将数据立即写入磁盘,可以调用fsync 直接写入。 |
文件重命名 | rename ,在linux系统的命令行中,常用mv 进行文件的重命名,实际上就是调用了系统的rename 接口。 |
查看文件信息 | stat 或fstat ,这将返回一个存储了当前文件系统的stat结构体。 |
删除文件 | unlink ,linux系统命令行的rm 命令实际上调用的是这个接口。 |
创建目录 | mkdir ,传入目录名和权限 |
打开目录 | opendir ,这将返回DIR类型的指针,DIR是<dirent.h>中定义的文件夹流对象。 |
读取目录 | readdir 读取DIR指针指向的目录,返回一个存储了目录信息的dirent结构体,每次会读取到对应DIR指针指向的文件夹下的一个文件或文件夹,以流的形式传输,读完了会返回个空指针。 |
关闭目录 | closedir |
删除目录 | rmdir ,这个接口只能删除空的目录,如果目录是非空的将会报错。 |
为什么删除文件的接口名称是unlink
,而不是delete
或者remove
?
这需要先解释下文件系统中的链接
link
,link
系统调用有两个入参,分别表示旧路径名和新路径名,当使用一个新的文件名链接到旧的文件名时,实际上是创建了另一种引用同一文件的方法,比如在命令行中使用ln old_file new_file
,就是重新建立了一个指向相同文件的路径。link
只是创建了一个名称,并将其指向了原有文件的相同inode号,并没有执行任何形式的复制,所以无论使用旧的路径还是新的路径都会访问到相同的inode号对应的文件,这甚至可以使用命令行来验证ls -i
可以查看文件对应的inode号。
每当删除一个路径时,只是删除了一个对原有文件的链接,只有当最后一个链接被删除,即再也没有该文件的链接时,这个文件才会被删除。unlink
就是取消一个对原有文件的链接,这就可以理解为什么取名为unlink了。
命令行中使用stat
可以查看文件当前有多少的链接。
上述的链接方式又被称为“硬链接”,但是存在无法创建目录的链接(目录没有inode号)和无法跨文件系统创建链接的局限,于是人们又创建了一种“软链接”的方式,也就是符号链接。通过ln -s
的方式来创建符号链接。
符号链接中存放的其实是文件的路径名。这样删除了符号链接并不会直接删除指向的文件,同样,如果删除了原有的文件,符号链接不会跟着删除,而是变成了一个无效的链接,因为其存储的路径名已经不存在了。
#include <iostream>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cassert>
using namespace std;
int main() {
mkdir("mydir", 0777);
auto fd = open("mydir/myfile", O_CREAT | O_WRONLY | O_TRUNC);
close(fd);
struct stat st;
stat("mydir/myfile", &st);
cout << st.st_mode << endl;
cout << st.st_blocks << endl;
cout << st.st_blksize << endl;
cout << st.st_uid << endl;
cout << st.st_size << endl;
DIR* dp = opendir(".");
assert(dp != nullptr);
struct dirent* d;
while((d = readdir(dp)) != nullptr) {
cout << d->d_ino << "\t" << d->d_name << endl;
}
unlink("mydir/myfile");
rmdir("mydir");
return 0;
}
输出
33832
0
4096
1000
0
8126551 a.out
8919082 mydir
8127742 create_process
8142539 float-min
8127611 new_file.cpp
8126969 float-min.cpp
8127700 new_file.out
8126538 ..
8127973 .vscode
8127768 dir.cpp
8127289 create_process.cpp
8126712 .
创建并挂载文件系统#
通常在操作系统中提供了创建文件系统的工具,名为mkfs,通过指定一个设备(磁盘分区)和文件系统类型(比如ext4),会在对应的设备上创建一个空的文件系统,然后调用mount
接口将文件系统挂载到执行的路径上。我们访问这个路径就相当于访问这个文件系统的根目录了。
通过这种挂载的方式,可以将各种不同的文件系统统一到一棵文件系统树中,而不是拥有多个独立的文件系统。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗