文章目录
操作系统作用
- 合理的分配和调度资源
- 操作系统是对硬件资源的一种抽象
中央处理器 CPU
- 负责从寄存器中取指令并且执行
- 寄存器保存了一些进程的状态信息
- CPU 进行处理两种模式的指令:
- kernel 模式:权限大,可以访问所有内存
- user 模式:权限小,只能访问部分内存
Multiprogramming
- Multiprogramming 就是让单个 CPU 看起来好像在同时处理几个程序,但是其实只有一个 CPU
User / Kernel mode 切换
- 每个进程在创建的时候都会有一个 mode bit
- 当进程处在 user mode 的时候, mode bit = 0,这个时候,如果进程想进行一些只有 kernel mode 才能进行的操作(比如说 printf 操作)这个时候,进程就会申请转成 kernel mode,然后这个请求被允许之后,mode bit = 1,然后就可以进行一些高级的操作了
- mode bit (PSW)是由硬件提供的,它被用来区分进程处于 user mode 还是 kernel mode
User 和 Kernel mode 区别
- 在 kernel mode, CPU 可以处理所有类型的指令 instructions,并且使用所有 hardware 提供的 features
- user mode 只能处理一部分的 instructions
- 两种不同的 mode 可以让起到一定的隔离作用,让普通的用户无法接触到 critical instructions
system call
-
system call 的目的:允许 user mode 的进程访问和处理一些只有 kernel mode 才能使用功能
-
什么属于 system call 的范畴呢?也就是 kernel mode 的进程相比于 user mode 的进程可以进行那些类型的核心操作呢?
- 进程控制 process control:加载或者处理进程,创建或销毁进程,获取进程的属性等…
- 文件管理 file management:创建,删除,打开,关闭,读写文件,设置和读取文件属性
- 设备管理 device management:请求、释放一个设备,读写一个设备
- 信息维护 information maintainance:获取或者设置时间或日期
- 通信 communication:创建或者删除通信连接
-
比如我们在 C 语言中常用的
exit(), printf, fopen
等命令都需要调用 system call
使用 system call 的目的
- 使用 system call 可以让一个 user mode 的进程来访问并且处理和使用那些只有 kernel mode 才能处理的指令或功能。
- 由于调用 system call 是耗时的,因此对于开发者来说,在设计系统的时候,对于一些时间严格要求的项目,需要关注 system call 的类型和使用方法,对于其他类型的项目,不必特别关注。
fork + exec 创建进程
- Unix 系统中 fork 创建的子进程和父进程具有完全同样的内容(同样的的内存映像,同样的环境字符串和同样的打开文件),要通过 exec 给子进程更改这些内容
- Windows 系统中没有这个命令,但是初始化的子进程和父进程不同
- 使用 fork 创建的进程都有一个 PID,然后 fork 的返回值就是 child 的 PID,给 child 的 process assign 一个 0
进程状态
- 就绪态 ready
- 运行态 running
- 阻塞态 blocked
- 运行可以到阻塞可以到就绪
- 就绪只能到运行
- 阻塞只能到就绪
中断种类 interrupt
- 中断可以打断 CPU 的正常运行
- 硬件设备产生的叫 true interrupt,真中断
- 用户程序产生的叫 pseudo-interrupt 伪中断,例如程序中的 divide by zero 这种异常会导致程序中断
- 用户可以通过 ctrl-c 主动地产生伪中断
线程 / 进程
-
线程之间共享的内容:
- 地址空间和内存(代码段,全局变量和 堆空间)
- 打开的文件
- 子进程 child process
- signal 和 signal handler
-
不共享的内容:
- 局部变量
- stack(存放线程调用的 function)
- 线程状态state
- 程序计数器(program counter):目前正在执行哪一个 instruction
相比进程使用线程的优势
- 更少的时间创建一个线程
- 更少的时间用来终止一个线程
- 更少的时间进行线程之间的切换
- 更少的时间进行线程之间的通信
kernel / user 线程的比较
https://www.cnblogs.com/mystudy2016/p/5862001.html
User level thread
优势
- 创建更加简单和迅速
- 可以在任何操作系统上被创建和使用
- 进行 user-level 线程切换的时候不需要使用 kernel mode 的特权,减少了对资源的消耗
缺点
- 当一个 user-level 的线程阻塞,整个进程中所有的 user-level thread 都会阻塞;因为 CPU 的调度是基于进程(或者 kernel 级线程)的,user 模式的线程阻塞的时候, CPU 的调度系统感知不到
Kernel level thread
优势
- 当 kernel-level thread 阻塞,进程的其他线程不会阻塞,因为 kernel-level 的进程是被 CPU 调度的
- 在 多个处理器 的系统中,一个进程的多个 kernel-level 线程可以在进程中被同时调度(像进程一样)
缺点
- 创建缓慢,几乎如同创建一个进程
竞态条件 race condition
-
发生的条件:
- 同时有多个进程可以对一个共享资源进行访问
- 所有的进程可以 read 或者 write 这个共享资源
-
特点:
- 输出取决于这些进程对共享资源的操作顺序
- 非常难以 debug
-
假设两个进程 A,B 同时执行这段代码:
- case1:
- A 执行 1, A 执行 2,B 执行 1
- 这时候由于 B 执行的时候满足 return 条件所以 B 直接 return
- case2:
- A 执行 1, B 执行1,A 执行2(stack 为空),B 执行2(发生错误)
- 上面的例子展示了因为执行顺序的不同而产生的不同结果,这个例子中就发生了 race condition
临界区 critical region
- 作用:禁止多个进程对共享资源同时访问
- 通过 critical region 来避免 race condition :
- 禁止同时两个进程进入临界区(保证互斥性 mutual exclusion)
- 对 cpu 的执行速度和数量没有任何假设(任何 CPU 情况都应该满足互斥性)
- 运行临界区以外的代码时,不能阻塞其他进程(progress)进入临界区;即,一个进程离开 critical region 的时候,不能阻碍其他进程进入 critical region
- 不能让想进入临界区的进程无限等待(bounded waiting)
通过 busy waiting 来避免临界区问题
lock variable
- 通过一个共享锁变量 lock
- 每次进程进入之前都要检查这把锁是否可用
- 这种情况存在疏漏,例如当一个进程读取锁 lock=0 然后在将lock 设为 1 之前另外一个进程读了这把锁 lock=0,那么这种情况会导致两个进程同时进入 critical region
strict alternation 严格轮转
- 一种对 critical region 实现 mutual exclusion 的方式:两个进程必须按照 A 进 B 出,A 出 B 入 的方式严格交替,否则就会出现问题,这种类型的 mutual exclusion 如果其中一方执行时间比较长就会造成问题,会违背 process 原则
busy waiting 特点
- 当一个进程试图进入 critical region 的时候会检查一个 entry 是否可进入
- 如果不能,就使用 while 循环来进行等待,直到可进入为止
缺点
- 浪费 CPU 资源
- 存在优先级反转问题:
- 低优先级的进程先执行
- 也存在进程 starving 的现象
死锁
- 当一个进程自己锁住一个资源尚未释放,去请求另一个共享资源,占有另一个共享资源的进程也在同时请求当前进程占用的资源
如何解决死锁
- 忽略死锁(重启程序)
- 死锁检测和修复
- 进行谨慎的资源分配
进程的类型
- 根据一个进程使用 CPU 和 IO 的情况,分为:
- CPU 密集型进程:CPU-Bound
- I/O密集型进程:IO-Bound
进程调度
哪些进程可以被调度器调度
- 处于 ready 状态的进程才能被调度器调度,调度的意思就是给它分配资源和 CPU 时间让它可以运行
为什么要调度
- 因为进程的运行需要消耗资源和 CPU 时间,而调度可以让不同的进程获得的资源更加合理,提高系统运行的效率
- 进程调度器负责决定执行哪个进程,执行多长时间。
调度器的目标
-
一般衡量调度器使用四个指标:
- 公平性:所有的进程获得公平的 CPU 时间
- 吞吐量:单位时间内完成的 process 的数量
- 周转时间:从进程开始到进程结束的时间
- 响应时间:一个请求从提交到第一次获得响应的时间
-
几种常用的时间指标:
-
不同的系统的调度目标不同,主要存在三种不同的系统:
- 批作业 batch work:进行重复的循环操作,几乎不需要和人进行交互,这种系统 不需要考虑及时响应交互式的请求
- 交互式系统 interaction:尽可能让用户和使用者不等待
- 实时系统
-
无论在哪种系统中,公平地调度都是一个很重要的目标
batch work system
- 在 batch work 系统中,一般使用下面的指标来衡量:
- 吞吐量:单位时间内完成的作业数量,越大越好
- 周转时间:一个批作业提交开始到该 作业完成为止的统计平均时间,越小越好
- CPU利用率:CPU 利用率并不是一个很好的衡量指标,因为吞吐量明显比 CPU 利用率更加重要,越忙越好
interaction system
- 在交互系统中,最重要的是响应时间:即从用户发出命令到得到响应之间的最小时间;
- 其次 均衡性 也很重要,因为用户对于一个事情的完成时间会有一个主观的预期和判断
real-time system
- 在实时系统中,我们使用下列指标:
- 满足截止时间:避免丢失数据
- 可预测性:在多媒体系统中避免品质降低
抢占式调度 & 非抢占式
- 非抢占式调度就是:一个进程只要开始,要么通过完成而释放 CPU 资源,要么被阻塞释放资源,执行过程中不可以被打断
- 抢占式调度:一个进程在执行过程中可以被其他进程抢占
各种系统中常用的调度算法
https://www.geeksforgeeks.org/cpu-scheduling-in-operating-systems/?ref=lbp
Batch work 系统中
先入先处理 FCFS (first-come-first-servered)
最短任务优先 shortest job first
- 先执行任务量最小的,这样可以 保证最小的周转时间
- == 但是要达到最短周转时间这个指标要求所有的进程都同时到达等待队列 ==
最短剩余时间优先 shortest remaining time next
- 不需要所有进程同时到达等待队列
- 抢占式的调度可能导致饿死
交互式系统中
Round-Robin
- 给每个进程都分配一个标准的 CPU 时间片,然后轮训所有的 ready 进程进行调度
priority 优先级调度
- 将多个优先级相似的进程放到同等的优先级队列中等待运行
- 相同优先级的时候使用 round robin
- 可能存在饿死问题
- 高优先级的队列中的进程处理完成之后,会降低自己的优先级
Memory 分级
- 寄存器 register
- 缓存 cache
- 主存 main memory
- 硬盘 hard disk
对内存进行抽象的意义
不抽象内存如何进行 multi-processes
Base Register & Limit Register
- 通过 base register 和 limit register 可以划定一部分特定的内存地址来分配各某个 process
- base register:决定了当前进程能够使用的内存的起点
- limit register:决定了当前进程能够使用的内存的终点
- 多个 process 可以在自己的内存段中进行操作,也可以实现 multiple processes
物理地址空间和虚拟地址空间
内存管理的作用
(连续)内存空间管理
Swapping
- 将硬盘和主存之间的数据进行交换,进而表现出更大的内存空间,将暂时用不到内存的 process 先转移到 disk 上,用的时候再 swap 回来
- 图中阴影的部分就是还没有使用的内存空间
- 从 (c),(d)中可以看到,A 进程被 swap 到 disk 中了,因此腾出了更多的内存空间可以运行其他的进程
Bitmaps & Linked list
-
为进程及时分配合理的空间
-
当进程使用结束后及时收回内存空间
-
保持对分配出去的内存空间的跟踪
-
bitmap:图中的 bitmap 使用了 8 个 bit 位,bit 位为 1 表示已经被 process 占用,为 0 表示当前空闲
-
linked list
-
linked list 上的每一个已经占据的内存块都用 P 表示
-
空闲的部分用 H 表示(hole),然后是开始的位置以及长度信息
-
缺点:
- search 的速度比较慢
- 有可能造成空间的浪费
连续内存管理算法
- 挑选内存管理算法的原因是为了在分配内存的时候更加高效
First fit
Next fit
Best fit
- 容易造成很多小的碎片导致内存浪费
Worst fit
- 每次都选用最大的 block 分配给进程
Quick fit
将 system 中所有的 available memory 进行分类,把相同大小的 memory 分成一类,存进 linked list 备用。当一个 process 需要分配空间的时候,就会找对应大小的区域,取出这个 类 中的第一片空间直接分配给进程
连续内存管理(分配)的问题
(非连续)虚拟内存(更加灵活的方式)
- 虚拟内存是一种抽象概念,只是逻辑上连续的空间,但是在物理上未必连续
- 代码和数据在运行的时候不一定非得在主存中
- 虚拟地址空间可能比物理地址空间要大得多
(虚拟)内存空间管理
Paging 分页技术
MMU (Memory Management Unit)