博客很久都没有更斯了,因为一直在忙于一个小项目的开发。

事情的起因是这样的:因为今年开设了操作系统课程,但是纯粹的理论学习始终给我一种漂浮在云中的感觉。为了能在实践中深刻理解操作系统的运行机制和x86CPU以及硬件原理,我决定自己动手写一个操作系统内核的Demo程序。

当然,一开始没有相关的基础自然要找资料去学习。在翻阅了于渊的《Orange’s 一个操作系统的实现》和川合秀实先生的《30天自制操作系统》后感觉这两本书都不是很适合初学者学习。前者体系略乱且在一开始就陷入了硬件机制的漩涡,容易让初学者找不到北;后者不需要初学者有足够的基础,但是在硬件机制等内容上过于简略。只适合一般的爱好者去使用,而作为计算机专业的同学只能作为参考。(肆意诋毁大神作品,罪过罪过……)

后来在Google上搜索到了《JamesM’s kernel development tutorials》这篇文档后,我立即被作者合理有序的安排所吸引。我理想中的教程就是这样的,应该一步一步逐渐搭建起整个系统原型,由“发现问题——寻找机制——建立策略”的流程来处理。而不是一股脑的告诉我们所有的硬件机制,然后才是一般性的实现策略。

我主张的学习方法就是先学习一个新事物的基础框架和基本的模式结构,而旁枝末节的细节问题完全可以交给实践去慢慢掌握。暂时用不到的东西就不要告诉读者,完全可以用到了再慢慢补充。同时以任务和实践的方式驱动学习过程,既提升了理论学习的速度,又充满了实践的乐趣和成就感。

不过我在实践的过程中逐渐发现了这篇教程还是过于简陋,很多地方知其然而不知其所以然,并且部分的代码存在BUG。虽然代码能在当前的测试中通过,但会导致复杂化后其它的模块出现问题。另外这里调试也存在问题,若是能实现内核代码级别的调试功能就能极大的方便学习和开发。

Read More

我们这次接着内存分页继续说。稍微插一句,虽然本系列的名字叫做保护模式汇编,可是到现在颇有些挂羊头卖狗肉的意味。我们只是在一个劲的谈理论,就连仅有的一点代码也是用C语言描述的,而不是汇编。不过我觉得这不是关键,我觉得只要我们掌握了理论就好,至于用什么语言描述都是次要的,你说呢?

言归正传,我们开始说分页机制。长时间以来,随着计算机技术的发展,存储器的容量在不断的高速增加着。但是说起内存(这里指RAM,下同)这个东西,它有一个很奇葩的特性,就是无论它有多大,都总是不够用(P.S.厨房的垃圾桶也一样)。现在我们看似拥有着以前的程序员想都不敢想的“天文数字”的内存,动辄就是几G十几G的。但是相信我,历史总是嘲弄人的。就像当年程序员们质疑32位地址线带来的4GB空间太大没有意义似的,我们也会有一天抱怨现在的内存太小的。

那么,既然内存总是不够用的,那内存不够用了怎么办?还有,使用过程中出现的内存碎片怎么办?假设我们有4GB的物理内存,现在有1、2、3、4一共4个程序分别各占据连续的1G内存,然后2、4退出,此时我们拥有着空闲的两段内存,却连一个稍大于1GB的程序都无法载入了。

当然了,这只是一个例子。不过按照一般的思路,在内存释放之后,我们如何回收呢?做碎片整理吗?即便我们不在乎整理过程带来的效率损失,光是程序加载时候的地址逐一重定位就是及其麻烦的。那怎么办?当然了,解决的办法是有的,聪明的计算机工程师们想到了采用分页的方式来管理物理内存。他们在逻辑上把内存划分为定长的物理页,同时将一个程序执行时候的线性地址地址空间划分为逻辑页,在分页机制工作的前提下,给硬件提供一组数据结构来保存这种映射关系。也就是说,线性地址是连续的,但是其实际指向的物理地址就不见得是连续的了。别忘了,RAM是随机存储器,读取任意一个地址的理论时间都是一样的(暂时让我们忘了cache吧…)。我们让CPU在寻址的时候,自动的去查找线性地址到物理地址的映射关系,从而找到实际的数据就好。严格说地址翻译是由MMU组件来进行的,但是现在MMU一般都是CPU的一个组成部分了,所以我们也不严格区分了。

