IA-32体系结构CPU保护模式和32位操作系统常见误区

系统软件开发系列文章之二:IA-32体系结构CPU保护模式和32位操作系统常见误区

 

20100604随笔版,不保证完全的学术严谨)

 

1、工作在80386保护模式上的32位操作系统使用80386保护模式的硬件任务切换功能支持多任务。

 

错误!现代32位操作系统,例如Windows(这里指Windows NT,包括Windows 2000XP及其后续版本)或者Linux,都是具有可移植性的操作系统,也就是说,操作系统的源程序,只需要修改与硬件相关的最底层部分(例如Windows中的硬件抽象层——HAL),即可重新编译以适应在不同CPU上运行。因此现代32位操作系统只能尽量使用各种不同32CPU都能支持的通用功能,对于某些CPU特有的硬件支持功能尽量不用,以保证可移植性。

 

例如,大多数32CPU只支持2个特权级,尽管80386支持Ring 0Ring 1Ring 2Ring 3合计4个特权级,但工作在80386上的WindowsLinux都只使用2个特权级——Ring 0Ring 3

 

再例如,除了80386支持分段内存寻址和段保护之外,其它32CPU很少支持分段内存寻址,因此工作在80386上的Windows,无论是在Ring 0还是在Ring 3上运行,其代码段、数据段和堆栈段的基址都是0,限长都是4GB——即所谓的“平坦(Flat)内存模式”,32位偏移量就等于线性地址,基本没有使用分段内存寻址,段保护也很少使用。Windows使用在32CPU中支持较为广泛的分页寻址和页保护。

 

因此,只有80386支持的保护模式硬件任务切换功能(通过TRTSS和任务门实现)通常是不会被现代32位操作系统使用的,WindowsLinux都通过软件实现任务切换。

 

尽管Windows不会使用80386保护模式的硬件任务切换功能,但是TSS(任务状态段)保存了当前任务(这里仅指80386硬件任务,与Windows进程/线程无关,实际上所有正常运行的Windows进程/线程,都对应同一个80386硬件任务)的某些重要信息,例如特权级改变时通常是一定要用到TSS的,所以Windows仍然至少要维护1TSS

 

这里给出一道思考题:

 

Windows中,工作于用户模式(Ring 3)的应用程序通常不能直接访问IO端口,也就是说,INOUT指令以及CC++中的_inp_outp函数对于一般应用程序是无效的,但在使用一种名为“WinIO”的第三方开发库(工具包)的情况下,则可以使用INOUT指令以及_inp_outp函数直接访问IO端口。

 

已知WinIO的核心是一个文件名为winio.sys的设备驱动程序,调用了3个未公开的Windows内核功能调用:

 

Ke386SetIoAccessMap

Ke386QueryIoAccessMap

Ke386IoSetAccessProcess

 

试查阅相关资料,思考一下这3个内核功能调用与TSS中的哪一部分重要信息相关?

 

280386保护模式分页内存寻址中,页目录、页表和页是3种完全不同的4KB块。

 

不准确!实际上,无论页目录、页表还是页,它们的本质都是4KB的页,页目录项和页表项的数据格式实际上是完全相同的。这就意味着,一个4KB的页目录同时也可以作为一个页表使用,一个4KB的页目录或者页表也可以当作普通的页来访问。

 

启用80386保护模式分页内存寻址之后,CR3寄存器保存页目录的物理地址,因此页目录必须常驻物理内存,而页表和页则可以在需要时予以分配。

 

3CR3寄存器、页目录项和页表项中存储的地址都是物理地址,但是启用80386保护模式分页内存寻址之后,程序员只能访问线性地址,如何知道页目录/页表自身对应的线性地址并维护页目录项/页表项呢?绕糊涂了……

 

不必糊涂,使用线性地址维护页目录项和页表项的方法非常简单。

 

如上所述,页目录也可以作为页表使用,页目录和页表又都可以作为普通页使用,那么可以在创建页目录时,将某一页目录项对应的物理地址设置为页目录自身的物理地址,也就是某一页目录项指向页目录自身。

 

例如,假设页目录的第2页目录项(页目录项索引为1)指向页目录自身,则以下线性地址(二进制,下同):

 

0000000001 0000000000 000000000000b

 

该线性地址对应第2页目录项指向的页表,但是第2页目录项指向的是页目录自身,因此页目录同时就作为页表使用,该线性地址又对应第1页表项指向的页,但是现在的页表就是页目录,因此该线性地址对应的是第1页目录项指向的“页”,即第1页表。最终,该线性地址指向第1页表(起始处),使用该线性地址即可直接访问第1页表。

 

以下线性地址:

 

0000000001 0000000001 000000000000b

该线性地址对应第2页目录项指向的页表,但是第2页目录项指向的是页目录自身,因此页目录同时就作为页表使用,该线性地址又对应第2页表项指向的页,但是现在的页表就是页目录,因此该线性地址对应的是第2页目录项指向的“页”——即页目录自身!最终,该线性地址指向页目录(起始处),使用该线性地址即可直接访问页目录。

 

Windows中,任何进程的页目录线性地址都是C0300000h,为什么呢?将十六进制转换成二进制,C0300000h地址对应的二进制地址是:

 

1100000000 1100000000 000000000000b

 

显然,只要将进程页目录的第769页目录项(页目录项索引为0300h,即1100000000b)指向进程页目录自身,则任何进程的页目录线性地址一定都是C0300000h,不管不同进程页目录的物理地址是否相同。

 

对于任何进程,还可以得出以下结论:

 

线性地址1100000000 0000000000 000000000000b=C0000000h一定指向进程第1页表;

线性地址1100000000 0000000001 000000000000b=C0001000h一定指向进程第2页表;

线性地址1100000000 0000000010 000000000000b=C0002000h一定指向进程第3页表;

……

 

这样一来,使用线性地址维护页目录项/页表项就变得非常简单了,如果需要添加新的页表或者页,只要通过线性地址修改对应页目录项/页表项,使其指向一块4KB空闲物理内存即可,然后可以立即通过对应这段物理内存的新线性地址访问之。操作系统通常需要维护一个数据结构,以标识4KB物理内存块的分配情况。

 

最后给出一道练习题:

 

Windows下,使用内核调试器(SoftICEWinDbg等)检查CR3寄存器的值,然后与索引为0300h的页目录项对应的物理地址,即线性地址C0300000h+0300h*04h处的DWORD(将低12位清空)相比较,看二者是否相同,为什么?

 

你可能感兴趣的:(系统软件开发)