Chinaunix首页 | 论坛 | 博客
  • 博客访问: 103593103
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类: LINUX

2008-05-08 10:39:13

 

第五章


Linux内核体系结构



在本章这里,我们首先要介绍的是Linux內核的编制模式和体系结构,然后详细描述了Linux內核原始码目錄中组织形式和子目錄中各个代码档的主要功能,以及基本呼叫的层次关系等。接着就是直接走入正题,从內核原始档案Linux/目錄下的第一个档Makefile开始,对每一行代码进行详细注释說明。本章內容可以看作是对內核原始码的总结概述,也可以作为閱读后续章节的参考资讯。对於较难理解的地方可以先跳过,待閱读到后面相关內容时再返回来参考本章內容。在閱读本章之前请先复习或学习有关80X86保护模式执行方式工作原理。

一个完整可用的作业 充主要由4部分组成:硬体、作业系统內核、作业系统服务和用戶应用程式,图5-l所示。用戶应用程式是指那些字处理程式、Internet浏览器程式或用户自行编制的各种应用程式;作业系统服务程式是指那些向用戶提供的服务被看作是作业系统部分功能的程式。在Linux作业系统上,这些程式包括X视窗系统、shell命令解释系统以及那些內核程式设计介面等系统程式;作业系统內核程既是本篇文章所感兴趣的部分,它主要用於对硬件资源的抽象和存取调度。






在汇流排控制器控制下,8259A晶片可以处于程式设计状态和操作状态,程式设计伏态是CPU使用IN或OUT指令对82 59A晶片进行初始化程式设计的状态。一旦完成了初始化程式设计,晶片即进入操作状态,此时晶片即可随时回应外部装置提出的中断请求(IRQ0 - IRQ15),同时系统还可以使用操作命令字随时修改其中断处理方式。透过中断判优选,晶片将选中当前最高优先顺序的中断清求作为中断服务对象,並透过CPU接脚INT通知CPU外中断请求的到来,CPU回应后,晶片从资料汇流排D7-D0将程式设计设定的当前服务对象的中断号送出,CPU由此获取对应的中断向量值,並执行中断服务程式。


5.4.3 中断向量表

上节已指出CPU是根据中断号获取中断向量值,即对应中断服务程式的入口位址直。因此为了让CPU由中断号查找到对应得中断向量,就需要在记忆体中建立一张查询表,即中断向量表(在32位元保护模式下该表称为中断描述符表,见下面說明)。80X86微机支持256个中断,对应每个中断需要安排一个中断服务程式。在80X86真实模式执行方式下,每个中断向量由4个位元组组成。这4个立元组指明了一个中断服务程式的段值和段內偏移值。因此整个向量表的长度为1024位元组。当80X86微机啟动时,ROM BIOS中的程式会在实体记
忆体开冶位址0x0000 : 0x0000处初始化並设置中断向量表,而各中断的预设中断服务程式则在BIOS中给出。由於中断向量表中向量是按中断号顺序排列,因此给定一个中断号N,那麼它对应的中断向量在记忆体中的位置就是0x0000 : N*4,即对应的中断服务程式入口位保存在实体记忆体0x0000 : N*4位置处。

在BIOS执行初始化操作时,它设置了两个8259A晶片支援的16个硬体中断向量和BIOS提供的中断号为0x10 - 0xlf 得中断呼叫功能向量等。对於实际沒有使习的向量则填入临时的哑中断服务程式位址。以后在系统开机载入作业系统时会根据实际需要修改某些中断向量的值 列如,对於DOS作业系统,它会重新役置中断0x20 – 0x2f的中断向量值。而对於Linux系统,除了在刚开始载入內核时需要用到BIOS提供的显示和磁碟读取操作中断功能,在內核正常执行之前则会在setup.s程式中重新初始化8259 A晶片並且在head.s程式中重新设置一张中断向量表(中断描述符表)。完全拋弃BIOS所提供的中断服务功能。

当Intel CPU执行在32位元保护模式下时,需要使用中断描述符表IDT (Interrupt Descriptor Table)来管理中断或异常。IDT是Intel 8086- -80186CPU中使用的中断向量表的直接替代物。其作用也类似于中断向量表,只是其中每个中断描述符项中除了含有中断服务程式位址以外,还包含有关特权级和描述符类別等资讯。Linux作业系统工作於80X86的保护模式下,因此它使用中断描述符表安来设置和保存各中断的“向量”资讯。


5.4.4Linux內核的中断处理


於于Linux內核来說,中断信号通常分两类 :硬体中断和软件中断(異常)。每个中断是由 0-255 之间的一个数字来标识。对於中断int0- -int3l(0x00- -0xff),每个中断的功能由Intel公司固定设定或保留用,属於软体中断,但Intel公司称之为異常。因为这些中断是在CPU执行指令时探测到異常晴況而引起的。通常还可分为故障(Fault)和陷阱(traps)两类。中断
int32- -int255(0x20- -0xff)可以由用戶自己设定。所有中断的分类以及执行后CPU的动作方式见表5-1所示。


Linux內核的主要用途就是为了与电脑硬件进行交流,实现对硬件部件的程式设计控制和介面操作,调度对硬件资源的存取,並为电脑上的用戶程式提供一个高级的执行环境和对硬件的虚拟介面。

在本章內容中,我们是基于Linux 0.12版的內核原始码,简明地描述Linux內核的基本体系结构、主要构成模组。然后对原始码中出现的几个重要资料结构进行說明。最后描述了建构Linux 0.12 內核编译实验环境的方法。


5.1 Linix內核模式

目前,作业系统內核的结构模式主要可分为整体式的单內核模式和层次式的微內核模式。而本文章所注释的Linux 0.12內核,则是採用了单內核模式。单內核模式的主要优点是內核代码结构紧湊,执行速度快,不足之处主要是层次结构性不強。

在单內核模式的系统中,作业系统所提供服务的流程为:应用主程序使用指定的参数值执行系统呼叫指令(int x80),使CPU从用戶态(User Mode)切換到核心态(Kernel Model),然后作业系统根据具体的参数值呼叫特定的系统呼叫服务程式,而这些服务程式则根据需要再呼叫底层的一些支援函数以完成特定的功能。在完成了应用程式所要求的服务后,作业系统又使CPU从核心态切換回用戶态,从而返回到应用程式中继续执行后面的指令。因此概要地讲,单內核模式的內核也可粗略地分为三个层次:呼叫服务的主程序层、执行系统呼叫的服务层和支援系统呼叫的底层函数。见图5-2所示。






5.2 Linux系统体系结构

