第四章 系统调用的三层机制(上)
知识点总结:
- 系统调用:系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口。
- 系统调用的功能特性:
- 把用户从底层的硬件编程中解放出来;
- 极大地提高了系统的安全性;
- 使用户程序具有可移植性。
- API:应用编程接口(application program interface)
- 一个API可能只对应一个系统调用,也可能内部由多个系统调用实现;
- 一个系统调用也可能被多个API调用;
- 不涉及与内核进行交互的API内部不会封装系统调用,如求绝对值的数学函数abs()。
- 系统调用三层机制:
- API
- 中断向量对应的system_call。(system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号)
- 系统调用内核处理函数sys_xyz()
- 什么是用户态和内核态:
- 用户态:(低级别指令)对于32位的4GB进程地址空间,只能访问0x00000000~0xbfffffff的地址空间。
- 内核态:(高指令执行级别)在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这时CPU执行级别就对应着内核态。CS:EIP的值可以是任意地址。
- 用户态进入内核态的主要方式:中断处理
- 中断/int指令发生后第一件事就是保护现场,中断处理结束前最后一件事是恢复现场。
- 系统调用的参数传递:
- 无法通过参数压栈的方式,而是通过比较特殊的寄存器传递参数的方式。
- 寄存器传递参数具有如下限制:
- 1)每个参数的长度不能超过寄存器的长度,即32位
- 2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp)
一.实验:使用库函数API time()来获取系统当前时间
实验过程&遇到的问题
2.解决办法
见下一个实验的解决办法。
3.代码分析
#include<stdio.h>
#include<time.h>
int main()
{
time_t tt; //t是一个int型的数值,记录当前系统的时间,
struct tm *t; //定义一个 struct tm 类型的指针变量 t
tt = time(NULL); //使用库函数 API time() 来获取 tt
t = localtime(&tt); //调用 localtime() 把tt变成 struct tm 这种结构的格式便于输出为可读格式
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;
}
二.实验:用汇编方式触发系统调用获取系统当前时间
1.遇到的问题
解决办法
修改代码如下:
可以正常运行:
2.内嵌汇编代码如下
asm volatile(
"mov $0,%%ebx
" //把EBX寄存器清零
"mov $0xd,%%eax
" //EAX寄存器传递系统调用号13
"int $0x80
" //触发系统调用陷入内核执行13号系统调用的内核处理函数
"mov %%eax,%0
" //通过EAX寄存器返系统调用值,把EAX寄存器的值放到tt变量中,即完成了C代码中嵌入汇编代码触发13号系统调用time
:"=m"(tt)
);
三.实验:含两个参数的系统调用(一)
先使用库函数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
" //将newname存入ecx寄存器
"movl %1,%%ebx
" //将oldname存入ebx寄存器
"movl $0x26,%%eax
" //把系统调用号38存入EAX寄存器中
"int $0x80" //执行系统调用陷入内核态,执行38号系统调用的内核处理函数
:"=a"(ret)
:"b"(oldname),"c"(newname)
);
if(ret == 0)
printf("Renamed successfully
");
else
printf("Unable to rename the file
");
return 0;
}
实验过程
将汇编代码中的 "=a" 换成 "=m" 之后,再次编译,运行结果如下图:
可以发现,hello.c 变成了 newhello.c ,却显示没有修改成功。其实确实执行了 sys_rename ,返回值0保存到EAX寄存器中。这里的修改失败是指 ret 不等于0。
ret 的限定符是 m ,也就是说 ret 是内存变量。想要使 ret 的值为0,需要先使EAX寄存器的0值传给 ret 。增加代码 “movl %%eax,%0” 如下:
再次编译运行:
遇到的问题
问题原因:在64位系统下去编译32位的目标文件,这样是非法的。
解决方案:用”-m32”强制用32位ABI去编译,即可编译通过。
解决方案:新增加的代码 “movl %%eax,%0” 的上一句汇编语言末尾没有添加" ",添加之后可以正常编译。
五.实验:通用的触发系统调用的库函数syscall
若内核增加一个新的系统调用,libc函数库的版本没有及时更新为其编写API函数,可以利用libc提供的syscall函数直接调用。
syscall函数原型:
extern long int syscall(long int sysno,...) __THROW
sysno是系统调用号,“...”是系统调用所带的参数。
以rename为例来看代码实现: