用户态、内核态和中断
-
内核态:处于高的执行级别下,代码可以执行特权指令,访问任意的物理地址,这时的CPU就对应内核态
-
用户态:处于低的执行级别下,代码只能在级别允许的特定范围内活动。在日常操作下,执行系统调用的方式是通过库函数,库函数封装系统调用,为用户提供接口以便直接使用。
-
Intel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0 3级分别表示内核态和用户态。cs寄存器的最低两位表明了当前代码的特权级,00或者11。
-
内核态cs:eip的值是任意的,即可以访问所有的地址空间。用户态只能访问其中的一部分内存地址(0x00000000-0xbbbbbbbf),0xc0000000以上的地址(逻辑地址而不是物理地址)只能在内核态下访问。
-
中断处理是从用户态进入内核态的主要方式,系统调用是一种特殊的中断。从用户态切换到内核态时,中断/int指令会在堆栈上保存用户态的寄存器上下文,其中包括用户态栈顶地址、当时的状态字、当时的cs:eip的值,还有内核态的栈顶地址、内核态的状态字、中断处理程序的入口。中断发生后的第一件事就是保存现场,保存一系列的寄存器的值;中断处理结束前的最后一件事就是恢复现场,退出中断程序,恢复保存寄存器的数据。特别说明: 保护现场:就是进入中断程序,保存需要用到的寄存器的数据;恢复现场:就是退出中断程序,恢复保存寄存器的数据。
系统调用
-
系统调用的意义:操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。把用户从底层的硬件编程中解放出来,极大的提高了系统的安全性,使用户程序具有可移植性。
-
API和系统调用:API是一个系统调用封装成的一个函数定义;系统调用通过软中断向内核发出一个明确的请求;Libc库定义的一些API引用了封装例程,目的是发布系统调用,让程序员写代码的时候可以通过函数调用而非汇编指令触发一个系统调用;一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API。
-
应用编程接口(application program interface, API) 和系统调用是不同的
-
不是每个API都对应一个特定的系统调用。API可能直接提供用户态的服务,比如一些数学函数;一个单独的API可能调用几个系统调用;不同的API可能调用了同一个系统调用。
-
系统调用的三层皮:xyz(API)、system_call(中断向量)、sys_xyz(中断服务程序)
-
内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数(使用eax寄存器来传递)
-
寄存器传递参数具有如下限制: 1)每个参数的长度不能超过寄存器的长度,即32位 2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp) 3)超过6个怎么办?超过6个的话就把某一个寄存器作为一个指针,指向某一块内存。
-
API-int 0x80 陷入内核态-systemcall-调用函数
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
1.实验视频中的time()库函数API
编写time.c文件
进行编译并且运行
结果正确输出了当前时间
将其更改为嵌入汇编代码:
编译并且运行得到与上面相同输出。
选择一个系统调用,参考视频中的方式使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
选择含有两个参数的系统调用rename,它在内核中的系统调用处理函数为sys_renameZ(),系统调用号为38,内核中的系统调用处理函数原型为:
asmlinkage long sys_rename(const char _user *oldname,const char _user *newname);
先使用库函数API触发rename系统调用,
利用嵌入式汇编代码触发rename:
编译并运行:
根据运行结果可以看出,修改文件名成功,20209315lm.c文件被改名为new20209315lm.c。
遇到的问题
1.在编写汇编代码时将asm volatile写成了asm volatitle导致编译错误
2.在编译嵌入式汇编代码触发系统调用时,使用gcc time.c -o time命令编译,显示编译出错,通过查找得知实验楼虚拟机是64位系统,书中的例子是x86-32撰写的,需要用gcc -m32 -o time time.c命令进行编译。