Interface
什么是操作系统接口
连接上层用户和操作系统软件
用户如何使用计算机?
- 命令行
- 图形按钮
- 应用程序
操作系统接口:连接操作系统和应用软件,如何连接?通过程序。
操作系统提供了重要的函数
接口表现为函数调用,又由系统提供,所以称之为系统调用
命令行的原理
图形按钮的原理
常用的OS接口
系统调用的实现
系统调用的直观实现
内核(用户)态,内核(用户)段
DPL
: 用来描述目标段的特权级,- 在操作系统的内核段中,GDT表项对应的DPL全部在 head.s 执行时初始化为0
CPL
: 当前的特权级
只有当前执行的特权级大于等于目标的特权级,才允许访问目标态的数据,即 CPL <= DPL
硬件提供了“主动进入内核的方法”
为什么系统调用可以实现用户态跳转到内核态呢,因为硬件提供了中断指令
对于 Intel x86
, 那就是中断指令 int
- int 指令将 CS 中的 CPL 改为0,进入内核
- 这是用户程序发起的调用内核代码的唯一方式(此时
CPL
= 3 而DPL
= 0)
系统调用的核心:
- 用户程序中包含一段包含 int 指令 的代码
- 操作系统写中断处理,获取想调程序的编号
- 操作系统根据编号执行相应的代码
系统调用的实现
Linux 系统调用的实现细节
write系统调用的细节
int 0x80 是系统调用的核心
系统调用时执行:
1 | _syscall3(int, write, int, fd, const char *, buf, off_t, count); |
对应关系 :
- int write(int fd, const int *buf, off_t, count)
- type name(atypa a, btype b, ctype c)
将宏展开就是:
1 | int write(int fd, const int *buf, off_t count) { |
"=a"(__res)
:=a
表示eax
, 表示代码运行结束后将eax
所代表的寄存器的值放入__res
变量""
内如果什么也不填写, 表示使用与上面同个位置的输出相同的寄存器,所以也是eax
""(__NR_##name)
:mov __NR_write, %eax
, 是将write赋值给eax
, 这是输入端"b"((long)(fd)
: 将fd赋值给ebx
补充内嵌汇编常用语法形式:
1
2
3
4
5
6 asm (
"汇编语句模板"
:输出寄存器
:输入寄存器
:会被修改的寄存器
)
int 0x80中断的处理
通过IDT表找到中断要跳转到哪个地方去执行
首先看看IDT表的初始化:
int 0x80
通过这个system_call
进行处理
这个宏用来设置中断处理门的IDT表:将这个表初始化好
一旦这个表初始化好,再遇到80号中断就从这个表中取出相应的中断处理函数跳到那里去执行
1 |
|
设置 dpl
等于 3,组装到表中
movl %%eax, %1\n\t" "movl %%edx, %2
:
- 这两条指令将
eax
和edx
分别赋值给这个表的低四位和高四位
"d"((char*)(addr), "a"(0x0008000))
: 将8赋值给段选择符
总结:在初始化时,
dpl
设置成3,故意想让cpl
等于3的用户代码能进来,以后执行int 0x80
时,根据IDT表中的段选择符和偏移地址合在一起设置成新的PC,此时 CS = 8, IP = &system_call, CS末尾的CPL此时设置为0, 特权级已经变成了 0 了,这样可以进入内核态执行啦 !
中断处理程序: system_call
通过_sys_call_table
找到 sys_write
的地址,然后调用sys_write
在 main 函数中, CPL=3
, 通过 int 0x80中断才能穿过接口,此时虽然 CPL等于3,但 int 0x80 的DPL=3,所以可以穿过去,然后将CPL设置成0,在 sys_call里面通过移动查表调用 sys_whoami()
, 这时候可以在内核态访问任何数据,可以调用printfk
函数直接往显存中写数据了。