用户态和内核态是如何切换的?
用户程序访问能力有限,一些非常重要的数据就需要转换成内核态才能去获取。这一转换只有通过系统调用,执行系统调用的也只有操作系统。
什么是系统调用?
系统调用是操作系统提供给程序设计人员使用系统服务的接口
- 系统调用流程
Linux 提供了 glibc 库, 它封装了系统调用接口, 对上层更友好的提供服务, 系统调用最终都会通过 DO_CALL 发起, 这是一个宏定义, 其 32 位和 64 位的定义是不同的:
-
32 位系统调用
- 用户态
- 将请求参数保存到寄存器
- 将系统调用名称转为系统调用号保存到寄存器 eax 中
- 通过软中断 ENTER_KERNEL 陷入内核态
- 内核态
- 内核的软中断陷入门收到系统调用会将用户态的寄存器保存到 pt_regs 结构中
- 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
- 并将寄存器中保存的参数取出来作为参数执行函数, 将返回值写入 pt_regs 结构的 ax 位置
- 通过iret指令根据 pt_regs 恢复用户态进程
- 用户态
-
64 位系统调用
- 用户态
- 将请求参数保存到寄存器
- 将系统调用名称转为系统调用号保存到寄存器 rax 中
- 通过 syscall 指令进入内核态
(注:由int中断指令改为syscall指令,减少了一次查表过程,性能应该有所提高)
- 内核态
- 将用户态的寄存器保存到 pt_regs 中
- 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
- 将寄存器中保存的参数取出来作为函数参数执行函数, 将返回值写入 pt_regs 的 ax 位置
- 通过sysretq指令返回用户态
- 用户态
64位完整往返流程图
加餐题:
什么是用户态和内核态?
用户态和内核态是操作系统的两种运行状态。
- 内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。
- 用户态:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
那么为什么要有用户态和内核态呢?
这个主要是访问能力的限制的考量,计算机中有一些比较危险的操作,比如设置时钟、内存清理,这些都需要在内核态下完成,如果随意进行这些操作,那你的系统得崩溃多少次。
外中断和异常有什么区别?
外中断是指由 CPU 执行指令以外的事件引起,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
而异常时由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。