欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

5. Linux的系统调用

程序员文章站 2024-03-20 13:17:22
...

1. 系统调用接口

系统调用接口(syscalls)是Linux内核与上层应用程序进行交互通信的唯一接口,如图5-4。从中断机制得知,用户程序通过直接或间接(通过库函数)调用中断int 0x80,并在eax寄存器指定系统调用功能号。不过通常应用程序都是通过标准接口定义的C函数库中的函数间接使用内核的系统调用,见图5-19。

5. Linux的系统调用

通常,系统调用使用函数形式进行,因此可带多个参数。对于系统调用执行的结果,它会在返回值中表示。通常负值表示错误,0表示成功。在出错时,错误的类型码被存放在全局变量errno中。通过库函数perror(),可以打印该错误码对应的出错字符串信息。

Linux内核中,每个系统调用都有唯一的系统调用功能号。定义在include/unistd.h第62行开始。这些系统调用功能号对应于include/linux/sys.h中定义的系统调用处理程序指针数组表sys_call_table[]中项的索引值。

用户程序想要使用这些系统调用符号时,需要像下面在包含进文件“<unistd.h>”之前定义符号“_LIBRARY_”。

#define _LIBRARY_
#include <unistd.h>

2. 系统调用处理过程

当应用程序通过中断调用int 0x80时,开始执行一个系统调用。eax存放系统调用号,参数可以一次放在ebx、ecx和edx中。所以Linux 0.12内核种用户程序可以向内核最多直接传递3个参数。处理器系统调用中断int 0x80过程是程序kernel/system_call.s中的system_call。

为方便系统调用,内核在include/unistd.h文件(150~200行)中定义了宏函数_syscalln(),其中n代表携带的参数个数,可以是0~3。若需要传递大块数据,可以传递指针。例如read()系统调用,其定义是:

int read(int fd, char *buf, int n)

若在用户程序中直接执行对应的系统调用,那么该系统调用的宏的形式是:

#define _LIBRARY
#include <unistd.h>

_syscall3(int, read, int, fd, char *, buf, int, n)

因此可以在用户程序中直接使用上面的_syscall3()来执行一个系统调用read(),而不用通过C函数库作中介。C函数库中函数最终调用系统调用形式和这里给出的完全一致。

对于include/unistd.h中给出的每个系统调用宏,都有2+2Xn个参数。其中第一个参数对应系统调用返回值类型;第二个参数是系统调用的名称;随后是参数的类型和名称。这个宏会被扩展成包含内嵌汇编语句的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)中返回实际读取的字节数。若返回小于0,表示操作出错,于是将出错号取反存入全局变量errno中,并向调用程序返回-1。

多个参数的系统调用,内核通常将这些参数作为一个参数缓冲块,并把这个缓冲块的指针作为一个参数传给内核。所以使用宏_syscall1()。

当进入内核中的系统调用处理程序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,然后用得到的值从表中获取被调用处理函数的地址。

3. Linux系统调用的参数传递方式

上面的寄存器传递参数的方法有个明显优点:当进入系统中断服务程序而保存寄存器值时,这些传递参数的寄存器也会被自动放在内核态堆栈上,因此不用专门对传递参数的寄存器进行特殊处理。

另一种使用Intel CPU提供的系统调用门参数传递方法,它在进程用户态堆栈和内核态堆栈自动复制传递的参数。