[Rx86OS-XX] x86保护操作系机制 (异常处理) 保护应用程序机制 库

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.26,28]


像“[Rx86OS-XVIII] 汇编应用程序和系统调用”中那样执行一个应用程序,应用程序可以修改操作系统程序。x86架构CPU提供了保护操作系统程序的功能。


1 保护操作系统

1.1 利用0x0d异常

在x86架构中,当应用程序试图破坏操作系统或者试图违背操作系统的设置时,就会自动产生0x0d中断,该中断也称为异常。要让x86产生0x0d异常,需要按照x86的规范进行一些设置。


能够产生0x0d中断的(其中)一个前提:

  • 为操作系统程序和每个任务中所调用的应用程序设定不同的内存空间(代码段和数据段)
  • 在运行每个程序之前设定段寄存器和其内存空间相对应

经过这两步设置后,只要当前运行的程序访问了其它程序的内存空间就会产生0x0d中断。一般将操作系统的数据段空间设为0x00000000~ 0xffffffff,也就是让它拥有访问所有内存空间的权限。将各任务中所调用的应用程序的数据段设定为互不相同的某一段内存空间。这样,操作系统程序在调用应用程序时,若应用程序内存访问超出自身的内存空间就会产生0x0d中断。只要在0x0d中断程序中终止这样的应用程序的运行就防止了应用程序修改操作系统程序的行为


只利用0x0d中断不能防止在应用程序中设置操作系统的数据段为应用程序的数据段的情况,这样应用程序照旧能够访问操作系统内存。另外,要保证各段寄存器得到正确的切换。当操作系统程序调用应用程序时的切换:[1]备份32位寄存器,[2]切换16位段寄存器指向应用程序内存空间,[3]调用应用程序,[4]应用程序返回,[4]切换16位段寄存器指向操作系统程序内存空间,[5]还原32位寄存器,[6]返回操作系统程序中;应用程序调用操作系统程序(系统调用,或者在运行应用程序时产生了中断)的切换:[1]备份32位寄存器,[2]切换16位段寄存器指向操作系统内存空间,[3]调用操作系统函数,[4]操作系统程序返回,[4]切换16位段寄存器指向应用程序内存空间,[5]还原32位寄存器,[6]返回应用程序中。


从操作系统程序到应用程序或者从应用程序到操作系统程序给16位段寄存器赋值相应段号的过程称为“栈切换”。让16位段寄存器指向“…程序”的内存空间是指让段寄存器的值指向“…程序”的数据段。代码段的改变靠callfar指令和retf完成。


1.2 区分应用程序和操作系统程序

对于x86架构CPU来说,在给程序指定内存空间即在GDT中注册时,如果在段属性的基础上再加上60h,CPU就会认为这是应用程序。如果在应用程序中“使用操作系统程序(段属性没有加60h的程序)的段号给段寄存器赋值”、“在应用程序中执行IN/OUT、CLI/ST/HLT指令”、“应用程序CALL规定之外的地址”等操作都会产生0x0d中断。


给段属性加上60h的CPU除了上述列举功能外,在和任务的TSS联合下还能让CPU自动实现栈切换。不过在CPU这个机制下,有一些诸如不允许“操作系统far-CALL应用程序”、“操作系统向应用程序段进行far-JMP”的限制。这就需要换一种方式(比如用压栈应用程序的段地址和偏移地址再用RETF的方式)调用应用程序,应用程序返回到操作系统程序的方式也相应的变化,虽然x86保护操作系统的机制根据TSS自动完成了栈切换过程


2 保护应用程序

保护应用程序就是禁止应用程序随意访问其他任务所拥有的内存段。x86架构CPU提供保护应用程序的机制--- LDT(local segment descriptor table,局部段描述符),LDT中的段设置只对某个应用程序有效。如果将某个应用程序段设置在LDT中,其他的任务无法使用该LDT。


描述GDT的结构体可用来描述LDT。LDT的内存地址保存在GDT中(在注册GDT时,具体往GDT中注册了什么依靠往GDT属性段中写入的值。如“书”中表明注册代码段的属性值为0x409a,表明注册数据段的属性值为0x4092,表明注册TSS的属性值为0x0089,表明注册LDT的属性值为0x0082),在GDT中对应一个GDT编号。在GDT中注册好LDT后,需要再往LDT在GDT中所在编号指定具体的程序(代码和数据所在内存空间),那么这个应用程序的内存空间就和LDT相对应最后,启动应用程序时,给各段寄存器赋予应用程序代码段和数据段的LDT段号(段号 * 8 + 4)。P.589-590.


3 栈异常

x86架构下,栈异常是指程序在使用自身的栈内存如在栈中定义inta[100]而使用了a[120]这样的超过a[100]栈内存的语句而产生的异常。当发生栈内存时,会产生0x0c中断。同理于0x0d异常,在0x0c中断函数中编写代码来终止具有栈异常的程序(并指出栈异常出处P.459)就能够避免栈使用的越界。


4 库

链接.obj文件时,链接器的一般设计是将整个文件链接,而不是在包含多个函数的.obj文件中挑出需要使用的部分,并舍弃不需要的部分。这样就使得应用程序中包含着那些根本不会调用的函数,应用程序会变大。


为了解决应用程序不包含不调用的函数,可以将每个函数都单独做在一个.obj文件。然后使用链接器时只包含需要的.obj文件。由此,随着函数的增多,.obj文件的数量也会变多,有时应用程序调用函数也挺多,这就使得跟在链接器后面的参数增多。不想让应用程序包含不调用的函数又不想链接阶段链接器的参数变多,可以使用“库”来解决这个问题。用库管理器程序将所有只包含一个函数的.obj文件创建成一个库(p.597),有了库文件之后,在需要调用库中某函数的应用程序中只需要一条语句(#include“库名.h”)就可以实现应用程序只包含所调用函数的代码。


在C语言中,有一些函数被称为“标准函数”,这些函数对于C语言来说是肺肠常用的函数。大多数情况下,C语言编译器的作者或则是操作系统的作者都会提供这样的库。


5 结构体化编程理念到面向对象编程

结构化编程:将程序的功能拆分为小的部件(函数),然后再组合起来构成一个完整的程序,这样,编写好的部件还可以保存起来,下次可以用在其它程序上面。依据结构化编程的思想,把将来可以用于其他程序的部件组织起来就构成了库。


随着结构化编程的普及,人类的程序开发能力大幅度提升,众多的开发者编写出了无数的库。随着时间推移,函数数量太多了无法对每个函数的使用方法进行有效的管理。为了解决这个问题,“面向对象编程”的新思想应运而生。--抄于“书”p.598.


总结

像这样的内容,只有结合x86架构CPU手册并写出正确的代码才算得上“知”了。


[x86OS] Note Over.

[2015.04.29-10:24]

你可能感兴趣的:([Rx86OS-XX] x86保护操作系机制 (异常处理) 保护应用程序机制 库)