哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)

文章目录

  • 0 系统调用的直观实现
  • 1 内核态与用户态
    • 1.1 硬件提供了进入内核态的方法
    • 1.2 write
    • 1.3 int 0x80;

0 系统调用的直观实现


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第1张图片


  • 从一个 W h o a m I WhoamI WhoamI的问题引入,系统调用打印用户名,用户名这个字符串是在内核当中的,因为要进到内核当中,所以这是系统调用
  • 当想把用户名从内核当中打出,为什么不能简单的调用printf()这条指令呢?
  • APP在内存中,OS也在内存中,APP想访问OS提供的内容为什么不能直接进行呢?直接jmp或者直接mov为什么不行?OS当中有很多很重要的东西,比如 r o o t root root用户的密码,所以会造成不安全的隐患
  • 任何一种输出数据到外设的系统调用,在某个时刻,这些数据会在操作系统内核的缓冲区中,这个时候就可能被泄漏,比如可以通过缓冲区或者显存看到 w o r d word word里面的内容,这些都是不太安全的,所以尽管都在内存当中,不让mov/jmp的原因

1 内核态与用户态

接上方,凭什么不让APP使用jmp指令到内存当中呢?因为硬件电路的原因,只有硬件电路有能力让你“不jmp/mov”。


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第2张图片


  • 区分内核态和用户态是一种处理器的“硬件设计”,从硬件的层面保证了这个机制(操作系统的很多功能都是需要依靠硬件设计来实现的,软硬件结合)
  • 内核态可以问任何数据,用户态不能访问内核数据:对于指令跳转也是一样
  • 硬件把内存分割成了很多区域(如上图的蓝色与红色的区域,生成了用户段与内核段,如上图的蓝色与红色区域代码),实现了内核与APP(用户程序)的隔离
  • DPL,CPL,RPL,都是基于硬件实现的
  • 就是比较当前态的特权级和目标段的特权级,这个特权级数字的含义见上图中的处理器保护环,数字越小,越接近内核
  • CPL就是CS的最低两位,DPL可以从GDT表中查到,在保护模式下指令地址的翻译是查GDT表,那么这个时候就可以查到目标指令的DPL,和当前态的特权级CPL比较,如果DPL>=CPL,那就说明当前态的特权级足以执行目标指令,否则就不允许执行
  • DPL:DPL是用来描述目标内存段的特权级的(PL就是特权级,D就是目标),例如上方的红色段代码,DPL就在GDT表中,一个GDT的表项就用来描述一段内存,初始化都置成0
  • CPL:当前的特权级,取决于执行的是什么指令,因为指令是PC,PC又是CS:IP组成的,所以CS当中的一部分就用来表示特权级, 什么内核段/用户段,都得靠段寄存器来做,CS:IP是指向当前要执行的指令地址,当前程序处于内核态还是用户态,用CS:IP的最低两位来表示,0是内核态,3是用户态,当前特权级为3,只能访问3,但是是0,就都可以访问,那1,2呢?

1.1 硬件提供了进入内核态的方法


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第3张图片


  • 对于Intel x86,那就是中断指令int
  • int指令将使CS中的CPL改成0,“进入内核”(是怎么做到的?下面笔记有解释)
  • 这是用户程序发起的调用内核代码的唯一方式

系统调用的核心

  1. 用户程序中包含一段包含t指令的代码(库函数)
  2. 操作系统写中断处理,获取想调程序的编号(所有的中断处理代码都是操作系统提前设置的)
  3. 操作系统根据编号执行相应代码

以C代码库编写的系统调用,在用户程序调用后,会首先进入C代码库函数,然后用汇编代码在约定的位置(栈或者寄存器)设置参数和系统调用编号,最后执行int指令。


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第4张图片


1.2 write


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第5张图片


  • 这是一段C语言内嵌汇编代码
  • 获取_NR_write,这是write的系统调用编号,将它放在eax寄存器中,参数放在ebX、ecx、edx中
  • 然后执行int0x80指令,这个中断执行后,会把返回值放在eax寄存器中
  • eax寄存器的值会赋给res,最终返回res

1.3 int 0x80;

  • int 0x80,需要在OS初始化的时候寻找线索
  • IDT表和GDT表的结构比较像,是特别用于中断处理函数寻址的表可以根据DT表找到这个中断的处理函数的入口地址
  • 下图展示的是操作系统初始化的时候,与it0x80和IDT表初始化相关的代码
  • 下图中,set_system_gatel函数就是在设置DT表,其两个参数说明了0x80中断的处理函数入口地址就是&system_cal,也就是说0x80这个中断专门用于表示系统调用
  • 下图中,可以看到IDT表中也有DPL,且当前情况下DPL被设置成了3,所以int 0x80中断可以在用户态下调用(总结一句话,系统调用对应的中断itOx80位于IDT表中的DPL=3,可以在用户态下调用)
  • 一旦调用int Ox80,CPL就会被置为0,因为这个中断的处理函数本身就在内核空间,那么CS自然就是内核段,那么CPL自然就变成0了
  • 将来中断返回的时候,可以肯定CPL会变回3

哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第6张图片


  • 系统调用对应的中断处理程序system_call
  • ds=es=0x10,表示将数据段置为内核的数据段地址
  • 其中调用了_sys_call_table的第4项

哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第7张图片


最后的一行sys代码,类似于目录,因为寻址方式,每个地址只需要4个字节就可以表示。所以这里是4。


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第8张图片


哈工大李治军老师操作系统笔记【4】:系统调用的实现(Learning OS Concepts By Coding Them !)_第9张图片


你可能感兴趣的:(OS,网络)