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

linux系统调用流程浅析 博客分类: linuxkernel系统调用 linuxkernel系统调用

程序员文章站 2024-03-06 14:03:32
...

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。