一. 原理简介
- 系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。
- 从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。
- 系统服务之所以需要通过系统调用来提供给用户空间的根本原因:对系统进行“保护”。Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。所以用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。
- 比如我们熟悉的“hello world”程序(执行时)就是标准的用户空间进程,它使用的打印函数printf就属于用户空间函数,打印的字符“hello word”字符串也属于用户空间数据。
- Linux中实现系统调用利用了0x86体系结构中的软件中断。软件中断和我们常说的中断(硬件中断)不同之处在于,它是通过软件指令触发而并非外设引发的中断,也就是说,又是编程人员开发出的一种异常(该异常为正常的异常)。
- int $0x80汇编指令的目的是产生一个编号为0x80的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项——也就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序:system_call()(别和系统调用服务程序混淆,这个程序在entry.S文件中用汇编语言编写)。
- 很显然,所有的系统调用都会统一地转到这个地址,但Linux一共有2、3百个系统调用都从这里进入内核,又该如何派发到它们到各自的服务程序去呢?解决这个问题的方法非常简单:首先Linux为每个系统调用都进行了编号(0—NR_syscall),同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程,因此在系统调入通过系统门陷入内核前,需要把系统调用号一并传入内核,在x86上,这个传递动作是通过在执行int0x80前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从eax中得到数据,然后再去系统调用表中寻找相应服务例程了。
- 除了需要传递系统调用号以外,许多系统调用还需要传递一些参数到内核,比如sys_write(unsigned int fd, const char * buf, size_t count)调用就需要传递文件描述符fd、要写入的内容buf、以及写入字节数count等几个内容到内核。碰到这种情况,Linux会有6个寄存器可被用来传递这些参数:eax (存放系统调用号)、 ebx、ecx、edx、esi及edi来存放这些额外的参数(以字母递增的顺序)。具体做法是在system_call( )中使用SAVE_ALL宏把这些寄存器的值保存在内核态堆栈中。
- 用户编程接口其实是一个函数定义,说明了如何获得一个给定的服务,比如read( )、malloc( )、free( )、abs( )等。它有可能和系统调用形式上一致,比如read()接口就和read系统调用对应,但这种对应并非一一对应,往往会出现几种不同的API内部用到同一个系统调用,比如malloc( )、free( )内部利用brk( )系统调用来扩大或缩小进程的堆;或一个API利用了好几个系统调用组合完成服务。更有些API甚至不需要任何系统调用——因为它并不是必需要使用内核服务,如计算整数绝对值的abs()接口。
- Linux的系统调用过程:
用户程序------>C库(即API):INT 0x80 ----->system_call(前面说的各种寄存器实现)------->系统调用服务例程-------->内核程序
二. Linux系统调用分类及列表
- 系统调用主要分为以下几类:
①控制硬件:系统调用往往作为硬件资源和用户空间的抽象接口,比如读写文件时用到的write/read调用。
②设置系统状态或读取内核数据:因为系统调用是用户空间和内核的唯一通讯手段,所以用户设置系统状态,比如开/关某项内核服务(设置某个内核变量),或读取内核数据都必须通过系统调用。比如getpgid、getpriority、setpriority、sethostname
③进程管理:一系统调用接口是用来保证系统中进程能以多任务在虚拟内存环境下得以运行。比如 fork、clone、execve、exit等
- 进程控制
fork | 创建一个新进程 |
clone | 按指定条件创建子进程 |
execve | 运行可执行文件 |
exit | 中止进程 |
_exit | 立即中止当前进程 |
getdtablesize | 进程所能打开的最大文件数 |
getpgid | 获取指定进程组标识号 |
setpgid | 设置指定进程组标志号 |
getpgrp | 获取当前进程组标识号 |
setpgrp | 设置当前进程组标志号 |
getpid | 获取进程标识号 |
getppid | 获取父进程标识号 |
getpriority | 获取调度优先级 |
setpriority | 设置调度优先级 |
modify_ldt | 读写进程的本地描述表 |
nanosleep | 使进程睡眠指定的时间 |
nice | 改变分时进程的优先级 |
pause | 挂起进程,等待信号 |
personality | 设置进程运行域 |
prctl | 对进程进行特定操作 |
ptrace | 进程跟踪 |
sched_get_priority_max | 取得静态优先级的上限 |
sched_get_priority_min | 取得静态优先级的下限 |
sched_getparam | 取得进程的调度参数 |
sched_getscheduler | 取得指定进程的调度策略 |
sched_rr_get_interval | 取得按RR算法调度的实时进程的时间片长度 |
sched_setparam | 设置进程的调度参数 |
sched_setscheduler | 设置指定进程的调度策略和参数 |
sched_yield | 进程主动让出处理器,并将自己等候调度队列队尾 |
vfork | 创建一个子进程,以供执行新程序,常与execve等同时使用 |
wait | 等待子进程终止 |
wait3 | 参见wait |
waitpid | 等待指定子进程终止 |
wait4 | 参见waitpid |
capget | 获取进程权限 |
capset | 设置进程权限 |
getsid | 获取会晤标识号 |
setsid | 设置会晤标识号 |
- 文件操作
fcntl | 文件控制 |
open | 打开文件 |
creat | 创建新文件 |
close | 关闭文件描述字 |
read | 读文件 |
write | 写文件 |
readv | 从文件读入数据到缓冲数组中 |
writev | 将缓冲数组里的数据写入文件 |
pread | 对文件随机读 |
pwrite | 对文件随机写 |
lseek | 移动文件指针 |
_llseek | 在64位地址空间里移动文件指针 |
dup | 复制已打开的文件描述字 |
dup2 | 按指定条件复制文件描述字 |
flock | 文件加/解锁 |
poll | I/O多路转换 |
truncate | 截断文件 |
ftruncate | 参见truncate |
umask | 设置文件权限掩码 |
fsync | 把文件在内存中的部分写回磁盘 |
- 文件系统操作
access | 确定文件的可存取性 |
chdir | 改变当前工作目录 |
fchdir | 参见chdir |
chmod | 改变文件方式 |
fchmod | 参见chmod |
chown | 改变文件的属主或用户组 |
fchown | 参见chown |
lchown | 参见chown |
chroot | 改变根目录 |
stat | 取文件状态信息 |
lstat | 参见stat |
fstat | 参见stat |
statfs | 取文件系统信息 |
fstatfs | 参见statfs |
readdir | 读取目录项 |
getdents | 读取目录项 |
mkdir | 创建目录 |
mknod | 创建索引节点 |
rmdir | 删除目录 |
rename | 文件改名 |
link | 创建链接 |
symlink | 创建符号链接 |
unlink | 删除链接 |
readlink | 读符号链接的值 |
mount | 安装文件系统 |
umount | 卸下文件系统 |
ustat | 取文件系统信息 |
utime | 改变文件的访问修改时间 |
utimes | 参见utime |
quotactl | 控制磁盘配额 |
- 系统控制
ioctl | I/O总控制函数 |
_sysctl | 读/写系统参数 |
acct | 启用或禁止进程记账 |
getrlimit | 获取系统资源上限 |
setrlimit | 设置系统资源上限 |
getrusage | 获取系统资源使用情况 |
uselib | 选择要使用的二进制函数库 |
ioperm | 设置端口I/O权限 |
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守护进程进行控制 |
vm86 | 进入模拟8086模式 |
create_module | 创建可装载的模块项 |
delete_module | 删除可装载的模块项 |
init_module | 初始化模块 |
query_module | 查询模块信息 |
*get_kernel_syms | 取得核心符号,已被query_module代替 |
- 内存管理
brk | 改变数据段空间的分配 |
sbrk | 参见brk |
mlock | 内存页面加锁 |
munlock | 内存页面解锁 |
mlockall | 调用进程所有内存页面加锁 |
munlockall | 调用进程所有内存页面解锁 |
mmap | 映射虚拟内存页 |
munmap | 去除内存页映射 |
mremap | 重新映射虚拟内存地址 |
msync | 将映射内存中的数据写回磁盘 |
mprotect | 设置内存映像保护 |
getpagesize | 获取页面大小 |
sync | 将内存缓冲区数据写回硬盘 |
cacheflush | 将指定缓冲区中的内容写回磁盘 |
- 网络管理
getdomainname | 取域名 |
setdomainname | 设置域名 |
gethostid | 获取主机标识号 |
sethostid | 设置主机标识号 |
gethostname | 获取本主机名称 |
sethostname | 设置主机名称 |
socketcall | socket系统调用 |
socket | 建立socket |
bind | 绑定socket到端口 |
connect | 连接远程主机 |
accept | 响应socket连接请求 |
send | 通过socket发送信息 |
sendto | 发送UDP信息 |
sendmsg | 参见send |
recv | 通过socket接收信息 |
recvfrom | 接收UDP信息 |
recvmsg | 参见recv |
listen | 监听socket端口 |
select | 对多路同步I/O进行轮询 |
shutdown | 关闭socket上的连接 |
getsockname | 取得本地socket名字 |
getpeername | 获取通信对方的socket名字 |
getsockopt | 取端口设置 |
setsockopt | 设置端口参数 |
sendfile | 在文件或端口间传输数据 |
socketpair | 创建一对已联接的无名socket |
- 用户管理
getuid | 获取用户标识号 |
setuid | 设置用户标志号 |
getgid | 获取组标识号 |
setgid | 设置组标志号 |
getegid | 获取有效组标识号 |
setegid | 设置有效组标识号 |
geteuid | 获取有效用户标识号 |
seteuid | 设置有效用户标识号 |
setregid | 分别设置真实和有效的的组标识号 |
setreuid | 分别设置真实和有效的用户标识号 |
getresgid | 分别获取真实的,有效的和保存过的组标识号 |
setresgid | 分别设置真实的,有效的和保存过的组标识号 |
getresuid | 分别获取真实的,有效的和保存过的用户标识号 |
setresuid | 分别设置真实的,有效的和保存过的用户标识号 |
setfsgid | 设置文件系统检查时使用的组标识号 |
setfsuid | 设置文件系统检查时使用的用户标识号 |
getgroups | 获取后补组标志清单 |
setgroups | 设置后补组标志清单 |