目录

一,CPU、内存、总线、设备驱动

1,计算机基本结构

2,设备驱动

3,Cache

4,DMA访问

二,进程、线程、锁

1,进程和线程

2,线程调度

3,Linux系统的进程和线程

4,原子操作、同步、锁

5,可重入

6,过度优化 VS 线程安全

三,并发并行、中断自陷、同步异步

1,并发、并行

2,中断、自陷

3,同步、异步

四,系统设计、调度管理

1,多CPU系统设计

2,多道程序设计 multiprogramming

3,分时系统

4,作业调度、进程调度


一,CPU、内存、总线、设备驱动

1,计算机基本结构

计算机可以分成CPU、内存、IO设备、其他部件。

总线(BUS)是用来连接所有部件的。

后来人们设计了高速设备处理的北桥芯片、低速设备处理的南桥芯片。

操作系统原理_C

北桥PCI连接CPU、内存等,南桥ISA连接低速设备,

系统总线是PCI BUS,低速设备采用ISA总线汇总到南桥,高速设备有各自的总线汇总到北桥。

2,设备驱动

对于硬件设备,操作系统中的硬件驱动程序来实现具体操作,向上提供统一的访问模式,软件开发只需要调用接口。

设备驱动由设备厂商开发,操作系统开发者为设备厂商提供接口,按照该接口开发的驱动就可以在该操作系统上使用。

3,Cache

操作系统原理_C_02

4,DMA访问

操作系统原理_信号量_03

二,进程、线程、锁

1,进程和线程

每个进程都有自己的独立内存空间。

线程,也可以成为轻量级进程,一个进程由一个到多个线程组成。

操作系统原理_信号量_04

2,线程调度

CPU的运行被切割成时间片,每个时间片运行一个线程,不断切换运行线程。

线程至少有3个状态:运行running、就绪ready、等待waiting

操作系统原理_C_05

3,Linux系统的进程和线程

Linux 对多线程的支持绩为贫乏, 事实上, 在 Linux 内核中并不存在真正意义上的线程概念。Linux 将所有的执行实体 (无论是线程还是进程) 都称为任务 (Task), 每一个任务概念上都类似于一个单线程的进程, 具有内存空间、执行实体、文件资源等。不过, Linux 下不同的任务之间可以选择共享内存空间, 因而在实际意义上, 共享了同一个内存空间的多个任务构成了一个进程, 这些任务也就成了这个进程里的线程。在 Linux 下, 用以下方法可以创建一个新的任务, 如表 1-2 所示。

操作系统原理_条件变量_06

4,原子操作、同步、锁

一个独立的不被其他操作影响的操作叫原子操作。

线程是共享数据的,为了避免同时读写导致数据异常,需要对数据进行访问同步,即一个线程访问的时候另外一个线程不能访问,这样的访问就是原子操作。

同步的最常见方法就是锁。

常见的锁:信号量、互斥量、临界区、读写锁、条件变量。

二元信号量 (Binary Semaphore) 是最简单的一种锁, 它只有两种状态: 占用与非占用。它适合只能被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时, 第一个试图获取该二元信号量的线程会获得该锁, 并将二元信号量置为占用状态, 此后其他的所有试图获取该二元信号量的线程将会等待, 直到该锁被释放。

对于允许多个线程并发访问的资源, 多元信号量简称信号量 (Semaphore), 它是一个 很好的选择。一个初始值为N 的信号量允许 N 个线程并发访问。线程访问资源的时候首先获取信号量, 进行如下操作:

操作系统原理_互斥量_07

- 将信号量的值减 1 。
- 如果信号量的值小于 0 , 则进入等待状态, 否则继续执行。

访问完资源之后, 线程释放信姿量, 进行如下操作:
- 将信号量的值加 1 。
- 如果信号量的值小于 1 , 唤醒一个等待中的线程。

