CPU功能:
1.控制器功能
- 取指令
- 分析指令
(指令控制) PC IR - 执行指令,发出各种操作命令
(操作控制)CU 时序电路 - 控制程序的输入以及结果的输出
(时间控制)CU 时序电路 - 总线管理
- 处理异常情况和特殊请求
(处理中断)中断系统
2.运算器功能
实现算数运算和逻辑运算
(数据加工) ALU 寄存器
cpu 组成
ALU 寄存器 中断系统 CU
硬件架构发展史
硬件架构发展史可以从CPU的发展史来划分。
第一阶段:CPU频率低
早期计算机没有复杂的图形功能,CPU核心频率不高,等于内存的频率,因而它们都直接连接在总线Bus上。I/O设备速度相比CPU和内存慢了很多,因而通过I/O控制器来协调I/O设备与总线之间的速度,并让CPU和I/O设备能够通信。
第二阶段:CPU倍频
随着技术的发展,CPU频率有了很大的提升,加上图形功能的要求,此时CPU频率是内存频率的倍数,内存频率跟不上而保持与总线频率一致,CPU以倍频方式与总线进行通信。3D游戏和多媒体的发展促使了图形芯片的诞生,而图形芯片需要CPU和内存之间大量交换数据,I/O总线实在太慢,人们因而设计了高速的北桥芯片方便高速交换数据。
北桥+南桥硬件架构
北桥运行速度高,如果低速设备也直接连在北桥,无疑会造成非常复杂的设计。因而引入南桥专门处理低速设备。
第三阶段:多核CPU
技术的发展带来了CPU的频率提升(从几十kHz到现在的4GHz),但是总归会遇到增长瓶颈(CPU制造工艺的物理极限),而日益增长的计算需求又要求CPU频率必须更快,因而人们想到的方法是通过增加CPU数量来提升速度,也就是多核CPU。
理想情况下速度的提高与CPU数量成正比,但实际上是不可能的,原因就是程序并不都能分解成若干个完全不相干的子问题。
书中举了个很直观的例子:
一个女人可以花10个月生出一个孩子,但是10个女人并不能在一个月就能生出一个孩子。
再比如现在的分布式系统,机器越多,耗费在通信上的时间也自然会越多。
对于操作系统和程序开发者来说,需要了解硬件的编程接口标准来通信,编写操作系统和驱动程序。
操作系统
硬件处理能力是有限的,操作系统需要做的,就是除了提供抽象接口之外,管理硬件资源。进一步说,就是要通过设计各种方法来发挥硬件的所有潜能,也就是硬件的三个核心指标:CPU、内存和I/O。
CPU:多任务系统
不能让CPU闲着,不能让CPU一次只能运行一个程序,为了提升利用效率,目前正在用而比较先进的操作系统模式就是多任务系统。它实现了:让每个应用程序都以进程的方式运行。每个进程都有自己独立的地址空间,使得进程之间的地址空间相互隔离CPU由系统根据进程优先级的高低统一分配抢占式的CPU分配机制可以让系统强制剥夺CPU资源并分配给它认为目前最需要的进程系统
如果分配给每个进程的时间都很短,保证了CPU在多个进程间快速的切换,就能造成很多进程都在同时运行的假象。
I/O:硬件抽象
成熟的操作系统保证访问硬件设备和访问普通的文件形式一样,操作系统开发者为硬件生产厂商提供了一系列接口和框架,按照这个接口和框架开发的驱动程序都可以在该操作系统上使用。这样程序员可以从硬件细节解脱出来,更多关注程序本身。繁琐的硬件细节交给操作系统的硬件驱动程序来完成。
内存:虚拟地址
多任务系统保证了CPU的高利用率,接下来的问题就是如何将计算机优先的物理内存分配给多个程序使用。直接按顺序分配内存的策略虽然简单,但是会遇到三个问题:
- 地址空间不隔离如果所有程序直接访物理地址,很容易造成一个程序改写其他程序的内存数据
- 程序运行的地址不确定程序每次运行都需要内存分配区域,而程序访问数据和指令跳转时的目标地址很多都是固定的,这样便涉及了程序的重定位。
- 内存使用效率低简单分配无法保证有效的内存管理机制,空间连续切换程序会导致大量数据的换入换出,效率底下。
解决方法就是增加中间层,使用间接的地址访问方法。
具体来说,就是在进程和物理地址之间添加虚拟地址实现进程间的隔离,也就是进程 -> 虚拟地址(中间层) -> 物理地址,中间通过映射对应起来。通过创建不存在的虚拟空间,每个进程都有了自己独立的虚拟空间,而且每个进程只能访问自己的地址空间,从而有效地做到了进程的隔离。
具体的实现方法包括分段和分页两种。分段是解决分配策略的前两个问题,分页则是解决第三个问题。
分段Segmentation
基本思路就是程序需要多少内存,就设置多大的虚拟内存并映射到某个物理地址空间,只要程序访问虚拟空间的地址超出了设置的内存大小,硬件自动判断为非法访问,拒绝请求。这样它实现了地址隔离,同时程序不需要重定位,不需要关心物理地址的变化(因为只需要按照虚拟空间的内存分配来编写程序)。但分段依然没有解决内存使用效率不高的问题。分段对内存区域的映射还是按照程序为单位,如果内存不足,依然会以整个程序为单位进行换入换出,造成大量磁盘访问操作从而严重影响速度。整个因果关系流可以表示为:内存不足 -> 整个程序换入换出 -> 大量磁盘访问操作 -> 严重影响速度
而事实上,程序在运行时在某个时间段只会频繁的使用一小部分数据,很多数据在整个时间段都不会用到,因而完全可以用更小粒度的内存分割和映射的方法,充分利用程序的局部性原理,这种方法就是分页。
分页Paging
分页的基本方法就是把地址空间人为地等分成固定的页,以页为单位进行数据的存取和交换。
通过一个叫MMU(Memory Management Unit)的部件来实现页映射。映射流可以表示为:CPU --虚拟地址--> MMU --物理地址--> 物理内存
线程及线程安全
在多核CPU基础之上,多线程是实现软件并发执行的一个重要方法。
何谓线程
简单来说,一个进程包含多个线程,每个线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成,各个线程之间共享程序的内存空间和进程级的资源,同时又互不干扰的并发执行。
多线程的好处有如下:
- 有效利用进程操作的等待时间
- 线程明确分工,比如一个负责交互,另一个负责计算。保证程序和用户之间的交互不被中断(一个线程很容易中断)
- 程序逻辑本身要求并发操作
- 单线程无法发挥计算机全部的计算能力,多核CPU+多线程势在必行
- 数据高效率共享
对于单处理器+多线程的情况,类似CPU多任务系统,轮流执行线程造成每个线程都「看起来」在同时执行的假象,专业名词叫线程调度。在线程调度中,线程至少有三种状态:运行Running、就绪Ready和等待Waiting互相切换。
线程安全
因为线程共享程序的内存空间和进程级的资源,因而一个进程访问的数据很可能也被其他线程访问(改变),线程安全处理的就是多线程程序并发时的数据一致性。不可再分的单指令操作称为原子操作,原子操作执行不会被打断。如果能够保证原子操作的正确先后,数据读写就不会出现问题,但这仅仅适用于比较简单的场合,复杂情况下更好的方法是锁。
同步与锁:多线程同时读写一个数据的安全防护
为了避免多线程同时读写一个数据产生不可预料的后果,需要对多个线程访问同一个数据做同步。
所谓同步,就是一个线程还没访问完一个数据,其他线程不能再对该数据进行访问,从本质上讲,保证了对数据的访问是原子化了。
同步的最常见方法是锁。线程访问数据的整个过程是:访问前获取锁 --> 访问结束释放锁
最简单的情况自然是二元锁,也称为二元信号量(Binary Semaphore),只有两种状态:占用和非占用。占用时其他线程无法获取锁,直到被释放转为非占用。非占用时第一个线程自然可以获取锁。
如果资源能够允许多个线程并发访问,那就是多元信号量。显然二元信号量是特殊情况。一个初始值为N的信号量允许N个线程并发访问,当线程访问资源时首先获取信号量,进行如下操作:
semaphore = semaphore - 1;if(semaphore < 0) current thread waiting;else { visit source, then release semaphore semaphore = semaphore + 1; if(semaphore < 1) woke up a waiting thread}
另外几种同步的方法包括:互斥量Mutex、临界区Critical Section、读写锁Read-Write Lock和条件变量Condition Variable。
互斥量与二元信号量类似,不同的地方在于:信号量可以被任意线程获取并释放,而互斥量要求哪个线程获得,它就必须要负责释放这个锁。
网友评论