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。