第四章 系统调用的三层机制(上)
一、用户态、内核态、中断
- Intel x86 CPU有4种不同的执行级别,分别是0、1、2、3,数字越小,特权越高,而Linux只采用了0,3两个特权级别,分别对应内核态和用户态。
- 内核态和用户态的区别:内核态时,CS:EIP的值可以是任意地址,用户态时只能访问0x00000000~0xbfffffff的地址空间。
- 进入内核态的两种方式:
(1)可能是硬件中断
(2)可能是用户态执行程序时调用了一个系统调用,陷入了内核态,叫做Trap. - 用户切换到内核态时,会保存用户态寄存器上下文(包括用户态栈顶指针、当时的状态字、当时的CS:EIP的值),同时,要把内核态的寄存器的值放到当前CPU中。
二、系统调用
- 含义:操作系统为用户态进程与硬件设备进行交互提供的一组接口。
- 功能(特性):把用户从底层的硬件编程中解救出来;极大地提高系统的安全性;使用户程序具有可移植性。
- API:应用程序编程接口,内部使用了系统调用的封装例程,主要目的是发布系统调用。
- API和系统调用的关系:一个API对应一个或多个系统调用,一个系统调用可能被多个API调用。不涉及与内核交互的API内部不会封装系统调用。
三、使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
(以sys_rename()给文件重命名的函数为例)
-
使用库函数API触发一个系统调用
C语言代码如下:
执行结果如下图:
-
汇编语言触发系统调用
汇编代码如下:
执行结果如下图:
分析:
主要汇编代码如下:
asm volatile("movl %2,%%ecx\n\t" //newname存入ecx
"movl %1,%%ebx\n\t" //oldname存入ebx
"movl $0x26,%%eax\n\t" //系统调用号存入eax
"int $0x80" //触发系统调用,陷入内核态
:"=a"(ret)
:"b"(oldname),"c"(newname) );
这里,把系统调用号38(16进制是0x26)存入 eax,将 oldname 存入 ebx,将 newname 存入 ecx,通过执行 int $0x80 来执行系统调用,使应用程序陷入内核态,system_call 根据传入的系统调用号在系统调用列表中查找对应的内核函数,根据 ebx 和 ecx 中保存的参数调用内核函数 sys_rename,执行完后将执行结果存放到 eax 中,最后返回 eax 中的值。
四、总结:
本章主要学习了系统调用的3层机制的系统调用工作机制:当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行system_call和系统调用内核函数。具体来说,通过执行int $0x80来触发系统调用,进入内核后,开始执行中断向量为128对应的中断服务程序system_call,这时,通过系统调用号将API函数和系统调用内核函数关联起来,这里需要EAX寄存器传递系统调用号。