https://github.com/CyC2018/CS-Notes
概述
- 并发:宏观上一段时间多个程序同时运行(OS引入进程线程来实现)
- 并行:同一时刻运行多个指令(需要硬件支持,如流水线、多核处理器、分布式操作系统)
- 共享:资源被并发的进程同时使用(包括互斥共享和同时共享)
- 虚拟:一个物理实体转换为多个逻辑实体
- 时分复用技术(多个进程在同一处理器并发执行,轮流占用处理器,快速切换)
- 空分复用技术(虚拟内存:物理内存抽象为地址空间)
- 异步:不是一次性执行完毕,不可知的速度前进。
基本功能
- 进程管理:控制、同步、通信、死锁
- 内存管理:内存分配、地址映射、虚拟内存
- 文件管理:文件存储空间、目录、读写
- 设备管理:I/O请求
system call
- 宏内核:将OS作为一个整体放到内核
- 微内核:将一部分OS功能移出内核降低复杂性,移出的部分划分各种服务并相互独立。在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。需要频繁地在用户态和核心态之间进行切换,有性能损失。
中断
- 外中断:由CPU指令外引起的:I/O中断
- 异常:CPU执行指令的内部事件引起,算术溢出等。
- 陷入:用户程序中使用系统调用
进程管理
- 进程(process):资源分配基本单位。进程控制块(process control block,PCB)描述进程基本信息和运行状态,创建和删除进程都是对PCB操作。
- 线程(thread): 线程是独立调度对基本单位。进程里有很多线程,如浏览器和qq是两个进程,浏览器中的HTTP请求、渲染都是线程。
- 区别:
- 拥有资源:process是资源分配基本单位,thread没有资源,可以访问process的资源。
- 调度:thread是调度的基本单位,thread的切换不会引起process切换
- 系统开销:创建/删除process时,OS要分配/回收资源,如内存,I/O,开销大;th read切换时,只需要保存和设置少量寄存器,开销小。
- 通信:thread可以直接通过读写同一进程中的数据通信,process通过IPC(Inter-Process Communication)通信
- ready和running可以互相转换,其他都是单向,ready通过调度算法获得CPU时间转换为running,running在分配的时间片用完后变成ready等待下一次调度。当缺少资源时会从running变成waiting(这种转换缺少的资源不包括CPU 时间)
Process scheduling algorithm
-
Batch processing system批处理:保证吞吐量和周转时间
- first-come first-served:按照请求的顺序调度,利于长期作业,可能导致短作业等待时间过长。
- shortest job first:安装运行时间最短调度,长作业可能会饿死
- shortest remaining first:shortest job first的抢占版本
-
Interactive system交互式:大量用户交互操作,需要快速响应
- 时间片轮转:进程按照first-come first-served排队,每次调度把CPU时间分配给队首进程,该进程可以执行一个时间片,时间结束由计时器发出时钟中断,调度程序停止执行并送入队尾,同时把CPU时间分给队首的进程。(时间片大小与算法效率息息相关:过长不能保证实时性,过小会导致切换太频繁,切换上时间花的太多)
- 优先级调度:为每个进程分配一个优先级,按照优先级调度。为了防止低优先级等不到调度,随时间推移增加等待进程的优先级。
- 多级反馈队列:多级队列是为需要连续执行多个时间片的进程考虑,设置多个队列,每个队列时间片大小不同。进程在第一个队列没执行完,就会被移到下一个队列。每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。(时间片轮转调度算法和优先级调度算法的结合)
-
Real-time system实时:请求在确定时间内得到回应
- 软实时和硬实时(是否可容忍超时)
Process synchronization进程同步
- 临界区:对临界资源访问的代码
- 每个进程进入需要先检查
// entry section
// critical section;
// exit section
- 同步与互斥
- 同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系
- 互斥:多个进程在同一时刻只有一个进程能进入临界区。
- 信号量Semaphore:整形变量,对其进行down和up操作(即P、V操作)
-
down:semaphore>0,执行-1;semaphore=0,进程睡眠,等待信号量大于0
-
up:对semaphore进行+1操作,唤醒睡眠的进程让其完成down操作
down和up要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁
typedef int semaphore;
semaphore mutex = 1;
void P1() {
down(&mutex);
// 临界区
up(&mutex);
}
void P2() {
down(&mutex);
// 临界区
up(&mutex);
}
使用信号量实现生产者-消费者问题
describe:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。
因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问
同步生产者和消费者的行为,需要记录缓冲区中物品的数量,数量可以使用信号量来进行统计,empty 记录空缓冲区的数量,full 记录满缓冲区的数量。empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。不能先对缓冲区进行加锁,再测试信号量。 不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 up(empty) 操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer() {
while(TRUE) {
int item = produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&mutex);
up(&full);
}
}//生产物品--请求缓冲区空闲资源--缓冲区上锁--缓冲区添加物品--缓冲区解锁--释放可用资源
void consumer() {
while(TRUE) {
down(&full);
down(&mutex);
int item = remove_item();
consume_item(item);
up(&mutex);
up(&empty);
}
}//请求访问可用资源--缓冲区上锁--缓冲区拿物品--缓冲区解锁--消费物品
- 管程
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,方便调用。c 语言不支持管程,示例代码使用了类 Pascal 语言
- 重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
- 管程引入了 条件变量 以及相关的操作,wait() 和 signal() 来实现同步操作。wait进程堵塞让出管程,signal唤醒被阻塞的进程。
经典同步问题
-
哲学家进餐问题
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。为了防止死锁的发生,可以设置两个条件:
- 必须同时拿起左右两根筷子;
- 只有在两个邻居都没有进餐的情况下才允许进餐。
-
读者-写者问题
允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。
一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。
进程通信
进程同步:控制多个进程按一定顺序执行;
进程通信:进程间传输信息
- 管道
通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写
#include <unistd.h>
int pipe(int fd[2]);
- 只支持半双工通信(单向交替传输);
- 只能在父子进程或者兄弟进程中使用
-
FIFO:命名管道,去除了管道只能在父子进程中使用的限制
-
消息队列:
- 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
-
信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问。 -
共享存储
允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。
需要使用信号量用来同步对共享存储的访问。
多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段。
- 套接字
与其它通信机制不同的是,它可用于不同机器间的进程通信。