这是本系列第三篇了,我们这次来谈谈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

为了后面学习操作系统的需要,从今天开始我要研究x86的汇编了。所以我决定开始总结并连载x86的汇编系列,这是第一篇——初探保护模式。

我假定读者接触过16位的汇编语言,并理解汇编语言的基本概念、熟悉8086处理器采用的“段寄存器 * 16 + 偏移地址”的寻址方法。

我们从80386处理器入手。首先,到了80386时代,CPU有了三种运行模式,即实模式、保护模式和虚拟8086模式。

实模式指的是8086CPU的运行模式,不过这是后来提出的概念,在8086时代只有当时的运行模式,自然也就没有“实模式”这么个提法。如果世界上只有一种性别的人,也就没有男人,女人这种名称了。8086的汇编中,我们对于实模式的各种机制应该算是比较了解了,其大致包括实模式1MB的线性地址空间、内存寻址方法、寄存器、端口读写以及中断处理方法等内容。

不过到了80386时代,引进了一种沿用至今的CPU运行机制——保护模式(Protected Mode)。保护模式有一些新的特色,用来增强多工和系统稳定度,比如内存保护,分页系统,以及硬件支持的虚拟内存等。大部分现今基于 x86的操作系统都在保护模式下运行,包括Linux、FreeBSD、以及 微软 Windows 2.0 和之后版本 [都指32位操作系统] 。

虚拟8086模式用于在保护模式下运行原来实模式下的16位程序,我们不关心。

事实上,现在的64位处理器,拥有三种基本模式(保护模式、实模式、系统管理模式)和一种扩展模式(IA-32e模式(又分兼容模式和64位模式)) 详见这里

我们先来研究保护模式,学校目前基本还处于只讲8086实模式的时代。至于现代CPU的模式……我们有精力再来研究吧。声明下,我不是在吐槽我们的大学教育,真的。

Read More

以前写过一篇《进程眼中的线性地址空间》,这是她的姊妹篇线程篇。而且和以前一样我们只谈32位Linux下的实现。另外读者可能还需要之前的一篇文章《Linux线程的前世今生》作为前期的辅助资料。

如果读者已经看过这两篇文章,那么我们就可以继续往下说了。

我简单列出上述文章中的几个要点:

  1. 32位操作系统下的每个进程拥有4GB的线性地址空间。

  2. 从Linux内核的角度来说,它并没有线程这个概念。在内核中,线程看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,比如地址空间)。

暂时有这两点就可以了。我们直接就能从第二点中看出来,一个进程创建的所有线程实际上是都是在它的线性地址空间里运行的。也就是说,一个进程所创建的所有线程没有创建新的地址空间,而是共享着进程所拥有的4G的线性空间罢了。除了地址空间还共享什么呢?大致还有文件系统资源、文件描述符、信号处理程序以及被阻断的信号等内容。不过即便是共享地址空间,但是每个线程还是有自己的私有数据的,比如线程的运行时栈。

Read More