Linux kernel: syscall

Linux 上对系统调用的封装

Linux在内核中提供了诸多系统调用,Linux kernel是如何封装一个系统调用?具体我们如何使用,常见的命令又是如何使用系统调用的呢?

Kernel使用一个SYSCALL_DEFINEx来封装源代码中的调用。

应用程序有四种调用的方式:

  • glibc库函数

  • syscall函数

  • linux系统调用宏

  • 软中断陷入

flowchart TB
    系统调用接口 ---> 系统调用
    subgraph 用户态
        应用程序 ---> |glibc库函数|glibc库函数
        应用程序 ---> |syscall函数|glibc库函数 ---> 系统调用接口
        应用程序 ---> linux系统调用宏 ---> 系统调用接口
        应用程序 ---> 软中断陷入 ---> 系统调用接口
    end
    subgraph 内核态
        系统调用
    end

Kernel的封装

通过SYSCALL_DEFINEx宏定义去封装一个系统调用,对应的架构在makefile中,使用tbl(table)来将一个方法制作成调用:xxx -> SYS_xxx。

#define __SYSCALL_DEFINEx(x, name, ...)                 \
    __diag_push();                          \
    __diag_ignore(GCC, 8, "-Wattribute-alias",            \
              "Type aliasing is used to sanitize syscall arguments");\
    asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))   \
        __attribute__((alias(__stringify(__se_sys##name))));    \
    ALLOW_ERROR_INJECTION(sys##name, ERRNO);            \
    static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
    asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
    asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))  \
    {                               \
        long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
        __MAP(x,__SC_TEST,__VA_ARGS__);             \
        __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));   \
        return ret;                     \
    }                               \
    __diag_pop();                           \
    static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
#endif /* __SYSCALL_DEFINEx */

在x86_64架构的syscall_64.tbl中定义了:

#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
165 common  mount           sys_mount

这样就将前面使用SYSCALL_DEFINE5封装的mount,对外暴露为sys_mount

glibc库函数

linux的系统调用由glibc提供封装,一个常用的方法可能包含多个不同的系统调用(printf: sys_open, sys_mmap, sys_write, sys_close ...);简单的调用是直接封装的(open: sys_open);多个glibc的方法可能对应一个系统调用(malloc, calloc, free: sys_brk)。

通过查看glibc源码(glibc-2.35),这部分是通过make-syscalls.sh脚本生成的,这个脚本将通用的Unix系统与Linux系统调用和对应架构上的系统调用使用汇编进行封装,所有的系统调用列表存放在sysdeps/unix各级目录下面的syscalls.list里面。

例如sysdeps/unix/syscalls.list

# File name Caller  Syscall name    Args    Strong name Weak names

accept      -   accept      Ci:iBN  __libc_accept   accept
access      -   access      i:si    __access    access
acct        -   acct        i:S acct
adjtime     -   adjtime     i:pp    __adjtime   adjtime
bind        -   bind        i:ipi   __bind      bind
chdir       -   chdir       i:s     __chdir     chdir
chmod       -   chmod       i:si    __chmod     chmod
chown       -   chown       i:sii   __chown     chown
chroot      -   chroot      i:s chroot
close       -   close       Ci:i    __libc_close    __close close
connect     -   connect     Ci:ipi  __libc_connect  __connect connect
dup         -   dup         i:i     __dup       dup
dup2        -   dup2        i:ii    __dup2      dup2
dup3        -   dup3        i:iii   __dup3      dup3
fchdir      -   fchdir      i:i     __fchdir    fchdir
fchmod      -   fchmod      i:ii    __fchmod    fchmod
fchown      -   fchown      i:iii   __fchown    fchown
fcntl       -   fcntl       Ci:iiF  __libc_fcntl    __fcntl fcntl
fstatfs     -   fstatfs     i:ip    __fstatfs   fstatfs
ftruncate   -   ftruncate   i:ii    __ftruncate ftruncate
getdomain   -   getdomainname   i:si    getdomainname
getgid      -   getgid      Ei:     __getgid    getgid
getgroups   -   getgroups   i:ip    __getgroups getgroups
gethostid   -   gethostid   i:  gethostid
gethostname -   gethostname i:bn    __gethostname   gethostname
getpeername -   getpeername i:ibN   __getpeername   getpeername
getpid      -   getpid      Ei:     __getpid    getpid
getpriority -   getpriority i:ii    __getpriority   getpriority
getrlimit   -   getrlimit   i:ip    __getrlimit getrlimit
getsockname -   getsockname i:ibN   __getsockname   getsockname
getsockopt  -   getsockopt  i:iiiBN getsockopt
getuid      -   getuid      Ei:     __getuid    getuid
ioctl       -   ioctl       i:iiI   __ioctl     ioctl __ioctl_time64
kill        -   kill        i:ii    __kill      kill
link        -   link        i:ss    __link      link
listen      -   listen      i:ii    __listen    listen
lseek       -   lseek       i:iii   __libc_lseek    __lseek lseek
madvise     -   madvise     i:pUi   __madvise   madvise
mkdir       -   mkdir       i:si    __mkdir     mkdir
mmap        -   mmap        b:aUiiii __mmap     mmap
mprotect    -   mprotect    i:aUi   __mprotect  mprotect
munmap      -   munmap      i:aU    __munmap    munmap
open        -   open        Ci:siv  __libc_open __open open
profil      -   profil      i:piii  __profil    profil
ptrace      -   ptrace      i:iiii  ptrace
read        -   read        Ci:ibU  __libc_read __read read
readlink    -   readlink    i:spU   __readlink  readlink
readv       -   readv       Ci:ipi  __readv     readv
reboot      -   reboot      i:i reboot
recv        -   recv        Ci:ibUi __libc_recv recv
recvfrom    -   recvfrom    Ci:ibUiBN   __libc_recvfrom __recvfrom recvfrom
recvmsg     -   recvmsg     Ci:ipi  __libc_recvmsg  __recvmsg recvmsg
rename      -   rename      i:ss    rename
rmdir       -   rmdir       i:s     __rmdir     rmdir
select      -   select      Ci:iPPPP    __select    __libc_select select
send        -   send        Ci:ibUi __libc_send __send send
sendmsg     -   sendmsg     Ci:ipi  __libc_sendmsg  __sendmsg sendmsg
sendto      -   sendto      Ci:ibUibn   __libc_sendto   __sendto sendto
setdomain   -   setdomainname   i:si    setdomainname
setegid     -   setegid     i:i     __setegid   setegid
seteuid     -   seteuid     i:i     __seteuid   seteuid
setgid      -   setgid      i:i     __setgid    setgid
setgroups   -   setgroups   i:ip    setgroups
sethostid   -   sethostid   i:i sethostid
sethostname -   sethostname i:pi    sethostname
setpgid     -   setpgrp     i:ii    __setpgid   setpgid
setpriority -   setpriority i:iii   __setpriority   setpriority
setregid    -   setregid    i:ii    __setregid  setregid
setreuid    -   setreuid    i:ii    __setreuid  setreuid
setrlimit   -   setrlimit   i:ip    __setrlimit setrlimit
setsid      -   setsid      i:      __setsid    setsid
setsockopt  -   setsockopt  i:iiibn setsockopt  __setsockopt
setuid      -   setuid      i:i     __setuid    setuid
shutdown    -   shutdown    i:ii    shutdown
sigsuspend  -   sigsuspend  Ci:p    sigsuspend
socket      -   socket      i:iii   __socket    socket
socketpair  -   socketpair  i:iiif  socketpair
statfs      -   statfs      i:sp    __statfs    statfs
swapoff     -   swapoff     i:s swapoff
swapon      -   swapon      i:s swapon
symlink     -   symlink     i:ss    __symlink   symlink
sync        -   sync        i:  sync
syncfs      -   syncfs      i:i syncfs
truncate    -   truncate    i:si    __truncate  truncate
umask       -   umask       Ei:i    __umask     umask
uname       -   uname       i:p     __uname     uname
unlink      -   unlink      i:s     __unlink    unlink
utimes      -   utimes      i:sp    __utimes    utimes
vhangup     -   vhangup     i:i vhangup
write       -   write       Ci:ibU  __libc_write    __write write
writev      -   writev      Ci:ipi  __writev    writev

然后根据syscall-template.S这个汇编生成对应的系统调用,使用对应架构中的宏定义生成汇编代码来制作可执行文件:

    (echo '#define SYSCALL_NAME $syscall'; \\
     echo '#define SYSCALL_NARGS $nargs'; \\
     echo '#define SYSCALL_ULONG_ARG_1 $ulong_arg_1'; \\
     echo '#define SYSCALL_ULONG_ARG_2 $ulong_arg_2'; \\
     echo '#define SYSCALL_SYMBOL $strong'; \\
     echo '#define SYSCALL_NOERRNO $noerrno'; \\
     echo '#define SYSCALL_ERRVAL $errval'; \\
     echo '#include <syscall-template.S>'; \\"
  ;;
  esac

syscall 函数

使用glibc提供的syscall函数编写系统调用:

// posix/unistd.h
#ifdef __USE_MISC
/* Invoke `system call' number SYSNO, passing it the remaining arguments.
   This is completely system-dependent, and not often useful.

   In Unix, `syscall' sets `errno' for all errors and most calls return -1
   for errors; in many systems you cannot pass arguments or get return
   values for all system calls (`pipe', `fork', and `getppid' typically
   among them).

   In Mach, all system calls take normal arguments and always return an
   error code (zero for success).  */
extern long int syscall (long int __sysno, ...) __THROW;

#endif  /* Use misc.  */

// sysdeps/unix/sysv/linux/syscall.c
long int
syscall (long int number, ...)
{
  va_list args;

  va_start (args, number);
  long int a0 = va_arg (args, long int);
  long int a1 = va_arg (args, long int);
  long int a2 = va_arg (args, long int);
  long int a3 = va_arg (args, long int);
  long int a4 = va_arg (args, long int);
  long int a5 = va_arg (args, long int);
  va_end (args);

  int r = INTERNAL_SYSCALL_NCS_CALL (number, a0, a1, a2, a3, a4, a5);
  if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (r)))
    {
      __set_errno (-r);
      return -1;
    }
  return r;
}