Linux內核主要由5个模组构成,它们分別是:行程调度模组、记忆体管理模组、档案系统模组、行程间通信模组和网路介面模组。

行程调度模组用来负责控制行程对CPU资源的使用。所採取的调度策略是各行程能夠公平合理地存取CPU,同时保证內核能及时地执行硬件操作,记忆体管理模组用於确保所有行程 能夠安全地共用机器主记忆体区,同时,记忆体管理模组还支援虛拟记忆体管理方式,使得Linux支援行程使用比实际记忆体空间更多的记忆体容量。並可以利用档案系统把暂时不用的记忆体资料块交換到外部储存装置上去,当需要时再交換回来。档案系统模组用於支援对外部装置的驱动和储存。虛拟档案系统模组透过向所有的外部储存装置提供一个通用的档介面,隐藏了各种硬件装置的不同细节。从而提供並支援与其他作业系统相容的多种档案系统格式。行程间通信模块子系统用於支援多种行程间的资讯交換方式。网路介面模块提供对多种网络通信标準的存取並支援许多网路硬体。

这几个模块之间的依赖关系见图5-3所示。其中的连線代表它们之间的依赖关系,虛線和虛框部分表示Linux 0.12中还未实现的部分(从Linux 0.95版才开始逐步实现虛拟档案系统,而网路介面的支援到0.96版才有)。






由图可以看出,所有的模组都与行程调度模组存在依赖关系。因为它们都需要依靠行程调度程式来挂起(暂停)或重新执行它们的行程。通常,一个模组会在等待硬件操作期间被挂起,而在操作完成后才可继续执行。例如,当一个行程试图将一资料块写到软碟上去时,软碟驱动程式就可能在啟动软碟旋转期问将该行程置为掛起等待状态,而在软碟进入到正常转速后再使得该行程能继续执行。另外3个模组也是由於类似的原因而与行程调度模组存在依赖关系。

其他几个模组的依赖关系有些不太明显,但同樣也很重要。行程调度子系统需要使用记忆体管理来调整一特定行程所使用的实体记忆体空间。行程间通信子系统则需要依靠记忆体管理器来支援共用记忆体通信机制。这种通信机制允许两个行程存取记忆体的同一个区域以进行行程间资讯的交換。虛拟档案系统也会使用网路介面来支援网路档案系统(NFS) ,同樣也能使用记忆体管理子系统提供记忆体虛拟碟(ramdisk)装置。而记忆体管理子系统也会使用档案系统来支援记忆体资料块的交換操作。

若从单內核模式结构模型出发,我们还可以根据Linux 0.12內核原始码的结构将內核主要模组绘制成图5-4所示的框图结构。






其中內核级中的几个方框,除了硬体控制方框以外,其他粗線方框分別对应內核原始码的目錄组织结构。

除了这些图中已经给出的依赖关系以外,所有这些模组还会依赖於內核中的通用资源。这些资源包括內核所有子系统都会呼叫的记忆体分配和收回函数、列印警告或出错非讯函数以及一些系统除错函数。


5.3 Linux內核对记忆体的管理和使用

本节首先说明Linux 0.12系统中比较直观的实体记忆体使用情況,然后结合Linux 0.12內核中的应用情況,再分別概要描述记忆体的分段和分页管理机制以及CPU多工操作和保护方式。最后我们再综合说明Linux 0.12系统中內核代码和资料以以及各个任务的代码和资料在虛拟位址、线性位址和实体位址之间的对应关系。


5.3.1实体记忆体

在linux0.12內核中,为了有效地使用机器中的实体记忆体,在系统初始化阶段记忆体被划分成几个功能区域,见图5-5所示







其中,Linux內核程式佔据在实体记忆体的开始部分,接下来是供硬碟或软碟等区块装置使用的高速缓冲区部分(其中要扣除显示卡记忆体和ROM BIOS所佔用的记忆体位址范围640K- -l MB) 。当一个行程子要读取区块装置中的数据时,系统会首先把数据读到高速缓冲区中;当有数据需要写到区块装置上去时,系统也是先将数据放到高速缓冲区中,然后由区 块装置驱动程式写到相应的装置上。记忆体的最后部分是供所有程式可以随时申请和使用的主记忆体区,內核程式在使用主记忆体区时,也同樣首先要向內核记忆体管理模组提出申请,並在申请成功后才能使用。对於含有RAM虛拟碟的系统,主记忆体区头部还要划去一部分,供虚拟盘存放数据。


由于电脑系统中所含的实际实体记忆体容有限,因此CPU中通常都提供了记忆体管理机制对系统中的记忆体进行有效的管理。在Intel 80386及以后的CPU中提供了两种记忆体管理(位址变換)系统:记忆体分段系统(Segmentation Systen)和分页系统(Paging System)。其中分页管理系统是可选择的,由系统程式师透过程式设计来确定是否採用。为了能有效地使用实体记忆体,Linux系统同寺採用了记忆体分段和分页管理机制。


5.3.2 记忆体位址空间概念

Linux0.12內核中,在进行位址映射操作时,我们需要首先分清3种位址以及它们之间的变換概念:a.程式(行程)的虚拟和逻辑位址:b.CPU的線性位址:C.实际实体记忆体地址。

虚拟位址(Virtual Address)是指由程式产生的由段选择符和段內偏移位址两个部分组成的位址。因为这两部分组成的位址並沒有直接用来存取实体记忆体,而是需要透过分段位址变換机制处理或映射后才对应到实体记忆体位址上,因此这种位址被称为虛拟位址。虛拟位址空间 GDT映射的全域位址空间和由LDT映射的区域位址空间组成。选择符的索引部分由13个Bit位表示,加上区分GDT和LDT的l个Bit位,因此Intel 80X86 CPU共可以索引16384个选择符。若每个段的长度都取最大值4G,则最大虚拟位址空间范围是16384*4G=64T。

逻辑位址(Logical Address)是指由程式产生的与段相关的偏移位址部分。在Intel保护模式下即是指程式执行代码段限长的偏移位址(假定代码段、资料段完全一樣)。应用程式师仅需与逻辑位址打交道,而分段和分页机制对他来說是完全透明的,仅由系统程式设计人员涉及 。不过有些资料並不区分逻辑位址和虛拟位址的概念,而是将它们统称为逻辑位址。

线性位址(Linear Address)是虛拟位址到体位址变換之间的中间层,是处理器可定址的记忆体空间(称为線性位址空间)中的位址。程式码会產生逻辑位址,或者說是段中的偏移位址,加上相应段的基底位址就生成了一个線性位址。如果啟用了分页机制,那麼線性位址可以再经变換以產生一个实体位址。若沒有啟用分页机制,那麼線性位址直接就是实体位址。Intel 80386的線性位址空间容良为4G。

