[Rx86OS-XIX] 应用程序和系统调用

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.22-24]


在没有利用x86架构CPU保护情况下,应用程序可以操作任何一个地址的内存(段号:偏移地址)。


1 执行一个跟操作系统同载体保存的汇编应用程序

Figure1. 汇编应用程序和操作系统程序

“书”中的汇编应用程序与操作系统程序在同一个载体之上。


[Rx86OS-XIX] 应用程序和系统调用_第1张图片

Figure2. 汇编程序的执行

汇编应用程序是一个文件。操作系统程序本身并不知道应用程序文件的任何信息(操作系统程序由很多个C文件和汇编文件组成,这些文件中的函数由链接器让它们发生关联)。磁盘的“文件信息区域”和“FAT”记录了被载入磁盘保存的应用文件信息。磁盘内容被载入内存后,“文件信息区域”和“FAT”就被载入到了相应的内存块中。操作系统程序能够根据在内存中的“文件信息区域”和“FAT”将应用程序内容载入到指定的内存中,再完成应用程序的GDT注册之类的操作。最后,操作系统程序根据应用程序在GDT中的信息使用跳转指令去执行它。


2 在汇编程序中调用操作系统的函数

在应用程序中调用操作系统的函数就是“系统调用”功能。“系统调用”是操作系统程序的一部分。系统调用将操作系统的函数给应用程序调用,编写操作系统的时候要区分应用程序和操作系统程序运行的内存空间。


2.1 通路

操作系统将汇编程序载入另一块内存到执行这个过程中,汇编应用程序的信息(GDT编号等)都被操作系统程序所知。汇编应用程序对操作系统程序却是一无所知。如果想在汇编程序中实现系统调用,汇编程序需要知道它在操作系统中所调用的函数的地址,还要想办法将参数传递给所调用的函数。


“x86实模式到保护模式转变的程序”会将操作系统程序注册到GDT中,编写操作系统的人知道操作系统程序的段地址。这也就得到了操作系统程序中各个函数的段地址。把操作系统程序载入内存中时采取的是连续载入的方式,所以,操作系统程序的各个函数在可执行文件中的偏移地址跟其在段首地址中的偏移地址是一样的。函数在操作系统可执行文件中的偏移地址可以通过查看二进制文件的工具查看(“书”中的编译器将此信息输出到文件中)。这就解决了操作系统函数的地址问题。


存在于操作系统程序中的函数有汇编函数,也有C语言函数。要将传递给操作系统程序的函数的参数压入到所调用函数的栈中(当前应用程序为汇编程序)。为了实现这个功能,“书”往操作系统程序中添加了一个汇编函数。因为这个汇编函数在操作系统程序中,能够很方便的知道操作系统的函数的信息,所以由这个汇编程序来完成对所要调用的操作系统的函数传递参数,然后再调用这个操作系统函数。只要我们在编译器的输出文件中查到这个汇编函数在整个文件中的偏移地址,在汇编应用程序中使用callfar指令来调用这个汇编函数即可。

[Rx86OS-XIX] 应用程序和系统调用_第2张图片

Figure3. 系统调用基本原理


2.2 提升

“书”中说CPU规格说明书中有明确的记载:CPU用于通知异常状态的中断最多只有32种,IRQ只有0~ 15。IDT中最多可以设置256个函数,说明还会剩下很多未使用的项。


按照2.1中实现系统调用的方式,操作系统程序的代码只要稍加改变(如升级)call_sys_call_x_offset这个值就会(不变可能性太小)发生改变。这就使得每改一变操作系统的代码就要求用户相应地改变一次应用程序。为了避免这个问题,选择将操作系统中调用系统函数的汇编程序注册到IDT中,然后在汇编应用程序中换用INT指令调用操作系统的汇编程序。这样,不管操作系统程序怎么更改,用户程序都不必跟着改。

[Rx86OS-XIX] 应用程序和系统调用_第3张图片

Figure4. 系统调用

采取将帮助应用程序调用系统函数的汇编函数注册到IDT再用INT指令调用的方式,不管操作系统代码怎么发生改变,随之发生改变的是注册到IDT的信息。应用程序就不必跟着改变了。


3 C语言应用程序

CPU用代码段和数据段(2者在GDT中注册)来管理程序的内存。


[1] 每个链接器都有组织C应用程序的可执行文件的方式。操作系统程序在调用C应用程序时需要解读链接器加在可执行文件中的信息,如“C代码入口地址”、“数据段(含栈)地址及大小(链接器根据应用程序得出应用程序数据段的大小并记录在可执行文件中的某位置,操作系统程序根据这个位置的数据为应用程序分配数据段内存空间。)”、“可运行在本操作系统上可执行程序的标识”等。(p-466)


[2] 操作系统提供的给应用程序的函数被操作系统注册到了IDT中(避免操作系统程序版本変更后应用程序跟着改变)。IDT中注册函数只能够用INT指令调用,C语言中无与汇编指令INT对应的语句。C应用程序调用操作系统的函数时,首先需要编写一个使用INT指令调用操作系统函数的函数,C程序通过调用这个汇编函数达到调用操作系统函数的目的。


总结

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

在这里没有区分应用程序栈和操作系统程序栈。

编写汇编函数的时候注意使用PUSH/POP指令备份所有的寄存器的值。尤其是在汇编函数中调用C语言程序时,C语言程序有可能改变某个寄存器的值。

比较复杂背景下的改变一般都不是独立存在的。如IDT需要和INT指令、IDT、GDT等搭配,GDT需要和GDTR、段寄存器等搭配。

CALL (FAR)的真实参数是内存地址(段地址:偏移地址),不是要目的内存地址里面是一个函数才可以使用CALL指令,目的内存地址里有什么就让CPU去执行什么(非指令产生异常)。


[x86OS] Note Over.

[2015.04.29]

你可能感兴趣的:([Rx86OS-XIX] 应用程序和系统调用)