系统调用的三层机制
用户态、内核态和中断
用户态: 在低的执行级别下,代码能够掌控的范围有所限制,只能访问部分内存。
内核态: 在高的执行级别下,代码可以执行特权指令,访问任意的物理内存。
中断: 从用户态进入内核态的主要方式。
- 硬件中断:在用户态进程执行时,硬件中断信号到来,进入内核态,就会执行这个中断对应的中断服务例程。
- 软中断:在用户态进程执行过程中,调用了一个系统调用(一种特殊中断),进入内核态。
系统调用概述
系统调用的功能特性:
- 把用户从底层的硬件编程中解放出来。操作系统为我们管理硬件,用户态进程不用直接与硬件设备打交道。
- 极大地提高系统的安全性。如果用户态进程直接与硬件设备打交道,会产生安全隐患,可能引起系统崩溃。
- 使用户程序具有可移植性。 用户程序与具体的硬件已经解耦合并用接口代替了,不会有紧密的关系,便于在不同系统间移植。
系统调用3层机制:
- 第一步,系统调用的库函数就是读者使用的操作系统提供的API,调用软中断向内核发出中断请求;
- 第二步,CPU切换到内核态并开始执行一个system_call和系统调用内核函数,具体通过int $0x80触发系统调用的执行;
- 第三步,进入内核,通过系统调用号将API函数和系统调用内核函数关联起来进行调用。
参数传递方式:
系统调用从用户态切换到内核态,在两种执行模式下使用不同的堆栈,通过比较特殊的寄存器传递参数。在x86-32中,EAX用于传递系统调用号,其余参数按顺序赋值给EBX,ECX,EDX,ESI,EDI和EBP,参数个数一般不超过6个。如果超过6个就把一个寄存器作为指针指向内存,通过内存传递更多的参数。
使用库函数API和C代码中嵌入汇编代码触发同一个系统调用
使用库函数API触发一个系统调用
调用函数time()来获取当前系统时间
#include<stdio.h>
#include<time.h>
int main()
{
time_t tt;
struct tm *t;
tt = time(NULL);
t = localtime(&tt);
printf("time:%d:%d:%d:%d:%d:%d
",t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
return 0;
}
执行结果
内嵌汇编语法
分类 | 限定符 | 描述 |
---|---|---|
通用寄存器 | “a” | 将输入变量放入eax |
- | “b” | 将输入变量放入ebx |
- | “c” | 将输入变量放入ecx |
- | “d” | 将输入变量放入edx |
- | “s” | 将输入变量放入esi |
- | “D” | 将输入变量放入edi |
- | “q” | 将输入变量放入eax,ebx ,ecx ,edx中的一个 |
- | “r” | 将输入变量放入通用寄存器,也就是eax ,ebx,ecx,edx,esi,edi中的一个 |
- | “A” | 把eax和edx,合成一个64位的寄存器(uselong longs) |
内存 | “m” | 内存变量 |
- | “o” | 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址 |
- | “V” | 操作数为内存变量,但寻址方式不是偏移量类型 |
- | “.” | 操作数为内存变量,但寻址方式为自动增量 |
- | “p” | 操作数是一个合法的内存地址(指针) |
寄存器或内存 | “g” | 将输入变量放入eax,ebx,ecx,edx中的一个或者作为内存变量 |
- | “x” | 操作数可以是任何类型 |
立即数 | “i” | 0-31 之间的立即数(用于32位移位指令) |
- | “J” | 0-63 之间的立即数(用于64 位移位指令) |
- | “N” | 0-255 ,之间的立即数(用于out 指令) |
- | “l” | 立即数 |
- | “n” | 立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n” |
操作数类型 | “=” | 操作数在指令中是只写的(输出操作数) |
- | “+” | 操作数在指令中是读写类型的(输入输出操作数) |
浮点数 | “f” | 浮点数 |
- | “t” | 第一个浮点寄存器 |
- | “u” | 第二个浮点寄存器 |
- | “G” | 标准的80387 |
- | “%” | 该操作数可以和下一个操作数交换位置 |
示例
C代码中嵌入汇编代码触发一个系统调用
汇编代码分析
asm volatile(
"mov $0,%%ebx" /*把EBX寄存器清零*/
"mov $0xd,%%eax" /*把0xd放到EAX寄存器中,EAX寄存器用于传递系统调用号*/
"int $0x80" /*触发系统调用陷入内核执行13号系统调用的内核处理函数*/
"mov %%eax,%0"/*通过EAX寄存器返回系统调用值*/
:"=m"(tt)
);
含两个参数的系统调用
汇编分析:
asm volatile(
"movl %2,%%ecx"//将newname存入ECX寄存器
"movl %1,%%ebx"//将oldname存入EBX寄存器
"movl $0x26,%%eax"//系统调用号38 (16 进制是0x26)存入EAX寄存器
"int $0x80"//执行系统调用陷入内核态
:"=a"(ret)
:"b"(oldname),"c"(newname)
);
从运行结果可以看到9318.c文件被重命名为zhaoshu.c。
遇到的问题
课本中tim.c代码有错误,将t->tm_mda改为t->tm_mday;
需要注意localtime函数使用0表示一月,因此输出结果与日常情况不符;