0%

HIT-OS interface & system call

Interface

什么是操作系统接口

连接上层用户和操作系统软件

用户如何使用计算机?

  1. 命令行
  2. 图形按钮
  3. 应用程序

操作系统接口:连接操作系统和应用软件,如何连接?通过程序。

操作系统提供了重要的函数

接口表现为函数调用,又由系统提供,所以称之为系统调用

命令行的原理

图形按钮的原理

常用的OS接口

系统调用的实现

系统调用的直观实现

内核(用户)态,内核(用户)段

  • DPL: 用来描述目标段的特权级,
  • 在操作系统的内核段中,GDT表项对应的DPL全部在 head.s 执行时初始化为0
  • CPL: 当前的特权级

只有当前执行的特权级大于等于目标的特权级,才允许访问目标态的数据,即 CPL <= DPL

硬件提供了“主动进入内核的方法”

为什么系统调用可以实现用户态跳转到内核态呢,因为硬件提供了中断指令

对于 Intel x86, 那就是中断指令 int

  •  int 指令将 CS 中的 CPL 改为0,进入内核
  • 这是用户程序发起的调用内核代码的唯一方式(此时 CPL = 3 而 DPL = 0)

系统调用的核心:

  1. 用户程序中包含一段包含 int 指令 的代码
  2. 操作系统写中断处理,获取想调程序的编号
  3. 操作系统根据编号执行相应的代码

系统调用的实现

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
2
3
4
5
6
7
8
int write(int fd, const int *buf,  off_t count) {
long __res;
__asm__volatile("int 0x80":"=a"(__res):""(__NR_##name),
"b"((long)(fd)), "c"((long)(buf)), "d"((long)(count)));
if(__res >= 0) return (type)__res;
errno = -__res;
return -1;
}
  • "=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
2
3
4
5
6
7
#define _set_gate(gate_addr, type, dpl, addr)\
__asm__("movw %%dx, %%ax\n\t" "movw %0, %%dx\n\t" \
"movl %%eax, %1\n\t" "movl %%edx, %2": \
:"i"((short)(0x8000+(dpl<<13)+15<<8)),
"o"(*((char*)(gate_addr))), "o"(*(4+(char*)(gate_addr))),
"d"((char*)(addr), "a"(0x0008000))
)

设置 dpl 等于 3,组装到表中

movl %%eax, %1\n\t" "movl %%edx, %2 :

  • 这两条指令将eaxedx分别赋值给这个表的低四位和高四位

"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函数直接往显存中写数据了。

求大佬赏个饭