操作系统概述
操作系统概念
大部分计算机有两种运行模式:内核态
和用户态
,软件中最基础的部分是操作系统
,它运行在内核态
中(管态
,核心态
)。操作系统具有硬件的访问权,可以执行机器能够运行的任何指令。软件的其他部分运行在用户态
下。
操作系统结构
内核
内核作为应用连接硬件设备的桥梁,应用程序只需要关心与内核交互,无需关心硬件的细节。
内核作用
- 管理进程、线程,分配CPU,进行进程调度
- 管理内存,决定内存的分配与回收
- 管理硬件设备,为进程与硬件设备之间提供通信能力
- 提供系统调用,如果应用程序需要更高权限运行的服务,则需要有系统调用,它是用户程序与操作系统之间的接口
内核工作原理
内核具备很高的权限,可以控制CPU、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,将内存划分为两个区域:
- 内核空间:只有内核程序可以访问
- 用户空间:专门给普通的应用程序访问
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有的空间
因此,当程序使用用户空间时,称之程序在用户态
中执行,而当程序使用内核空间时,称之程序在内核态
中执行。
应用程序如果需要访问内核空间,就需要通过系统调用
当应用程序使用系统调用
时,会产生一个中断,CPU会中断当前正在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序,当内核处理完成后,主动触发中断,将CPU执行权限交回给用户程序,回到用户态继续工作。
Linux内核: 内存管理,进程管理,设备驱动程序,文件系统,网络管理
Windows内核
内存管理
虚拟内存
将进程所使用的地址隔离
开来,让操作系统为每个进程分配独立的一套虚拟地址
,互相隔离。
操作系统会提供一种机制,将不同的进程的虚拟地址和不同内存的物理地址映射起来。
当程序需要访问虚拟地址时,由操作系统转换成不同的物理地址,不同进程运行时写入的是不同的物理地址,便不会产生冲突。
- 虚拟内存地址:普通应用程序使用的内存地址
- 物理内存地址:实际存储在硬件的空间地址
操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的 内存管理单元(MMU) 的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示:
内存分段
程序由若干逻辑分段组成,可以由 代码分段、数据分段、栈段、堆段组成。 不同的段具有不同属性,因此使用分段的形式将这些段分离开来。
分段机制下,虚拟地址和物理地址如何映射?
分段机制下的虚拟地址由两部分组成,段选择因子
和段内偏移量
。
-
段选择因子
就保存在段寄存器里面。段选择子里面最重要的是段号
,用作段表的索引。段表
里面保存的是这个段的基地址
、段的界限
和特权等级
等。 -
虚拟地址中的
段内偏移量
应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。
缺陷
- 内存碎片
- 内存交换效率低下
举例:
关闭Chrome浏览器,释放的空间如果小于需要分配的进程内存则会成为内存碎片无法使用。
该问题可以通过内存交换
解决。
先将某个进程的内存存入硬盘,然后再从硬盘读入到空缺部位,此时内存碎片便被消化掉,可以理解为整理内存移动到同一端。
内存分页
分页是把整个虚拟和物理内存空间切成一段固定尺寸的大小,这样一个连续并且尺寸固定的内存空间,称为页。Linux下,每页4KB
。
虚拟地址与物理地址之间通过页表来映射。
页表实际上存储在 CPU 的内存管理单元 (MMU) 中,于是 CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址。
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
分页如何解决分段的内存碎片、内存交换效率低下的问题?
分页,内存的单位以页为单位(非常小),因此可以避免分段之间空间的浪费(内存碎片)。
当内存空间不足时,操作系统可以通过LRU
方式替换页(换出
)。一旦需要再加载到内存中(换入
)。因此,一次性写入磁盘的只有少数的几个页,消耗的时间较少,内存交换效率就相对较高
分页机制可以在加载程序时,不用一次性全部把程序加载到物理内存中。可以在进行虚拟内存和物理内存的页之间映射后,并不把页真的加载到物理内存中,而是只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存中
分页机制下,虚拟地址与物理地址如何映射?
分页机制下,虚拟地址氛围两个部分,页号
和页内偏移
。页号作为页表的索引,页表
包含物理页每页所在物理内存的基地址
,这个基地址与页内偏移的组合形成了物理内存地址。
内存转换步骤:
- 把虚拟内存地址,切分为页号和偏移量
- 根据页号,从页表中查询对应物理页号
- 通过物理页号,加上偏移量,得到物理内存地址
简单分页缺陷
页的大小很小,Linux一般是4KB
,而正常虚拟空间有4GB
(32位),那么大约需要上百万个页,则每个页表项
需要4个字节存储,整个空间的映射就需要4MB
的内存来存储页表。
每个进程都有自己的虚拟空间,因此需要消耗很大内存来存储,64位
系统需要更多空间。
多级页表
由于局部性原理
,每个进程只需要很小部分的虚拟内存空间,因此只需要映射一小部分的二级表,节省了空间。
使用二级分页机制,一级页表便可以覆盖整个4GB
虚拟空间,若 如果某个一级页表的页表项没有使用到,则无需创建对应的二级页表,只在需要时才创建对应的二级页表。
对于 64 位的系统,两级分页肯定不够了,就变成了四级目录,分别是:
● 全局页目录项 PGD(Page Global Directory);
● 上层页目录项 PUD(Page Upper Directory);
● 中间页目录项 PMD(Page Middle Directory);
● 页表项 PTE(Page Table Entry);
页表缓存
TLB
(Translation Lookaside Buffer)
多级页表解决空间上的问题,但是虚拟地址到物理地址的转换多了几道工序,降低了地址转换速度。
局部性原理,一段时间内,整个程序的执行仅限于程序中的一部分。相应的,执行锁访问的存储空间也局限于某个内存区域。
利用该特性,将最常用的几个页表项存储到访问速度更快的硬件中。在CPU芯片中有一个专门存放程序最常访问的页表项的Cache
,该Cache
结束TLB
,通常称为页表缓存、转址路旁缓存、快表。
在 CPU 芯片里面,封装了内存管理单元(Memory Management Unit)芯片,它用来完成地址转换和 TLB 的访问与交互。
有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。
Linux内存管理
逻辑地址是段式内存管理转换前的地址,线性地址则是 页式内存管理 转换前的地址。
● 32 位
系统的内核空间占用 1G
,位于最高处,剩下的 3G
是用户空间;
● 64 位
系统的内核空间和用户空间都是 128T
,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。
虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
用户空间分布:
● 程序文件段,包括二进制可执行代码;
● 已初始化数据段,包括静态常量;
● 未初始化数据段,包括未初始化的静态变量;
● 堆段,包括动态分配的内存,从低地址开始向上增长;
● 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)
● 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;
进程与线程
进程
进程概念
编写的代码是一个存储在硬盘的静态文件,编译后生成二进制可执行文件,运行后装载到内存中,然后由CPU执行每一条指令,这个 在运行中的程序,称之为进程
。
并行与并发区别
进程状态
生成PCB的原因:
- 系统初始化
- 用户通过系统提供的API创建新进程
- 批处理作业初始化
- 由现有进程派生子进程
一个进程,因为某种原因被创建了,那么它可以按照以下步骤进行一系列的初始化
- 给新进程分配一个进程ID
- 分配内存空间
- 初始化PCB
- 进入就绪队列
五状态模型
就绪 -> 运行:当操作系统内存在着调度程序,当需要运行一个新进程时,调度程序选择一个就绪态的进程,让其进入运行态。
运行 -> 就绪:运行态的进程,会占有CPU(参照一开始的饼状图)。每个进程会被分配一定的执行时间,当时间结束后,重新回到就绪态。
运行 -> 阻塞:进程请求调用系统的某些服务,但是操作系统没法立即给它(比如这种服务可能要耗时初始化,比如I/O资源需要等待),那么它就会进入阻塞态。
阻塞 -> 就绪:当等待结束了,就由阻塞态进入就绪态。
运行 -> 终止:当进程表示自己已经完成了,它会被操作系统终止。
七状态模型
一旦排队的进程多了,对于有限的内存空间将会是极大的考验。为了解决内存占用问题,可以将一部分内存中的进程交换到磁盘中,这些被交换到磁盘的进程,会进入挂起状态
。
挂起状态可以分为两种:
● 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
● 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行;
进程控制结构
对于一个被执行的程序,操作系统会为该程序创建一个进程。进程作为一种抽象概念,可将其视为一个容器,该容器聚集了相关资源,包括地址空间、线程、打开的文件、保护许可等。而操作系统本身就是一个进程,程序 = 算法 + 数据结构
,因此对于单个进程,可以使用一种数据结构进行表示(进程控制块 PCB
)。
进程控制块 PCB (process control block) 作为描述进程的数据结构。
PCB 是进程存在的唯一标识, 这意味着一个进程的存在,必然有一个PCB,如果进程消失,那么PCB也会随之消失。
PCB 包含的信息
PCB 组织方式
通常是通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列。比如:
● 将所有处于就绪状态的进程链在一起,称为就绪队列;
● 把所有因等待某事件而处于等待状态的进程链在一起就组成各种阻塞队列;
● 另外,对于运行队列在单核 CPU 系统中则只有一个运行指针了,因为单核 CPU 在某个时间,只能运行一个程序。
那么,就绪队列和阻塞队列链表的组织形式如下图:
除了链接的组织方式,还有索引方式,它的工作原理:将同一状态的进程组织在一个索引表中,索引表项指向相应的 PCB,不同状态对应不同的索引表。
一般会选择链表,因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。
进程切换
当一个正在运行中的进程被中断,操作系统指定另一个就绪态的进程进入运行态,这个过程就是进程切换,也可以叫上下文切换。
切换过程:
1.保存处理器上下文环境:将CPU程序计数器和寄存器的值保存到当前进程的私有堆栈里
2.更新当前进程的PCB(包括状态更变)
3.将当前进程移到就绪队列或者阻塞队列
4.根据调度算法,选择就绪队列中一个合适的新进程,将其更改为运行态
5.更新内存管理的数据结构
6.新进程内对堆栈所保存的上下文信息载入到CPU的寄存器和程序计数器,占有CPU
进程上下文切换场景
- 进程调度算法,时间片轮转,每个进程轮流执行一段时间,时间片耗尽则由系统挂起,切换到其他等待执行的进程运行。
- 进程在系统资源不足时,由系统挂起等到资源满足后才可以运行。
- 当进程通过睡眠函数
sleep
这样的方法主动挂起时,也会重新调度。 - 当有优先级更高的进程运行时,未来保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
- 当发生硬件中断时,CPU上的进程会被中断挂起,转而去执行内核中的中断服务程序。
线程
线程是比进程更小的能独立运行的基本单位。
线程概念
线程是进程中的一条执行流程。
同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流是相对独立的。
线程是CPU调度的基本单位
线程的上下文切换
线程与进程最大区别: 线程是调度的基本单位,进程是资源拥有的基本单位。
任务调度其实是线程调度,而进程只是给线程提供了虚拟内存、全局变量等资源。
- 当进程只有一个线程时,可以认为进程就等于线程。
- 当进程有多个线程时,这些线程会共享虚拟内存和全局变量等资源,在上下文切换时无需修改。
线程有自己的私有数据,例如栈和寄存器,上下文切换时需要进行保存切换。