互斥量 (Mutex) 和二元信号量很类似, 资源仅同时允许一个线程访问, 但和信号量不同的是, 信号量在整个系统可以被任意线程获取并释放, 也就是说, 同一个信号量可以被系统中的一个线程获取之后由另一个线程释放。而互斥量则要求哪个线程获取了互斥量, 哪个线程就要负责释放这个锁, 其他线程越刞代㽶去释放互斥量是无效的。

临界区 (Critical Section) 是比互斥量更加严格的同步手段。在术语中, 把临界区的锁的获取称为进入临界区, 而把锁的译放称为离开临界区。临界区和互斥量与信号量的区别在于, 互斥量和信号量在系统的任何进程里都是可见的, 也就是说, 一个进程创建了一个互斥量或信号量, 另一个进程试图去获取该锁是合法的。然而, 临界区的作用范围仅限于本进程, 其他的进程无法获取该锁。除此之外, 临界区具有和互斥量相同的性质。

操作系统原理_linux_08

条件变量 (Condition Variable) 作为一种同步手段, 作用类似于一个栅栏。对于条件变量, 线程可以有两种操作, 首先线程可以等待条件变量, 一个条件变量可以被多个线程等待。其次, 线程可以唤醒条件变量, 此时某个或所有等待此条件变量的线程都会被唤醒并继续支持。也就是说, 使用条件变量可以让许多线程一起等待某个事件的发生, 当事件发生时(条件变量被唤醒), 所有的线程可以一起恢复执行。

5,可重入

一个函数被重入, 表示这个函数没有执行完成, 由于外部因素或内部调用, 又一次进入该函数执行。一个函数要被重入, 只有两种情况:
(1) 多个线程同时执行这个函数。
(2) 函数白身 (可能是经过多层调用之后) 调用自身。

函数可重入指的是,该函数重入之后,不会产生任何不良后果。

可重入函数的特点:

- 不使用任何(局部)静态或全局的非 const 变量。
- 不返回任何(局部)静态或全局的非 const 变量的指针。
- 仅依赖于调用方提供的参数。
- 不依赖任何单个资源的锁 (mutex 等)。
- 不调用任何不可重入的函数。

6,过度优化 VS 线程安全

(1)一些复杂的优化行为,会让锁失效。

案例一:将变量写入寄存器而不写回

操作系统原理_linux_09

编译器为了提高访问速度,将变量x写入寄存器而不写回,可能导致最终x的值是1,本来应该是2的。

案例二:交换指令顺序

操作系统原理_linux_10

编译器可能会为了提高效率而交换相邻两条无关指令的顺序,CPU的动态调度功能也会交换指令执行的顺序。

这样,最终结果可能就是r1=r2=0,本来是不可能这样的。

(2)使用volatile关键字,可以阻止编译器为了提高访问速度,将变量x写入寄存器而不写回,可以阻止编译器操作volatile变量的指令顺序。

所以,volatile可以解决案例一,但是无法解决案例二,因为CPU有动态调度。

(3)单例模式的double-check

案例三:还是交换指令顺序

操作系统原理_互斥量_11

这个是单例模式,申请一个对象,双重if判断是为了降低lock的调用,提高效率。

操作系统原理_linux_12

而(2)(3)的顺序是可以颠倒的,所以如果函数并发调用,就会有一个正在调用构造函数的对象的指针,被提供给调用方,从而引发错误。

CPU通常会提供一条屏障指令,用来组织指令交换顺序的时候穿过屏障,有的叫barrier,有的叫lwsync

例如,可以这样来解决上面的问题:

操作系统原理_C_13

三,并发并行、中断自陷、同步异步

1,并发、并行

操作系统原理_信号量_14

2,中断、自陷

操作系统原理_条件变量_15

操作系统原理_C_16

操作系统原理_条件变量_17

3,同步、异步

操作系统原理_C_18

四,系统设计、调度管理

1,多CPU系统设计

操作系统原理_信号量_19

2,多道程序设计 multiprogramming

操作系统原理_条件变量_20

3,分时系统

将主机执行时间分成一个个时间片。

4,作业调度、进程调度

操作系统原理_条件变量_21