3.8.1. linux系统调用
3.8.1.1. 系统调用原理
 
系统调用是linux内核为用户态程序提供的主要功能接口,通过系统调用,用户态进程能够临时切换到内核态,使用内核态才能访问的硬件和资源完成特定功能。 系统调用由linux内核和内核模块实现,内核在处理系统时还会检查系统调用请求和参数是否正确,以保证对特权资源和硬件访问的正确性。通过这种方式,linux 在提供内核和硬件资源访问接口的同时,保证了内核和硬件资源的正确性和安全性
不同架构的系统调用实现细节存在差异,但总体上可以归结为以下三个内容:
- 在用户空间触发系统调,通用的库函数libc或glibc面向用户层,通过本身的逻辑处理找到对应的系统调用接口,并通过软中断的方式进入内核系统调用,不同架构下 系统调用软中断的方式不同,x86通过0x80软中断进入内核系统调用处理,arm通过swi指令 
- 不同架构下系统调用中断处理不同,但功能是一致的,那就是通过传递下来的系统调用号,找到系统调用入口信息,并将系统调用传递的参数传递给内核里系统调用实现 
- 内核部分系统调用的实现 
系统调用可以分为六大类
- 进程控制(process control) 
- 文件管理(file manipulation) 
- 设备管理(device manipulation) 
- 信息维护(information maintenance) 
- 通信(conmunication) 
- 保护(protection) 
3.8.1.2. 系统调用用户空间的实现
用户空间触发系统调用的方法很多,包括直接调用汇编命令进行触发,也可以通过”syscall”函数进行触发等,这里重点介绍”syscall”函数.用户空间可以通过
调用 syscall() 函数来触发指定的系统调用,其函数定义如下
#include <sys/syscall.h>
int syscall(int number, ...);
syscall()执行一个系统调用,根据number参数和所有的汇编语言接口来调用哪个系统调用。示例如下
int main(void)
{
    int nice = 0;
    /*
    *   __NR_nice是sys_nice的系统调用号,nice是传递给系统调用的一个参数
    *   该函数会直接调用内核的sys_nice()函数实现修改当前进程的nice值
    */
    syscall(__NR_nice, nice);
}
syscall()函数可以间接的调用系统调用,移除了库函数对调用过程的影响。下面是syscall()在不同架构中的实现
3.8.1.3. arm架构
arm架构将系统调用入口信息放置在 arch/arm/tools/syscall.tbl
#
# Linux system call numbers and entry vectors
#
# The format is:
# <num>     <abi>   <name>                  [<entry point>                  [<oabi compat entry point>]]
#
# Where abi is:
#  common - for system calls shared between oabi and eabi (may have compat)
#  oabi   - for oabi-only system calls (may have compat)
#  eabi   - for eabi-only system calls
#
# For each syscall number, "common" is mutually exclusive with oabi and eabi
#
0   common  restart_syscall         sys_restart_syscall
1   common  exit                    sys_exit
2   common  fork                    sys_fork
3   common  read                    sys_read
4   common  write                   sys_write
5   common  open                    sys_open
6   common  close                   sys_close
# 7 was sys_waitpid
8   common  creat                   sys_creat
9   common  link                    sys_link
10  common  unlink                  sys_unlink
11  common  execve                  sys_execve
12  common  chdir                   sys_chdir
13  oabi    time                    sys_time32
14  common  mknod                   sys_mknod
15  common  chmod                   sys_chmod
16  common  lchown                  sys_lchown16
# 17 was sys_break
# 18 was sys_stat
19  common  lseek                   sys_lseek
20  common  getpid                  sys_getpid
21  common  mount                   sys_mount
22  oabi    umount                  sys_oldumount
23  common  setuid                  sys_setuid16
24  common  getuid                  sys_getuid16
25  oabi    stime                   sys_stime32
26  common  ptrace                  sys_ptrace
27  oabi    alarm                   sys_alarm
# 28 was sys_fstat
29  common  pause                   sys_pause
30  oabi    utime                   sys_utime32
# 31 was sys_stty
# 32 was sys_gtty
33  common  access                  sys_access
34  common  nice                    sys_nice
# 35 was sys_ftime
36  common  sync                    sys_sync
37  common  kill                    sys_kill
38  common  rename                  sys_rename
39  common  mkdir                   sys_mkdir
40  common  rmdir                   sys_rmdir
41  common  dup                     sys_dup
42  common  pipe                    sys_pipe
43  common  times                   sys_times
# 44 was sys_prof
45  common  brk                     sys_brk
46  common  setgid                  sys_setgid16
47  common  getgid                  sys_getgid16
# 48 was sys_signal
49  common  geteuid                 sys_geteuid16
50  common  getegid                 sys_getegid16
51  common  acct                    sys_acct
52  common  umount2                 sys_umount
只需要在表中填入系统调用号,abi类型信息,系统调用名字,系统调用函数名字后就可以确定一个唯一的系统调用入口
3.8.1.4. arm64架构
arm64架构将系统调用入口信息放在 include/uapi/asm-generic/unistd.h
#define __NR_io_setup 0
__SC_COMP(__NR_io_setup, sys_io_setup, compat_sys_io_setup)
#define __NR_io_destroy 1
__SYSCALL(__NR_io_destroy, sys_io_destroy)
#define __NR_io_submit 2
__SC_COMP(__NR_io_submit, sys_io_submit, compat_sys_io_submit)
#define __NR_io_cancel 3
__SYSCALL(__NR_io_cancel, sys_io_cancel)
#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32
#define __NR_io_getevents 4
__SC_3264(__NR_io_getevents, sys_io_getevents_time32, sys_io_getevents)
#endif
/* fs/xattr.c */
#define __NR_setxattr 5
__SYSCALL(__NR_setxattr, sys_setxattr)
#define __NR_lsetxattr 6
__SYSCALL(__NR_lsetxattr, sys_lsetxattr)
#define __NR_fsetxattr 7
__SYSCALL(__NR_fsetxattr, sys_fsetxattr)
#define __NR_getxattr 8
__SYSCALL(__NR_getxattr, sys_getxattr)
#define __NR_lgetxattr 9
__SYSCALL(__NR_lgetxattr, sys_lgetxattr)
#define __NR_fgetxattr 10
__SYSCALL(__NR_fgetxattr, sys_fgetxattr)
#define __NR_listxattr 11
__SYSCALL(__NR_listxattr, sys_listxattr)
#define __NR_llistxattr 12
__SYSCALL(__NR_llistxattr, sys_llistxattr)
#define __NR_flistxattr 13
__SYSCALL(__NR_flistxattr, sys_flistxattr)
#define __NR_removexattr 14
__SYSCALL(__NR_removexattr, sys_removexattr)
#define __NR_lremovexattr 15
__SYSCALL(__NR_lremovexattr, sys_lremovexattr)
#define __NR_fremovexattr 16
__SYSCALL(__NR_fremovexattr, sys_fremovexattr)
只需要在该表中定义一个系统调用号,再通过调__SYSCALL()函数确定系统调用函数信息就可以确定一个唯一的系统调用入口
3.8.1.5. 系统调用内核实现
系统调用内核实现就是系统调用再内核的实现过程,在接收到来自用户空间的系统调用参数之后,内核核心实现就根据自己的逻辑进行处理,处理完毕之后,内核可以将结果返回给用户空间, 也可以拷贝相应的数据用户空间,内核部分的核心实现通过以下宏进行定(位于include/linux/syscalls.h)
#ifndef SYSCALL_DEFINE0
#define SYSCALL_DEFINE0(sname)                                      \
    SYSCALL_METADATA(_##sname, 0);                          \
    asmlinkage long sys_##sname(void);                      \
    ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);              \
    asmlinkage long sys_##sname(void)
#endif /* SYSCALL_DEFINE0 */
#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_DEFINEx(x, sname, ...)                              \
    SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
通过上面的宏,内核会创建一个名为sys_syscall_name()的函数,并定义了参数的类型,例如sys_nice()的定义如下
SYSCALL_DEFINE1(nice, int, increment)
{
    long nice, retval;
    /*
     * Setpriority might change our priority at the same moment.
     * We don't have to worry. Conceptually one call occurs first
     * and we have a single winner.
     */
    increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH);
    nice = task_nice(current) + increment;
    nice = clamp_val(nice, MIN_NICE, MAX_NICE);
    if (increment < 0 && !can_nice(current, nice))
        return -EPERM;
    retval = security_task_setnice(current, nice);
    if (retval)
        return retval;
    set_user_nice(current, nice);
    return 0;
}
3.8.1.6. linux系统调用的列表
- 进程控制 
| 系统调用 | 描述 | 
|---|---|
| fork | 创建一个新进程 | 
| clone | 按指定条件创建子进程 | 
| execve | 运行可执行文件 | 
| exit | 中止进程 | 
| _exit | 立即中止当前进程 | 
| getdtablesize | 进程所能打开的最大文件数 | 
| getpgid | 获取在指定进程组标识号 | 
| setpgid | 设置指定进程组标识号 | 
| getpgrp | 获取当前进程组标识号 | 
| setgprp | 设置当前进程组标识号 | 
| getpid | 获取进程标识号 | 
| getppid | 获取父进程标识号 | 
| getpriority | 获取调度优先级 | 
| setpriority | 设置调度优先级 | 
| modify_ldt | 读写进程的本地描述表 | 
| nanosleep | 使进程睡眠指定的时间 | 
| nice | 改变分时进程的优先级 | 
| pause | 挂起进程,等待信号 | 
| personality | 设置进程运行域 | 
| prctl | 对进程进行特定操作 | 
| ptrace | 进程跟踪 | 
| sched_getparam | 获取进程的调度参数 | 
| sched_getscheduler | 获取指定进程的调度策略 | 
| sched_setscheduler | 设置指定进程的调度策略和参数 | 
| sched_yield | 进程主动让出处理器,并将放在调度队列队尾 | 
| vfork | 创建一个子进程,以供执行新程序,常与execve同时使用 | 
| wait | 等待子进程终止 | 
| waitpid | 等待指定子进程终止 | 
| capget | 获取进程权限 | 
| capset | 设置进程权限 | 
| getsid | 获取会话标识号 | 
| setsid | 设置会话标识号 | 
- 文件系统控制 
| 系统调用 | 描述 | 
|---|---|
| fcntl | 文件控制 | 
| open | 打开文件 | 
| creat | 创建新文件 | 
| close | 关闭文件描述符 | 
| read | 读文件 | 
| write | 写文件 | 
| readv | 从文件读取数据到缓冲数组中 | 
| writev | 将缓冲数组里的数据写入文件 | 
| pread | 对文件进行随机读 | 
| pwrite | 对文件进程随机写 | 
| lseek | 移动文件指针 | 
| _llseek | 在64位地址空间里移动文件指针 | 
| dup | 复制已打开的文件描述字 | 
| dup2 | 按指定条件复制文件描述字 | 
| flock | 文件加/解锁 | 
| poll | I/O多路转换 | 
| truncat | e截断文件 | 
| vumask | 设置文件权限掩码 | 
| fsync | 把文件在内存中的部分写回磁盘 | 
- 文件系统操作 
| 系统调用 | 描述 | 
|---|---|
| acess | 确定文件的可存取性 | 
| chdir | 改变当前工作目录 | 
| chmod | 改变文件权限 | 
| chown | 改变文件的属主或用户组 | 
| chroot | 改变根目录 | 
| stat | 改变文件状态信息 | 
| statfs | 获取文件系统信息 | 
| readdir | 读取目录项 | 
| getdents | 读取目录项 | 
| mkdir | 创建目录 | 
| mknod | 创建索引节点 | 
| rmdir | 删除目录 | 
| rename | 文件改名 | 
| link | 创建软链接 | 
| symlink | 创建符号链接 | 
| unlink | 删除链接 | 
| readlink | 读取符号链接的值 | 
| mount | 挂载文件系统 | 
| unmount | 卸载文件系统 | 
| ustat | 读取文件系统信息 | 
| utime | 改变文件的访问修改时间 | 
| quotactl | 控制磁盘配额 | 
- 系统控制 
| 系统调用 | 描述 | 
|---|---|
| ioctl | I/O总控制函数 | 
| _sysctl | 读写系统参数 | 
| acct | 启动或禁止进程 | 
| getrlimit | 获取系统资源上限 | 
| setrlimit | 设置系统资源上限 | 
| getrusage | 获取系统资源是应用情况 | 
| uselib | 选择要使用的函数库 | 
| ioperm | 设置端口的权限 | 
| iopl | 改变进程I/O权限级别 | 
| outb | 低级端口操作 | 
| reboot | 重新启动 | 
| swapon | 打开交换文件和设置 | 
| swapoff | 关闭交换文件和设备 | 
| bdflush | 控制bdflush守护进程 | 
| sysfs | 获取核心支持的文件系统类型 | 
| sysinfo | 获取系统信息 | 
| adjtimex | 调整系统时钟 | 
| alarm | 设置进程的闹钟 | 
| getitimer | 获取计数器值 | 
| setitimer | 设置计数器值 | 
| gettimeofday | 获取时间和时区 | 
| settimeofday | 设置时间和时区 | 
| stime | 设置系统日期和时间 | 
| time | 获取系统时间 | 
| times | 获取进程运行时间 | 
| uname | 获取当前UNIX系统的名称、版本和主机等信息 | 
| vhangup | 挂起当前终端 | 
| nfsservctl | 对NFS守护进程进行控制 | 
| create_module | 创建可装载的模块项 | 
| delete_module | 删除可装载的模块项 | 
| init_module | 初始化模块 | 
| query_module | 查询模块信息 | 
- 内存管理 
| 系统调用 | 描述 | 
|---|---|
| brk | 改变数据段空间的分配 | 
| mlock | 内存页面加锁 | 
| munlock | 内存页面解锁 | 
| mlockall | 调用进程所有内存页面加锁 | 
| munlockall | 调用进程所有内存页面解锁 | 
| mmap | 映射虚拟内存页 | 
| munmap | 去除内存页映射 | 
| mremap | 重映射虚拟内存地址 | 
| msync | 将映射内存中的数据写回磁盘 | 
| mprotect | 设置内存映射保护 | 
| getpagesize | 获取页面大小 | 
| sync | 将内存缓冲区数据写回磁盘 | 
| cacheflush | 将指定缓冲区中内存磁盘 | 
- 网络管理 
| 系统调用 | 描述 | 
|---|---|
| getdomainname | 取域名 | 
| setdomainname | 设置域名 | 
| gethostid | 获取主机识别号 | 
| sethostid | 设置主机识别号 | 
| gethostname | 获取主机名称 | 
| sethostname | 设置主机名称 | 
- socket控制 
| 系统调用 | 描述 | 
|---|---|
| socketcall | socket系统调用 | 
| socket | 建立socket | 
| bind | 绑定socket到端口 | 
| connect | 远程连接主机 | 
| accept | 响应socket连接请求 | 
| send | 通过socket发送信息 | 
| sendto | 发送UDP信息 | 
| recv | 通过socket接收信息 | 
| recvfrom | 接收UDP信息 | 
| recvmsg | 通过socket接收信息 | 
| listen | 监听socket端口 | 
| select | 对多路同步I/O进行轮询 | 
| shutdown | 关闭socket上的连接 | 
| getsockname | 取得本地socket名字 | 
| getpeername | 获取通信对方的socket名字 | 
| getsockopt | 获取端口设置 | 
| setsockopt | 设置端口参数 | 
| sendfile | 在文件或端口间传输数据 | 
- 用户管理 
| 系统调用 | 描述 | 
|---|---|
| getuid | 获取用户标识号 | 
| setuid | 设置用户标识号 | 
| getgid | 获取组标识号 | 
| setgid | 设置组标识号 | 
| getegid | 获取有效组标识号 | 
| setegid | 设置有效组标识号 | 
| seteuid | 设置有效用户标识号 | 
- 进程间通信 
| 系统调用 | 描述 | 
|---|---|
| ipc | 进程间通信总控制调用 | 
- 信号 
| 系统调用 | 描述 | 
|---|---|
| sigaction | 设置对指定信号的处理方法 | 
| sigprocmask | 更具参数对信号集中的信号执行阻塞/解除阻塞等操作 | 
| sigpending | 对指定的被阻塞信号设置队列 | 
| sigsuspend | 挂起进程等待特定信号 | 
| kill | 向进程或进程组发信号 | 
- 消息 
| 系统调用 | 描述 | 
|---|---|
| msgctl | 消息控制操作 | 
| msgget | 获取消息队列 | 
| msgsnd | 发消息 | 
| msgrecv | 接收消息 | 
- 管道 
| 系统调用 | 描述 | 
|---|---|
| pipe | 创建管道 | 
- 信号量 
| 系统调用 | 描述 | 
|---|---|
| semctl | 信号量控制 | 
| semget | 获取一组信号量 | 
| semop | 信号量操作 | 
- 共享内存 
| 系统调用 | 描述 | 
|---|---|
| shmctl | 控制共享内存 | 
| shmget | 获取共享内存 | 
| shmat | 连接共享内存 | 
| shmdt | 拆卸共享内存 |