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内核中的RCU机制

Linux 内核Linux 内核特性 ( 组织形式 | 进程调度 | 内核线程 | 多平台虚拟内存管理 | 虚拟文件系统 | 内核模块机制 | 定制系统调用 | 网络模块架构 )

Linux 系统调用

Linux 内核编译步骤及配置详解

Linux(内核剖析):21---中断之中断上下文中断处理机制的实现/proc/interrupts