本文为学习李治军老师《操作系统原理、实现与实践》第三至五章的总结,主要讲述操作系统多进程视图。
参考资料:
第三章 多进程
使用CPU
- CPU的工作原理:取址-执行
- 为提升CPU的利用率,一个CPU上交替执行多个程序:并发;
多进程图像
- 进程:进行中的的程序;
- 进程描述的是描述程序以及反映程序执行信息的数据结构(PCB)的总和;
多进程基本问题
-
多进程的组织与进程状态
- 进程由PCB(进程控制块)组成,包含了PID、资源分配情况、进程运行情况。对用户而言,可以看到PID,对操作系统而言,底层需要处理的是PCB,可以通过队列组织;
- 进程状态:新建态、就绪态、运行态、阻塞态、退出态
-
多进程的交替:队列操作+调度( schedule())+切换(保存现场-恢复);
-
进程间的影响分离:内存管理(地址隔离);
-
进程间的通信与合作:进程同步(生产者-消费者模型)。
实践项目3
- 进程运行轨迹的跟踪与统计
第四章 线程的切换与调度
线程与进程
-
进程 = 资源 (包括寄存器值,PCB,内存映射表)+ 指令序列
- 进程的切换:资源切换(内存映射表切换) + 指令流切换(PC切换)
- 进程是资源分配的最小单位;
-
线程 = 指令序列(保留了并发的优点,避免了进程切换的代价)
- 线程的切换:指令流切换(PC切换);
- 线程是程序执行的最小单位;
-
线程与进程的对比
线程 进程 详细描述 能否并发 能 能 能够做到程序的交替执行 切换内容 指令流 指令流+资源 进程切换时需切换内存映射表 切换代价 小 大 线程切换时不需切换内存映射表 创建速度 快 慢 创建线程时直接使用进程共有的资源部分 相互影响 操作同一内存 完全分离 多线程使用相同的地址空间,即内存映射表 安全性 小 大 一个线程可访问同进程下其他线程的资源 隶属关系 隶属于一个进程 隶属于操作系统 线程不能脱离进程而存在 特征 程序执行的最小单位 资源分配的最小单位 进程包含资源及指令序列
用户级线程的切换与创建(两个栈)
-
用户级线程:由用户程序直接管理,对操作系统透明;
-
用户级线程之间的切换(yield()函数)
-
两个线程不可以共用一个栈,因此多个线程有多个TCB(thread control block);
-
用户级线程的切换过程:
-
在切换位置上(图示B函数中)调用yield()函数;
-
yield()函数找到下一个线程的TCB,并保存现场(如通用寄存器值入栈等);
-
yield()函数根据当前线程的TCB和下一个线程的TCB完成用户栈的切换:
tcb1.esp = esp ;将寄存器esp中的值保存在当前线程TCB中,此时esp = 1000 esp = tcb2.esp ;从下一线程的TCB中取出保存的esp赋给esp寄存器,此时esp = 2000
-
执行yield()的 } ,根据esp寄存器的值2000在栈中找到相应地址300赋给PC,并执行对应程序C,注意图示中esp2000处为404,因为绘制的是D函数已执行后的状态。
-
-
-
用户级线程的创建
- 两个线程的样子:两个TCB、两个栈、切换的PC在栈中;
- ThreadCreate的核心就是用程序:申请栈、申请TCB、func等入栈、关联TCB和栈。
内核级线程的切换与创建(两套栈)
-
用户级线程的缺点:线程都是在用户段,内核感知不到有多个线程,当浏览器整个进程在调用网卡等待的时候,内核会自动切换到下一个进程,因此一旦内核阻塞,其用户部分的多线程没有并发性的效果。
-
内核级线程
- 是操作系统在一套进程资源下创建的、可以并发执行的多个执行序列,使用同一地址空间,适用于多核处理器结构;
-
多处理器与多核
- 并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,互不抢占CPU资源;
- 只有内核级线程才能发挥多核性能,因为内核级线程共用一套MMU,即内存映射表,但是有多个CPU,可以一个CPU执行一个内核级线程;
-
用户级线程、内核级线程、进程之间的关系
- 三者都属于CPU管理范畴;
- 进程:要执行指令序列,除了通过分配栈、创建数据结构记录执行位置外,还要分配内存资源,进程创建涉及到计算机硬件资源的分配,所有进程必须在操作系统的内核中创建,因此进程中的那个执行序列实际上就是一个内核级线程;
- 内核级线程:是操作系统在一套进程资源下创建的、可以并发执行的多个执行序列,操作系统为每个这样的执行序列创建了相应的数据结构来实现内核级线程的控制;
- 用户级线程:上次应用程序可以创建并交替执行多个指令序列,对操作系统而言不可见。
-
内核级线程之间的切换
-
内核级线程切换需要完成:TCB切换、栈切换、PC指针切换;
-
内核级线程的TCB存储在操作系统内核中,因此必须进入内核才能引起切换–中断;
-
内核级线程切换的五个阶段:
-
第一阶段:执行int中断进入,保存现场;
-
第二阶段:调用schedule,引起TCB切换;
-
第三阶段:内核栈的切换;
current->esp = esp ;将寄存器esp中的值保存在当前线程TCB中 esp = next->esp ;从下一线程的TCB中取出保存的esp赋给esp寄存器
-
第四阶段:中断返回;
-
第五阶段:执行iret完成用户栈切换,即切换用户态程序PC指针及相应用户栈;
-
-
-
内核级线程的创建
- 创建TCB,主要存放内核的esp指针;
- 分配内核栈,存放用户态程序的PC指针、用户栈地址及执行线程;
- 分配用户栈,存放进入用户态函数需要用到的参数
CPU调度策略
- CPU调度的含义与算法准则
- 如果CPU支持线程,则线程是CPU调度的基本单位,否则进程是基本单位;
- PC 机上的任务调度策略主要考虑如下三个基本准则
- 任务的周转时间,即任务从新建到该任务完成所经历的全部时间;
- 任务的响应时间,即从用户向某程序发起一个交互操作到该任务响应这个操作之间经历的时间;
- 系统吞吐量,即一段时间区域内计算机系统能完成的任务总数;
- 若干CPU调度的基本算法
- 先来先服务 (FCFS, first come first served);
- 最短作业优先调度 (SJF, shortest job first);
- 最短剩余时间优先调度(SRTF, shortest remaining time first),可抢占式调度算法;
- 轮转调度 (RR, round robin);
- 多级队列调度 (multilevel queues):就绪队列分为相对独立的队列,前台交互采用RR,后台/底层批处理采用SRTF,通常让前台队列具有更高优先级;
- 多级反馈队列调度 (multilevel feedback queues):核心基础是RR,保证了任务响应时间,动态调整(采用统计IO进行优先级动态调整)可以将多种任务组合,并且解决了 SRTF 中的任务时长预测问题。
实践项目4
- 基于内核栈切换的进程切换
第五章 进程同步
进程同步问题
-
进程同步的基本结构:一个进程需要在需要同步的地方停下来等待依赖进程,当发现依赖进程完成了和同步对应的工作以后,这个进程再继续向前执行。
从信号到信号量
-
只发信号不能解决全部问题;
-
信号量:一个整型变量,用来记录和进程同步有关的重要信息;
- 信号量是一个数据对象以及操作这个数据对象的两个操作:
- 数据对象:信号量数值以及想用的阻塞进程队列;
- 两个操作:对信号量的数值的加1(P操作)和减1(V操作),并根据加减后的信号量数值决定进程的睡眠和唤醒;
- 生产者消费者问题:
- 信号量empty:表示缓存区中剩余的位置,初值为BUFFER_SIZE;
- 信号量full:缓存区中可用单元的个数,初值为0;
- 信号量mutex:互斥进入;
- 信号量是一个数据对象以及操作这个数据对象的两个操作:
临界区:对信号量的保护
-
临界区的引出:
- 由于共同修改信号量,可能造成共享数据语义错误,且错误由多个进程并发操作共享数据引起,错误和调度顺序有关,难于发现和调试;
- 临界区:进程中的一段代码,依次至多只允许一个进程进入;
- 临界区基本原则:互斥进入;
- 好的临界区保护原则:有空让进,有限等待;
-
临界区的软件实现
-
轮换法:不满足有空让进;
-
标记法:可能存在无限等待;
-
非对称标记法:Peterson算法,结合了标记和轮转两种思路;
-
多进程临界区问题:Lamport面包店算法
-
-
临界区的硬件实现
- 关中断:在单CPU情况下,采用cli()和sti()开关中断,使其无法感受到中断向量;
- 硬件原子指令法:使用TestAndSet为临界区加锁,且执行该指令时,CPU会锁住内存总线,禁止其他CPU在该指令结束前访问内存。
信号量的实现与使用
sem_open()
的功能是创建一个信号量,或打开一个已经存在的信号量;sem_wait()
是信号量的 P 原子操作,如果继续运行的条件不满足,则令调用进程等待在信号量 sem 上,返回 0 表示成功,返回 -1 表示失败;sem_post()
是信号量的 V 原子操作,如果有等待 sem 的进程,它会唤醒其中的一个,返回 0 表示成功,返回 -1 表示失败;sem_unlink()
的功能是删除名为 name 的信号量,返回 0 表示成功,返回 -1 表示失败。
死锁现象及死锁处理
- 死锁现象的出现:多个进程互相等待对方持有的资源,而造成谁也无法往前执行的僵局;
- 死锁现象的四个必要条件:
- 互斥:资源必须处于非共享模式,即一次只有一个进程可以使用;
- 不可抢占:资源不能被抢占,只能在持有资源的进程完成任务后,该资源才会被释放;
- 占有并等待:一个进程至少应该占有一个资源,并等待另一资源;
- 循环等待:有一组等待进程
{P0, P1,..., Pn}
,P0
等待的资源被P1
占有,P1
等待的资源被P2
占有,…,Pn-1
等待的资源被Pn
占有,Pn
等待的资源被P0
占有;
- 解决死锁的方法:
- 死锁预防:破坏死锁的必要条件(不灵活);
- 破坏”请求与保持“:限制并发进程对资源的请求,如一次性申请进程的所有资源;
- 破坏”循环等待“:不让资源等待形成环路,如按序申请资源;
- 死锁避免:检测每个资源请求,如果造成死锁就拒绝(过于保守);
- 如使用银行家算法,但时间开销过大;
- 死锁的检测/恢复:当死锁发生时,能够检测死锁的发生,并对相关进程采用回滚的方式进行恢复;
- 死锁忽略:操作系统不采取任何措施。
- 死锁预防:破坏死锁的必要条件(不灵活);
实践项目5
- 信号量的实现和应用