赞
踩
我们以mount
系统调用为例, 自顶向下分析它的实现
最外层的mount
系统调用定义如下
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data)
{
return ksys_mount(dev_name, dir_name, type, flags, data);
}
其中ksys_mount()
是mount
功能的具体实现, 与系统调用本身的定义关系不大, 涉及系统调用定义最关键的就是宏定义SYSCALL_DEFINE5
, 我们继续看SYSCALL_DEFINE5
的实现, 为方便阅读我删除了无关紧要的代码
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
可以看到SYSCALL_DEFINE5
最终会调用__SYSCALL_DEFINEx
, 而__SYSCALL_DEFINEx
也是一个宏定义, 该定义的实现是与架构相关的, ARM64的实现在/arch/arm64/include/asm/syscall_wrapper.h
, 同样为方便阅读我删除了无关紧要的代码
#define __SYSCALL_DEFINEx(x, name, ...) \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \
{ \
return __do_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \
} \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
可以看到__SYSCALL_DEFINEx
实现了一个函数__arm64_sys##name
, 对应本例是__arm64_sys_mount
, 这个函数就是我们的系统调用, 系统调用表sys_call_table
中存放的就是这个函数的地址; 实际上__arm64_sys_mount
系统调用的实现就是直接调用了__do_sys_mount
, 而__SYSCALL_DEFINEx
也帮我们写好了函数__do_sys_mount
定义的开头部分
让我们回到最开头的系统调用宏SYSCALL_DEFINE5
, 现在我们可以把这个宏展开如下
static inline long __do_sys_mount(char __user * dev_name, char __user * dir_name, char __user * type, unsigned long flags, void __user * data);
asmlinkage long __arm64_sys_mount(const struct pt_regs *regs)
{
return __do_sys_mount(dev_name, dir_name, type, flags, data);
}
static inline long __do_sys_mount(char __user * dev_name, char __user * dir_name, char __user * type, unsigned long flags, void __user * data)
{
return ksys_mount(dev_name, dir_name, type, flags, data);
}
可以看到实际系统调用的实现如下
__arm64_sys_mount(const struct pt_regs *regs)
|--> __do_sys_mount(dev_name, dir_name, type, flags, data)
|--> ksys_mount(dev_name, dir_name, type, flags, data)
与老版本内核不同的是4.19内核的所有系统调用都只有一个参数:const struct pt_regs *regs
(事实上这是从4.17内核开始做的改动), 并且通过宏SC_ARM64_REGS_TO_ARGS
把regs
中的参数转换成传统的系统调用参数, 实际上就是把regs
中的0-5号寄存器的值分别当做传统系统调用函数的第1-6个参数
至此, 我们简要分析了定义一个系统调用的关键步骤, 当然我们没有过多的讨论具体细节, 有兴趣的读者可以参考下面的资料深入研究
参考资料
https://elixir.bootlin.com/linux/v4.19.274/source/fs/namespace.c#L3052
https://elixir.bootlin.com/linux/v4.19.274/source/include/linux/syscalls.h#L218
https://elixir.bootlin.com/linux/v4.19.274/source/arch/arm64/include/asm/syscall_wrapper.h
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。