操作系统基础知识
- 1. 进程的基本状态:运行、就绪、阻塞
- 2. 进程的调度方法有两类:抢占式、非抢占式
- 3. 在所有调度算法中,短作业优先调度算法的平均等待时间最短;
- 4. 临界资源:同时只能一个进程使用的资源称为临界资源;
- 5. 访问临界资源应遵循的准则
- 6. 互斥:同类进程需要互斥使用资源;
- 7. 信号量的物理意义:
- 8. 任何一个进程在进入临界区之前应调用P操作,退出临界区时应调用V操作;
- 9. 死锁产生的4个必要条件:(互不请环)
- 10. 不让死锁发生的策略可以分为静态和动态两种,死锁避免属于动态策略。
- 11. 在可共享系统资源不足时,可能出现死锁。但是,不适当的进程推进顺序也可能产生死锁;
- 12. 预先静态分配法破坏了死锁产生必要条件中的请求和保持条件,
- 13. 资源剥夺法和撤销进程的方法破坏了不可剥夺条件,
- 14. 资源的按序分配策略可以破坏环路等待条件;
- 15. 执行状态只能由就绪状态转换,而无法有阻塞状态直接转换;
- 16. 进程创建:
- 17. 进程撤销:
- 18. 阻塞原语(P原语):将进程由执行状态转为阻塞状态;
- 19. 唤醒原语(V原语):由阻塞状态变为就绪状态;
- 20. 线程引入的目的:
- 21. 进程与线程
- 22. 多线程模型
- 23. 处理器的三级调度
- 24. 衡量调度算法的性能
- 25. 引起进程调度的原因:
- 26. 常见调度算法
- 27. 互斥实现方法
- 28. 经典同步问题
- 29. 哲学家进餐问题
本篇博文的内容主要介绍操作系统的一些基础知识,这些对于高级编程非常重要,是多线程编程进阶的必备内容。
1. 进程的基本状态:运行、就绪、阻塞
1.1 引起状态转化的事件通常有:
- 时间片用完运行转就绪)、
- 等待事件(运行转阻塞)、
- 等待条件发生(阻塞转就绪)、
- 进程调度(就绪转运行);
2. 进程的调度方法有两类:抢占式、非抢占式
3. 在所有调度算法中,短作业优先调度算法的平均等待时间最短;
4. 临界资源:同时只能一个进程使用的资源称为临界资源;
- 在进程中,访问临界资源的代码段称为临界区;
- 为保证进程互斥访问临界资源,应在进程的临界区之前设置进入去,在临界区后设置退出区;
5. 访问临界资源应遵循的准则
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待
- 空、忙、有、让
6. 互斥:同类进程需要互斥使用资源;
7. 信号量的物理意义:
- 当信号量值大于零时,表示可用资源的数目;
- 当信号量值小于零时,其绝对值为在该信号量上等待的进程个数;
8. 任何一个进程在进入临界区之前应调用P操作,退出临界区时应调用V操作;
9. 死锁产生的4个必要条件:(互不请环)
- 互斥、
- 请求和保持、
- 不可剥夺
- 环路等待;
10. 不让死锁发生的策略可以分为静态和动态两种,死锁避免属于动态策略。
11. 在可共享系统资源不足时,可能出现死锁。但是,不适当的进程推进顺序也可能产生死锁;
12. 预先静态分配法破坏了死锁产生必要条件中的请求和保持条件,
13. 资源剥夺法和撤销进程的方法破坏了不可剥夺条件,
14. 资源的按序分配策略可以破坏环路等待条件;
15. 执行状态只能由就绪状态转换,而无法有阻塞状态直接转换;
16. 进程创建:
- 申请一个空间PCB,并指定唯一的进程标识符PID;
- 分配必要的资源;
- PCB初始化;
- 将PCB插入到就绪队列;
17. 进程撤销:
- 从PCB集合中找到被撤销进程的PCB;
- 若撤销进程正处于执行状态,则立即停止该进程的执行,设置重新调度标志,以便进程撤销后将处理器分配给其他进程;
- 若撤销进程还有子孙进程,还应将该进程的子孙进程予以撤销;
- 回收被撤销进程占有的资源,或者归还父进程,或者归还给系统。最后,回收PCB。
18. 阻塞原语(P原语):将进程由执行状态转为阻塞状态;
19. 唤醒原语(V原语):由阻塞状态变为就绪状态;
20. 线程引入的目的:
- 使多个程序并发执行,以改善资源利用率和提高系统吞吐量;
- 减少程序并发执行所付出的时空开销,使操作系统具有更好的并发性;
21. 进程与线程
- 线程是独立调度的基本单位;进程是拥有资源的基本单位;
22. 多线程模型
22.1 多对一模型
将多个用户级线程映射到一个内核级线程上;只要一个用户级线程阻塞,就会导致整个进程阻塞;
22.2 一对一模型
内核级线程与用户级线程一一对应;好处:当一个线程阻塞时,不影响其他线程运行;
22.3 多对多模型
将多个用户级线程映射到多个内核级线程(内核级线程数不多于用户级线程数);这种模型不仅可以使多个用户级线程真正意义上并行执行,而且不会限制用户级线程的数量。用户可以自由创建所需的用户级线程,多个内核级线程根据需要调用用户级线程,当一个用户级线程阻塞时,可以调度执行其他线程。
23. 处理器的三级调度
23.1 高级调度(作业调度)
按照一定的原则从外存上处于后备状态的作业中选择一个或者多个,给它们分配内存、输入输出设备等必要的资源;频率较低,平均几分钟一次;
23.2 中级调度
将处于外存对换区中的具备运行条件的进程调入内存,或者将处于内存中的暂时不能运行的进程交换到外存对换区;主要涉及内存管理与扩充,中级调度可以理解为在幻夜时将页面在外存与内存之间调度;
23.3 低级调度(进程调度)
按照某种策略和方法从就绪队列中选取一个进程,将处理器分配给它;频率很高,一般隔几十毫秒运行一次;
23.4 作业调度为进程被调用做准备,进程调度使进程被调用。换言之,作业调度的结果是为作业创建进程,而进程调度的结果是进程被执行;作业调度次数少,进程调度频率高;有的系统可以不设置作业调度,但进程调度必须有;
24. 衡量调度算法的性能
- CPU利用率
- 系统吞吐量:单位时间内CPU完成作业的数量;长作业会导致系统吞吐量下降;短作业则相反;
- 响应时间
- 周转时间:作业从提交至完成的时间间隔,包括等待时间和执行时间;
a. 平均周转时间:多个作业周转时间的平均值;
b. 带权周转时间:作业周转时间与运行时间的比;
c. 平均带权周转时间:平均…
25. 引起进程调度的原因:
- 正常结束:当前运行进程运行结束;
- 请求资源:当期运行进程因某种原因,比如IO请求、P操作、阻塞原语等,从运行状态进入到阻塞状态;
- 执行完系统调度等系统程序后返回用户进程;
- 在采用抢占调度方式的系统中,一个具有更高优先级的进程要求使用处理器,则使当前运行进程进入到就绪队列;
- 在分时系统中,分配给该进程的时间片已用完;
26. 常见调度算法
26.1 先来先服务(作业调度、进程调度)
常被结合在其他调度策略中使用;
26.2 短作业优先(作业调度、进程调度)
在所有作业同时到达时,SJF调度算法是最佳算法,平均周转时间最短;对长作业不利;
26.3 优先级调度(作业调度、进程调度)
抢占式优先级调度和非抢占式调度
- 静态优先级:在创建进程时确定,确定之后在整个进程运行期间不再改变;确定优先级的依据:进程类型(系统进程、用户进程)、作业的资源要求、用户类型和要求;
- 动态优先级:创建进程时确定一个优先级,在进程运行过程中再根据情况的变化调整优先级;确定优先级的依据:进程占有CPU时间的长短、就绪进程等待CPU时间的长短来决定;
- 在优先级相同的情况下,通常按照先来先服务或者短作业顺序优先的顺序执行;
26.4 时间片轮转(进程调度)
26.5 高响应比优先调度算法(作业调度)
- 响应比=作业响应时间/估计运行时间=(作业等待时间+估计运行时间)/估计运行时间
26.6 多级队列调度算法(进程调度)
根据进程的性质或类型,将就绪队列划分为若干个独立的队列,每个进程固定地分属于一个队列。每个队列采用一种调度算法,不同的队列可以采用不同的调度算法。
26.7 多级反馈队列调度算法(进程调度)
27. 互斥实现方法
27.1 方法一
保证互斥访问临界资源,但存在的问题时强制两个进程以交替次序进入临界区,很容易造成资源利用不充分。例如:当进程P0退出临界区后将turn置为1,以便允许进程P1进入临界区,但如果进程P1暂时并未要求访问该临界资源,而P0又想再次访问临界资源,则它无法进入临界区。不能保证实现“空闲让进”准则;
int turn = 0;
P0:
{
Do{
while (turn != 0);
进程P0的临界区代码CS0;
turn = 1;
进程P0的其他代码;
}while (true);
}
P1:
{
Do{
while (turn != 1);
进程P1的临界区代码CS1;
turn = 0;
进程P1的其他代码
}while(true);
}
27.2 方法二
解决了“空闲让进”的问题,但两个进程能够同时进入各自的临界区:当两个进程都为进入临界区时,它们各自的访问标志都为false,若此时刚好两个进程同时都想进入临界区,并且都发现对方的标志值为false(当两进程交替执行了检查语句后,都满足flag[]==false的条件),于是两个进程同时进入了各自的临界区,违背了“忙则等待”。
判-设-“忙”
enum boolean {false,true};//设置数组元素类型
boolean flag[2] = {false,false};//设置标志数组
P0:
{
Do{
while (flag[1]);//flag[1]为真表示P1在访问临界区,P0等待
flag[0] = true;
进程P0的临界区代码CS0;
flag[0] = false;
进程P0的其他代码;
}while (true);
}
P1:
{
Do{
while (flag[0]);//flag[0]为真表示P0在访问临界区,P0等待
flag[1] = true;
进程P1的临界区代码CS0;
flag[1] = false;
进程P1的其他代码;
}while (true);
}
27.3 方法三
可以防止两进程同时进入临界区,但存在两个进程都进不了临界区的问题:当两个进程同时想进入临界区时,它们分别将自己的标志设置为true,并且同时去检查对方的状态,发现对方也要进入临界区,于是都阻塞自己,结果导致两者都无法进入临界区,造成“死等”现象,违背了“有限等待”的准则;
判-设-“有”
enum boolean {false,true};//设置数组元素类型
boolean flag[2] = {false,false};//设置标志数组
P0:
{
Do{
flag[0] = true;
while (flag[1]);//flag[1]为真表示P1在访问临界区,P0等待
进程P0的临界区代码CS0;
flag[0] = false;
进程P0的其他代码;
}while (true);
}
P1:
{
Do{
flag[1] = true;
while (flag[0]);//flag[0]为真表示P0在访问临界区,P0等待
进程P1的临界区代码CS0;
flag[1] = false;
进程P1的其他代码;
}while (true);
}
27.4 方法四
利用flag[]解决临时资源的互斥访问,利用turn解决“饥饿”现象;
enum boolean {false,true};//设置数组元素类型
boolean flag[2] = {false,false};//设置标志数组
int turn;
P0:
{
Do{
flag[0] = true;
turn = 1;//此时p0为进入临界区,仍然允许p1进入临界区
while (flag[1]&&turn==1);//flag[1]为真表示P1希望访问临界区,turn=1表示p1可以进入临界区,因此p0等待
进程P0的临界区代码CS0;
flag[0] = false;
进程P0的其他代码;
}while (true);
}
P1:
{
Do{
flag[1] = true;
turn = 0;//此时P1未进入临界区,仍然允许P0进入临界区(进入去)
while (flag[0]&&turn==0);//flag[0]为真表示P0希望访问临界区,turn=0表示p0可以进入临界区,因此p1等待
进程P1的临界区代码CS0;
flag[1] = false;
进程P1的其他代码;
}while (true);
}
28. 经典同步问题
28.1 先对资源信号量进行P操作,再对互斥信号量进行V操作,否则会导致死锁
28.2 生产者-消费者
semaphore full = 0;
semaphore empty = n;
semaphore metex = 1;
Producer()
{
while (true)
{
Produce an item put in nextp;//nextp为临时缓冲区
P(empty);//申请一个空缓冲区
P(mutex);//申请使用缓冲池
将产品放入缓冲池;
V(mutex);//缓冲池使用完毕,释放互斥信号量
V(full);//增加一个满缓冲区
}
}
Consumer()
{
while (true)
{
P(full);//申请一个满缓冲区
P(mutex);//申请使用缓冲池
取出产品;
V(mutex);//缓冲池使用完毕,释放互斥信号量
V(empty);//增加一个空缓冲区
consumer the item in nextc;//消费掉产品
}
}
28.3 读写者
28.3.1 读者优先算法
semaphore rmutex = 1;//保证对readcount的互斥访问
semaphore mutex = 1;
int readcount = 0;//用于记录读者数量,初值为0
reader()
{
while (true)
{
P(rmutex);
//如果是第一个读者,要阻止写者进入
if (readcount==0)
{
P(mutex);
}
readcount++;
V(rmutex);//释放readcount的使用权,允许其他读者使用
进行读操作;//读不加互斥访问检验,表示可允许同时读
P(rmutex);
readcount--;
if (readcount==0)
{
V(mutex);
}
V(rmutex);
}
}
writer()
{
while (true)
{
//加互斥访问,表示只能有一个写进程写数据
P(mutex);
进行写操作;
V(mutex);
}
}
28.3.2 公平情况算法(按照到达顺序进行操作)
semaphore rmutex = 1;//用于读者互斥访问readcount
semaphore mutex = 1;
semaphore wmutext = 1;//用于存在写者时禁止新读者进入
int readcount = 0;//用于记录读者数量,初值为0
reader()
{
while (true)
{
P(wmutex);//检测是否有写者存在,无写者时进入
P(rmutex);
//如果是第一个读者,要阻止写者进入
if (readcount==0)
{
P(mutex);
}
readcount++;
V(rmutex);//释放readcount的使用权,允许其他读者使用
V(wmutex);
进行读操作;//读不加互斥访问检验,表示可允许同时读
P(rmutex);
readcount--;
if (readcount==0)
{
V(mutex);
}
V(rmutex);
}
}
writer()
{
while (true)
{
P(wmutex);//检测是否有其他写者存在,无写者时进入
//加互斥访问,表示只能有一个写进程写数据
P(mutex);
进行写操作;
V(mutex);
V(wmutex);
}
}
28.3.3 写者优先算法
semaphore rmutex = 1;//用于读者互斥访问readcount
semaphore mutex = 1;
semaphore wmutex = 1;//用于存在写者时禁止新读者进入
semaphore readable = 1;//用于表示当前是否有写者
int readcount = 0;//用于记录读者数量,初值为0
int writecount = 0;//用于记录写者数量,初值为0
reader()
{
while (true)
{
P(readable);//检查是否存在写者,若没有,则占用,进行后续操作
P(rmutex);
//如果是第一个读者,要阻止写者进入
if (readcount==0)
{
P(mutex);
}
readcount++;
V(rmutex);//释放readcount的使用权,允许其他读者使用
V(readable);
进行读操作;//读不加互斥访问检验,表示可允许同时读
P(rmutex);
readcount--;
if (readcount==0)
{
V(mutex);
}
V(rmutex);
}
}
writer()
{
while (true)
{
P(wmutex);//检测是否有其他写者存在,无写者时进入
//若为第一个写者,则阻止后续读者进入
if (writecount==0)
{
P(readable);
}
writecount++;
V(wmutex);
//加互斥访问,表示只能有一个写进程写数据
P(mutex);
进行写操作;
V(mutex);
P(wmutex);
writer--;
if (writecount==0)
{
V(readable);
}
V(wmutex);
}
}
29. 哲学家进餐问题
semaphore fork[5] = {1,1,1,1,1};
philosopher(int i)
{
while (true)
{
思考;
想吃饭;
//判断是否为奇数号哲学家,奇数号哲学家先拿左边筷子
if (i%2!=0)
{
P(fork[i]);
P(fork[(i+1)%5]);
进餐;
V(fork[i]);
V(fork[(i+1)%5]);
}
//偶数号哲学家先拿右边筷子
else
{
P(fork[(i + 1) % 5]);
P(fork[i]);
进餐;
V(fork[(i + 1) % 5]);
V(fork[i]);
}
}
}