读书笔记:LINUX内核完全剖析:基于0.12内核

读书笔记:LINUX内核完全剖析
  IBM PC及其兼容机主要使用 独立编址方式,采用独立的I/O地址空间对控制设备中的寄存器进行寻址和访问,IBM PC也部分地使用统一编址。对于使用EISA、PCI等总线结构的PC,有64KB的I/O地址空间可供使用。在普通Linux系统下通过查看/proc/ioports文件可以得到相关控制器或设置使用的I/O地址(cat /proc/ioports)。

基本输入/输出程序BIOS
1981年IBM PC刚推出时系统只带有640KB的RAM主存储器,由于8088/8086CPU只有20根地址线,因此内存寻址范围最高为1024KB(1MB)。现在CPU的物理内存寻址范围已经高达4GB(甚至更高)。为了与原来的PC在软件上兼容, 系统1MB以下物理内存使用分配上仍然保持与原来PC基本一致。只是原来系统ROM中的基本输入输出程序BIOS一直处于CPU能寻址的内存最高端位置处,现在BIOS原来所在的位置将在计算机开机初始化时被用作BIOS的影子区域,即BIOS代码仍然会被复制到这个区域(参考BIOS研发技术剖析)。除了地址从0xA0000-0xFFFFF(640KB-1MB)和0xFFFE0000-0xFFFFFFFF(4GB最后64KB)范围以外的所有内存都可以用做系统内存。这两个特定范围被用于I/O设备和BIOS程序。

当计算机系统上电后,CPU会自动把 代码段寄存器CS设置为0xF000,其 段基地址被设置为0xFFFF0000,段长度设置为64KB(详细解释可见protected mode software architecture 第70页)。而IP被设置为0xFFF0,因此CPU代码指针指向0xFFFFFFF0处(4GB空间最后16B)。BIOS会在这里存放一条跳转指令JMP,跳转到BIOS代码中64KB范围内某一条指令执行。由于目前PC/AT中BIOS容量大多有1MB-2MB,为了执行BIOS中超过64KB的其他BIOS代码或数据。BIOS程序会首先使用32位访问方式把数据段寄存器的访问范围设置成4GB(而非原来的64KB)。这样CPU就可以在0-4GB范围执行和操作数据。此后,BIOS在执行一些硬件检测和初始化操作后,就会把与原来PC兼容64KB BIOS代码和数据复制到内存低端1MB末端的64KB处,然后跳转到这个地方 让CPU真正运行在实地址模式下(类似于8086运行方式 20根地址线)。具体BIOS的发展是随着CPU的发展而发展的(参考BIOS研发技术剖析)。



保护模式和实地址模式:
关于实地址模式和保护模式:http://blog.csdn.net/heiworld/article/details/24371677 注意这里面介绍的这句话: Intel选择了在段寄存器的基础上构筑保护模式,并且保留段寄存器16位。
在保护模式下,它的段范围不再受限于64K,可以达到4G。

分段机制:
  80x86处理器使用了一种称为 段的寻址技术,从内存中寻址需要使用段地址和段内偏移地址。段地址部分使用16位的段选择符指定,段内偏移地址部分使用32位的值来指定。这两部分构成48位地址称为 逻辑地址。80x86从逻辑地址到物理地址变换过程使用了 分段分页两种机制。第一阶段使用分段机制把程序的逻辑地址变换为线性地址,第二阶段使用分页机制把线性地址转换为物理地址。
  分段提供了一种机制,用于把处理器可寻址的线性地址空间划分成一些较小的称为段的受保护地址空间区域。段可以用于存放程序代码、数据、堆栈,或者用来存放系统数据结构(如TSS或LDT)。

段选择符是16位标识符,用于提供段描述符表的偏移量,包含3个字段:请求特权级RPL(位0、1)、表指示标志TI(位2)、索引值(位3-15)。表索引字段TI用来指出包含指定段描述符的段描述符表GDT或LDT。对应用程序来说段选择符是作为指针变量的一部分而可见,但选择符的值通常是由链接编辑器或链接加载程序进行设置或修改。由于段选择符中有14位用于作为索引项,所以逻辑地址空间可包含最多16K(2^14)个段。每个段最长可达4GB(偏移地址32位)。段寄存器是用于存放段选择符的寄存器。为了避免每次访问内存时都去引用描述符表,段寄存器除去存放段选择符部分,其余的是描述符缓冲区(影子寄存器)。当一个段选择符被加载到一个段寄存器可见部分时,处理器同时把段选择符指向的段描述符中的段地址、段限长以及访问控制信息加载到段寄存器的影子寄存器区域。
段描述符表是段描述符的数组。有两种描述符表:全局描述符表GDT和局部描述符表LDT。虚拟空间被分割成大小相等的两半,一半是由GDT映射的全局虚拟地址空间,一半是由LDT映射的局部虚拟地址空间。包含LDT表的段必须在GDT表中有一个段描述符项。而GDT本身并不是一个段,而是线性空间中的一个数据结构。访问LDT需要使用其段选择符。为了在访问LDT时减少地址转换次数,LDT的段选择符、基地址、段限长以及访问权限需要存放在LDTR寄存器中。

全局描述符表寄存器GDTR和局部描述符表寄存器LDTR:GDTR寄存器用于存放全局描述符表GDT的32位的线性基地址和16位的表限长值。基地址指定GDT表中字节0在线性地址空间中的地址。GDT的限长以字节位单位,因为段描述符总是8字节长,所以GDT的限长值应该设置为总是8的倍数减1,GDT的基地址应该进行内存8字节对齐。LDTR寄存器用于存放局部描述符表LDT的32位线性基地址、16位限长和描述符属性值以及段选择符。

段描述符用于向处理器提供有关一个段的位置和大小信息以及访问控制的装态信息。每个段描述符的长度是8字节,含3个主要字段:段基地址、段限长和段属性。段描述符通常由编译器、链接器、加载器或者操作系统来创建。段类型字段用行指定段或门的类型、说明段的访问种类以及段的扩展方向。

把逻辑地址转换成线性地址:
(1)使用段选择符中的偏移值在GDT或LDT表中定位相应的段描述符(仅当一个新的段选择符加载到段寄存器中时才需要这一步)。
(2)利用段描述符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于段界限内。
(3)把段描述符中取得的段基地址加到偏移量上,最后形成一个线性地址。

使用LDTR寄存器中的存放的局部描述符表线性基地址定位局部描述符表LDT,通过段选择符(TI = 1)来指定局部描述符表的序列。

分段机制保护:

Conforming and Non-Conforming Code Segment
代码段可以是一致性的或者非一致性的。向更高特权级一致性代码段的执行控制转移 (只要DPL <= CPL就可以转移),允许程序以当前特权级CPL执行。向一个不同特权级(高于或低于)的非一致性代码段的转移将导致一般保护异常(即DPL = CPL 才能转移),除非使用了一个调用门或任务门。所有数据段都是非一致性的,即意味着它们不能被低特权级的程序或过程访问,但可以被更高特权级的程序或过程访问,而无需使用特殊的访问门。


请求特权级RPL(Requested Privilege Level)处于段选择符的1、0位
描述符特权级字段DPL(Descriptor privilege level)处于段描述符的14、13位
当前特权级CPL(Current Privilege Level)处于CS和SS段寄存器的1、0位
protected mode software architecture中提法:
the CPL(current privilege level) of the current program
the RPL(requestor privilege level) in the segment register
the DPL(descriptor privilege level) of the target code segment

the RPL represent the privilege level of the program that created the 16-bit value (the 16-bit value that is palced in the CS register during execution of a far jump or a far call instruction may have been created either by the program currently executing, or may have ben passed to it by another program as a parameter)。 RPL根据不同段跳转确定,以动态刷新CPL(比如 JMP 00D0:0003 中就指定了RPL为00)。

CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。 (个人认为可以看成是段描述符未加载入CS前,该段的DPL,加载入CS后就存入CS的低两位,所以叫做CPL,其值就等于原段DPL的值)

RPL说明的是进程对段访问的请求权限(Request Privilege Level),是对于段选择符而言的,每个段选择符有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定的,两次访问同一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样虽然它对该段仍然只有特权为3的访问权限。(个人认为是以CPL来访问段DPL所出示的“证件(RPL)”,如出示的“证件”权级范围在CPL之内且满足DPL的特权检查规则:DPL >= max{CPL,RPL},就能正常通过DPL;反之则不会通过还会发生错误)

DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}

普通转跳(没有经过Gate 这东西):即JMP或Call后跟着48位全指针(16位段选择子+32位地址偏移),且其中的段选择子指向代码段描述符,这样的跳转称为直接(普通)跳转。普通跳转不能使特权级发生跃迁,即不会引起CPL的变化,看下面的详细描述:

    目标是一致代码段:
     要求:CPL >= DPL ,RPL不检查。

          转跳后程序的CPL = 转跳前程序的CPL
     
    目标是非一致代码段:
     要求:CPL = DPL AND  
RPL<= DPL

          转跳后程序的CPL = 转跳前程序的CPL



