linux系统调用流程浅析

1. 内核部分

1-1. 系统调用函数的定义

系统调用函数的原型定义在内核代码include/linux/syscalls.h中,
除此之外在该头文件中还提供了如下的宏

#define __SC_DECL1(t1, a1)      t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
...
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)

#define SYSCALL_DEFINE(name) asmlinkage long sys_##name
#define __SYSCALL_DEFINEx(x, name, ...)                                 \
        asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

#define SYSCALL_DEFINEx(x, sname, ...)                          \
        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
...
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

系统调用函数的定义分散在内核的各个模块,内核的各个模块使用上面宏构建系统调用函数。

以socket相关的系统调用函数为例,

socket相关的系统调用函数定义在net/socket.c中,有一个共通的接口,其定义如下:
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
该函数编译展开后,可以得到如下的函数
asmlinkage long sys_socketcall(int call, unsigned long __user *args)

1-2. 系统调用表

每一个系统调用都有自己的编号,以x86架构的系统为例,
系统调用的编号保存在如下的文件中
arch/x86/include/asm/unistd_64.h
arch/x86/include/asm/unistd_32.h
而系统调用表sys_call_table则定义在如下的文件中
arch/x86/kernel/syscall_table_32.S
arch/x86/kernel/syscall_64.c

根据系统调用编号,可以在系统调用表中找到相应的系统调用函数。

1-3. 系统调用初始化

在x86架构中,系统调用是通过软件中断0x80来触发的。
linux系统在启动时使用如下的方式注册0x80软件中断的处理函数

start_kernel(void)[init/main.c]

`-- trap_init(void)[arch/x86/kernel/traps.c]

     `-- set_system_trap_gate(SYSCALL_VECTOR, &system_call)[arch/x86/include/asm/desc.h]

system_call函数是由汇编实现的,其实现在如下文件中可以找到

arch/x86/kernel/entry_32.S
arch/x86/kernel/entry_64.S

system_call根据传入的系统调用编号结合系统调用表,找到并执行相应的系统调用处理函数。

2. glibc

glibc对系统调用进行了封装,简化了系统调用的使用。以socket相关的系统调用函数为例进行说明。
socket相关的系统调用实际上都是执行内核中的sys_socketcall函数,sys_socketcall会根据传入的参数call决定具体执行哪个处理函数。在内核中call定义在include/linux/net.h中,而在glibc中则提供了一个相同的头文件sysdeps/unix/sysv/linux/socketcall.h

glibc中socket相关的系统调用,共通处理部分定义在如下的文件中
sysdeps/unix/sysv/linux/sh/socket.S
sysdeps/unix/sysv/linux/i386/socket.S

其它socket相关的系统调用函数则定义在sysdeps/unix/sysv/linux目录下

当socket.S未被其它文件引入时socket.S会生成socket函数,而当被其它文件引入时则会生成引入文件对应的函数。
以bind为例,bind.S通过引入socket.S来生成bind函数
sysdeps/unix/sysv/linux/bind.S 

#define	socket	bind
#define	NARGS	3
#define NO_WEAK_ALIAS	1
#include <socket.S>
weak_alias (bind, __bind)

在socket.S中系统调用的编号和call会被传递给内核,内核根据系统调用编号和call决定接下来执行哪个具体的处理函数。当系统调用函数实际上不存在时,则实际会调用socket/socket.c中的socket函数

int      
__socket (domain, type, protocol)
     int domain;
     int type; 
     int protocol;
{       
  __set_errno (ENOSYS);
  return -1;
}

此时就会返回错误,并且将errno设置为ENOSYS。

相关推荐