1.计算机硬件简单介绍
1.1 基本硬件组成
-
处理器
一般是CPU,计算机用来完成运算的主要组成,其中一般还包含多级缓存和支持的指令集. -
存储器
RAM,运行中的程序计数器(指向下一条CPU指令的指针)及其堆栈和变量等一切相关信息会放在缓存中,方便CPU调取,速度比起CPU的内部缓存会慢,又快于磁盘等大容量存储设备. -
磁盘
计算机用于存储数据的设备,速度很慢,但是掉电后非易失,存储容量大,廉价等特性让其成为现代计算机系统不可缺少的设备. -
I/O设备
输入输出设备. -
总线
用于将上述的所有设备链接起来的抽象概念,现在计算机系统一般会有多种总线,用桥(bridge)再连接各级总线.
1.2 硬件间的通讯
以一个最普通的作业为例,假设它的运行包括如下步骤
1. 运算
2. 等待I/O设备输入
3. 从磁盘读取数据
4. 运算
5. 结束
- 假设在没有操作系统,在最简单的情况下.CPU会通过一个循环忙等待将会从步骤2中输入的数据,然后通过总线通知磁盘读取数据,然后又会以相同的方法等待步骤3返回的数据.
细心的你可能会看到这个没有操作系统的计算机存在巨大的资源浪费.
-
在装有操作系统的计算机上,在步骤2和步骤3中,CPU不会进入一个忙等待过程,而是会将当前作业的程序计数器,变量,堆栈等信息存储在内存中的一个特殊表中,然后调取下一个作业开始执行.
当I/O设备完成输入,或者磁盘完成读取操作后,这些设备会通过总线,向中断控制器发出中断信号,再由中断控制器通知CPU.
CPU决定是将当前任务暂存(挂起),代而执行之前挂起的作业,还是等待合适的时候再执行先前程序,取决于操作系统调度算法,总之,这样就通过操作系统完成了比较高效的作业执行.
上述模型在交互较少的情况下有着良好的效率,但是在交互式系统中,多个用户可能希望多个作业在感官上同时执行,而不是等待上一个作业执行完毕或等待I/O或读写磁盘时再执行下一个作业.这时就要求操作系统必须实现作业的并行,起码是伪并行,因为真正的并行在单CPU计算机上是不可能的.
2.单CPU计算机实现伪并行
2.1 为什么需要并行
-
现代计算机系统中,在一般情况下,同一个作业中,CPU的运算时间远远低于I/O或读写磁盘存储器这类耗时操作所需要的时间.
在没有并行的时候,CPU在完成运算后就需要等待I/O,这是个巨大的资源浪费 -
在交互式系统中,一般要求多个用户的进程在感官上同时执行,或者单个用户的多个进程间也要求感官上的同时运行,如果做不到这一点,该系统的用户体验也是极其恶劣的.
所以,尤其在交互式系统中,(伪)并行将是一个合格的操作系统不得不考虑的问题.
2.2 操作系统的作用
个人认为操作系统有以下职责:
- 封装硬件,提供简单接口
- 提供诸如进程,线程,地址空间,文件等的抽象
2.3 操作系统如何实现伪并行
首先需要了解什么是进程,什么是线程
1. 进程:是操作系统对一个正在运行的程序的抽象.其中包括该程序的程序计数器,寄存器,变量的当前值等.
2. 线程:是一种轻量化的进程,与进程不同的是它与其他同属一个进程的线程间是共享寄存器的.
-
一句话解释系统实现(伪)并行的方法:
CPU在所有进程间快速切换. -
如何触发切换
这种切换的出发机制有两种可能,一种是时钟中断,一种是I/O中断.具体的实现细节在不同的操作系统中是不同的,但是大体上是类似的,CPU在经历了N个时钟中断后将当前的进程挂起,转而运行下一个进程(具体运行哪一个程序,这取决于操作系统的调度算法).或者当进程在等待I/O操作,或有耗时操作,需要CPU忙等待时,CPU就会运行下一进程. -
如何切换
进程切换过程如下:- 时钟中断,或I/O中断,或读写数据到达,发生进程切换
- 原有进程的程序计数器,变量当前值,堆栈等被保存在寄存器的一个特殊表中
- 调取下一个进程的程序计数器,变量当前值,堆栈等
- 运行该进程
对于用户而言,相当于操作系统为其进程或线程提供了一个单独的CPU和寄存器.
3.互斥问题
在实现了(伪)并行后,马上就会有一个严重的问题摆在面前"竞争条件"
举例说明:(假设有两个进程A,B)
1. A将变量a写入共享寄存器,准备使用显示器显示到屏幕上.
2. 就在A准备但还没有显示a时,发生了中断.
3. CPU将A挂起,转而运行B.
4. B将变量b写入相同位置,准备显示.
5. 再次发生中断,在b没有显示的时候,CPU切换回A进程.
6. A错误的显示了变量b.
这样A的变量a在未被A察觉的情况下永远的丢失了.
这就是"竞争条件,"为了解决这一问题,A在访问共享内存时,B必须被阻止访问到该寄存器.即该寄存器必须被"互斥"的访问.
3.1 单CPU计算机如何解决互斥问题
由此可见,竞争条件问题形成的充分条件有两个,一是要有共享资源,二是要在特定的时间发生中断.只要打破了其中的一条,就能完成互斥.实现资源的不共享是不可能的,所以我们必须能够让CPU屏蔽中断信号.
在实现互斥的问题上,我们必须引入一个概念"锁变量".这个变量为0时,表示共享资源可以被访问,为1时表示共享资源已被锁,不能访问.
为每个共享资源加入锁变量后,在进程想要访问共享资源时直接读取锁变量就知道资源是否可用.
具体实现如下:
1. 进程A想要访问共享资源,通知CPU屏蔽所有中断.
2. A判断资源是否可用,可用则将锁变量置1.不可用则伺机再从第一步重新执行.
3. CPU开始接收中断正常运行.
4. A完成操作,将锁变量置0.
这样就实现了单CPU计算机系统上的互斥,同样还有很多算法也可以实现互斥,如Peterson算法.但是包括上述方法,在具体使用场景中,用的很少.一般使用TSL指令来实现互斥.
3.2 多CPU(或多核)计算机解决互斥问题
在多CPU(多核心CPU)计算机系统中,通过屏蔽中断,是无法实现互斥的.因为就算让进程所在的CPU屏蔽了中断,也不能保证运行在其他CPU上的进程不会访问到共享资源.所以需要一种全新的互斥方法.
TSL(Test and Set Lock)指令.这是一种硬件级别的指令,其核心方法是直接锁定内存总线.
TSL的汇编代码进行了如下操作:
1. 将锁变量复制到程序寄存器中,并将原来的锁变量置1.
2. 将寄存器中的值与0做对比,等于则表示可以访问共享资源,不等于则伺机再执行TSL.
3. 操作结束后,讲锁变量置0.
有了TSL指令,就可以在多CPU计算机系统上实现互斥.
在Pthread中有关互斥量的操作,都是这个原理.
3.3 信号量和互斥量
用TSL可以原子的实现信号量和互斥量,也可以理解为信号量和互斥量是对TSL指令的封装.
-
信号量是记录未被执行的锁和释放的次数,在解决界缓冲问题的时候,通过多个信号量来标记每个进程的状态,从而决定进程的行为.过程较为复杂,再次不做详述.
-
互斥量是简化的信号量,因为互斥量是一个二元信号量,一般是0和其他.用于标记两种状态.
值得注意的是不管是在信号量还是互斥量操作中,未获得锁后是直接放弃CPU使用(thread_yeild调用),而不是忙等待.
4.哲学家就餐
现在尝试用以上互斥方法解决哲学家就餐问题.
4.1 问题描述
- 有N个哲学家,哲学家状态有两种,就餐和思考,并随机发生.
- 每个哲学家面前有一碗面,左右各一把叉子.共有N碗面,N个叉子(两个相邻的哲学家之间只有一把叉子).
- 哲学家就餐需要有两把叉子,并且只能在自己左右取.规定,哲学家先拿左叉后拿右叉.
- 让每个哲学家顺利完成就餐和思考.
4.2 问题分析
- 如果N位哲学家同时就餐,拿起身边的左叉,等待右叉就会发生死锁.
- 如果在一位哲学家就餐时用互斥量锁住所有的叉子,不让其他哲学家就餐,这样是可以的,但是会造成资源的浪费.
- 所以为了最大化的使用资源,得想办法锁定与就餐的哲学家有关的叉子,而不是锁定全部.
4.3 尝试解决
该哲学家左右都是处于非就餐状态的哲学家时,这个哲学家就可以就餐,这样就解决了资源的最大化利用.
使用三个主要变量
1. 每个哲学家的状态数组state[],状态有THINKING,HUNGERY,EATING.
2. 互斥量mutex,用于获取两把叉子.
3. 数组s[],由于哲学家在想就餐的时候不一定能立即实现,所以需要信号量来记录未实现的就餐需求.
步骤:
1. 原子性检查左右哲学家的状态
2. 如果左右的哲学家都不是处于EATING状态,则表示可以就餐.
3. 如果不是,则将自己挂起(对信号量s[i]执行down操作,以记录这次就餐请求),等待2中描述的状态.
4. 完成就餐放下所有叉子.
互斥:
1. 哲学家必须原子性的检查自己左右的哲学家是否处于EATING状态.使用mutex.
2. 在未达到预期状态(左右都是非EATING状态的哲学家)的时候,用一个信号量s[i]记录用餐请求,等待情况出现.完成就餐后up这个信号量.
来自博客:http://newlooc.com
网友评论