设备和驱动程序
Everything is a File
文件:有 “名字” 的数据对象
- 字节流 (终端,random)
- 字节序列 (普通文件)
文件描述符
- 指向操作系统对象的 “指针”
- Everything is a file
- 通过指针可以访问 “一切”
- 对象的访问都需要指针
- open, close, read/write (解引用), lseek (指针内赋值/运算), dup (指针间赋值)
I/O 设备
I/O 设备 = 一个能与 CPU 交换数据的接口/控制器
- 就是 “几组约定好功能的线” (寄存器)
- 给寄存器 “赋予” 一个内存地址 (Address Decoder)
- CPU 可以直接使用指令 (in/out/MMIO) 和设备交换数据
- 是的,就这么简单
总线
提供设备的虚拟化:注册和转发
- 把收到的地址 (总线地址) 和数据转发到相应的设备上
- 例子: port I/O 的端口就是总线上的地址
- IBM PC 的 CPU 其实只看到这一个 I/O 设备
今天获得 “CPU 直连” 的标准设备
- 接口
- 75W 供电
- 所以我们需要 6-pin, 8-pin 的额外供电
- 数据传输
- PCIe 6.0 x16 带宽达到 128GB/s
- 总线自带 DMA (专门执行 memcpy 的处理器)
- 中断管理
- 将设备中断转发到操作系统 (Message-signaled Interrupts)
应用程序访问设备
一个设备会被多个程序使用,而设备的使用是基于上面的寄存器进行交互,如果不加以管理是非常混乱的。
通过操作系统虚拟化设备 => Everything is a File
源自于Everything is a File,设备被虚拟化为Unix上/dev/中的文件,而这些文件就是设备上的寄存器
驱动程序(Drive),把系统调用 “翻译” 成与设备能听懂的数据(就是一段普通的内核代码)
配置设备
设备交互的复杂不在于如何使用,而在于配置设备
设备不仅仅是数据,还有配置
两种实现方法
- 控制作为数据流的一部分 (ANSI Escape Code)
- 提供一个新的接口 (request-response)
存储设备原理
一个能反复改写的状态
经过这么多存储媒介的更迭,但是存储设备的抽象缺没有变更
从1Bit到1TB
- 寻址能力的代价
- 磁盘:位置划分 + 扇区头
- 电路:行 (字线) 和列 (位线) 选通信号
这些都会消耗额外的资源 (面积)
解决方法:按块访问
- “一块” 可以共享 metadata
- 物理分割、Erase 信号、纠错码……
- 磁盘是
struct block disk[NUM_BLOCKS]
- Block 是读/写的最小单位
- Block devices 块设备 (ls -l /dev/sd*)
透明抽象的代价
- 不经意间的读/写放大 (read/write amplifications)
- 存储设备在实际执行读写操作时,处理的数据量超过用户实际请求的数据量
- 随机读/写一个 byte,都会导致大量数据传输
- 文件系统的实现应该能够感知 “块” 的概念
Linux Bio
Linux中通过Linux BIO系统实现块设备管理
- 上层 (进程、文件系统……) 可以任意提交请求
- 下层 (Bio + Driver) 负责调度
![[assets/持久化/file-20250606140816646.png]]
最后的版本答案——电存储
![[assets/持久化/file-20250606140442119.png]]
文件系统API
存储设备的抽象
磁盘 = 块(字节)序列
文件,虚拟的磁盘
怎么管理文件?
- 树状分层索引:信息的局部性
- 路径 => 结构化查询 => 智能检索
链接
Linux系统文件元数据
-
Type:d (directory), l (link), p (pipe), c (char), b (block)
-
Mode(权限)rwx (user, group, other)
- 例子:0o755 = rwx (111) r-x (101) r-x (101)
-
Links(硬连接):引用计数 (硬链接,包括目录)
-
Extended Attributes (xattr) 更多的元数据
- 每个文件可以维护一个任意的 key-value dictionary
ssize_t fgetxattr(
int fd,
const char *name,
void value[.size],
size_t size
);
int fsetxattr(
int fd,
const char *name,
const void value[.size],
size_t size,
int flags
);
联合挂载
将硬盘设备虚拟化,得到了文件系统
将文件目录虚拟化,得到了容器、快照
在后面关于容器的课程会详细讨论......
文件系统实现
文件系统实现就是在块设备(硬盘)上实现一个数据结构,来对数据进行管理
通过文件系统,映射到磁盘上对应的数据,从而进行管理
LVM
传统意义上,磁盘的虚拟化 => 文件系统
现在通常是,使用LVM(Logical Volume Manager,逻辑卷管理器)
![[assets/持久化/file-20250625141445505.png]]
LVM是一种块设备虚拟化技术,它允许操作系统将多个物理存储设备组合成一个或多个逻辑卷,从而提供更灵活和高效的存储管理。
LVM的架构:
- 物理层
- 物理卷
- 卷组
- 逻辑卷
- 文件系统
- 物理硬盘首先被Linux挂载为
/dev/sda
, /dev/sdb
,将物理硬盘变为Linux上可用的资源
- 然后LVM将挂载的硬盘,纳入并初始化设备为物理卷,也就是LVM系统中的数据结构
- 卷组(VG) 将多个物理卷(PV)组合成一个统一的存储资源池,作为LVM的核心管理单元
- 基于卷组的资源,LVM构建出逻辑设备——逻辑卷,并将逻辑卷挂载到Linux上,作为一个标准的块设备
/dev/vg_name/lv_name
不过这部分内容不是这里的重点,这里主要探讨如何实现文件系统
FAT
文件是由多个块设备中的块组成的,多个块按一定的顺序排列,就组成的文件
于是乎文件在块设备的存储问题,变成了如何管理块设备中的块关系
目录树
目录就是个普通文件
- 但在 metadata 里标记一下 (mode = directory)
- 操作系统把数据理解成 struct dirent[]
- Quiz: 为什么不把元数据 (大小、文件名、……) 保存在文件的头部?
- DOS: “8 + 3” 文件名 “AUTOEXEC.BAT”
- Linux: /etc/xdg/autostart/
- Windows: shell
inode
联合数据结构 ext2