本书是一本为渴望深入理解计算机操作系统设计与实现的读者量身打造的实践指南。全书共18章,涵盖了诸多关键主题,包括如何启动操作系统,管理内存和异常,创建进程并实现进程的协作,开发设备驱动程序,构建文件系统从而读写文件等内容,并以此为基础构建出可供用户程序使用的系统调用接口。通过这些接口,创建出小型的命令行解析器,它可根据用户输入的命令动态加载硬盘上的程序执行。为降低学习难度,尽可能地减少汇编代码的使用,并且向读者提供了一套简单易用的开发环境。通过翔实的原理分析和示例代码,读者能够清楚地看到一个小型的操作系统是如何一步步构建出来的。通过本书,读者不仅能够掌握操作系统的设计原理,还能亲自动手实践,构建属于自己的操作系统。无论你是计算机专业的学生,还是希望扩展技术深度的开发者,本书都能为你提供丰富的学习资源和实践指导。通过本书,你将不仅仅了解操作系统“是什么”,更会明白它“为什么这么做”以及“如何实现”。
通过翔实的原理分析和示例代码,让读者能够清楚地看到一个小型的操作系统是如何一步步构建出来的,还能亲自动手实践。
前言
本书介绍
记得很多年前,我在学校的图书馆书架上发现了一本厚厚的关于Linux 0.11内核源码分析的书籍。不过,由于该书仅从源码分析的角度介绍操作系统的实现,再加之自己的编程水平有限,因此最终仅阅读了一小部分章节。后来,偶尔也能找到介绍手写操作系统的书籍,不过,书中充斥着大量的汇编代码、复杂的设计算法且代码难于调试,这也导致我很难读完这些书。
手写操作系统是一件非常令人兴奋的事情,上述因素不应当成为前进之路上的障碍。能不能将这个事情变得简单一些,使得初学者能够更容易地学习?
在经过一系列的摸索之后,我找到了一种解决方案,并以此为基础做出了一套介绍操作系统实现的课程,最终完成此书。在书中,我尽可能地简化了操作系统的实现过程,从而让初学者能够更专注于操作系统的实现。
例如,该操作系统既不支持64位架构,也不支持多核,内部设计算法也尽可能简单且易于理解。对于初学者而言,这些内容并不特别重要。尽管如此,这个操作系统仍然具备了一个操作系统应当具备的功能,如进程管理、虚拟内存、文件系统、系统调用和设备管理等。更重要的是,本书提供了一套简易的开发环境,学习者可以很方便地对代码进行调试。
本书特点
相比市面上已有的同类书籍,本书具备如下特点:
整体共6000余行代码,汇编代码仅约100行,其余均为可读性好的C代码。
无论是操作系统还是应用程序,所有代码均可一键编译和调试。
尽可能采用简单易懂而非复杂的设计算法降低学习难度。
各章节内容紧密联系,循序渐进地展示了操作系统的实现过程。
内容安排
对于大多数初学者而言,操作系统设计是一项复杂且烦琐的工作。同时,由于操作系统各模块之间存在复杂的关联; 因此,要采用一种简单且易于理解的方式将其设计过程展示出来并非一件易事。
本书针对初学者的学习方式,对各章节的安排如下表所示。
章节
功 能 模 块
主 要 内 容
第1章设计目标无了解目标操作系统运行的硬件环境及设计目标
第2章配置开发环境无分别介绍如何在Windows和Linux系统中安装开发所需的各项工具,从而构建开发操作系统所需的环境
第3章启动操作系统无完成操作系统最基本的初始化和自加载工作,进入C环境运行,实现简单的日志打印接口
第4章内存管理内存管理实现位图算法用于管理物理内存的分配和释放,创建页表并启用虚拟内存
第5章异常管理
第6章实现多进程运行
第7章进程的同步与互斥
进程管理
创建系统中优质个进程first,构建就绪列表和延时列表,实现进程的有序运行以及进程睡眠等功能,实现互斥锁和信号量
第8章屏幕显示与键盘读取
第9章读写硬盘
第10章统一管理设备
设备管理
为键盘、屏幕以及硬盘设计驱动程序,抽象出一套简单易用、统一的接口用于对这些设备进行访问
第11章读写设备文件
第12章读写普通文件
第13章文件系统的实现
文件系统
将硬件设备视作设备文件,抽象出相应的文件访问接口; 支持对硬盘上FAT16分区中普通文件的读写; 实现文件系统模块,提供统一接口用于访问设备文件和普通文件
第14章从硬盘加载程序执行
内存管理
进程管理
将位于硬盘上的shell可执行程序加载到内存中执行,并为其创建进程地址空间
第15章实现系统调用
第16章支持内存分配和printf()打印
第17章实现命令行解释器
第18章进程的创建与退出
进程管理
系统调用
实现系统调用机制,使得shell能够请求操作系统完成某些工作; 引入C语言标准库,丰富shell可直接使用的功能接口; 实现fork()、execve()等系统调用接口
每个章节首先结合当前设计目标介绍设计原理; 之后再介绍如何用代码实现。在阅读本书时,请注意参考上表,以明确当前所读的内容在整个系统设计中所处的位置。
学习基础和方法
阅读本书的读者应当具备一定的编程基础,如熟练使用C语言、了解汇编语言编程(无须熟练)、对计算机组成原理和操作系统工作原理有初步的了解。能有Linux系统编程的经验。
在学习过程中,可能会遇到很多问题和挑战。为此,给出相关建议供参考:
不要仅停留于阅读本书,多参考书中内容动手实践。
对于与硬件设备相关的控制代码,可以先不求甚解,直接引用书中的代码。
阅读每章时,可先尝试自行思考如何设计,再与书中介绍的设计方法进行对比。
以完成系统设计为首要目标,代码优化和功能增强等在完成本书学习后再进行。
使用git等版本管理工具保存每一步的开发成果,以笔记形式记录学习过程。
由于篇幅有限,对于一些不太重要的代码,本书不会全部列出。请参考本书提供的配套源码进行理解。
在阅读完本书之后,强烈建议读者尝试在该操作系统上进行功能扩展或算法优化,甚至从头设计一个完整的操作系统。通过这种方式,你对相关内容的掌握将会变得更为深入。
参考资料
在操作系统的开发过程中,涉及很多软硬件方面的知识。本书仅给出部分较为核心的内容介绍。更为详细的介绍,可以从以下渠道获取:
维基网站给出了操作系统开发时所需要的硬件参考文档、示例代码及资料链接。请在阅读本书时,参考该网站的内容。
Intel 64 and IA32 Architectures Software Developers Manual: 该文档共3卷,由Intel公司编写。其中,第三卷介绍CPU系统编程相关的内容,与操作系统开发密切相关。
此外,本书还提供了各章节的配套代码、开发工具和相关文档。请读者扫描书后二维码下载并适时参考,从而获取更多有用信息。
作者2025年9月
李述铜毕业于四川大学,嵌入式系统工程师,长期从事嵌入式软件与底层系统开发工作,具备扎实的底层原理功底与丰富的实践经验。多年来致力于将复杂的技术讲解结构化、通俗化,创作了一系列深入浅出的技术视频与实战课程,如《从0手写嵌入式操作系统》《从0手写TCP/IP协议栈》等。累计学员超过5万人,收获业内广泛好评,帮助众多初学者和工程师建立系统性的嵌入式思维。
目录
第1章设计目标
1.1运行环境
1.2设计目标
1.2.1系统功能
1.2.2设计成果
1.2.3系统结构
1.3本章小结
第2章配置开发环境
2.1考虑因素
2.2所需工具
2.3安装开发工具
2.3.1为Windows安装开发工具
2.3.2为Linux安装开发工具
2.4配置VSCode
2.5整体测试
2.6本章小结
第3章启动操作系统
3.1设计目标
3.2加载操作系统到内存
3.2.1实模式
3.2.2启动流程
3.2.3最小的操作系统
3.2.4加载操作系统的其余部分
3.3检查系统内存大小
3.4进入保护模式
3.4.1保护模式
3.4.2分段机制
3.4.3段描述符
3.4.4段描述符表
3.4.5进入保护模式
3.5让内核打印启动信息
3.5.1解析可变参数
3.5.2格式化生成字符串
3.5.3打印输出
3.6本章小结
第4章内存管理
4.1管理物理内存
4.1.1选择内存区域
4.1.2定义位图结构
4.1.3实现相关接口
4.1.4开关中断保护
4.1.5初始化内存管理
4.2开启分页机制
4.2.1工作原理
4.2.2创建页表并启用
4.3页的分配与释放
4.3.1开启效果
4.3.2分配虚拟页
4.3.3释放虚拟页
4.4测试效果
4.4.1内存映射分析
4.4.2权限分析
4.5本章小结
第5章异常管理
5.1异常简介
5.1.1什么是异常
5.1.2处理流程
5.2捕获除0异常
5.2.1异常类型
5.2.2初始化异常系统
5.3解析异常栈帧信息
5.3.1压栈过程
5.3.2手动压栈
5.3.3解析压栈内容
5.4解析错误码
5.4.1选择子相关错误码
5.4.2页异常错误码
5.4.3运行效果
5.5本章小结
第6章实现多进程运行
6.1实现内核链表
6.1.1定义结点
6.1.2定义链表
6.1.3链表操作
6.2创建进程
6.2.1进程简介
6.2.2进程与TSS
6.2.3创建优质个进程
6.3实现进程切换
6.3.1进程切换效果
6.3.2sys_yield实现
6.3.3配置TSS
6.3.4深入分析切换过程
6.4让进程能够睡眠
6.4.1睡眠效果
6.4.2让进程进入睡眠
6.4.3创建空闲进程
6.4.4让进程从睡眠中唤醒
6.5进程调度算法
6.5.1问题现象
6.5.2时间片轮转
6.5.3调度算法的实现
6.5.4运行效果
6.6本章小结
第7章进程的同步与互斥
7.1互斥锁
7.1.1实现原理
7.1.2实现互斥锁
7.1.3应用示例
7.2信号量
7.2.1实现原理
7.2.2实现信号量
7.2.3应用示例
7.3本章小结
第8章屏幕显示与键盘读取
8.1屏幕显示
8.1.1显示原理
8.1.2驱动程序实现
8.2读取键盘
8.2.1实现原理
8.2.2驱动程序实现
8.3构造tty
8.3.1实现原理
8.3.2设备缓存
8.3.3驱动程序实现
8.3.4运行效果
8.4改进日志输出
8.5本章小结
第9章读写硬盘
9.1工作原理
9.1.1硬盘连接
9.1.2逻辑结构
9.1.3LBA寻址
9.1.4分区表
9.2驱动程序实现
9.2.1基本交互接口
9.2.2硬盘初始化
9.2.3中断配置
9.2.4读写硬盘
9.2.5其他接口
9.3运行效果
9.4互斥问题
9.5本章小结
第10章统一管理设备
10.1实现原理
10.1.1问题分析
10.1.2实现原理
10.2接口层实现
10.2.1定义设备结构
10.2.2实现转换层
10.2.3导入设备驱动
10.3运行效果
10.4修改日志输出
10.5本章小结
第11章读写设备文件
11.1实现原理
11.1.1设备命名
11.1.2转换实现
11.2设计实现
11.2.1定义文件结构
11.2.2接口实现
11.2.3效果分析
11.3运行效果
11.4本章小结
第12章读写普通文件
12.1基本概念
12.1.1文件
12.1.2文件系统
12.2FAT文件系统
12.2.1链式存储
12.2.2存储结构
12.3接口实现
12.3.1挂载文件系统
12.3.2遍历目录
12.3.3删除文件
12.3.4打开和关闭文件
12.3.5读取和写入文件
12.4多级目录
12.5本章小结
第13章文件系统的实现
13.1让进程管理自己的文件
13.1.1实现原理
13.1.2具体实现
13.2提供统一的文件访问接口
13.2.1实现原理
13.2.2具体实现
13.3运行效果
13.4本章小结
第14章从硬盘加载程序执行
14.1构建应用程序
14.1.1构建流程
14.1.2创建工程
14.1.3构建结果
14.2加载应用程序
14.2.1进程地址空间
14.2.2ELF文件格式
14.2.3加载过程实现
14.2.4运行效果
14.3向进程传递参数
14.3.1参数传递原理
14.3.2参数传递实现
14.3.3运行效果
14.4本章小结
第15章实现系统调用
15.1基本概念
15.1.1系统调用
15.1.2用户态与内核态
15.2特权级设置
15.2.1特权级划分
15.2.2特权级机制
15.2.3特权级配置
15.2.4保护机制
15.3实现原理
15.4添加msleep()
15.4.1构造系统调用函数
15.4.2执行Trap指令
15.4.3执行系统调用
15.4.4运行效果
15.5添加更多系统调用
15.5.1添加方法
15.5.2运行效果
15.6本章小结
第16章支持内存分配和printf()打印
16.1Newlib的底层依赖
16.1.1什么是C语言标准库
16.1.2桩函数
16.2支持动态内存分配
16.2.1基本原理
16.2.2实现sbrk()系统调用
16.2.3运行效果
16.3支持printf()打印
16.3.1基本原理
16.3.2具体实现
16.3.3运行效果
16.4本章小结
第17章实现命令行解释器
17.1shell的功能
17.2实现原理
17.3具体实现
17.3.1打开文件描述符
17.3.2读取命令行
17.3.3解析命令
17.3.4判断是否是内置命令
17.3.5执行内置命令
17.4运行效果
17.5本章小结
第18章进程的创建与退出
18.1创建测试工程
18.1.1创建工程结构
18.1.2编译结果分析
18.2利用fork()创建子进程
18.2.1fork()简介
18.2.2实现原理
18.2.3实现fork()系统调用
18.2.4实现getpid()系统调用
18.2.5运行效果
18.3利用execve()加载可执行程序
18.3.1execve简介
18.3.2实现原理
18.3.3实现execve()系统调用
18.3.4实现sys_execve()
18.3.5execve()执行流程
18.3.6运行效果
18.4进程退出
18.4.1相关系统调用
18.4.2实现_exit()系统调用
18.4.3实现wait()系统调用
18.4.4设置root_task
18.4.5运行效果
18.5支持加载程序运行
18.6进程异常退出
18.7多个shell同时运行
18.8本章小结