一、内核
内核的4种基本能力:
- 进程调度:管理进程、线程,决定哪个进程、线程使用CPU;
- 内存管理:管理内存的分配和回收;
- 硬件通信:管理硬件设备,为进程与硬件设备之间提供通信能力;
- 系统调用:如果应用程序要运行更高权限运行的服务,就需要系统调用,是用户程序与操作系统之间的接口。
内核是怎么工作的?
内核具有很高的权限,可以控制CPU、内存、硬盘等硬件,而应用程序权限很小,因此大多数操作系统把内存分为两个区域:内核空间和用户空间。
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。应用程序如果需要进入内核空间,需要通过系统调用,产生一个中断。发生中断,CPU中断正在执行的用户程序,转而跳转到中断处理程序,开始执行内核程序。内核处理完成后,主动触发中断,CPU回到用户态继续工作。
二、内存管理
2.1 虚拟内存
操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
进程持有的虚拟地址会通过CPU中的内存管理单元(MMU)的映射关系,来转换为物理地址,然后再通过物理地址访问内存。
操作系统如何管理虚拟地址与物理地址的关系?
内存分段和内存分页。
2.2 内存分段
程序是由若干个逻辑分段组成,不同的段有不同属性,因此可用分段形式把段分离出来。
分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量。
- 段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。
- 虚拟地址中的段内偏移量应该位于0和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量到物理内存地址。
2.3 内存分页
分段的好处是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小的内存空间,称为页(Page)。Linux中,每一页的大小为4KB。
虚拟地址与物理地址之间通过页表来映射。
页表是存储在内存里的,内存管理单元(MMU)负责将虚拟地址转换为内存地址的工作。
而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内存空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
分页是怎么解决分段的内存碎片、内存交换效率低的问题?
由于内存空间是预先划分好的,释放都是以页为单位释放的,不会产生无法给进程使用的小内存。
如果内存空间不够,操作系统会把正在运行的进程中“最近没被使用”的内存页释放,暂时写在硬盘上,即换出(Swap out)。一旦需要,再加载进来,即换入(Swap in)。
分页不需要一次性把程序加载到物理内存中,只有在程序运行时,需要用到对应的虚拟内存页里面的指令和数据时,再加载到物理内存中。
分页机制下,虚拟地址和物理地址是如何映射的?
在分页机制下,虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,基地址与页内偏移的组合就成了物理内存地址。
2.4 多级页表
2.5 段页式内存管理
段页式内存管理实现的方式:
- 先将程序划分为多个有逻辑意义的段;
- 把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页
地址结构就由段号、段内页和页内位移三部分组成。
用于段页式地址变换的数据结构是每一个程序一张段表,每个段表又建立一张页表,段表中的地址是页表的起始地址,页表中的地址为某页的物理页号。
2.6 Linux内存管理
在Linux操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间。
用户空间内存,从低到高分别是7种不同的内存段:
- 程序文件段,包括二进制可执行代码;
- 已初始化数据段,包括静态常量;
- 未初始化数据段,包括未初始化的静态变量;
- 堆段,包括动态分配的内存,从低地址开始向上增长;
- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(与硬件和内核版本有关);
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是8MB。
在7个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用C标准库的malloc()或者mmap(),就可以分别在堆和文件映射段动态分配内存。
三、进程与线程
CPU可以从一个进程切换到另一个进程,在切换前必须要记录当前进程中运行的状态信息。
在一个进程的活动期间至少具备三种状态:
- 运行状态(Running):该时刻进程占用CPU;
- 就绪状态(Ready): 可运行,由于其他进程处于运行状态而暂时停止运行;
- 阻塞状态(Blocked):该进程正等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,也无法运行;
此外,还有两个基本状态:
- 创建状态(New):进程正在被创建时的状态;
- 结束状态(Exit):进程正在从系统消失时的状态;
在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。
这时,需要用挂起状态,描述进程没有占用实际的物理内存空间的情况。而阻塞状态是等待某个事件的返回。
挂起状态一共有两种:
- 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
- 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立即运行;
导致进程挂起的原因不只是因为进程所使用的内存空间不在物理内存,还包括如下情况:
- 通过sleep让进程间歇性挂起,工作原理是设置一个定时器,到期后唤醒进程。
- 用户希望挂起一个程序的执行;
进程的控制结构
在操作系统中,用进程控制块(Process control block, PCB)数据结构来描述进程的。
PCB是进程存在的唯一标识,具体包含以下信息:
- 进程描述信息:
- 进程标识符:标识各个进程,每个进程都有一个且唯一的标识符;
- 用户标识符:进程归属的用户,主要为共享和保护服务;
- 进程控制盒管理信息:
- 进程当前状态,如new、ready、running、waiting或blocked等;
- 进程优先级:进程抢占CPU时的优先级;
- 资源分配清单:
- 有关内存地址空间或虚拟地址空间的信息,所以打开文件的列表和所使用的I/O设备信息
- CPU相关信息:
- CPU中各个寄存器的值,当进程被切换时,CPU的状态信息都会被保存在相应的PCB中,以便进程重新执行时,能从断电处继续执行。
每个PCB通常是通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列。
- 将所有处于就绪状态的进程链在一起,称为就绪队列;
- 把所有因等待某事件而处于等待状态的进程链在一起就组成各种阻塞队列;
- 另外,对于运行队列在单核CPU系统中则只有一个运行指针,因为单核CPU在某个时间,只能运行一个程序。
进程的控制
01 创建进程:
- 为新进程分配一个唯一的进程标识号,并申请一个空白的PCB,PCB是有限的,若申请失败则创建失败;
- 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源;
- 初始化PCB;
- 如果进程的调度队列能够接纳新进程,那就将进程插入到就绪队列,等待被调度运行;
02 终止进程:
- 查找需要终止进程的PCB;
- 如果处于执行状态,则立即终止该进程的执行,然后将CPU资源分配给其他进程;
- 如果还有子进程,则应该将所有子进程终止;
- 将该进程所拥有的全部资源都归还给父进程或者操作系统;
- 将其从PCB所在的队列移除;
03 阻塞进程
- 找到将要被阻塞进程标识号对应的PCB;
- 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行;
- 将该PCB插入到阻塞队列中去;
04 唤醒进程
- 在该事件的阻塞队列中找到相应进程的PCB;
- 将其从阻塞队列中移除,并置其状态为就绪状态;
- 把该PCB插入到就绪队列中,等待调度程序调度;
进程的阻塞和唤醒是一对功能相反的语句。
进程的上下文切换
CPU寄存器(CPU内部的一个容量小,但是速度极快的内存)和程序计数器(存储CPU正在执行的指令位置,或者即将执行的下一条指令位置)是CPU在运行任何任务前,所必须依赖的环境,这种环境就是CPU上下文。
CPU上下