先写几行可执行的代码直观感受一下系统调用
.data
s:
.ascii "hello world\n"
len = . - s
.text
.global _start
_start:
movl $4, %eax /* write system call number */
movl $1, %ebx /* stdout */
movl $s, %ecx /* the data to print */
movl $len, %edx /* length of the buffer */
int $0x80
movl $1, %eax /* exit system call number */
movl $0, %ebx /* exit status */
int $0x80
编译并运行
as -o main.o main.S
ld -o main.out main.o
./main.out
整体逻辑很简单,设置必要的寄存器,触发0x80中断,调用系统功能终端输出。可见,系统调用需要操作系统的中断功能配合完成。操作系统维护一个中断向量表,用来管理各种中断处理逻辑,比如各个硬件外设的中断。而这里的0x80中断是专门处理系统调用的。
除了 0x80中断 触发系统调用,还可以通过 syscall 触发系统调用,当前 x86-64 系统默认使用这种方式。在 32 bit 系统下常用的是 sysenter。下面示例是通过 syscall 触发系统调用:
.data
hello_world:
.ascii "hello world\n"
hello_world_len = . - hello_world
.text
.global _start
_start:
/* write */
mov $1, %rax
mov $1, %rdi
mov $hello_world, %rsi
mov $hello_world_len, %rdx
syscall
/* exit */
mov $60, %rax
mov $0, %rdi
syscall
汇编运行:
as -o hello_world.o hello_world.S
ld -o hello_world.out hello_world.o
./hello_world.out
reference 这里
什么是系统调用
应用程序中使用入诸如 folk
, open
, read
, write
这类函数时其实就是在系统调用了。
应用程序通过系统调用进入内核完成一些任务,比如创建进程,网络io,活着磁盘读写。
不通的cpu架构下,应用程序通过不同的方法和底层指令来触发系统调用,作为一个应用程序程序开发者,可以不用关系系统调用具体是如何触发的,只需要像调用普通函数一样调用glibc提供的封装库就行了。
先了解几个概念
- CPU privilege levels
用户进程如果想进行io操作,只能通过系统调用让内核完成。那么用户进程就不能么,当然不能,这是死规定。那么如何阻止用户进程执行此类操作呢。
cpu 权限等级 就是用来规范用户进程的操作的。
cpu 权限等级 需要长篇大论,这里简言之:
- 权限等级 是一种控制手段,规定一个等级中有那些cpu指令和io操作可以执行。
- 内核运行在
ring 0
,权限最大。用户进程运行在ring 3
。
用户进程如果想进行特权操作,那就需要切换cpu 权限等级以便内核运行。