第四章·系统调用的三层机制(上)
本章的重点在于用户态程序如何触发系统调用?
一、用户、内核、中断
-
IntelX86有四种不同的执行级别。Linux操作系统中只采用了其中的0和3两个特权级别,分别对应内核态和用户态。
-
用户态和内核态显著的区分方法就是CS:EIP的指向范围,在内核态时CS:EIP的值可以是任意的地址。但在用户态只能访问0x00000000~0xbfffffff的地址空间。也就是0xc0000000以上的地址空间只能在内核态下访问。
-
系统调用也是一种中断(中断处理是从用户态进入内核态的主要方式)。
-
进入及内核态是有中断触发,可能是硬件中断,也可能是Trap。
-
int指令触发中断机制会保存用户态堆栈顶地址、当时的状态字、当时的CS:EIP的值。
-
中断发生后的第一件事是保存现场刚开始执行SAVE_ALL,中断处理结束前的最后一件事是恢复现场就会执行restore_all和iret。
二、系统调用概述
-
系统调用的意义是操作系统为用户态进程与硬件设备进行交互提供了一组接口。
-
一个API可能只对应一个系统调用,也可能内部由多个系统调用实现,一个系统调用也可能被多个API调用。
-
系统调用的三层机制分别为xyz(),system_call,sys_xyz()。
-
内核通过给每个系统调用一个编号来区分,即系统调用号,将API函数xyz()和系统调用内核函数sys_xyz()关联起来了。
-
EAX用于传递系统调用号。
-
传递参数时参数按顺序赋值给EBX、ECX、EDX、ESI、EDI、EBP。如果参数超过6个,就把某一个寄存器作为指针指向内存,这样可以通过内存来传递更多的参数。
-
应用程序系统调用(API)和系统调用不同的API知识函数定义。系统调用是通过软中断向内核发出中断请求。
三、实验
使用库函数API触发rename系统调用
- 代码如下:
#include<stdio.h>
int main()
{
int ret;
char *oldname="hello.c";
char *newname="newhello.c";
ret = rename(oldname,newname);
if(ret == 0)
printf("Renamed successfully
");
else
printf("Unable to rename the file
");
return 0;
}
结果:
嵌入式汇编代码触发rename系统调用
汇编语言代码:
#include<stdio.h>
int main()
{
int ret;
char *oldname="hello.c";
char *newname="newhello.c";
asm volatile(
"movl %2,%%ecx
"
"movl %1,%%ebx
"
"movl $0x26,%%eax
"
"int $0x86
"
"movl %%eax,%0"
:"=m"(ret)
:"b"(oldname),"c"(newname)
);
if(ret == 0)
printf("Renamed successfully
");
else
printf("Unable to rename the file
");
return 0;
}
成功改为newhello.c