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详解的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统调用SYSCALL_DEFINE详解

Linux系统调用SYSCALL_DEFINE详解

Linux系统调用之SYSCALL_DEFINE(转)

Socket内核调用数SYSCALL_DEFINE3

32位系统调用表入口点如何映射到x86_64中的SYSCALL_DEFINE

linux 内核网络发送技术栈