2)各个操作系统之间系统调用不兼容。例如Windows系统和Linux系统之间的系统调用基本上就完全不同。
”解决问题可以通过增加层来实现“,于是库挺身而出,它作为系统调用和程序之间的一个抽象层可以保持着这样的特点:
1)使用简便。因为库本来就是语言级别的,它一般都设计的相对比较友好。
2)形式统一。运行库有它的标准,叫做标准库。
例如,在C语言中用fread来读取文件,在Windows下这个函数调用ReadFile,在Linux,调用read系统调用。但是都可以使用C运行库的fread来读取文件。
运行库将不同的操作系统的系统调用包装为统一固定的接口,使得同样的代码,在不同的操作系统下都可以直接编译,并产生一致的效果。
3.2.1 特权级和中断
现代的CPU常常可以在多种截然不同的特权级别下执行指令,分别为用户模式和内核模式,也被称为用户态和内核态。由于有多种特权模式的存在,操作系统就可以让不同的代码运行在不同的模式上,以限制它们的权力,提高稳定性和安全性。
系统调用运行在内核态,而应用程序基本都是运行在用户态的。用户态的程序如何运行内核态的代码?操作系统一般是通过中断来从用户态切换到内核态。什么是中断呢?中断是一个硬件或软件发出的请求,要求CPU暂停当前的工作转去处理更加重要的事情。
中断一般具有两个属性,一个称为中断号(从0开始),一个称为中断处理程序。不同的中的具有不同的中断号,而同时一个中断处理程序一一对应一个中断号。在内核中,有一个数组称为中断向量表,这个数组的第n项包含了指向第n号中断的中断处理程序的指针。当中断到来时,CPU会暂停当前执行的代码,根据中断的中断号,在中断向量表中找到对应的中断处理程序,并调用它。中断处理程序执行完成后,CPU会继续执行之前的代码。

通常意义上,有两种类型的中断,一种是硬中断,另一种是软中断,软中断一般是一条指令带有一个参数记录中断号,使用这条指令用户可以手动触发某个中断处理程序。在i386下,int 0x80这条指令会调用低0x80号中断的处理程序。
由于中断号是很有限的,操作系统不舍得用一个中断号来对应一个系统调用,而更倾向于用一个或少数几个中断号来对应所有的系统调用。例如,在Linux则使用int 0x80来触发所有的系统调用。对于同一中断号,操作系统如何知道是哪一个系统调用要被调用呢?和中断一样,系统调用都有一个系统调用号,这个系统调用号通常就是系统调用在系统调用表中的位置,例如Linux里fork的系统调用号是2.这个系统调用号在执行int指令前会被放置在某个固定的寄存器里,对应的中断代码会取得这个系统调用号,并且调用正确的函数。以Linux的int 0x80为例,系统调用号是由eax来传入的。用户将系统调用号放入eax中,然后使用int 0x80调用中断,中断服务程序就可以从eax里取得系统调用号,进而调用相应的函数。
3.2.2 基于int的Linux的经典系统调用实现
图以fork为例的Linux系统调用的执行过程

1)触发中断
例如:
fork函数是对系统调用fork的封装,fork的汇编代码:

当用户调用某个系统调用的时候,实际上是执行了以上一段汇编代码。CPU执行到int $0x80时,会保存现场以便恢复,接着会将特权状态切换到内核态。然后CPU便会查找中断向量表中的0x80号元素。
2)切换堆栈
在实际执行中断向量表中的第0x80号元素所对应的函数之前,CPU首先还要进行 堆栈的切换。在Linux中,内核态和用户态使用的是不同的堆栈,两者各自负责各自的函数调用,互不干扰。但在应用程序调用0x80号中断时,程序的执行 流程从用户态切换到内核态,这时程序的当前栈必须也相应地从用户栈切换到内核栈。从中断处理程序返回后,程序的当前栈从内核栈切换会用户栈。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-76057-8.html
要掌控这门技能