操作系统学习手记03
操作系统学习手记:当计算机与世界相连
计算机的价值在于与我们交互,与外部世界交换信息。这一章,我们就来聊聊这个“交换”的过程——I/O(输入/输出)以及信息得以长久保存的基石——文件系统。
那么,就让我们从最基本的 I/O 开始!
L25 IO与显示器
I/O,顾名思义就是输入(Input)和输出(Output)。CPU 和内存是计算机的核心,而所有连接到这个核心上的其他设备,比如键盘、鼠标、显示器、硬盘、网卡等,都统称为外围设备或I/O设备。操作系统的一个核心职责,就是管理这些设备,并为上层应用提供一个统一、简洁的交互接口。
CPU 如何与这些形形色色的设备沟通呢?主要有两种方式:
- 端口 I/O (Port I/O):CPU 使用特殊的
in
和out
指令,通过一个独立的“端口地址空间”来访问设备。每个设备控制器会占用几个端口号,CPU 通过这些端口号来发送命令、读取状态、传输数据。 - 内存映射 I/O (Memory-Mapped I/O):将设备控制器的寄存器、数据缓冲区等映射到物理内存地址空间的一部分。这样一来,CPU 就可以像访问普通内存一样,使用
mov
等常规指令来和设备交互,不再需要特殊的I/O指令。这种方式更现代,也更灵活。
我们先来看一个最直观的输出设备——显示器。 我们之所以能在屏幕上看到五彩斑斓的图像,是因为有一块专门的内存区域,叫做显存(Video RAM 或 Framebuffer)。这块内存被映射到了显示设备的控制器上。
- 工作原理:操作系统(或显卡驱动)向这块特定的内存区域写入数据,每一个或几个字节就对应了屏幕上的一个像素点的颜色和亮度信息。显示控制器会不断地、以极高的频率(比如每秒60次)读取整个显存的内容,并根据这些数据控制电子枪或液晶像素点,从而在屏幕上绘制出完整的图像。
- 本质:对程序来说,在屏幕上显示一个“A”,本质上就是计算出“A”这个字符的像素点阵,然后将这些像素对应的颜色值,写入到显存中正确的位置。我们平时调用的图形库API,最终都是在执行这个操作。而管理显存、提供绘图接口,就是显示器驱动程序的核心任务。
L26 键盘
说完了输出,我们再来看最经典的输入设备——键盘。 你有没有想过,当你按下键盘上的一个键时,计算机是如何知道你按了哪个键的?这个过程是中断驱动的,非常精妙。
- 按下按键:当你按下键盘上的一个键时,键盘内部的芯片会检测到这个动作,并生成一个编码,这个编码叫做扫描码(Scan Code),它唯一地标识了被按下的那个物理按键(注意,不是字符’A’,而是“A键”这个位置)。
- 发送信号:键盘控制器会将这个扫描码发送给主板上的键盘接口。
- 触发中断:主板上的中断控制器接收到信号后,会向 CPU 发送一个硬件中断请求(IRQ)。
- CPU响应:CPU 在执行完当前指令后,会检测到这个中断信号。它会立刻停下手中正在运行的程序,保存好当前的“工作现场”(寄存器状态等)。
- 执行中断服务程序:CPU 根据中断号,在中断向量表中查找到对应的处理程序的地址,这个程序就是键盘中断服务程序(ISR - Interrupt Service Routine)。然后跳转到这个地址开始执行。
- 处理输入:键盘ISR会从键盘控制器的端口读取到那个扫描码。然后,操作系统会对这个扫描码进行解释,比如结合
Shift
键的状态,把它翻译成真正的ASCII字符(例如,’a’ 或 ‘A’)。 - 放入缓冲区:翻译好的字符会被放入一个系统级的键盘缓冲区中。
- 恢复现场:ISR 执行完毕后,CPU 会恢复之前保存的现场,回到被打断的程序那里,继续往下执行。整个过程快到用户毫无察觉。
当你在一个文本编辑器里写东西时,编辑器程序会通过系统调用,从那个键盘缓冲区里读取字符,然后显示在屏幕上。这就是一次完整的按键输入-处理-输出的流程。
L27 生磁盘的使用
显示器和键盘是瞬时设备,而**磁盘(硬盘)**则是**持久化存储**的基石,关机后数据依然存在。在文件系统这个优雅的抽象出现之前,使用磁盘是一种非常原始和直接的方式。
- 物理结构:一个机械硬盘由多个盘片(Platter)**组成,每个盘片有两个面,每个面又被划分为很多同心圆,称为**磁道(Track)**。每个磁道又被进一步划分为若干个扇形的区域,称为扇区(Sector)**。扇区是磁盘读写的最小物理单位,通常是512字节或4KB。
- 生磁盘使用(Raw Disk Access):操作系统可以通过磁盘控制器,直接向磁盘发送命令,比如“读取第 N 个扇区的数据”或“向第 M 个扇区写入数据”。这里的 N 和 M 是扇区的绝对物理地址。
- 视角:在“生磁盘”的视角下,整个硬盘就像一个巨大的一维字节数组(或者说扇区数组)。操作系统可以直接访问这个数组的任意位置。
- 用途:这种方式虽然对上层应用来说非常不便且危险(一不小心就可能覆盖掉重要数据),但它却是操作系统本身构建更高级功能的基础。比如,文件系统的初始化、磁盘分区的管理,都离不开对磁盘扇区的直接读写。
L28 用文件使用磁盘
直接操作扇区太反人类了。没人愿意记着“我的文档第一部分在第18324个扇区,第二部分在第98234个扇区”。我们需要一个更友好的抽象层。于是,文件(File) 这个概念诞生了。
- 什么是文件:文件是操作系统为用户提供的一个逻辑存储单元。从用户的角度看,文件就是一段连续的、有名字的数据流。你可以从头读到尾,或者直接定位到中间某个位置,而完全不需要关心这些数据到底被存放在磁盘的哪些扇区,以及这些扇区是否连续。
- 文件属性(元数据):除了数据本身,每个文件还有一堆描述它的信息,比如:文件名、文件类型、大小、创建时间、修改时间、所有者、访问权限等等。这些信息被称为元数据(Metadata)。
- 操作系统的任务:文件系统的核心任务之一,就是实现从逻辑文件到物理磁盘块的映射。当用户请求读取文件的第1000个字节时,文件系统需要计算出这第1000个字节位于文件的第几个逻辑块,然后通过文件的元数据查到这个逻辑块对应磁盘上的哪个物理扇区,最后才发起真正的磁盘I/O请求。
文件这个抽象,是计算机科学史上最伟大的发明之一。它将混乱无序的物理存储,变成了我们今天习以为常的、结构化的信息管理方式。
L29 目录与文件系统
有了文件,我们解决了单个数据集合的存储问题。但很快,成千上万个文件堆在一起,找一个文件又成了新难题。于是,目录(Directory) 或 文件夹(Folder) 的概念应运而生。
- 目录:目录本身也是一种特殊的文件。它的内容不是普通的用户数据,而是一个列表,记录了存放在这个目录下的文件名以及指向这些文件元数据的位置信息。
- 目录结构:
- 单级目录:所有文件都在一个大目录下,文件名不能重复。简单,但文件一多就乱了。
- 两级目录:每个用户有一个主目录,文件都放在自己的主目录下。解决了不同用户间的文件名冲突问题。
- 树形结构目录:这是现代操作系统普遍采用的结构。目录可以包含子目录,形成一个层次化的树状结构。这种结构清晰、灵活,扩展性强。
将文件、目录以及管理它们的一整套机制(包括元数据结构、磁盘空间管理、数据块分配策略等)结合在一起,就构成了文件系统(File System)。
一个磁盘分区在被使用前,必须先用某个文件系统(如FAT32, NTFS, ext4)进行格式化。格式化的过程,就是在磁盘上建立起这个文件系统的基本数据结构,比如:
- 引导块(Boot Block):存储了从该分区启动操作系统所需的信息。
- 超级块(Superblock):包含了整个文件系统的“宏观”信息,如文件系统类型、块大小、块总数、inode总数、空闲块信息等。是文件系统的“户口本”。
- 元数据区:存放文件的元数据。在类Unix系统中,这通常是 inode(索引节点) 表。每个文件或目录都有一个唯一的inode,里面记录了文件的所有属性和指向数据块的指针。
- 数据区(Data Blocks):真正存放文件内容的地方。
L30 目录解析代码实现
当我们给出一个路径,比如 /home/user/notes.txt
,文件系统是如何找到这个文件的呢?这个过程就是路径解析。
让我们模拟一下这个“按图索骥”的过程:
- 从根开始:路径以
/
开头,表示从根目录开始。文件系统通过访问超级块,可以找到根目录的 inode 在磁盘上的位置。 - 查找
home
:文件系统读取根目录的数据块。前面说了,目录的内容是一个列表。它在这个列表中搜索名为home
的条目。 - 找到
home
的 inode:找到home
条目后,就从中获取了home
目录的 inode 号。 - 读取
home
目录:文件系统根据home
的 inode,找到home
目录的数据块,并读取其内容。 - 查找
user
:在home
目录的内容列表中,搜索名为user
的条目,从而找到user
目录的 inode 号。 - 读取
user
目录:重复上述步骤,根据user
的 inode 读取user
目录的数据。 - 查找
notes.txt
:在user
目录的内容列表中,搜索名为notes.txt
的条目,最终找到这个文件的 inode 号。 - 访问文件:现在,文件系统拿到了
notes.txt
的 inode。这个 inode 里包含了文件的所有信息,最重要的是指向其数据块的指针。至此,我们就可以根据这些指针,去磁盘的数据区读取文件的真正内容了。
这个过程涉及多次磁盘I/O,为了提高效率,操作系统会大量使用缓存(Cache),将最近访问过的目录内容和 inode 缓存在内存中,避免重复的磁盘读取。
总结
正是这些看似乎理所当然的抽象,才使得我们能够方便地与计算机交互、高效地管理海量数据。