使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
符钰婧 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
此实验使用了122号系统调用uname来获取当前UNIX系统的名称、版本和主机等信息。
一、实验过程
1、使用库函数API进行调用的代码【namel.c】如下:
#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct utsname testbuff;
int fb=0;
fb=uname(&testbuff);
if(fb<0)
{
perror("uname");
return 0;
}
else
{
printf("sysname:%s
nodename:%s
release:%s
version:%s
machine:%s
",testbuff.sysname,testbuff.nodename,testbuff.release,testbuff.version,testbuff.machine);
#if _UTSNAME_DOMAIN_LENGTH - 0
# ifdef __USE_GNU
printf(" domainame:%s
",testbuff.domainname);
# else
printf(" __domainame:%s
",testbuff.__domainname);
# endif
#endif
}
return 0;
}
编译后输出结果为:
2、然后将部分代码修改之后变为使用汇编方式触发系统调用,部分代码【namel-asm.c】如下:
int fb;
asm volatile(
"mov $0,%%ebx "
"mov $0x7A,%%eax "
"int $0x80 "
"mov %%eax,%0 "
: "=m" (testbuff)
);
fb=uname(&testbuff);
l 下面来分析汇编代码调用系统调用的工作过程。
第一个move是先将ebx清零,即令ebx为NULL;
第二个move是将0x7A(uname是122号系统调用)放到eax中(eax是传递系统调用号的),即用eax传递参数;
执行int $0x80指令开始进行系统调用,返回值用eax存储;
然后将eax放到%0(即变量testbuff)中。
编译后输出结果为:
二、总结
从视频和实验的学习中大概了解了系统调用的工作机制,尽管明白的肯定不够透彻,也足够帮助我消化这周的学习内容。
1、中断处理是从用户态进入内核态主要的方式,系统调用是一种特殊的中断。
中断处理的完整过程(由中断信号或者int指令完成):
将cs:eip的值,堆栈段寄存器当前的栈顶(ss:eip)和当前的标志寄存器(eflags)保存到内核堆栈中;同时将当前中断服务例程的入口加载到cs:eip中,当前堆栈段和eip也加载到CPU中。
执行完以上以上步骤之后,当前CPU在执行下一条指令时,就已经开始执行整个中断处理程序的入口了。
此时已经开始操作内核态的堆栈了。
若完成中断服务之后不发生进程调度,则继续执行指令(RESTORE_ALL和iret),然后返回到原来的状态;
若发生进程调度,那么当前发生的状态都会暂时的保存在系统中,当下一次发生调度再次回到当前进程时就继续执行指令RESTORE_ALL和iret。
2、系统调用的工作机制:
用户态中xyz()函数就是系统调用对应的API;
这个API中封装了一个系统调用,这个系统调用会触发int 0x80的一个中断;
0x80这个中断向量就对应着system_call(内核代码的入口起点);
内核代码中可能会执行到对应的中断服务程序sys_xyz();
在中断服务程序执行完之后,可能会执行ret指令,此时可能会发生进程调度;
如果没有发生进程调度,就执行iret,返回到用户态接着执行其他指令。
总结:系统调用的三层皮:xyz(API)、system_call(中断向量)和sys_xyz(服务程序)。