实体位址(Physical Address)是指出现在CPU外部位址汇流排上的定址实体记体的位址信号,是位址变換的最终结果地址。如果啟用了分页机制,那么線性位址会使用页目录和页表中的项变換成实体位址。如果沒有啟用分员机制,那麼線性位址就直接成为实体位址了。

虛拟储存(或虛拟记忆体) (Virtual Memory)是指电脑呈现出要比实际拥有的记忆体大得多的记忆体量。因此它允许程式师编制並执行比实际系统拥有的记忆体大得多的程式。这使得许多大型专案也能夠在具有有限记忆体资源的系统上实现。一个很恰当的的比喻是:你不需要很长的轨道就可以让一列火车从上海开到北京。你只需要足夠长的铁轨(比如說3公里)就可以完成这个任务。採取的方法是把后面的铁轨立刻铺到火车的前面,只要你的操作足夠快並能满足要求,列车就能象在一条完整轨道上执行。这也就是虛拟记忆体管理需要完成的任务。在Linux 0.12內核中,给每个程式(行程)都划分了总容量为64MB的虛拟记忆体空间。因此程式的逻辑位址范围是Ox0000000到0x4000000。

如上所述,有时我们也把逻辑位址称为虛拟位址。因为逻辑位址与虛拟记忆体空间的概念类似,并且是与实际实体记忆体容量无关。


5.3.3 记忆体分段机制

在记忆体分段系统中,一个程式的逻辑位址透过分段机制自动地映射(变換)到中间层的4GB (2³²)线性位址空间中。程式每次对记忆体的引用都是对记忆体段中记忆体的参照引用。程 式引用一个记忆体位址时,透过把相对应的段基址加到程式师看得见的逻辑位址上就形成了一个对应的線性位址。此时若沒有啟用分页机制,则该線性位址被送到CPU的外部位址汇流排上,用於直接定址对应的实体记忆体。见图5-6所示。



图5-6 虚拟位址(逻辑位址)到实体位址的变换过程






CPU进行位址变换(映射)的主要目的是为了解決虛拟记忆体空间到实体记忆体空间的映射问题。虚拟记忆体空间的含义是指一种利用二级或外部储存空间,使程式能不受实际实体记忆体量限制而使用记忆体的一种方法。通常虛拟记忆体空间要比实际实体记忆体量大得多。


那麼虛拟储存管理是怎样实现的呢? 原理与上述列车运行的比喻类似。首先,当一个程式需要使用一块不存在的记忆体时(也即在记忆体页表项中已标出相应记忆体页面不在记忆体中),CPU就需要一种方法来得知这个情況。这是透过80386的页错误異常中断来实现的。当一个行程引用一个不存在页面中的记忆体位址时,就会触发CPU产生页出错異常中断,並把引起中断的線性位址放到CR2控制暂存器中。因此处理该中断的过程就可以知道发生页異常的确切位址,从而可以把行程要求的页面从二级储存空间(比如硬碟上)载入到实体记忆体中。如果此时实体记忆体已经全部佔用,那麼可以借助二级储存空间的一部分作为交換缓冲区(Swapper)把记忆体中暂时不使用的页面交換到二级缓冲区中,然后把要求的页面调入记忆体中。这也就是记忆体管理的缺页载入机制,在Linux 0.12內核中是在程式mm/memory.c中实现。

Intel CPU使用段(Segment)的概念来对程式进行定址。每个段定义了记忆体中的某个区域以及存取的优先顺序等资讯。假定大家知晓真实模式下记忆体定址原理,现在我们根据CPU真实模式和保护模式下定址方式的不同,用比较的方法来简单說明32位元保护模式执行机制下记忆体定址的主要特点。

在真实模式下,定址一个记忆体位址主要是使用段和偏移值,段值被存放在段寄存器中(例如ds) ,並且段的长度被固定为64KB。段內偏移位址存放在任意一个可用於定址的寄存器中(例如si)。因此,根据段寄存器和偏移寄存器中的值,就可以算出实际指向的记忆体位址,见图5-7(a)所示。

而在保护模式执行方式下,段寄存器中存放的不再是被定址段的基底位址,而是一个段描述符表(Segment Descriptor Table)中某一描述符项在表中的索引值。索引值指定的段描述符项中含有需要定址的记忆体段的基底位址、段的长度值和段的存取特权级別等信息。定址的记忆体位置是由该段描述符项中指定的段基底位址值与一个段內偏移值组合而成。段的长度可变,由描述符中的內容指定。可见,和真实模式下的定址相比,段寄存器值換成了段描述符表中相应段描述符的索引值以及段表选择位和特权级,称为段选择符(Segment Selector) ,但偏移值还是使用了原真实模式下的概念。这樣,在保护模式下定址一个记忆体位址就需要比真实模式下多一道手续,也即需要使用段描述符表。这是由於在保护模式下存取一个记忆体段需要的资讯比较多,而一个16位的段寄存器放不下这麼多內容。示意图见图5-7 (b)所示。注意,如果你不在一个段描述符中定义一个记忆体線性位址空间区域,那么该位址区域就完全不能被定址,CPU将拒絕存取该位址区域。







每个描述符佔用8个位元组,其中含有所描述段在線性位址空间中的起始位址(基址) ,段的长度、段的类型(例如代码段和资料段) 、段的特权级別和其他一些资讯。一个段可定义的最大长度是4GB。

保存描述符项的描述表有3种类型,每种用於不同目的。全域描述符表GDT(Global Descriptor Table)是主要的基本描述符表,该表可被所有程式用於参照引用存取一个记忆段。中断描述符表IDT(Interrupt Descriptor Table)保存有定义中断或異常处理过程的段描述符。IDT表直接替代了8086系统中的中断向量表。为了能在80X86保护模式下正常执行,我们必须为CPU定义一个GDT表和一个IDT表。最后一种类型的表是区域描述符表LDT(Local
Descriptor Table)。该表应用於多工系统中,通常每个任务使用一个LDT表。作为对GDT表的扩充,每个LDT表为对应任务提供了更多的可用描述符项,因而也为每个任务提供了可定位记忆体空间的范围。这些表可以保存在線性位址空间的任何地方。为了让CUP能定位GDT表、IDT表和当前的LDT表,需要为CPU分別设置GDTR、IDTR和LDTR三个特殊寄存器。这些寄存器中将储存对应表的32位元線性基底位址和表的限长位元组值。表限长值是表的长度值-1。

当CPU要定址一个段时,就会使用16位的段寄存器中的选择符来定位一个段描述符。在80X86 CPU中,段寄存器中的值右移3位即是描述符表中一个描述符的索引值。13位元的索引值最多可定位8192(0- -8191)个的描述符项。选择符中位元2 (TI)用来指定使用哪个表。若该位是0则选择符指定的是GDT表中的描述符,否则是LDT表中的描述符。

每个程式都可有若干个记忆体段组成。程式的逻辑位址(或称为虛拟位址)即是用於定址这些段和段中具体位址位置。在Linux 0.12中,程式逻辑位址到線性位址的变換过程使用了CPU的全域段描述符表GDT和区域段描述符表LDT 。由GDT映射的位址空间称为全域位址空间,由LDT映射的位址空间则称为区域位址空间,而这两者构成了虛拟位址的空问。具体的使用方式见图5-8所示。






图中画出了具有两个任务时情況。可以看出,每个任务的区域描述符表LDT本身也是由GDT中描述符定义的一个记忆体段,在该段中存放著对应任务的代码段和资料段描述符,因此LDT段很短,其段限长通常只要大於24位元组即可。同樣,每个任务的任务状态段TSS也是由GDT中描述符定义的一个记忆体段,其段限长也只要满足能够存放一个TSS资料结构就夠了。


对於中断描述符表idt,它保存在內核代码段中。由於在Linux 0.12內核中,內核和各任务的代码段和资料尽都分別被映射到線性位址空间中相同基址处,且段限长也一樣,因此內核的代码段和资料段是重疊的,各任务的代码段和资料段分別也是重疊的,参见图5-10 图5-11所示。任务状态段TSS(Task State Segment)用於在任务切換时CPU自动保存或恢复相关任务的当前执行上下文(CPU当前状态) 。例如对於切换出的任务,CPU就把其暂存器等资讯保存在该任务的TSS段中,同时CPU 目新切換进任务的TSS段中的资讯来设置各寄存器,以恢复该任务的执行环境,参见图4-37所示。在Linux 0.12中,每个任务的TSS段內容被保存 在该任务的任务资料结构中。另外,Linux 0.12內核中沒有使用到系统段描述符。



5.3.4 记忆体分页管理

若採用了分页机制,则此时線性位址只是一个中间结果,还需要使用分页机制进行变換,再最终映射到实际实体记忆体位址上。与分段机制类似,分页机制允许我们重新定向(变换) 每次记忆体引用,以适应我们的特殊要求。使用分页机制最普遍的场合是当然记忆体实际上被分成很多凌乱的区块时,它可以建立一个大而连续的记忆体空间映射,好让程式不用操心和管理这些分散的记忆体区块。分页机制增強了分段机制的效能。另外,页位址变換建立在段变換基础之上,任何分页机制的保护措施不会取代段变換的保护措施而只是进行更进一步的检查操作。

记忆体分页管理机制的基本原理是将CPU整个線性记忆体区域划分成4096位元组为1页的记忆体页面。程式申请使用记忆体时,系统就以记忆体页为单位进行分配。记忆体分页机制的实现方式与分段机制很相似,但並不如分段机制那麼完善。因为分页机制是在分段机制之上实现的,所以其结果是对系统记忆体具有非常灵活的控制权,并且在分段机制的记忆体保护上更增加了分页保护机制。为了在80X86保护模式下使用分页机制,需要把控制寄存器CR0的最高Bit位(位31)置位。

在使用这种记忆体分页管理方法时,每个执行中的行程(任务)可以使用此实际记忆体容量大得多的连续位址空间。为了在使用分页机制的条件下把線性位址映射到容量相对很小的实体记忆体空间上,80386使用了页目錄表和页表。页目錄表项与页表项格式基本相同,都佔用4个位元组,並且每个页目錄表或页表必须只能包含1024个页表项。因此一个页目錄表或一个页表分別共佔用l页记忆体。页目錄项和页表项的小区別在於页表项有个已写D (Dirty),而页目錄则没有。

線性位址到实体位址的变換过程见图5-9所示。图中控制寄存器CR3保存著是当前页目錄表在实体记忆体中的基底位址(因此CR3也被称为页目錄基底位址暂存器PDBR)o。32 元的線性位址被分成三个部分,分別用来在页目錄表和页表中定位对应的页目录项和页表项以及在对应的实体记忆体页面中指定页面內的偏移位置。因为l 个页表可有1024项,因此一个页表最多可以映射1024*4KB = 4MB记忆体; 因为一个页目錄表最多有1024项,对应1024个二级页表,因此一个页目錄表最多可以映射1024*4MB = 4GB容量的记忆体。即一个页目錄表就可以映射整个线性位址空间范围。

由於Linux 0.1x系统中内核和所有任务都共用同一个页目錄表,使得任何时刻处理器線性位址空间到实体位址空间的映射函数都一樣。因此为了让內核和所有任务都不互相重疊和干扰 ,它们都必须从虛拟位址空间映射到線性位址空间的不同位置,即佔用不同的线性位址空间范围。






对於Intel 80386系统,其CPU可以提供多达4G的線性位址空间。一个任务的虛拟位址需要首先透过其区域段描述符变換为CPU整个線性位址空间中的位址,然后再使用页目錄表PDT (一级页表)和页表PT (二级页表)映射到实际实体位址页上。为了使用实际实体记忆体,每个行程的线性位址透过二级记忆体页表动态地映射到主记忆体区域的不同实体记忆体页上。由於Linux 0.12中把每个行程最大可用虛拟记忆体空间定义为64MB,因此每个行程的逻辑位址透过加上(任务号)*64MB,即可转换为线性空间中的位址。不过在注释中,在不至於搞混的情況下我们有时将行程中的此类位址简单地称为逻辑位址或線性位址。

对於Linux 0.12系统,內核设置全域描述符表GDT中的段描述符项数最大为256,其中2项空间、2项系统使用,每个行程使用两项。因此,此时系统可以最多容纳(256-4)/2 = 126个 任务,並且虛拟位址范围是((256-4)/2)*64MB約等於8G。但0.12內核中人工定义最大任务数NR_TASKS = 64个,每个任务逻辑位址范围是64M,並且各个任务在线性位址空间中的起始位置是(任务号)*64MB。因此全部任务所使用的线性位址空间范围是64MB*64 = 4G,见图5-10所示。图中示出了当系统具有4个任务时的情況。內核代码段和资料段被映射到线性位址空间的开始16MB部分,並且代码和资料段都映射到同一个区域,完全互相重疊。而第1个任务(任务0)是由內核“人工”啟动执行的,其代码和资料包含在內核代码和资料中,因此该任务所佔用的线性位址空间范围比较特殊。任务0的代码段和资料段的长度是从線性位址0开始的640KB范围,其代码和资料段也完全重疊,并且与內核代码段和资料段有重疊的部分。实际上,Linux 0.12中所有任务的指令空间I (Instruction)和资料空间D (Data)都合用一块记忆体,即一个行程的所有代码、资料和堆疊部分都处於同一记忆体段中,也即是I&D不分离的一种使用方式。

