扒开系统调用的三层皮(上)
注:作者:臧文君,原创作品转载请注明出处,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、用户态、内核态和中断处理过程
1、权限级别
为什么有权限级别的划分?
当系统中所有程序员编写的代码都可以使用特权指令,系统很容易崩溃。
也是让系统更稳定的机制。
2、在Linux代码中如何区分用户态和内核态?
判断cs:eip的值:
在内核态中,cs:eip可以是任意的值,在32位的x86系统中有4G的地址空间。
3、中断处理是从用户态进入内核态的主要方式。
例:硬件中断,系统调用(是一种特殊的中断)。
4、从用户态切换到内核态时:必须保存用户态的寄存器上下文!
中断发生后的第一件事就是保存现场。
保护现场就是进入中断程序,保存需要用到的寄存器的数据;
恢复现场就是退出中断程序,恢复保存寄存器的数据。
iret指令与中断信号(包括int指令)发生时的CPU做的动作正好相反。
5、中断处理的完整过程
int0x80:指系统调用
保存:将当前的cs:eip、ss:esp、eflags保存到内核堆栈中。
加载:把当前的中断信号或系统调用相关联的中断处理程序的入口加载到cs:eip中,同时将当前的堆栈段ss和esp也加载到CPU中。
二、系统调用概述
1、系统调用的意义
2、操作系统提供的API和系统调用的关系
3、应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系
系统调用的三层皮:API,中断向量对应的中断服务程序,系统调用服务程序。
例:xyz、system_call和sys_xyz。
4、系统调用程序及服务例程
中断向量0x80与system_call绑定起来。
系统调用号将xyz和sys_xyz关联起来了。
5、系统调用的参数传递方法
注:函数调用传递参数使用的是压栈的方式。
若超过6个,就将某一个寄存器作为指针,指向一块内存,在进入内核态之后,可以访问所有的地址空间,通过内存来传递参数。
三、使用库函数API和C代码中嵌入汇编代码触发同一个系统调用
1、使用库函数API来获取系统当前时间
1 #include<stdio.h> 2 #include<time.h> 3 int main(){ 4 time_t tt; 5 struct tm *t; 6 tt = time(NULL); 7 t = localtime(&tt); 8 printf(“time:%d:%d:%d:%d:%d:%d\n”,t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); 9 return 0; 10 }
命令:
编写time程序:vi time.c
编译:gcc time.c -o time -m32
执行:./time
2、C代码中嵌入汇编代码的写法
1 #include <stdio.h> 2 #include <time.h> 3 int main(){ 4 time_t tt; 5 struct tm *t; 6 asm volatile( 7 "mov $0,%%ebx\n\t" 8 "mov $0xd,%%eax\n\t" //使用eax传递系统调用号,这里time是13 9 "int $0x80\n\t" 10 "mov %%eax,%0\n\t" //系统调用的返回值使用eax存储,和普通函数一样 11 : "=m" (tt) 12 ); 13 t = localtime(&tt); 14 printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900,t->tm_mon+1, t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); 15 return 0; 16 }
命令:
vi time-asm.c
gcc time-asm.c -o time-asm -m32
./time-asm
实验:
1、库函数API中的break中断函数
代码如下:
1 #include <stdio.h> 2 3 int main(){ 4 5 int i,a; 6 7 for(i=0;i<100;i++){ 8 9 i=4*i+3; 10 11 if(i>100){ 12 13 break; 14 15 } 16 17 } 18 19 printf(“i=%d > 100\n”,i); 20 21 return 0; 22 23 }
截图:
2、在test.c代码中嵌入汇编代码,break的系统调用号为17(0x11)
代码如下:
1 #include <stdio.h> 2 3 int main(){ 4 5 int i,a; 6 7 for(i=0;i<100;i++){ 8 9 i=4*i+3; 10 11 if(i>100){ 12 13 asm volatile( 14 15 "mov $0,%%ebx\n\t" 16 17 "mov $0x11,%%eax\n\t" //使用eax传递系统调用号,这里break是17 18 19 "int $0x80\n\t" 20 21 "mov %%eax,%0\n\t" //系统调用的返回值使用eax存储,和普通函数一样 22 23 : "=m" (a) 24 25 ); 26 27 } 28 29 } 30 31 printf(“i=%d > 100\n”,i); 32 33 return 0; 34 35 }
截图:
总结:
这次课主要学习的是系统调用,我认为系统调用的工作机制是:当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,由API、中断向量和中断处理程序协调完成。
而在系统调用的过程中,系统调用号使用eax寄存器传递参数。寄存器在传递参数时也有如下限制:
1)每个参数的长度不能超过寄存器的长度,即32位。
2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp),若超过6个,就将某一个寄存器作为指针,指向一块内存,在进入内核态之后,可以访问所有的地址空间,通过内存来传递参数。