x86的实模式与保护模式

重定位困境

如果程序中的地址用的是物理地址,那么程序就必须在特定的内存位置才能执行,换个位置就不能执行了:

image

一个程序在内存中换个地方就不能正确执行了,这岂不是笑话。况且现在的程序在哪块内存中执行都是操作系统分配的,程序本身是不能决定的。

分段机制

为了解决上述问题,就出现了逻辑地址:

image

“程序起始地址”就是所谓的段地址。段地址由操作系统存分配,用户程序只需使用偏移地址,CPU执行时会自动将偏移地址加上段地址转化成物理地址。“段地址:偏移地址”的形式称为逻辑地址

逻辑地址并不是唯一的。例如,0000:7c00,也可以写成7c00:0000,因为它们的物理地址都是7c00+0000=7c00,而偏移地址起始为0在编程的时候比较方便。

实际上,现在的一个程序有好几个段,如数据段用来存储数据、代码段用来存储代码、栈段用来存储栈等,为了编程方便这些段的偏移地址都是0。

中断处理过程

中断发生时,处理器根据中断号从中断向量表中取出处理程序的逻辑地址,保护现场后跳转到中断处理程序执行。

image

数据保护的难题

没有保护模式之前,用户程序可以随意修改段寄存器,这就意味着用户程序可以访问和修改内存中所有的数据,包括中断向量表、其它程序的数据、操作系统的数据等。例如:

mov ax,0     ;段地址为0的一段区域,是中断向量表
mov ds,ax
mov byte [0x30],0

这种修改可能是无意的,比如程序编写错误,也可能是有意的,比如木马程序。所以程序的权限必须加以限制。

保护模式

为了解决程序可以直接设置段寄存器,从而访问任意内存的问题,提出了保护模式。

在保护模式下,用描述符对每个段的长度和特权级进行指定。如果访问超过段界限的内存区域时,处理器会引发异常中断。

image

操作系统加载应用程序时,会为其在描述符表中创建描述符。在跳转到应用程序时,跳转指令会直接或间接的给出段地址,也就是修改了段寄存器。

特权级

保护模式虽然可以保证应用只能访问自己的内存,但还有几个问题没有解决:

  1. 应用可以修改段寄存器,从而“破解”保护模式
  2. 操作系统调用应用程序后,应用程序返回内核时,返回地址是在栈中的,应用程序修改这个地址,就可以执行任意代码。

为了解决以上问题,引入特权级概念,特权级的原则如下:

  1. 只有高特权级才能执行特权指令
  2. 高特权级代码不能调用低特权级代码
  3. 低特权级代码调用高特权级代码时,必须通过门描述符

引入特权级后,再来回看未解决的问题:

  1. 只有高特权级才能执行特权指令,应用程序特权级最低,所以不能修改段寄存器,只要段寄存器不被修改,保护模式就限制它只能访问自己的内存
  2. 高特权级代码不能调用低特权级代码,这个原则实际的目的是禁用从低特权级向高特权级返回,从而杜绝了应用修改返回地址的问题。高特权级不能调用低特权级的问题很好解决,因为高特权级可以模拟从高特权级向低特权级返回,只需向栈中压入返回地址,然后调用返回指令即可。
  3. 应用程序不被信任,不能直接访问硬件,访问硬件的功能在操作系统中。而应用程序想要访问硬件,必须调用高特权级的操作系统代码,我们当然不能让应用程序随意调用高特权级的代码,为此专门设计了门描述符。门描述符和全局描述符基本类似,调用门操作符后会从低特权级变为高特权级,然后执行门操作符指定的代码。这些“门”是操作系统故意开放的,执行的内容是可控的,所以安全有保障。门调用可以理解为操作系统为应用程序提供的执行高特权级功能的API。

image

多任务

操作系统为了管理多个任务,会为每一个任务创建任务控制块TCB,在TCB中记录了该任务的详尽信息。所有的任务任务控制块TCB都会被放到一个链表中成为TCB链表。

任务控制块的内容是操作系统决定的,不同的操作系统各有不同,但一定包括TSS和LDT的地址。TSS用来保存该任务的寄存器和栈的信息,当从其它任务切换回该任务时,会从TSS中恢复寄存器和栈。LDT是局部描述符表,作用GDT一样,是用来限制访存的范围,多任务时GDT只保存操作系统的描述符,每个任务的描述符会保存在各自的LDT中。

image

保护模式下的中断处理过程

保护模式下的中断处理原理上与实模式的类似,都是根据中断号找对应的处理函数,不同的是保护模式要引入保护机制,所以增加了GDT/LDT表,中断描述符表变复杂了,在内存中的位置由寄存器IDTR指定。

image

分页机制

因为段的大小不一,系统运行一段时间后,会出现有很多小的空间,虽然总的剩余空间很多但是这些小空间都不连续,无法利用,造成浪费。还有一个问题是,连续分配内存不能动态的增加。

image

为了解决这个问题,引入了分页机制,把内存划分为大小相等的页。每个段可以包含多个页,因为多个页可以不连续,所以可以充分利用内存。对于需要动态增加内存的程序,也可以随时增加页。

image

开启分页机制后,原来的分段机制还在,相当于在分段的基础上再做分页。

image

为了根据线性地址找到页的物理地址,操作系统必须维护一张表,把线性地址转换成物理地址,单级页表会导致页表占用的内存较大,所以通常使用多级页表:

image

需要注意的是,页表是操作系统维护的,但是查询内存的过程,从将段基址+偏移得到线性地址,再根据根据线性地址去页表中找到页的物理地址,这些都是由硬件完成的。

分页后还有一个问题,用户任务调用内核服务必须能够访问GDT才行,现在分页后,任务的页目录中只有任务的页表,任务是根本访问不到GDT。而且切换到内核是不切换CR3寄存器的,所以每个任务都必须能访问到内核。

多级页表可以很方便的解决这个问题。想让用户任务能访问内核的内存,只需要将内核的页表添加到用户任务的页目录中即可。

image


参考:

posted @ 2021-02-28 15:59:27
评论加载中...
发表评论