系统调用
1.系统调用和普通函数完全不同,系统调用实际上是0x80号中断对应的中断处理程序的子程序。换句话说,在linux系统上,0x80中断是系统调用的统一入口。某个具体的系统调用是这个中断处理程序的子程序,进入具体某个系统调用是通过内核定义的系统调用号码来实现的。linux通过执行如下汇编代码陷入内核执行系统调用:int 0x80; //这一句是进入系统调用统一入口。
2.每个系统调用在内核里面都对应一个号码,这个号码是在/usr/include/i386-linux-gnu/asm/unistd_32.h中定义的。如下图,图1所示
图1 内核定义系统调用号码
在执行"int 0x80;"进行中断之前,应用层会做如下准备工作:
1.把系统调用号码赋值给寄存器EAX;
2.把系统调用需要的参数按次序赋值给寄存器EBX,ECX,EDX等等。
这样,等下0x80中断发生的时候,系统调用需要的全部信息就能通过这些寄存器传递给中断处理程序了。
注:实际上系统调用需要的参数也可以使用应用程序的栈传入内核。稍后实验环节可以看到,这个不用太纠结。
系统调用执行流程
如下图图2:
图2 系统调用执行流程
ssize_t write(int fd, const void *buf, size_t count);
系统调用举例
原型:例子:
write(1,"abc\n",5); //往屏幕上打印"abc\n"。注意,buf里面是5个字符,第5个是字符串结尾0
1.根据/usr/include/i386-linux-gnu/asm/unistd_32.h的宏定义,我们可以得出write()在linux上的系统调用号是4;
2.write()需要3个参数;
3.在应用层,把系统调用号4赋给EAX。
movl $4, (%eax);
4.在应用层,把fd(1是屏幕输出)赋给EBX。
movl $1, %ebx;
5.在应用层,把buf首地址赋给ECX。
movl $.LC0, %ecx (下面实验环节可看到$.LC0对应buf首地址);
6.在应用层,把buf携带的有效数据长度赋给EDX。
movl $5, %edx;
7.陷入内核,进入系统调用统一入口。
int $0x80;
8.内核执行write()系统调用;
9.系统调用返回。
系统调用实验
1.原始C程序
图3 write.c
C程序执行结果,如图4
图4
2.使用gcc将C程序编译为汇编
gcc -S write.c
会生成write.s,汇编内容如图5
图5 write.s
直接编译write.s,生成a.out,并执行。如图6
图6
3. 现在我们自己写一个
汇编mywrite.s,执行write系统调用。
汇编内容如图7
mywrite.s编译后执行结果,如图8
汇编内容如图7
图7 mywrite.s
mywrite.s编译后执行结果,如图8
图8
现在,我们成功使用int 0x80进行了系统调用。