Linux系统调用SYSCALL_DEFINE详解
Posted 彼 方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统调用SYSCALL_DEFINE详解相关的知识,希望对你有一定的参考价值。
Linux系统调用SYSCALL_DEFINE详解
Linux源码可以去这里 https://mirrors.edge.kernel.org/pub/linux/kernel/ 下载,本文是基于
linux-2.6.34
版本来讲解的,老版本代码比较简洁,更容易看懂。
学过Linux系统编程的小伙伴应该都知道,Linux的系统调用在内核中的入口函数都是 sys_xxx ,但是如果我们拿着内核源码去搜索的话,就会发现根本找不到 sys_xxx 的函数定义,这是因为Linux的系统调用对应的函数全部都是由 SYSCALL_DEFINE 相关的宏来定义的。 SYSCALL_DEFINE 相关的宏的实现如下:
#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
不用看下面的代码也能大概猜的出来这里的 SYSCALL_DEFINEx (这里说的SYSCALL_DEFINE0~6这7个宏)里面的x代表的是系统调用参数个数,接下来继续看 SYSCALL_DEFINEx 宏的实现:
#define SYSCALL_DEFINEx(x, sname, ...) \\
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
这又是一个替换的宏定义,继续往下看 __SYSCALL_DEFINEx 宏的实现:
#define __SYSCALL_DEFINEx(x, name, ...) \\
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \\
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \\
asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \\
\\
__SC_TEST##x(__VA_ARGS__); \\
return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \\
\\
SYSCALL_ALIAS(sys##name, SyS##name); \\
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
可以看到, __SYSCALL_DEFINEx 宏里面定义的东西有点多,看起来有点杂,我们这里就以socket为例来说明。sys_socket 函数的声明在linux-2.6.34\\include\\linux\\syscalls.h
中,如下所示:
asmlinkage long sys_socket(int, int, int);
可以看到它有三个参数,对应的就是前面的 SYSCALL_DEFINE3 ,我们在linux-2.6.34\\net\\socket.c
可以找到如下定义:
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
code...
可以看到确实使用了 SYSCALL_DEFINE3 ,在宏中##表示的是字符串连接符,__VA_ARGS__代表前面…里面的可变参数,将上面的代码展开如下:
SYSCALL_DEFINEx(3, _socket, int, family, int, type, int, protocol)
code...
再进一步展开之后的结果如下:
asmlinkage long sys_socket(__SC_DECL3(__VA_ARGS__)); \\
static inline long SYSC_socket(__SC_DECL3(__VA_ARGS__)); \\
asmlinkage long SyS_socket(__SC_LONG3(__VA_ARGS__)) \\
\\
__SC_TEST3(__VA_ARGS__); \\
return (long) SYSC_socket(__SC_CAST3(__VA_ARGS__)); \\
\\
SYSCALL_ALIAS(sys_socket, SyS_socket); \\
static inline long SYSC_socket(__SC_DECL3(__VA_ARGS__))
code...
第一行的代码就是我们期盼的 sys_socket 了,但是这个只是函数声明而已,向下接着看,定义其实在最后一行,结尾没有加分号,下面再加上一对大括号已经对应的实现代码(这里省略了),这可不就是定义吗。但是这个函数的名字有点不对劲啊,怎么是 SYSC_socket 呢,不应该是 sys_socket 吗?
这里其实是借助了 SYSCALL_ALIAS ,根据名字就可以知道,这个宏定义的意思其实就是将 SyS_socket 的别名设为 sys_socket ,也就是说调用 sys_socket 其实就是在调用 SyS_socket ,而我们可以看到 SyS_socket 中调用了 SYSC_socket ,结果这一系列的折腾最终还是可以调用到真实的函数。 SYSCALL_ALIAS 定义如下:
#define SYSCALL_ALIAS(alias, name) \\
asm ("\\t.globl " #alias "\\n\\t.set " #alias ", " #name "\\n" \\
"\\t.globl ." #alias "\\n\\t.set ." #alias ", ." #name)
综上,我们可以得出,当我们调用 sys_socket 时,事实上是调用了 SyS_socket ,而 SyS_socket 里面又会调用 SYSC_socket 。从正常的思维来看的话,***SyS_socket ***和 SyS_socket 的传入参数应该得是一样的,事实上它们并不一样(使用的宏不一样),对应的处理可变参的宏实现如下:
#define __SC_DECL1(t1, a1) t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)
#define __SC_LONG1(t1, a1) long a1
#define __SC_LONG2(t2, a2, ...) long a2, __SC_LONG1(__VA_ARGS__)
#define __SC_LONG3(t3, a3, ...) long a3, __SC_LONG2(__VA_ARGS__)
#define __SC_LONG4(t4, a4, ...) long a4, __SC_LONG3(__VA_ARGS__)
#define __SC_LONG5(t5, a5, ...) long a5, __SC_LONG4(__VA_ARGS__)
#define __SC_LONG6(t6, a6, ...) long a6, __SC_LONG5(__VA_ARGS__)
#define __SC_CAST1(t1, a1) (t1) a1
#define __SC_CAST2(t2, a2, ...) (t2) a2, __SC_CAST1(__VA_ARGS__)
#define __SC_CAST3(t3, a3, ...) (t3) a3, __SC_CAST2(__VA_ARGS__)
#define __SC_CAST4(t4, a4, ...) (t4) a4, __SC_CAST3(__VA_ARGS__)
#define __SC_CAST5(t5, a5, ...) (t5) a5, __SC_CAST4(__VA_ARGS__)
#define __SC_CAST6(t6, a6, ...) (t6) a6, __SC_CAST5(__VA_ARGS__)
#define __SC_TEST(type) BUILD_BUG_ON(sizeof(type) > sizeof(long))
#define __SC_TEST1(t1, a1) __SC_TEST(t1)
#define __SC_TEST2(t2, a2, ...) __SC_TEST(t2); __SC_TEST1(__VA_ARGS__)
#define __SC_TEST3(t3, a3, ...) __SC_TEST(t3); __SC_TEST2(__VA_ARGS__)
#define __SC_TEST4(t4, a4, ...) __SC_TEST(t4); __SC_TEST3(__VA_ARGS__)
#define __SC_TEST5(t5, a5, ...) __SC_TEST(t5); __SC_TEST4(__VA_ARGS__)
#define __SC_TEST6(t6, a6, ...) __SC_TEST(t6); __SC_TEST5(__VA_ARGS__)
这些宏有嵌套展开的过程(这里再一次突出了宏的重要性,C/C++程序员一定掌握宏的使用,不能看到书里说不要用宏就真的相信然后到哪都说写代码不能宏…),展开过程如下:
__SC_DECL3(int, family, int, type, int, protocol)
-> int family, __SC_DECL2(int, type, int, protocol)
-> int family, int type, __SC_DECL1(int, protocol)
-> int family, int type, int protocol
__SC_LONG3(int, family, int, type, int, protocol)
-> long family, __SC_LONG2(int, type, int, protocol)
-> long family, long type , __SC_LONG1(int, protocol)
-> long family, long type, long protocol
__SC_CAST3(int, family, int, type, int, protocol)
-> (int) family, __SC_CAST2(int, type, int, protocol)
-> (int) family, (int) type, __SC_CAST1(int, protocol)
-> (int) family, (int) type, (int) protocol
__SC_TEST3(int, family, int, type, int, protocol)
-> __SC_TEST(int); __SC_TEST2(int, type, int, protocol)
-> __SC_TEST(int); __SC_TEST(int); __SC_TEST1(int, protocol)
-> __SC_TEST(int); __SC_TEST(int); __SC_TEST(int);
-> BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long));
将这些东西代入前面展开的那一堆代码中,就能得到如下的比较清晰明了的代码了:
asmlinkage long sys_socket(int family, int type, int protocol); \\
static inline long SYSC_socket(int family, int type, int protocol); \\
asmlinkage long SyS_socket(long family, long type, long protocol) \\
\\
BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long)); \\
return (long) SYSC_socket((int) family, (int) type, (int) protocol); \\
\\
SYSCALL_ALIAS(sys_socket, SyS_socket); \\
static inline long SYSC_socket(int family, int type, int protocol)
code...
到这里总算是看明白了,其实里面做的工作,就是将系统调用的参数统一变为了使用long类型来接收,然后再强转为本来参数类型。据说这样折腾是为了修复一个漏洞来的。
以上是关于Linux系统调用SYSCALL_DEFINE详解的主要内容,如果未能解决你的问题,请参考以下文章