计算机基础知识回顾
SMP(Symmetrical Multi-Processing)对称多处理器:每个CPU在系统中所处的地位和所发挥的功能都是一样的。通常情况下的程序不可能分解成多个不相干的子问题,除非商用的计算环境(充分发挥每个CPU的能力),所以多核处理器(Mutli-core Processor)和 SMP 通常是一个概念。
层次结构,计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。开发工具与应用程序属于同一个层次,因为它们都使用运行库提供的 应用程序编程接口(Application Programming Interface)。运行库使用操作系统提供的 系统调用接口(System call Interface),通常以软件中断方式实现提供。操作系统内核层使用硬件层提供的 硬件规格(Hardware Specification)
操作系统的一个功能是提供抽象的接口,另一个主要功能是管理硬件资源(CPU,存储器(内存和磁盘)和 I/O 设备)
不要让CPU打盹:早期的CPU是 单一 服务式,简直浪费,后来出现了分时系统(Time-Sharing System),以 协作 的方式主动让闲CPU,供其他程序使用,再到现在,操作系统接管了所有硬件资源,本身运行在一个受硬件保护的级别,所有程序都以进程的方式运转,并且允许 抢占式 的CPU分配方式
设备驱动(Device Driver),操作系统 将硬件逐渐抽象成一些列的概念。比如:UNIX 中硬件设备的访问形式跟访问普通的文件形式一样,Windows 中,图像硬件被抽象成 GDI,声音和多媒体设备被抽象成 DirectX 对象等,向上提供统一的接口供程序使用,设备驱动完成向下对硬件的适配
I/O 设备:内存和磁盘的读取和写入是通过 I/O 端口寄存器来实现的,提供读取和写入的地址,来完成磁盘数据操作
即操作系统以进程概念,使得每个进程从逻辑上来看都是独占计算机资源,实质上操作系统以抢占式的 CPU 策略,I/O 抽象模型的方式来管理这些资源
操作系统是如何管理内存的
解决的问题:如何将计算机 有限 的物理内存分配给 多个 程序使用
直接分配物理内存的弊端:地址空间不隔离(程序直接访问物理内存),内存使用效率低(缺乏有效的内存管理机制,使得频繁换入换出数据,内存的使用分配以程序为单位(粒度太大)),程序运行的地址不固定(程序每次装入内存执行时,数据和指令跳转涉及到重定位问题)
解决方法: 增加中间层来 隔离,把程序给出的地址看作是一种 虚拟地址(Virtual Address),通过某些映射方法,保证任意一个程序所能够访问的物理内存区域跟另外一个程序相互不重叠,以达到地址空间隔离的效果
分段(Segmentation):段映射,以程序所使用的的内存为单位,从虚拟地址映射到物理地址,映射结果唯一,解决了隔离和程序地址不固定的问题
分页(Paging):页分割,把常用的数据和代码页装载到内存中,把不常用的代码和数据保存在磁盘里,当用到的时候再取出来。若用到的页不在内存中,操作系统会受到页错误(Page Fault),然后将需要的页装载到内存中。硬件级别:MMU(Memory Management Unit)用来支持虚拟地址到物理地址的转换,已集成到CPU内部
程序执行流最小单元:线程(Thread),又称为轻量级进程(Lightweight Process,LWP)
基础概念
线程私有(局部变量,函数的参数,TLS数据),线程之间共享(全局变量,堆数据,函数里的静态变量,程序代码,打开的文件,这些就是进程所有的东西)
调度(线程数>处理器时(或者单处理器应对多线程时),会模拟出来一种并发的状态,即切换不同的线程行为,称之为线程调度
至少三种状态:运行,就绪,等待,以时间片的改变作为区分
调度算法:优先级调度(Priority Schedule),轮转法(Round Robin),也区分出 IO密集型线程(IO Bound Thread) 和 CPU密集型线程(CPU Bound Thread),IO 总比 CPU 能获得更高的优先级
为了防止 饿死(Starvation) 现象: 用户指定优先级/根据进入等待状态的频繁程序提升或降低优先级/长时间得不到执行而被提升优先级,也出现了 可抢占式线程(Preemption)
线程安全
竞争与原子操作:多个线程同时访问一个共享数据,为此引出 原子性(Atomic),但原子性操作只适用于简单的操作,更复杂的数据结构访问,通常用 锁 的手段
同步与锁:同步(Synchronization) 是一种机制:保证线程访问数据的原子性。锁(Lock) 是一种手段,每个线程在访问数据或资源之前,首先试图 获取(Accquire) 锁,并在访问结束之后 释放(Release) 锁
锁的演进:二元信号量(Binary Semaphore),只允许一个线程独占,其状态有两个:占用 和 非占用,可以由一个线程获取,另一个线程释放;多元信号量(N Semaphore),允许 N 个线程并发访问共享资源;互斥量(Mutex),类似于二元信号量,但谁获得谁负责释放;临界区(Critical Section):不同于之前的信号量,互斥量(作用域为进程内的所有线程),临界区的作用范围仅限于本线程,其他线程无法获取临界区的锁,因此也就无法访问;条件变量(Condition Variable),使用条件变量可以让许多线程一起等待某个事件的发生,当事件发生时,所有线程可以一起恢复执行
读写锁(Read-Write Lock):三种状态分别是,自由(Free),共享(Shared)和独占(Exclusive),当处于自由状态时,试图以任何一种方式获取锁都能成功,并将锁至于对应的状态。如果锁处于共享状态,其他线程以共享的方式获取锁仍会成功(读),此时这个锁分配给了多个线程。如果其他线程试图以独占的方式获取已经处于共享状态的锁,那么他将必须等在锁被所有的线程释放后,才能将锁的状态置成独占(写)
可重入(Reentrant):即幂等性的函数,可以称为可重入函数
过度优化:使用 volatile 关键字,解决 1. 阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回,2. 阻止编译器调整操作 volatile 变量的质量顺序,但还是无法阻止CPU动态调度换序
线程内部三种模型
多线程库的实现方式:对用户来说如果有三个线程在同时执行(用户态线程(User Thread)),对内核来说很可能只有一个线程(内核线程(Kernel Thread)),下面提及的模型都是 UT:KT
一对一模型:真正意义上的并发调度,缺点是受内核线程数量限制,内核线程上下文切换调度开销较大,使得用户线程执行效率低下
多对一模型:高效的上下文切换和几乎无限制的线程数量,缺点是一个用户线程阻塞了,那么所有线程都将无法执行
多对多模型:多个用户线程映射到多个内核线程,切换方便且避免了一个阻塞全局的情况