任务1的線性位址空间范围也只有从64MB开始的640KB长度。它们之间的详细对应关系见后面說明。任务2和任务3分別被映射線性位址128MB和192MB开始的地方,並月它们的逻辑位址范围均是64MB 。由於4G位址空间范围正好是CPU的线性位址空间范围和可定址的最大实体位址空间范围,而且在把任务0和任务l的逻辑位址范围看作64MB时,系统中同时可有任务的逻辑位址范围总和也是4GB,因此在0.12內核中比较容易混淆三种位址概念。







如果也按照线性空间中任务的排列顺序排列虛拟空间中的任务,那麼我们可以有图5-11所示的系统I 同时可拥有所有任务在虛拟位址空间中的示意图,所佔用虛拟空间范围也是4GB (中沒有考虑內核代码和资料在虛拟空间中所佔用的范围。另外,在图中对於行程2和行程3还分別给出了各自逻辑空间中代码段和资料段(包括资料和堆栈内容)的位置示意图。








我们还需要注意的,是行程逻辑位址空间中代码段(Code Section)和资料段(DataSection)的概念与CPU分段机制中的代码段和资料段不是同一个概念。CPU分段机制中段的概念确定了在线性位址空间中一个段的用途以及被执行或存取的約束和限制,每个段可以设置在4GB线性位址空间中的任何地方,它们可以相互独立也可以完全重疊或部分重叠。而行程在其逻辑位址空间中的代码段和资料段则是指由编译器在编译程序和作业系统在载入程式时规定的在行程逻辑空间中顺序排列的代码区域、初始化和未初始化的资料区域以及堆栈区域。行程逻辑位址空间中代码段和资料段等结构形式见图所示。有关逻辑位址空间的說明请参阅记忆体管理一章內容。







5.3.5CPU多工和保护方式

Intel 80X86 CPU是分为4个保护级别,0级别具有最高优先顺序,而3级别优先顺序最低。在Linux 0.12作业系统里是使用了CPU的0和3两个保护级。內核代码本身会由系统中的所有任务共用。而每个任务则都有自己的代码和资料区,这两个区域保存於区域位址空间,因 次系统中的其他任务是看不见的(不能存取的) 。而內核代码和资料是由所有任务共用的,因此它保存在全域位址空间中。图5-13出示了这种结构的示意图。图中同心圆代表CPU的保护级別 (保护层) ,这裡仅使用了CPU的0级和3级。而径向射线则用来区分系统中的各个任务。每条径向射線指出了各任务的边界。除了每个任务虛拟位址空问的全域位址区域,任务1中的位址与任务2中相同位址处是无关的。

当一个任务(行程)执行系统呼叫而陷入內核代码中执行时,我们就称行程处於內核执行态(或简称为內核态) 。此时处理器处於特权级最高的(0级)內核代码中执行。当行程处於內核态时,执行的內核代码会使用当前行程的內核堆栈。每个行程都有自己的內核堆栈。当行程在执行用戶自己的代码时,则称其处於用戶执行态(用戶态) 即此时处理器在特权级最低的(3级)用戶代码中执行。当正在执行用戶程式突然被中断程式中断时,此时用戶程式也可以象征性地称为处於行程的內核态。因为中断处理程式将使用当前行程的內核堆栈。这与处於內核态的行程的状态有些类似。行程的內核态和用戶态将在后面有关行程执行状态一节中作更详细说明。






5.3.6 虛拟位址、线性位址和实体位址之间的关系

前面我们根据记忆体分段和分页机制详细說明了CPU的记忆体管理方式。现在我们以Linux 0.12 系统为例,详细說明內核代码和资料以及各任务的代码和资料在虛拟位址空间、线性位址空间和实体位址空间中的对应关系。由於任务0和任务l的生成或建立过程比较特殊,我们将对它们分別进行描述。


内核代码和资料的位址

对於Linux 0.12內核代码和资料来說,在head.s程式的初始化操作中已经把內核代码段和资料段都设置成为长度为16MB的段。在线性位址空间中这两个段的范围重疊,部是从线性位址0开始到位址0xFFFFFF共16MB地址范围。在该范围中含有內核所有的代码、內核段表(GDT、IDT、TSS) 、页目錄表和內核的二级页表、內核区域资料以及內核临时堆栈(将被用作第l个任务即任务0的用戶堆栈)。其页目录和二级页表已设置成把0- -16MB的线性位址空间一一对应到实体位址上,佔用了4个目录项,即4个二级页表。因此对於內核代码或资料的位址来說,我们可以直接把它们看作是实体记忆体中的位址。此时內核的虛拟位址空间、线性位址空间和实体位址空间三者之间的关系可用图5-14来表示。







因此,预设情況下Linux 0.12內核最多可管理16MB的实体记忆体,共有4096个实体页面(页框) ,每个页面4KB。透过上述分析可以看出:①內核代码段和资料段区域在線性位址空间和实体位址空间中是一樣的。这樣设置可以大大简化內核的初始化操作。②GDT和IDT在內核资料段中,因此它们的線性位址也同樣等於它们的实体位址。在真实模式下的setup.s程式初始化操作中,我们曾经设置过临时的GDT和IDT,这是进入保护模式之前必须设置的。由于这两个表当时处於实体记忆体大约0x90200处,而进入保护模式后內核系统模组处於实体记忆体。开始位置,并且0x90200处的空间将被挪作他用(用於高速缓冲),因此在进入保护模式后,在执行的第l个程式head.s中我们需要重新设置这两个表。即设置GDTR和IDT指向新的GDT和IDT,描述符也需要重新载入。但由於开啟分页机制时这两 个表的位置沒有变动,因此无须再重新建立或移动表位置。③除任务0以外,所有其他他任务所需要的实体记忆体页面与線性位址中的不同或部分不同,因此內核需要动态地在主记忆体区中为它们作映射操作,动态地建立页目錄项和页表项。虽然任务1的代码和资料也在內核中,但由於他需要另行分配获得记忆体,因此也需要自己的映射表项。


虽然Linux 0.12可管理16MB实体记忆体,但是系统中並不是一定要有这些实体记忆体。机器中只要有4MB(甚至2MB)实体记忆体就完全可以执行Linux 0.12系统了。若机器只有4MB实体记忆体,那麼此时內核4MB- -1 6MB位址范围就会映射到不存在的实体记忆体位址上。但这並不妨碍系统的执行。因为在初始化时內核记忆体管理程式会知道机器中所含实体记忆体量的确切大小,因而不会让CPU分页机制把線性位址页面映射到不存在的4MB- -16MB中去。內核中这樣的预设设置主要是为了便於系统实体记忆体的扩充,实际並不会用到不存在的实体记忆体区域。如果系统有多於16MB的实体记忆体,由於在init/main.c程式中初始化时限制了对16MB以上记忆体的使用,並且这裡內核也仅映射了0- -16MB的记忆体范围,因此在16MB之上的实体记忆体将不会用到。


透过在这裡为內核增加一些页表,並且对init/main.c程式稍作修改,我们可以对此限制进行扩充。任 在系统中有32MB实体记忆体的情況下,我们就需要为內核代码和资料段建立8个二级页表项来把32MB的線性位址范围映射到实体记忆体上。


任务0的位址对应关系

任务0是系统中一个人工啟动的第一个任务。它的代码段和资料段长度被设置为640KB。该任务的代码和资料直接包含在內核代码和资料中,是从線性位址0开始的640KB內容,因此可以它直接使用內核代码已经设置好的页目录和页表进行分页位址变換。同样它的代码和资料段在線性位址空间中也是重疊的。对应的仟务状态段TSS0也是手工预设置好的,並且位於任务0资料结构资讯中,参见include/linux/sched.h 第156行开始的资料。TSS0段位於內核sched.c程式的代码中,长度为104位元组,具体位置可参见图5-24中“任务0结构资讯”项所示。三个位址空中的映射对应关系见图5-15所示。








由於任务0直接被包含内核代码中,因此不需要为其再另外分配记忆体页。它执行时所需要的內核态堆栈和用戶态堆栈空间也都在內核代码区中,並且由於在內核初始化时(head.s )这些內核页面在页表项中的属性都已经被设置成了 0b111,即对应页面用戶可读写並且存在,因此用戶堆栈user_stack[ ]空间虽然在內核空间中,但任务0仍然能对其进行读写操作。


任务1的位址对应关系

与任务0类似,任务1也是一个特殊的任务。它的代码也在內核代码区域中。与任务0不同的是在線性位址空间中,系统在使用fork ( )建立任务l (init行程)时为存放任务1的二级页表而在主记忆体区申请了一页记忆体来存放,並复制了父行程(任务0)的页目錄和二级页表项。因此任务1有自己的页目錄和页表表项,它把任务1 佔用的线性空间范围64MB- -128MB(实际上是64MB- -64MB+640KB)也同样映射到了实体位址0- -640KB处。此时任务l的长度也是640KB,並且其代码段和资料段相重疊,只佔用一个页目錄项和一个二级页表。另外,系统还会为任务l 在主记忆体区域中申请一页记忆体用来存放它的任务资料结构和用作任务l 核堆栈空间。任务资料结构(也称行程控制块PCB)资讯中包括任务l的TSS段结构资讯。见图5-16所示。







任务l的用戶态堆栈空间将直接共用使用处於內核代码和资料区域(線性位址0- -640KB)中任务0的用户态堆栈空间user_stack[ ](参见kernel/sched.c第82- -87行) ,因此这个堆栈要在任务1实际使用之前保持“干淨”,以确保被复制用於任务l的堆栈不含有无用资料。在刚开始建立任务l时,任务0用戶态堆栈user_stack[ ]与任务1共用使用,但当任务l开始执行时,由於任务1映射到user stack[ ]处的页表项被设置成唯读,使得任务l在执行堆栈操作时将会引起写页面異常,从而由内核另行分配主记忆体区页面作为堆栈空间使用。


其他任务的位址对应关系

对於被建立的从任务2开始的其他任务,它们的父行程都是init(任务l)行程。我们已经知道,在Linux 0.1 2系统中共可以有64个行程同时存在,下面我们以任务2为例来說明其他任何任务对位址空间的使用情況。


从任务2开始,如果任务号以nr来表示,那麼任务nr在線性位址空间中的起始位置将被设定在nr*64M 莛。例如任务2的开始位置= nr*64MB = 2*64MB = 128MB。任务代码段和资料段的最大长度被设置为64MB,因此任务2佔有的線性位址空间范围是128MB- -192MB,共佔用64MB/4MB = 16个页目錄项。虛拟空间中任务代码段和资料段都被映射到線性位址空间相同的范围,因此它们也完全重疊。图5-17 显示出了任务2的代码段和资料段在三种位址空间中的对应关系。


在任务2被建立出来之后,将在其中执行execve( )函数来执行shell程式。当內核透过复制任务1刚建立任务2时,除了佔用線性位址空间范围不同外(128MB- -128MB+640KB),此时任务2的代码和资料在三种位址空间中的关系与任务1的类似。当任务2的代码(init ( ))呼叫execve( )系统呼叫开始载入並执行shell程式时,该系统呼叫会叫释放掉从任务l复制的页目錄和页表表项及相应记忆体页面,然后为新的执行程式shell重新设置相关页目錄和页表表项。图5-17给出的是任务2中开始执行程式时的情況,即任务2原先复制任务的代码和资料被shell程式的代码段和资料段替換后的情況。图中显示出已经映射了一页实体记忆体页面的情況。这十 ;注意,在执行execve()函数时,系统虽然在線性位址空间为任务2分配了64MB的空间范围,但是內核並不会立刻为其分配和映射实体记忆体页面。只有当任务2开始执行时由於发生缺页而引起異常时才会由记忆体管理程式为其在主记忆体区中分配並映射一页实体记忆体到其線性位址空间中。这种分配和映射实体记忆亿体页面的方法称为需求载入(Load ondemand)。参见记忆体管理一章中的相关描述。






从linux內核0.99版以后,对记忆体空间的使用方式发生了变化。每个行程可以单独享用整个4G的位址空间范围。如果我们能理解本节說描述的记忆体管理概念,那麼对於现在所使用的Lin 2.x內核中所使用的记忆体管理原理也能立刻明白。由於篇幅所限,这裡对此再說明。


5.3.7 用戶申请记忆体的动态分配

当用戶应用程式使用C函数库中的记忆体分配函数malloc( )申请记忆体时,这些动态申请的记忆体容量或大小均臣 层次的C程式库函数malloc( )来进行管理,內核本身並不会插手管理。因为内核已经为每个行程(除了任务0和1,它们与內核代码一起常驻记忆体中)在CPU的4G線性位址空间中分配了64MB的空间,所以只要行程执行时定址的范围生它的64MB范围內,內核也同樣会透过记忆体缺页管理机制自动为定址对应页面分配实体记忆体页面並进行映射操作。但是內核会为行程使用的代码和资料空间维护一个当前位置值brk,这个值
保存在每个行程的资料结构中。它指出了行程代码和资料(包括动态分配的资料空间)在行程位址空间中的末端位置 。当malloc( )函数为程式分配记忆体时,它会透过系统呼叫brk( )把程式要求新增的空间长度通知內核,內核代码从而可以根据malloc ( )所提供的资讯来更新brk 的值,但並此时並不为新申请的空间映射实体记忆体页面。只有当程式定址到某个不存在对应实体页面的位址时,內核才会进行相关实体记忆体页面的映射操作。



若行程代码定址的某个资料所在酽 面不存在,並且该页面所处位置属於行程堆范围,即不属於其执行档映射档蚤 抅记忆体范围中,那麼CPU就会產生一个缺页異常,並在異常处理程式中为 宅的页面分配並映射一页实体记忆体页面。至於用戶程式此次申请记忆体的位 徂长度数量和在对应实体页面中的具体位置,则均由C程式库中记忆体分配区 malloc()负责管理。內核以页面为单位分配和映射实体记忆体,该函数则具体 家用戶程式使用了一页记忆体的多少位元组‘剩余的容量将保留给程式再申请意体时使用。

当用戶使用记忆体释放函数free ( ) 动态释放已申请的记忆体区块时,C库中约记忆体管理函数就会把所释放的记忆体区块标记为空间,以备程式再次申请记忆体时使用。在这个过程中內核为该行程所分配的这个实体页面並不会被释放掉。只有当行程最终结束时內核才会全面收回已分配和映射到该行程位址空间范围的所有实体记忆体页面。

有关程式库函数malloc ( )和free ( ) 的具体代码实现请参见內核库中的lib/malloc.c程式。



5.4 中断机制

本节介绍中断机制基本原理和相关的可程式化控制器硬体逻辑以及Linux系统中使用中断的方法。有关可程式化控制器的具体程式设计方法请参见下一章setup.s程式后的說明。


5.4.1中断操作原理

微型电脑系统通常包括输入输出装置。处理器向这些装置提供服务的一种力法是使用轮询方式。在这种方法中处理器顺序地查询系统中的每个装置,“询问”它们是否需要服务。这种方法的优点是软体程式设计简单,但缺点是太耗处理器资源,影响系统效能。向装置提供服务的另一种方法是在装置需要服务时自己向处理器提出请求。处理器也只有在装置提出请求时才为其提供服务。

当装置向处理器提出服务请求时,处理器会在执行完当前的一条指令后立刻应答装置的请求,並转而执行该装置的相关服务程式。当服务程式执行完成后,处理器会接著去做刚才被中断的程式。这种处理方式就叫做中断(Interrupt)方法,而装置向处理器发出的服务请求则称为中断请求(IRQ - InterruptRequest) 。处理器回应请求而执行的装置相关程式则被称为中断服务常式或中断服务过程(ISR - Interrupt Service Routine)。

可程式化控制器(PIC - Programmable Interrupt Controller)是微机系统中管理装置中断请求的管理者。它透过连接到装置的中断请求接腳接受装置发出的终端服务请求信号。当装置啟动其中断请求IRQ信号时,PIC立刻会检测到。在同时收到几个装置的中断服务请求的情況下,PIC会对它们进行优先顺序比较並选出最高优先顺序的中断请求进行处理。如果此时处理器正在执行一个装置的中断服务过程,那麼PIC还需要把选出的中断请求与正在处理的中断请求的优先顺序进行比较,並基於该比较结果来确定是否向处理器发出一个中断信号。当PIC向处理器的INT接腳发出一个中断讯号时,处理器会立刻停下当时所做的事情並询问PIC需要执行哪个中断服务请求。PIC则透过向资料汇流排发送出与中断请求对应的中断号来告知处理器要哪个中断服务过程。处理器则根据读取的中断号透过查询中断向量表(或32位元保护模式下的中断描述符表)取得相关装置的中断向量(即中断服务程式的位址)並开始执行中断服务程式。当中断服务程式执行结束,处理器就继续执行中断信号打断的程式。

以上描述的是输入输出装置的中断服务处理过程。但是中断方法並非一定与硬体相关,它也可以用於软体中。透过使用int指令並使用其运算元指明中断号,就可以让处理器去执行相应的中断处理过程。PC/AT系列PC机共提供了对256个中断的支援,其中大部分都用於软体中断或異常,異常是处理器在处理过程中检测到错误而產生的中断操作。只有下面及的一些中断被用於装置上。


5.4.2 80X86 PC机的中断子系

在使用80X86组成的微机系统中採用8259A可程式化中断控制器晶片。每个8259A晶片可以管理8个中断源。透过多片串联方式(cascade) ,8259A能构成最多管理64个中断向量的系统。PC/AT系列兼容机中,使用了两片8259A晶片,共可管理15级中断向量。其级连示意图见图5-18所示。其中从晶片的INT接腳连接到主晶片的IR2接腳,即8259A从晶片发出的中断信号将作为8259A主晶片的IRQ2输入信号。主8259A晶片的埠基底位址是0x20,从晶片是0xA0 。 IRQ9接腳的作用与PC/XT的IRQ2相同,即PC/AT机利用硬件电路把使用IRQ2的装置的IRQ2接腳重新定向到了PIC的IRQ9接腳上,並利用BIOS中的软体把IRQ9的中断int 71 重新定向到了IRQ2的中断int 0x0A的中断处理过程。这樣一来可使得任何亿 IRQ2的PC/XT的8位设配卡在PC/AT机下面仍然能正常使用。做到了PC机系列的向下相容性。







在汇流排控制器控制下,8259A晶片可以处于程式设计状态和操作状态,程式设计伏态是CPU使用IN或OUT指令对82 59A晶片进行初始化程式设计的状态。一旦完成了初始化程式设计,晶片即进入操作状态,此时晶片即可随时回应外部装置提出的中断请求(IRQ0 - IRQ15),同时系统还可以使用操作命令字随时修改其中断处理方式。透过中断判优选,晶片将选中当前最高优先顺序的中断清求作为中断服务对象,並透过CPU接脚INT通知CPU外中断请求的到来,CPU回应后,晶片从资料汇流排D7-D0将程式设计设定的当前服务对象的中断号送出,CPU由此获取对应的中断向量值,並执行中断服务程式。


5.4.3 中断向量表

上节已指出CPU是根据中断号获取中断向量值,即对应中断服务程式的入口位址直。因此为了让CPU由中断号查找到对应得中断向量,就需要在记忆体中建立一张查询表,即中断向量表(在32位元保护模式下该表称为中断描述符表,见下面說明)。80X86微机支持256个中断,对应每个中断需要安排一个中断服务程式。在80X86真实模式执行方式下,每个中断向量由4个位元组组成。这4个立元组指明了一个中断服务程式的段值和段內偏移值。因此整个向量表的长度为1024位元组。当80X86微机啟动时,ROM BIOS中的程式会在实体记
忆体开冶位址0x0000 : 0x0000处初始化並设置中断向量表,而各中断的预设中断服务程式则在BIOS中给出。由於中断向量表中向量是按中断号顺序排列,因此给定一个中断号N,那麼它对应的中断向量在记忆体中的位置就是0x0000 : N*4,即对应的中断服务程式入口位保存在实体记忆体0x0000 : N*4位置处。

在BIOS执行初始化操作时,它设置了两个8259A晶片支援的16个硬体中断向量和BIOS提供的中断号为0x10 - 0xlf 得中断呼叫功能向量等。对於实际沒有使习的向量则填入临时的哑中断服务程式位址。以后在系统开机载入作业系统时会根据实际需要修改某些中断向量的值 列如,对於DOS作业系统,它会重新役置中断0x20 – 0x2f的中断向量值。而对於Linux系统,除了在刚开始载入內核时需要用到BIOS提供的显示和磁碟读取操作中断功能,在內核正常执行之前则会在setup.s程式中重新初始化8259 A晶片並且在head.s程式中重新设置一张中断向量表(中断描述符表)。完全拋弃BIOS所提供的中断服务功能。

当Intel CPU执行在32位元保护模式下时,需要使用中断描述符表IDT (Interrupt Descriptor Table)来管理中断或异常。IDT是Intel 8086- -80186CPU中使用的中断向量表的直接替代物。其作用也类似于中断向量表,只是其中每个中断描述符项中除了含有中断服务程式位址以外,还包含有关特权级和描述符类別等资讯。Linux作业系统工作於80X86的保护模式下,因此它使用中断描述符表安来设置和保存各中断的“向量”资讯。


5.4.4Linux內核的中断处理


於于Linux內核来說,中断信号通常分两类 :硬体中断和软件中断(異常)。每个中断是由 0-255 之间的一个数字来标识。对於中断int0- -int3l(0x00- -0xff),每个中断的功能由Intel公司固定设定或保留用,属於软体中断,但Intel公司称之为異常。因为这些中断是在CPU执行指令时探测到異常晴況而引起的。通常还可分为故障(Fault)和陷阱(traps)两类。中断
int32- -int255(0x20- -0xff)可以由用戶自己设定。所有中断的分类以及执行后CPU的动作方式见表5-1所示。







主linux系统中,则将int32- -int47(0x20- -0x2f)对应於8259A中断控制晶片发出的硬件中断请求信号IRQ0- -IRQ15 (见表5-2所示) ,並把程式程式设计发出的系统呼叫(system call)中断设置为int l28(0x80) 。系统呼叫中断是用戶程式使用作业系统资源的唯一介面。






在系统初始化时,內核在head.s程式中首先使用一个哑中断向量(中断描述符)对中断描述符表(Interrupt Descriptor Tabl IDT)中所有256个描述符进行了预设设置(boot/head.s,78)。这个哑中断向量指向一个预设的“无中断”处理过程(boot/head.s,150) 。当发生了一个中断而又沒有重新设置过该中断向量时就会显示资讯“未知中断(Unknown interrupt)”。这裡对所有256项都进行设置可以有效防止出现一般保护性错误(A general protection fault) (異常13)。否则的话,如果设置的IDT少於256项,那么在一个要求的中断所指定的描述符耳大於设置的最大描述符项时,CPU就回产生一个一般保护出错(異常13) 。另外,如果硬件出现问题而沒有把装置的向量放到资料汇流排上,此时CPU通常会从资料汇流排上读入全l (0xff)作为向量,因此会去读取IDT表中的第256项,因此也会造成一般保护出错。对於系统中需要使用的一些中断,內核会在其继续初始化的处理过程中(init/main.c) 重新设置这些中断的中断描述符项,让它门指向对应的实际处理过程。通常,硬件异常中断处理过程(int0 - -int 31)都在traps.c的初始化函数中进行了重新设置(kernel/traps.c,第185行),而系统呼叫中断 int l28则在调度程式初始化函数中进行了重新设置(kernel/sched.c,第417行) 。

另外,在设置中断描述符表IDT时Linux內使用了中断门和陷阱门两种描述符。它们之间的区別在於对标志寄存器EFLAGS中的中断允许标志IF的影响。由中断门描述符执行的中断会重定IF标志,因此可以避免其他中断干扰当前中断的处埋,随后的中断结束指令iret会从堆栈上恢复IF标志的原值;而透过陷阱门执行的中断则不会影响IF标志。请参阅第11章中对include/asm/system.h档的說明。



5.4.5 标志寄存器的中断标志

为了避免竞爭条件和中断对临界代码区的干扰,在Linux 0.12內核代码中许多地方使用了cli和sti指令。cli指令用来重定CPU标志寄存器中的中断标志,使得系统在执行cli指令后不会回应外部中断。sti指令用来设置标志寄存器中断的标志,以允许CPU能识別並响应外部设置发出的中断。当进入可能引起竞爭代码区时,內核中就会使用cli指令来关闭对外部中断的回应,而在执行完整竞爭代码区时內核就会执行sti指令以重新允许CPU回应外部中断。例如,在修改档超级区块(Super Block)的锁定标志和任务进入/退出等待佇列操作时都需要首先使用cli指令关闭CPU对外部中断的回应,在操作完成之后再使用sti指令开啟对外部中断的回应。如果不使用cli、sti指令对,即在需要修改一个档超级区块时不使用cli来关闭对外部中断回应,那麼在修改之前判断出该超级区块锁定标志沒有置位元而想设置这个标志时,若此时正好发生系统时钟中断而切换到其他任务去执行,並且碰巧其他任务也需要修改这个超级区块,那麼此时这个其他任务会先设置超级区块的锁定标志并且对超级区块进行修改操作。当系统又切換回原来的任务时,此时该任务不会再去判断锁定标志就会继续执行设置
超级区块的锁定标志,从而造成两个任务对临界代码区的同时多重操作,引起超级区块数据的不一致性,严重时会导致內系统崩溃。


To be continued........




出处:南方Linux
阅读(303) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~