sysno为系统调用号

返回系统调用的返回值,调用失败返回-1

linux系统调用宏

这部分在内核通过宏定义实现,x86架构的汇编直接写在nolibc.h中,其他架构的汇编写在对应架构中(注释说的,但是咱没找到)。

#define my_syscall0(num)                                                      \
({                                                                            \
    long _ret;                                                            \
    register long _num asm("eax") = (num);                                \
                                          \
    asm volatile (                                                        \
        "int $0x80\n"                                                 \
        : "=a" (_ret)                                                 \
        : "0"(_num)                                                   \
        : "memory", "cc"                                              \
    );                                                                    \
    _ret;                                                                 \
})

软中断陷入

其实glibc的实现都是软中断int $0x80的封装,以chmod为例,创建一个test文件。

touch test
ll 
-rw-r--r-- 1 oripoin oripoin 0 Aug 12 12:45 test

使用内联汇编调用`SYS_chmod

#include <stdio.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/types.h>

int main()
{
    long rc;
    unsigned short mode = 0777;
    char *file_name = "./test";
    asm(
            "int $0x80"
            : "=a" (rc)
            : "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
    );
    if (rc == -1)
       perror("SYS_chmod chmod fail\n");
    else
        printf("SYS_chmod chmod succeed\n");
    return 0;
}
ll
-rwxr-xr-x 1 oripoin oripoin 16K Aug 12 12:45 chmod_test*
-rw-r--r-- 1 oripoin oripoin 522 Aug 12 12:45 main.cpp
-rw-r--r-- 1 oripoin oripoin   0 Aug 12 12:45 test
知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
上一篇
下一篇