Read More

这是本系列第三篇了,我们这次来谈谈x86的段页式内存管理。这篇文章的定位是阐述分段分页的来历和要解决的问题。需要阐述细节的地方,我会贴出相关的文档和代码。

首先,如果我这个标题让你觉得段页式是一种方式而且密不可分的话,那我先说声抱歉了。其实分段和分页没什么必然联系。只不过Intel从8086开始,其制造的CPU就以段地址+偏移地址的方式来访问内存。后来要兼容以前的CPU,Intel不得不一直保留着这个传统。分段可以说是Intel的CPU一直保持着的一种机制,而分页只是保护模式下的一种内存管理策略。不过想开启分页机制,CPU就必须工作在保护模式,而工作在保护模式时候可以不开启分页。

关于保护模式的段机制我们在系列一里面已经谈过不少,而且我们也谈过“绕过”分段的平坦模式。那么,我们下文的重点就是谈谈在设置平坦模式的环境之后,进行内存分页管理的问题了。光说不练是假把式,这次我们就贴上来一些代码具体感受一下吧。

首先是设置全局段描述符表。我们给出全局段描述符表和全局描述符表寄存器的结构体定义:

注意结构体定义后面的那个 __attribute__((packed)) 很重要,这是GCC的扩展,用来设置该结构体不进行字节对齐。什么?你不知道什么是字节对齐?那么你先去谷歌一下再回来接着看吧。

Read More

如果你对中断是什么都不清楚的话,还是先Google一下中断的定义和基本概念吧,这里给出一个链接: http://zh.wikipedia.org/zh/%E4%B8%AD%E6%96%B7

好了,看完了这个链接,我想你已经大致明白了什么是中断,还有中断的作用了吧?我们再来总结下,其实简单说中断就是一种通知机制罢了。我们知道操作系统的一个核心任务就是和连接在主板上的所有的硬件设备进行通信,但是CPU和这些外设的速率根本就不在一个数量级上,倘若CPU向某一个设备发出一个请求并且一直等待反馈结果的话,这样带来的性能损失是不可接受的。而且CPU在运行期间需要得知外设所发生的事件,轮询显然是不可取的,那么就迫切需要一种机制来帮助我们解决这个问题。

肩负着这一伟大使命,中断应运而生。当中断发生时,典型的处理方式就是打断CPU目前正在做的事情,CPU会保留当前的执行现场,转移到该中断事先安排好的中断处理函数去执行,执行结束之后再回来恢复之前的执行现场去执行。

从物理学的角度看,中断其实就是一种电信号,一般由硬件设备生成并送入中断控制器统一协调(当然需要一个“协调机构”了,试想所有设备不区分轻重缓急的和CPU发送中断信号的恐怖场景…)。中断控制器就是个简单的电子芯片,其作用就是将汇集的多路中断管线,采用复用技术只通过一条中断线和CPU相连接。既然中断控制器这里只有一条线和CPU相链接,那么为了区分各个设备,中断自然就有编号了。

补充一下,其实CPU的中断管脚并非只有一根,其实是有NMI和INTR两个管脚,因为从严重性上来看,中断是分为两类的,首先NMI管脚触发的中断是需要无条件立即处理的,这种类型的中断是不会被阻塞和屏蔽的,所以叫做非屏蔽中断(Non Maskable Interrupt, NMI)。事实上一旦产生了NMI中断,就意味着CPU遇到了不可挽回的错误,一般不会进行处理,只是给出一个错误信息。而我们之前所说的中断控制器连接的管脚叫做INTR,这类中断有两个特点,分别是数量多和可屏蔽。而我们主要关注的正是INTR中断。

我举一个通俗的例子,假设你就是CPU,你正在看书(执行任务),突然间你的鼻涕流下来了(一个NMI中断),这个自然是不可以屏蔽的,不然会流到嘴里的…(好恶心),你现在把书反着扣在桌子上避免找不到页码(保留当前执行现场),取出纸巾…(此处省略几十个字),OK,你处理完后把书拿起来继续看(恢复之前的执行现场)。这就是一个中断的处理过程,其实很简单是不是?这是不可屏蔽中断,那么可屏蔽的呢?还是刚刚那个场景,你在看书,手机响了(一个INTR中断),但是你在学习期间不想被打扰,就无视它了…这就是可屏蔽中断了。

Read More