linux系统调用实现机制详解(内核4.14.4)
Posted badman250
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux系统调用实现机制详解(内核4.14.4)相关的知识,希望对你有一定的参考价值。
linux系统调用实现机制详解(内核4.14.4)
https://yq.aliyun.com/articles/522766?spm=a2c4e.11155435.0.0.25d33312xbNbM5
1.1 linux系统调用介绍
linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。和普通库函数调用相似,只是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态。
在Linux中,每个系统调用被赋予一个系统调用号。通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。
系统调用号一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回一ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。
结合具体源码来看下实现机制。
1.2 系统调用表和调用号
具体号子分配在文件arch/x86/entry/syscalls/syscall_64.tbl中定义,如下:
0 common read sys_read
1 common write sys_write
2 common open sys_open
3 common close sys_close
………
30 common shmat sys_shmat
31 common shmctl sys_shmctl
32 common dup sys_dup
33 common dup2 sys_dup2
34 common pause sys_pause
35 common nanosleep sys_nanosleep
36 common getitimer sys_getitimer
37 common alarm sys_alarm
38 common setitimer sys_setitimer
39 common getpid sys_getpid
40 common sendfile sys_sendfile64
41 common socket sys_socket
…….
也可以在arch/x86/include/generated/uapi/asm/unistd_64.h文件中查找到系统调用号。
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
#define __NR_lseek 8
……
1.2 系统调用声明
在文件(include/linux/syscalls.h)中定义了系统调用函数声明,函数声明中的asmlinkage限定词,这用于通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。例如系统调用getpid()在内核中被定义成sys_ getpid。这是Linux中所有系统调用都应该遵守的命名规则.
如下:
asmlinkage long sys_kill(pid_t pid, int sig);
1.3 系统调用实现
不同的系统调用实现在不同的文件中,例如sys_read 系统调用实现在fs/read_write.c文件中,sys_socket定义在net/socket.c中。
例如sys_socket的原型如下:
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
其中3表示有3个参数,用于解析参数时候使用。
查看宏SYSCALL_DEFINE3的定义,定义也在include/linux/syscalls.h中,如下:
#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__)
#define SYSCALL_DEFINE_MAXARGS 6
#define SYSCALL_DEFINEx(x, sname, ...) \\
SYSCALL_METADATA(sname, x, __VA_ARGS__) \\
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \\
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \\
__attribute__((alias(__stringify(SyS##name)))); \\
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \\
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \\
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \\
\\
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \\
__MAP(x,__SC_TEST,__VA_ARGS__); \\
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \\
return ret; \\
\\
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
我们看到SYSCALL_DEFINE3指向SYSCALL_DEFINEx,而SYSCALL_DEFINEx指向__SYSCALL_DEFINEx,在__SYSCALL_DEFINEx宏中调用真正的原型,如sys_socket(其也定义在syscalls.h)。
所以SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 就是sys_socket函数。具体实现后续会在linux协议栈中进行介绍。
置于为什么会这么复杂,因为linux发展过程中难免碰到各种漏洞,有些则是因为修改漏洞需要,例如CVE-2009-0029漏洞
https://bugzilla.redhat.com/show_bug.cgi?id=479969
1.4 系统调用总接口
之前在arch/x86/kernel/entry_64.S中实现了system_call的系统调用总接口。根据系统参数参数号来执行具体的系统调用。
现在所有socket相关的系统调用,都会使用sys_socketcall的系统调用,如下socketcall的代码片段,根据参数进入switch…case…判断操作码,跳转至对应的系统接口:
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
……
switch (call)
case SYS_SOCKET:
err = sys_socket(a0, a1, a[2]);
break;
case SYS_BIND:
err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_LISTEN:
err = sys_listen(a0, a1);
break;
case SYS_ACCEPT:
err = sys_accept4(a0, (struct sockaddr __user *)a1,
(int __user *)a[2], 0);
break;
case SYS_GETSOCKNAME:
err =
sys_getsockname(a0, (struct sockaddr __user *)a1,
(int __user *)a[2]);
break;
case SYS_GETPEERNAME:
err =
sys_getpeername(a0, (struct sockaddr __user *)a1,
(int __user *)a[2]);
这里的变量定义在文件include/uapi/linux/net.h中,如下
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
#define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */
#define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
1.5 系统调用流程
Linux内核MMU机制实现讲解
Linux 内核Linux 内核特性 ( 组织形式 | 进程调度 | 内核线程 | 多平台虚拟内存管理 | 虚拟文件系统 | 内核模块机制 | 定制系统调用 | 网络模块架构 )