从用户态到内核态的切换
理解用户态和内核态
- 如上图所示, Linux操作系统的体系架构分为用户态和内核态, 内核从本质上来看也是一种软件资源, 用来控制计算机的硬件资源, 并为上层的应用程序提供运行环境,.
- 用户态即是上层应用程序的活动空间, 应用程序的执行必须依靠于内核提供的资源, 包括CPU, 存储器, I/O资源, 为了让上层应用访问到这些资源, 内核必须为上层应用提供访问的接口: 系统调用
- 系统调用是操作系统的最小功能单位, 这些系统调用根据不同的场景进行扩展和裁剪, 可以将系统调用看做一个汉字的’笔画’, 而一个"汉字"则可以看作是一个上层应用. 要实现一个完整的汉字(一个上层应用), 就需要很多的系统调用(笔画)
- 为什么要有用户态和内核态?
- 由于需要限制不同的程序之间的访问能力, 防止获取别的程序的内存数据或者获取外围设备的数据, CPU划分出两个权限等级----用户态和内核态
用户态和内核态的切换
系统调用
- 所有的应用程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 从键盘获取输入等, 唯一可以做这些的事情就是操作系统, 所以程序就需要先请求以操作系统的名义来执行---------这时就有这样一个机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行指令------这就是系统调用(CPU中的实现称为陷阱指令).工作流程如下:
- 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈, 以此表明需要操作系统提供服务
- 用户态程序执行
陷阱指令 - CPU切换到内核态, 跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
- 这些指令被称之为陷阱, 或者系统调用处理器, 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
- 系统调用完成之后, 操作系统会重置CPU为用户态返回系统调用结果
- 内核态与用户态是操作系统的两种运行级别, 跟CPU没有必然的联系, Linux使用Ring3级别运行内核态, RIng0作为内核态, Ring3状态不能访问RIng0的地址空间, 包括数据和代码, Linux的4G地址空间, 前3G是用户空间, 后面1G是内核态的地址空间, 是大家共享的, 这里存放了整个内核的代码和所有的内核模块以及内核所维护的数据.
- 用户运行一个程序, 这个程序创建的进程开始是运行在用户态的, 如果要执行文件操作, 网络数据发送等的操作, 必须通过send, write等的系统调用来实现, 这些系统调用会调用内核中的代码来完成操作, 这时必须切换到RIng0, 然后进入3G-4G内核空间去执行这些代码完成操作, 完成后切换回RIng3回到用户态, 这样用户态的程序不能随意操作内核空间, 具有一定的保护作用.
异常
异常处理中华最常见的就是缺页异常
- Linux操作系统使用页式管理机制通过页面目录, 页面表, 将每一个线性地址转换成物理地址, 但并不是每一次CPU都能访问到相应的物理内存单元, 这样映射失败, 便会产生缺页异常
- 缺页异常: CPU通过地址总线可以访问连接在地址总线上的所有外设, 包括物理内存, IO设备等, 但是从CPU发出的访问地址并非是这些外设在地址总线上的物理地址, 而是一个虚拟地址, 由MMU将虚拟地址转换成为物理地址再从地址总线上发出, MMU这种虚拟地址和物理地址的转换关系是需要创建的, 并且MMU可以设置这个物理页是否可以进行写操作, 当没有创建一个虚拟地址到物理地址的映射, 或者创建了映射,但是物理页不可写时, MMU会通知CPU产生一个缺页异常
- 当MMU中确实没有创建虚拟页物理页的映射关系, 并且在该虚拟地址之后再也没有当前进程的线性区vma时, 这肯定是一个编码错误, 将杀掉这个进程
- 当MMU中确实没有创建虚拟页物理页的映射关系, 并且在该虚拟地址之后存在当前进程的线性区vma时, 这很可能是一个缺页异常, 并且可能是栈溢出导致的缺页异常
- 当使用malloc/mmap等希望访问物理空间的库函数/系统调用之后, 由于Linux并未真的给新创建的vma映射物理页, 此时进行写操作, 会产生上面2的情况导致缺页异常, 如果先进行读取操作也会导致缺页异常, 将被映射给默认的零页, 等在进行写操作时, 仍会产生缺页异常, 这次必须分配物理内存, 进入写时拷贝流程
- 使用fork等系统调用创建子进程时, 子进程不论有无自己的vma, 他的vma都有对于物理页的映射, 但他们共同映射的这些物理页属性为只读, 即Linux并没有给子进程分配真正的物理页, 当父进程任何一方要写相应的物理页时, 导致缺页异常的写时拷贝.
中断
- 中断是在计算机运行过程中, 出现某一些意外情况需要主机干预, 机器能自动停止正在运行的程序并转入处理新情况的程序, 处理完毕之后又返回原来被暂停的程序继续运行.