通过调用门的跳转:当段间转移指令JMP和段间转移指令CALL后跟着的目标段选择子指向一个调用门描述符时,该跳转就是利用调用门的跳转。这时如果选择子后跟着32位的地址偏移,也不会被cpu使用,因为调用门描述符已经记录了目标代码的偏移。使用调门进行的跳转比普通跳转多一个步骤,即在访问调用门描述符时要将描述符当作一个数据段来检查访问权限,要求指示调用门的选择子的 RPL≤门描述符DPL,同时当前代码段CPL≤门描述符DPL,就如同访问数据段一样,要求访问数据段的程序的CPL≤待访问的数据段的DPL,同时选择子的RPL≤待访问的数据段或堆栈段的DPL。只有满足了以上条件,CPU才会进一步从调用门描述符中读取目标代码段的选择子和地址偏移,进行下一步的操作。

   从调用门中读取到目标代码的段选择子和地址偏移后,我们当前掌握的信息又回到了先前,和普通跳转站在了同一条起跑线上(普通跳转一开始就得到了目标代码的段选择子和地址偏移),有所不同的是,此时,CPU会将读到的目标代码段选择子中的RPL清0,即忽略了调用门中代码段选择子的RPL的作用。完成这一步后,CPU开始对当前程序的CPL,目标代码段选择子的RPL(事实上它被清0后总能满足要求)以及由目标代码选择子指示的目标代码段描述符中的DPL进行特权级检查,并根据情况进行跳转,具体情况如下:

 

   目标是一致代码段:
     要求:CPL >= DPL ,RPL不检查,因为RPL被清0,所以事实上永远满足RPL <= DPL,这一点与普通跳转一致,适用于JMP和CALL。
          转跳后程序的CPL = 转跳前程序的CPL,因此特权级没有发生跃迁。
                           

    目标是非一致代码段:

  当用JMP指令跳转时:
     要求:CPL = DPL (RPL被清0,不检查),
若不满足要求则程序引起异常。
          转跳后程序的CPL = DPL
     因为前提是CPL=DPL,所以转跳后程序的CPL = DPL不会改变CPL的值,特权级也没有发生变化。如果访问时不满足前提CPL=DPL,则引发异常。

    当用CALL指令跳转时:

     要求:CPL >= DPL(RPL被清0,不检查),若不满足要求则程序引起异常。

          转跳后程序的CPL = DPL

    当条件CPL=DPL时,程序跳转后CPL=DPL,特权级不发生跃迁;当CPL>DPL时,程序跳转后CPL=DPL,特权级发生跃迁,这是我们当目前位置唯一见到的使程序当前执行优先级(CPL)发生变化的跳转方法,即用CALL指令+调用门方式跳转,且目标代码段是非一致代码段。

http://blog.csdn.net/feijj2002_/article/details/4597174
http://blog.csdn.net/csujiangyu/article/details/46531931

分页机制:

分页机制

80x86使用4K(2^12)字节固定大小的页面,对齐于4K地址边界处。分页机制把4GB(2^32)的线性地址和物理地址空间划分成2^20个页面,通过管理线性地址和物理地址映射关系实现分页(线性地址的低12位可作为页内偏移量直接作为物理地址的低12位)。
如果包含线性地址的页面当前不在物理内存中,处理器就会产生一个页错误异常,处理器把用于线性地址转换成物理地址时所需的信息及处理器产生错误异常所需的信息存储于页目录和页表中。为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的缓冲器件(转换查找缓冲区TLB)中。
CR3含有存放页目录表页面的物理地址。页目录表页面是页对齐的,所以该寄存器只有高20位是有效的,在往CR3中加载新值时低12位必须设置为0。使用MOV指令加载CR3时,会让TLB无效。80x86处理器并没有维护页转换高速缓存和页表中数据的相关性,但是需要操作系统软件来确保它们一致,因此操作系统必须在改动过页表以后刷新高速缓冲以确保两者一致。通过简单重新加载寄存器CR3,就可对高速缓存器的刷新操作。

页表用于记录2^20个页面的物理基地址(20位)和相关属性(12位),每项占4字节。

两级页表结构

物理地址的转换分成两步进行,每步转换其中10bit
第一级称为页目录(存放于1个4K页面),具有2^10个4B的表项,线性地址的最高10位用于索引第二级(页表),故需要2^10(1K)个页表。
第二级称为页表(存放于1个4K页面), 具有2^10个4B的表项,线性地址的中间10位用于获取页面20位物理基地址。
部分二级页表存放于磁盘上,页目录中的目录项有一个存在属性用于指明二级页表是否存在

分页机制保护:
读写标志R/W和用户/超级用户标志U/S提供分页机制保护。特权级0,1,2为超级用户级,特权级3为普通用户级
0-1MB内存空间用于内核系统(其实内核指使用0-640KB,剩下的部分被高速缓冲和设备内存占用)


猜你喜欢

转载自blog.csdn.net/asmartkiller/article/details/77871708