描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787111766445
编辑推荐
1)知名编译与系统专家海纳撰写,将沉淀20余年的Linux系统研习所得倾注书中
2)打通“古早”工具链障碍,让系统实现得以复现,各个模块环环相扣,让人欲罢不能
3)用不到10000行代码,从零实现“小而精”的Linux 0.12操作系统,涵盖基础知识、关键Linux内核模块以及文件系统
4)带你穿透层层迷雾,窥见繁复的Linux系统设计的绚丽“天光”
2)打通“古早”工具链障碍,让系统实现得以复现,各个模块环环相扣,让人欲罢不能
3)用不到10000行代码,从零实现“小而精”的Linux 0.12操作系统,涵盖基础知识、关键Linux内核模块以及文件系统
4)带你穿透层层迷雾,窥见繁复的Linux系统设计的绚丽“天光”
内容简介
本书由知名编译与操作系统专家海纳撰写,将沉淀20余年的Linux系统研究所得倾注书中。本书用不到1万行代码复现了Linux 0.12内核,打通了“古早”工具链障碍,模块实现环环相扣,带你穿透“迷雾”,窥见Linux系统设计的绚丽“天光”。
本书共8章,从逻辑上分为四部分。第一部分(第1和2章)是基础知识,介绍开发内核所需的基础知识,包括开发环境和调试环境的搭建,以及i386保护模式等。第二部分(第3~6章)是核心模块,逐步实现进程、中断、系统调用、内存管理、字符设备驱动和块设备驱动等模块。第三部分(第7章)是文件系统,涵盖Minix文件系统、管理inode、管理普通文件、管理目录、文件链接、执行程序等内容。第四部分(第8章)是系统服务接口,介绍一些重要的函数,这部分不是内核的主要部分,但为了让shell程序正确地运行,这些函数也是必须实现的。
本书共8章,从逻辑上分为四部分。第一部分(第1和2章)是基础知识,介绍开发内核所需的基础知识,包括开发环境和调试环境的搭建,以及i386保护模式等。第二部分(第3~6章)是核心模块,逐步实现进程、中断、系统调用、内存管理、字符设备驱动和块设备驱动等模块。第三部分(第7章)是文件系统,涵盖Minix文件系统、管理inode、管理普通文件、管理目录、文件链接、执行程序等内容。第四部分(第8章)是系统服务接口,介绍一些重要的函数,这部分不是内核的主要部分,但为了让shell程序正确地运行,这些函数也是必须实现的。
目 录
目录
前言
第 1 章 基础知识和环境准备 1
1.1 操作系统概述 1
1.1.1 功能和架构 1
1.1.2 操作系统的发展历史 3
1.2 配置环境 5
1.2.1 配置开发环境 5
1.2.2 配置运行环境 6
1.3 第一个内核程序 9
1.3.1 打印 Hello World 9
1.3.2 开机引导程序 11
1.4 汇编语言 12
1.4.1 内嵌汇编 12
1.4.2 链接器的工作原理 16
1.4.3 初识 makefile 18
1.5 小结 21
第 2 章 保护模式 22
2.1 进入保护模式前的准备 23
2.1.1 加载并执行 setup 23
2.1.2 获取硬件信息 27
2.2 内存管理 30
2.2.1 A20 地址线 31
2.2.2 全局描述符 31
2.2.3 页表的原理 34
2.2.4 进入保护模式 38
2.3 中断机制 46
2.3.1 中断描述符表 46
2.3.2 可编程中断控制器 48
2.3.3 特权级 50
2.4 时钟中断 52
2.4.1 初始化中断控制芯片 52
2.4.2 设置中断描述符表 53
2.4.3 重设全局描述符表 56
2.4.4 时钟中断实验 57
2.5 小结 59
第 3 章 进入内核 61
3.1 开启分页管理 61
3.1.1 设置页表 61
3.1.2 设置栈指针 66
3.2 实现格式化打印67
3.2.1 初始化终端和控制台 68
3.2.2 操作显示控制器 72
3.2.3 支持换行和回车 75
3.2.4 格式化打印结果并输出 81
3.3 设置内存和陷阱处理 92
3.3.1 初始化内存管理 92
3.3.2 初始化系统调用 94
3.3.3 处理 CPU 异常 98
3.4 小结 107
第 4 章 创建进程 108
4.1 创建 INIT 进程 108
4.1.1 创建进程控制块 108
4.1.2 切换特权级 113
4.2 创建第二个进程 118
4.2.1 准备进程控制块 119
4.2.2 复制进程控制块 120
4.2.3 实现进程切换 122
4.3 第一个系统调用:fork 129
4.3.1 系统调用和中断 129
4.3.2 定义 fork 函数 132
4.3.3 内存的写时复制 135
4.3.4 处理页保护中断 142
4.3.5 完善调度器 145
4.4 小结 149
第 5 章 支持字符设备150
5.1 支持键盘 150
5.1.1 键盘的工作原理 150
5.1.2 解析扫描码 154
5.1.3 构建缓冲区 156
5.1.4 完善屏幕回显功能 159
5.1.5 回显字符 165
5.2 优化输入功能 170
5.2.1 控制台和远程终端 170
5.2.2 系统调用 read 函数 178
5.3 优化输出功能 181
5.3.1 向标准输出写字符串 181
5.3.2 ANSI 转义序列 186
5.3.3 支持转义序列 188
5.4 修改终端状态 196
5.4.1 支持方向键 197
5.4.2 修改控制台标志位 198
5.5 小结 203
第 6 章 支持块设备 204
6.1 硬盘的基本结构 204
6.1.1 初始化硬盘信息 204
6.1.2 硬盘分区表 208
6.1.3 硬盘控制器编程 214
6.1.4 设置硬盘中断 217
6.2 管理缓冲区 221
6.2.1 初始化缓冲区 221
6.2.2 申请缓冲区 224
6.2.3 缓冲区相关的读写操作 230
6.3 硬盘驱动 237
6.3.1 读写硬盘 237
6.3.2 读硬盘分区表 241
6.4 软盘驱动 243
6.4.1 软盘的工作原理 243
6.4.2 实现定时器 246
6.4.3 DMA 读写 249
6.4.4 软盘控制器 253
6.4.5 实现软盘操作函数 263
6.5 小结 268
第 7 章 文件系统 269
7.1 Minix 文件系统 269
7.1.1 Minix 文件系统的基本
结构 270
7.1.2 定义超级块 270
7.1.3 初始化超级块 272
7.2 管理 inode 275
7.2.1 文件的元信息 275
7.2.2 管理位图 284
7.2.3 删除文件 289
7.2.4 进程支持文件操作 291
7.2.5 目录结构 295
7.3 管理普通文件 298
7.3.1 根据路径查找 inode 298
7.3.2 打开文件 305
7.3.3 文件的读操作 309
7.3.4 读写普通文件 312
7.4 管理目录 316
7.4.1 创建目录和文件 316
7.4.2 删除目录:rmdir 321
7.4.3 新建文件 325
7.5 文件链接 328
7.5.1 创建硬链接 328
7.5.2 创建软链接 330
7.5.3 通过链接访问文件 333
7.5.4 删除文件链接 334
7.6 执行程序 336
7.6.1 a.out 格式 337
7.6.2 执行程序:execve 340
7.6.3 缺页中断 347
7.6.4 实验:运行第一个可执行
程序 353
7.7 小结 356
第 8 章 系统服务接口358
8.1 POSIX 接口 358
8.1.1 时间管理 359
8.1.2 挂载文件系统 362
8.2 管理进程 364
8.2.1 信号处理 365
8.2.2 管道通信 371
8.2.3 进程同步 378
8.2.4 进程退出 380
8.3 构建 C 语言库 383
8.4 小结 386
附录 显示模式 387
前言
第 1 章 基础知识和环境准备 1
1.1 操作系统概述 1
1.1.1 功能和架构 1
1.1.2 操作系统的发展历史 3
1.2 配置环境 5
1.2.1 配置开发环境 5
1.2.2 配置运行环境 6
1.3 第一个内核程序 9
1.3.1 打印 Hello World 9
1.3.2 开机引导程序 11
1.4 汇编语言 12
1.4.1 内嵌汇编 12
1.4.2 链接器的工作原理 16
1.4.3 初识 makefile 18
1.5 小结 21
第 2 章 保护模式 22
2.1 进入保护模式前的准备 23
2.1.1 加载并执行 setup 23
2.1.2 获取硬件信息 27
2.2 内存管理 30
2.2.1 A20 地址线 31
2.2.2 全局描述符 31
2.2.3 页表的原理 34
2.2.4 进入保护模式 38
2.3 中断机制 46
2.3.1 中断描述符表 46
2.3.2 可编程中断控制器 48
2.3.3 特权级 50
2.4 时钟中断 52
2.4.1 初始化中断控制芯片 52
2.4.2 设置中断描述符表 53
2.4.3 重设全局描述符表 56
2.4.4 时钟中断实验 57
2.5 小结 59
第 3 章 进入内核 61
3.1 开启分页管理 61
3.1.1 设置页表 61
3.1.2 设置栈指针 66
3.2 实现格式化打印67
3.2.1 初始化终端和控制台 68
3.2.2 操作显示控制器 72
3.2.3 支持换行和回车 75
3.2.4 格式化打印结果并输出 81
3.3 设置内存和陷阱处理 92
3.3.1 初始化内存管理 92
3.3.2 初始化系统调用 94
3.3.3 处理 CPU 异常 98
3.4 小结 107
第 4 章 创建进程 108
4.1 创建 INIT 进程 108
4.1.1 创建进程控制块 108
4.1.2 切换特权级 113
4.2 创建第二个进程 118
4.2.1 准备进程控制块 119
4.2.2 复制进程控制块 120
4.2.3 实现进程切换 122
4.3 第一个系统调用:fork 129
4.3.1 系统调用和中断 129
4.3.2 定义 fork 函数 132
4.3.3 内存的写时复制 135
4.3.4 处理页保护中断 142
4.3.5 完善调度器 145
4.4 小结 149
第 5 章 支持字符设备150
5.1 支持键盘 150
5.1.1 键盘的工作原理 150
5.1.2 解析扫描码 154
5.1.3 构建缓冲区 156
5.1.4 完善屏幕回显功能 159
5.1.5 回显字符 165
5.2 优化输入功能 170
5.2.1 控制台和远程终端 170
5.2.2 系统调用 read 函数 178
5.3 优化输出功能 181
5.3.1 向标准输出写字符串 181
5.3.2 ANSI 转义序列 186
5.3.3 支持转义序列 188
5.4 修改终端状态 196
5.4.1 支持方向键 197
5.4.2 修改控制台标志位 198
5.5 小结 203
第 6 章 支持块设备 204
6.1 硬盘的基本结构 204
6.1.1 初始化硬盘信息 204
6.1.2 硬盘分区表 208
6.1.3 硬盘控制器编程 214
6.1.4 设置硬盘中断 217
6.2 管理缓冲区 221
6.2.1 初始化缓冲区 221
6.2.2 申请缓冲区 224
6.2.3 缓冲区相关的读写操作 230
6.3 硬盘驱动 237
6.3.1 读写硬盘 237
6.3.2 读硬盘分区表 241
6.4 软盘驱动 243
6.4.1 软盘的工作原理 243
6.4.2 实现定时器 246
6.4.3 DMA 读写 249
6.4.4 软盘控制器 253
6.4.5 实现软盘操作函数 263
6.5 小结 268
第 7 章 文件系统 269
7.1 Minix 文件系统 269
7.1.1 Minix 文件系统的基本
结构 270
7.1.2 定义超级块 270
7.1.3 初始化超级块 272
7.2 管理 inode 275
7.2.1 文件的元信息 275
7.2.2 管理位图 284
7.2.3 删除文件 289
7.2.4 进程支持文件操作 291
7.2.5 目录结构 295
7.3 管理普通文件 298
7.3.1 根据路径查找 inode 298
7.3.2 打开文件 305
7.3.3 文件的读操作 309
7.3.4 读写普通文件 312
7.4 管理目录 316
7.4.1 创建目录和文件 316
7.4.2 删除目录:rmdir 321
7.4.3 新建文件 325
7.5 文件链接 328
7.5.1 创建硬链接 328
7.5.2 创建软链接 330
7.5.3 通过链接访问文件 333
7.5.4 删除文件链接 334
7.6 执行程序 336
7.6.1 a.out 格式 337
7.6.2 执行程序:execve 340
7.6.3 缺页中断 347
7.6.4 实验:运行第一个可执行
程序 353
7.7 小结 356
第 8 章 系统服务接口358
8.1 POSIX 接口 358
8.1.1 时间管理 359
8.1.2 挂载文件系统 362
8.2 管理进程 364
8.2.1 信号处理 365
8.2.2 管道通信 371
8.2.3 进程同步 378
8.2.4 进程退出 380
8.3 构建 C 语言库 383
8.4 小结 386
附录 显示模式 387
前 言
前言
为何要写作本书
作为当今世界上最成功的开源项目之一,Linux 内核源码具有巨大的学习价值。学习
内核不仅仅能提升编码水平、架构能力,更能全面地了解硬件接口和计算机原理。但是随
着 Linux 内核的功能越来越强大,内核变得越来越复杂,代码量也在急剧地膨胀。这就给
刚入门的新手带来了巨大的学习困难。
学习一个复杂的系统有两种常用的方式。
一种是直接扎进系统源码中,按逻辑顺序逐个分析各个模块。但复杂系统的特点是模
块之间的关联非常强,相关的数据结构非常庞大,调试环境的搭建也往往比较烦琐。这就
使得很多人在开始阶段就困难重重,要想坚持下去,往往需要很大的定力。
另一种是从零开始将系统从简单到复杂地实现一遍。这种学习方式的优点非常明显,
入门阶段比较容易,每次新增几十行甚至几行代码就可以实现一个新的功能,整个学习过
程是循序渐进的。但对 Linux 系统来说,从零开始写一遍并不容易。这是因为 Linux 诞生
于 1991 年,当时 Linus Torvalds 所使用的很多构建工具现在已经很难找到了,例如 ld86
等编译工具,有些工具即使找到了也很难运行在现代的操作系统上。
为了解决这个问题,我编写了本书。本书的目标是使用现代的操作系统(例如 Ubuntu
20)和编译器(例如 GCC7 或者 GCC9)从零开始实现 Linux 0.12 的内核代码。在这个
过程中,我改写了很多不适用于现代编译器的代码,并且通过链接选项或者链接脚本重现
了早期的文件系统和可执行文件格式,让操作系统可以运行在现代的 Bochs 或者 QEMU
等仿真软件上。
IV
读者对象
本书面向的人群主要是对操作系统感兴趣的人,尤其是那些想深入学习 Linux 内核但
又不知道该如何入门的人。对于计算机专业的学生而言,本书则是一本非常好的实验指导
书,通过一步步地跟随本书实现全部的功能,不仅可以深入地掌握操作系统的基本功能,
还可以加深对计算机接口的了解。
如何阅读本书
本书共 8 章,从逻辑上分为四部分。
第一部分(第 1 和 2 章)是基础知识,介绍开发内核所需的基础知识,包括开发环境
和调试环境的搭建,以及 i386 保护模式等。
第二部分(第 3~6 章)是核心模块,逐步实现进程、中断、系统调用、内存管理、字
符设备驱动和块设备驱动等模块。这一部分涉及的也是传统操作系统内核最主要的模块,
这些模块之间相互依赖,彼此联系,实现起来难度很大。在写这一部分的时候,我花了很
多心思进行构思,自始至终坚持每次只实现一个小功能,这样阅读的难度就不大了。
第三部分(第 7 章)是文件系统。Linux 0.12 沿用了 Minix 这一早期的文件系统,早
期的文件系统所支持的文件大小、磁盘大小都不算大,数据结构相对比较简单。这一部分
篇幅虽然不小,但是难度并不大,因为这一部分几乎不涉及硬件操作,相比前面的章节,
文件系统实现起来没有那么烦琐。Linux 0.12 的文件系统虽然相对简单,但是它的超级块、
inode 等设计却被一直沿用下来,文件系统中的 mount、unmount 等系统调用,以及将
管道、字符设备都抽象成文件的设计也一直沿用至今。现代 Linux 的 VFS(Virtual File
System,虚拟文件系统)的设计哲学是 “一切皆文件”,这种设计思想在早期的文件系统中
已经初见端倪。相比现代文件系统的完备和庞大,学习早期的文件系统无疑要简单很多。
在文件系统中还有一个非常重要的话题,那就是如何加载运行一个可执行程序。在现
代操作系统中,可执行程序的文件格式是 ELF,但在 Linux 1.0 版本之前,则主要使用
a.out 格式。a.out 格式是一种古老但非常简单的二进制文件格式,它只有代码段和数据
段,而代码会被加载到进程空间的 0 地址处,所以它的加载过程非常简单。ELF 文件是
由专门的加载器(也称为 loader,在 Linux 上主要就是 ld.so)加载运行的,而 a.out 则
由内核直接实现。鉴于此,我们采用 Minix 文件系统和 a.out 格式。
在这三个主要部分之外,本书第四部分(第 8 章)实现了一些重要的系统服务接口,
例如用于管理系统时间的一大类函数,用于管理输入/输出的 ioctl 等,这一部分的篇幅
V
不算大,不是内核的主要部分,但为了让 shell 程序正确地运行,这些函数也是必须要实
现的。
本书是一本非常偏重实践的书,实践性远强于理论性。跟随本书可以一步步地实现一
个完整的操作系统,学习曲线非常平缓,这对新手非常友好。但这样的安排也会带来一个
问题:本书不适宜段落式地阅读和学习,因为后面的很多功能都依赖于前面的实现。当
然,如果你完全掌握了本书的内容,再回头阅读本书的代码,完全可以根据自己的喜好重
新安排进程,那时就不必遵循本书的章节顺序了。等到那个时候,读者也完全有能力沿着
Linux 的主线代码继续深入学习,如进一步支持图形界面、网络等功能。
勘误与支持
由于水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读
者批评指正。如果读者有更多的宝贵意见,可以通过邮箱 [email protected] 联系我,期待得
到读者的反馈,让
为何要写作本书
作为当今世界上最成功的开源项目之一,Linux 内核源码具有巨大的学习价值。学习
内核不仅仅能提升编码水平、架构能力,更能全面地了解硬件接口和计算机原理。但是随
着 Linux 内核的功能越来越强大,内核变得越来越复杂,代码量也在急剧地膨胀。这就给
刚入门的新手带来了巨大的学习困难。
学习一个复杂的系统有两种常用的方式。
一种是直接扎进系统源码中,按逻辑顺序逐个分析各个模块。但复杂系统的特点是模
块之间的关联非常强,相关的数据结构非常庞大,调试环境的搭建也往往比较烦琐。这就
使得很多人在开始阶段就困难重重,要想坚持下去,往往需要很大的定力。
另一种是从零开始将系统从简单到复杂地实现一遍。这种学习方式的优点非常明显,
入门阶段比较容易,每次新增几十行甚至几行代码就可以实现一个新的功能,整个学习过
程是循序渐进的。但对 Linux 系统来说,从零开始写一遍并不容易。这是因为 Linux 诞生
于 1991 年,当时 Linus Torvalds 所使用的很多构建工具现在已经很难找到了,例如 ld86
等编译工具,有些工具即使找到了也很难运行在现代的操作系统上。
为了解决这个问题,我编写了本书。本书的目标是使用现代的操作系统(例如 Ubuntu
20)和编译器(例如 GCC7 或者 GCC9)从零开始实现 Linux 0.12 的内核代码。在这个
过程中,我改写了很多不适用于现代编译器的代码,并且通过链接选项或者链接脚本重现
了早期的文件系统和可执行文件格式,让操作系统可以运行在现代的 Bochs 或者 QEMU
等仿真软件上。
IV
读者对象
本书面向的人群主要是对操作系统感兴趣的人,尤其是那些想深入学习 Linux 内核但
又不知道该如何入门的人。对于计算机专业的学生而言,本书则是一本非常好的实验指导
书,通过一步步地跟随本书实现全部的功能,不仅可以深入地掌握操作系统的基本功能,
还可以加深对计算机接口的了解。
如何阅读本书
本书共 8 章,从逻辑上分为四部分。
第一部分(第 1 和 2 章)是基础知识,介绍开发内核所需的基础知识,包括开发环境
和调试环境的搭建,以及 i386 保护模式等。
第二部分(第 3~6 章)是核心模块,逐步实现进程、中断、系统调用、内存管理、字
符设备驱动和块设备驱动等模块。这一部分涉及的也是传统操作系统内核最主要的模块,
这些模块之间相互依赖,彼此联系,实现起来难度很大。在写这一部分的时候,我花了很
多心思进行构思,自始至终坚持每次只实现一个小功能,这样阅读的难度就不大了。
第三部分(第 7 章)是文件系统。Linux 0.12 沿用了 Minix 这一早期的文件系统,早
期的文件系统所支持的文件大小、磁盘大小都不算大,数据结构相对比较简单。这一部分
篇幅虽然不小,但是难度并不大,因为这一部分几乎不涉及硬件操作,相比前面的章节,
文件系统实现起来没有那么烦琐。Linux 0.12 的文件系统虽然相对简单,但是它的超级块、
inode 等设计却被一直沿用下来,文件系统中的 mount、unmount 等系统调用,以及将
管道、字符设备都抽象成文件的设计也一直沿用至今。现代 Linux 的 VFS(Virtual File
System,虚拟文件系统)的设计哲学是 “一切皆文件”,这种设计思想在早期的文件系统中
已经初见端倪。相比现代文件系统的完备和庞大,学习早期的文件系统无疑要简单很多。
在文件系统中还有一个非常重要的话题,那就是如何加载运行一个可执行程序。在现
代操作系统中,可执行程序的文件格式是 ELF,但在 Linux 1.0 版本之前,则主要使用
a.out 格式。a.out 格式是一种古老但非常简单的二进制文件格式,它只有代码段和数据
段,而代码会被加载到进程空间的 0 地址处,所以它的加载过程非常简单。ELF 文件是
由专门的加载器(也称为 loader,在 Linux 上主要就是 ld.so)加载运行的,而 a.out 则
由内核直接实现。鉴于此,我们采用 Minix 文件系统和 a.out 格式。
在这三个主要部分之外,本书第四部分(第 8 章)实现了一些重要的系统服务接口,
例如用于管理系统时间的一大类函数,用于管理输入/输出的 ioctl 等,这一部分的篇幅
V
不算大,不是内核的主要部分,但为了让 shell 程序正确地运行,这些函数也是必须要实
现的。
本书是一本非常偏重实践的书,实践性远强于理论性。跟随本书可以一步步地实现一
个完整的操作系统,学习曲线非常平缓,这对新手非常友好。但这样的安排也会带来一个
问题:本书不适宜段落式地阅读和学习,因为后面的很多功能都依赖于前面的实现。当
然,如果你完全掌握了本书的内容,再回头阅读本书的代码,完全可以根据自己的喜好重
新安排进程,那时就不必遵循本书的章节顺序了。等到那个时候,读者也完全有能力沿着
Linux 的主线代码继续深入学习,如进一步支持图形界面、网络等功能。
勘误与支持
由于水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读
者批评指正。如果读者有更多的宝贵意见,可以通过邮箱 [email protected] 联系我,期待得
到读者的反馈,让
评论
还没有评论。