系统调用的三层机制
用户态、内核态和中断
- 用户态。较低的执行级别,只能访问一部分内存,只能执行一部分指令。
- 内核态。高级执行级别,可以访问任意物理内存,可以执行特权指令。
- 中断。系统从用户态进入内核态的主要方式。有硬件中断和软中断。系统调用就是通过软中断进入内核态。
- Intel x86 CPU有4种不同的执行级别,分别是0、1、2、3,数字越小,特权越高,Linux操作系统中只是采用了其中0和3两个特权级别,分别对应内核态和用户态。
API和系统调用 - API(应用程序编程接口)就是系统调用的库函数,是一个函数定义。
- 系统调用是通过软中断向内核发出了中断请求,int指令的执行就会触发一个中断请求,一般每个系统调用对应一个系统调用的封装例程,函数库再用这些封装历程定义出给程序员调用的API。这样把系统调用最终封装成方便程序员使用的库函数。
实验:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
实现书本中的time()库函数API
tim.c代码如下:
#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
",t->tm_year+1900,t->tm_mon+1,t->tm_mday);
return 0;
}
运行结果:
问题:刚开始显示2020 9 28我以为是我的虚拟机时间错误了,然后调用date函数查看了一下发现时间正确,所以我去查了一下localtime的定义,发现localtime是用0表示一月,所以我在程序中做了修改,让显示的月份+1。
选用exit()系统调用进行实验
exit.c代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a;
while(1)
{
printf("input 1 to exit!
");
scanf("%d",&a);
if(a==1)
{
exit(0);
}
}
return 0;
}
运行结果:
C代码中嵌入汇编代码,代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a;
while(1)
{
printf("input 1 to exit!
");
scanf("%d",&a);
if(a==1)
{
asm volatile(
"movl $0x01,%%eax
"
"int $0x80
"
"movl %%eax,%0
"
:"=m" (exit)
);
}
}
return 0;
}
汇编代码调用系统调用的工作过程
exit库函数需要的系统调用参数是系统调用号。系统调用的参数按照顺序分别放在ebx、ecx、edx、esi、edi及ebp中。
资料网站:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/exit.c
http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
应用程序在用户态调用API函数,API将封装的系统调用号及参数保存到eax,ebx等寄存器,读取0x80中断向量触发中断,然后陷入内核态,中断服务程序根据系统调用号调用并执行对应的系统调用函数,系统调用函数执行完毕后将结果存放的eax中并返回给程序,程序返回的用户态。
sys_exit的系统调用号为1,先将其赋给eax,使用int 0x80触发中断,然后中断处理程序保存现场,进程进入内核态。系统调用的返回值使用eax传递。将eax的值保存到定义的exit中.