写这篇文章是看到一个as汇编器里编写的汇编代码,有一个指令int 80h没有搞懂,然后自己查资料发现不少东西,本文旨在浅显的分析linux的系统调用,主要是linux0.12内核来说!
目前操作系统内核的结构模式主要分为整体式的单内核和层次式的微内核模式。而0.12内核是单内核模式。在单内核模式的系统中,操作系统提供服务的流程:应用程序使用指定的参数值执行系统调用指令(int 80h),使CPU从用户态切换到内核态,然后操作系统根据具体的参数值调用特定的服务程序,而这些服务程序则根据需要再调用底层的一些支持函数以完成特定的功能。在完成了应用程序要求的服务之后,操作系统又降CPU从核心态切换回用户态,从而返回到应用程序中继续执行后面的指令。因此概要的讲,单内核模式的内核分为3个层次:调用服务的应用程序、执行系统调用的服务层、支持系统调用的底层函数。
1.系统调用接口
系统调用(通常称为syscalls)接口是Linux内核和上层应用程序进行交互通信的唯一接口。应用程序、库函数和内核系统调用的关系如下:
用户程序通过直接或间接(通过库函数)调用中断int 0x80,并在寄存器eax中指定系统调用功能号,即可使用内核资源,包括系统硬件资源。不过通常应用程序都是使用具有标准接口定义的C函数库中的函数间接地使用内核的系统调用。通常,系统调用使用函数形式进行调用,因此可以带有一个或者多个参数。对于系统调用执行的结果,它会在返回值中表示出来。
在Linux内核中,每个系统调用都有一个唯一的系统调用功能号。这些功能号定义在include/unistd.h。例如write系统调用的功能号是4,定义为符号__NR_write。这些系统调用功能号实际上对应于include/linux/sys.h中定义的系统调用处理程序指针数组表sys_call_table[]中项的索引。因此write系统调用用的处理程序指针就位于该数组的第四项。
当我们想在自己的程序中使用这些系统调用符号,需要像下面所示在包括文件
2.系统调用处理过程
当应用程序经过库函数向内核发出一个中断调用int 0x80时,就开始一个系统调用。其中寄存器eax中存放着系统调用号(比如调用write,eax就存储4),而携带的参数一次放在寄存器ebx(第一个参数)、ecx(第二个参数)、edx(第二个参数)中,因此Linux0.12内核中用户程序可以向内核最多传递3个参数,当然也可以不带参数。处理系统调用中断0x80的过程是在程序kernel/system_call.s中的system_call。
为了方便执行系统调用,内核源码在include/unistd.h文件中定义了宏函数_syscalln(),其中n代表携带参数的个数,可以是0-3。
例如对于read()系统调用,其定义是int read(int fd,char *buf,int n)
若在用户程序中直接执行对应的系统调用,那么该系统调用的宏的形式如下:
#define _LIBRARY_
#include
_syscall3(int,read,int,fd,char*,buf,int,n)
因此,我们可以在用户程序中直接使用上面的_syscall3()来执行一个系统调用read(),而不用通过C函数库作中介。实际上,C函数库中函数最终调用的形式和这里给出的完全一样。这里我们看到,每个系统调用宏都有2+2*n个参数,第一个参数对应返回值类型,第二个参数是系统调用的名称,随后是系统调用所携带参数的类型和名称,这个宏会被扩展成包含内嵌汇编的c函数,如下所示:
int read(int fd,char *buf,int n)
{
long __res;
__asm__ volatile(
"int $0x80"
:"=a" (__res)
:"0" (__NR_read),"b" ((long)(fd),"c" ((long)(buf),"d" ((long)(n)));
if(__res>=0)
return int __res;
errno=-__res;
return -1;
}
可以看出这个宏经过展开就是一个读系统调用的具体实现。其中使用嵌入式汇编语法以功能号__NR_read(3)执行了Linux系统中断调用0x80。该中断调用在eax(__res)寄存器中返回了实际读取的字节数。
可以看出,这个宏经过展开就是一个读操作系统调用的具体实现。其中使用了嵌入式汇编语句以功能号_NR_read(3)执行了Linux的系统中断调用0x80,当进入内核中的系统调用处理程序kernel/sys_call.s后,system_call的代码会首先检查eax中的系统调用功能号是否在有效系统调用号范围内,然后根据sys_call_table[]函数指针表调用执行相应的系统调用处理程序。
call _sys_call_table(,%eax,4)
这句汇编操作数的意思是间接调用地址在_sys_call_table+%eax*4处的函数。由于sys_call_table[]指针每项4字节,因此这里需要给系统调用功能号乘以4。