什么是系统调用

上层的应用系统是如何使用操作系统的呢? 操作系统提供的系统调用(接口)来供上层应用来和操作系统交互,一般类unix系统上的接口都符合POSIX协议(Portable Opearating System interface of Unix) 是一个IEEE制定的一个标准接口库。那么为什么要有系统调用而不是用户能直接访问内核呢?因为应用不能随意访问内核数据,否则内核可能会被上层应用所破坏。

操作系统如何进行权限限制

操作系统将内存区域分为了用户态和内核态,由于CS:IP是当前的指令,所以使用CS的最低两位来表示: 0是内核态,3是用户态。
每次访问时,要访问的数据段的DPL >= 当前指令的CPL时才能访问,内核段的DPL都是为0(在初始化阶段DPL就都置成了0) 这样内核态可以访问任何数据,用户态不能访问内核数据. 通过DPL和CPL就实现了用户态无法直接访问内核态的内容

DPL是目标内存段的特权级 CPL是当前内存段的特权级

如何访问内核函数

操作系统提供了中断指令int 0x80来主动进入内核,这是用户程序发起的调用访问内核代码的唯一方式

  • 用户程序中包含一段包含int指令的代码,通常是由库函数通过内联汇编插入
  • 操作系统写中断处理,获取想调程序的编号
  • 操作系统根据编号执行相应的代码

调用系统函数时会通过内联汇编代码插入int 0x80的中断指令,(不仅会插入中断指令,还会将系统调用编号设置给 %eax 寄存器)

内核接收到int 0x80中断后,需要查询IDT表来取出中断处理函数地址,这个地方比较细节的地方是,int 0x80的idt表中的DPL被设置成了3,所以才能从用户态能直接访问int 0x80的中断指令的。

  1. void sched_init(void)
  2. {set_system_gate(0x80, &system_call)}
  3. linux/include/asm/system.h
  4. #define set_system_gate(n, addr) \
  5. _set_gate(&idt[n],15,3,addr); //idt是中断向量表基址
  6. #define _set_gate(gate_addr, type, dpl, addr)\
  7. __asm__(“movw %%dx,%%ax\n\t movw %0,%%dx\n\t\
  8. movl %%eax,%1\n\t movl %%edx,%2”:\
  9. :”i”((short)(0x8000+(dpl<<13)+type<<8))),“o”(*(( \
  10. char*)(gate_addr))),”o”(*(4+(char*)(gate_addr))),\
  11. d”((char*)(addr),”a”(0x00080000))

image.png
这是一段在系统初始化阶段执行的代码,主要工作是用来设置0x80的中断处理IDT表项,设置相应的&system_call入口函数的地址。并且注意到这里面会将0x80的IDT表项的DPL设置为3,这样CPL=3用户态的程序就能进入,跳入IDT表之后,段选择符是8,将段和偏移设置为新的pc,cs=8,ip=&system_call,这个cs=8 还要再找gdt表,就是从内核0位开始,此时CPl已经变成了0。也就完成了特权级的转化。
再下一步,在system_call中会根据%eax中跳转到相应的处理函数执行,这样就完成了一个系统函数的调用过程

image.png