王道408---OS---进程与线程

一、进程的堵塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新任务可做等,进程便通过调用阻塞原语(Blo©k),使自己由运行态变为阻塞态。可见,阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞态。

二、进程是系统进行资源分配和调度的基本单位,线程是一个基本的CPU执行单位

三、线程的实现方式

img

用户级线程

在用户级线程中,有关线程管理(创建、撤销和切换等)的所有工作都由应用程序在用户空
间中完成,内核意识不到线程的存在。

这种实现方式的优点如下:
①线程切换不需要转换到内核空间,节省了模式切换的开销。
②调度算法可以是进程专用的,不同的进程可根据自身的需要,对自己的线程选择不同的调度算法。
③用户级线程的实现与操作系统平台无关,对线程管理的代码是属于用户程序的一部分。

这种实现方式的缺点如下:
①系统调用的阻塞问题,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程都被阻塞。
②不能发挥多处理机的优势,内核每次分配给一个进程的仅有一个CPU,因此进程中仅有一个线程能执行。

在用户级线程中,进程A包含一个线程,进程B包含100个线程,由于用户级线程以进程作为调度单位,则进程A的一个线程分到的时间是进程B一个线程分到的时间的100倍

内核级线程

在操作系统中,无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,与内核紧密相关。内核级线程同样也是在内核的支持下运行的,线程管理的所有工作也是在内核空间内实现的。

这种实现方式的优点如下:
①能发挥多处理机的优势,内核能同时调度同一进程中的多个线程并行执行。
②如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机,也可运行其他进程中的线程。③内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小。④内核本身也可采用多线程技术,可以提高系统的执行速度和效率。

这种实现方式的缺点如下:
同一进程中的线程切换,需要从用户态转到核心态进行,系统开销较大。这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的。w

组合方式

四、错题 2.1

线程之间的通信不一定必须使用系统调用函数 (p4-T2)

与进程之间线程的通信可以直接通过它们共享的存储空间

并发进程失去封闭性,是指并发进程共享变量,其执行结果与速度有关 (T12)

程序封闭性是指进程执行的结果只取决于进程本身,不受外界影响。也就是说,进程在执行过程中不管是不停顿地执行,还是走走停停,进程的执行速度都不会改变它的执行结果。失去封闭性后,不同速度下的执行结果不同。

T22、T23

39.进程处于(C)时,它处于非阻塞态。

A.等待从键盘输入数据
B.等待协作进程的一个信号
C.等待操作系统分配CPU时间
D.等待网络数据进入内存

进程有三种基本状态,处于阻塞态的进程由于某个事件不满足而等待。这样的事件一般是I/O操作,如键盘等,或是因互斥或同步数据引起的等待,如等待信号或等待进入互斥临界区代码段等,等待网络数据进入内存是为了进程同步。而等待CPU调度的进程处于就绪态,只有它是非阻塞态。

跨进程的用户线程调度需要内核参与

在用户级线程中,系统感知不到线程的存在,调度的对象是进程,因此需要内核参与

设备分配不会创建新进程 (T46)

设备分配是通过在系统中设置相应的数据结构实现的,不需要创建进程

五、调度指标与调度算法

img

1、FCFS先来先服务

2、SJF短作业优先

3、高响应比优先调度算法
img

4、时间片轮转

5、优先级调度算法
I/O繁忙作业优先于计算繁忙作业【因为IO操作要及时完成,他没有办法长时间保存数据】
系统进程优先于用户进程
抢占式
非抢占式

6、多级队列算法

7、多级反馈队列算法
多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合与发展
img

8、比较
img

六、错题2.2

先来先服务有利于CPU繁忙型的作业,而不利于I/O繁忙型的作业

FCFS调度算法比较有利于长作业,而不利于短作业。所谓CPU繁忙型作业,是指该类作业需要大量的CPU时间进行计算,而很少请求/O操作,故采用FCFS可从容完成计算。I/O繁忙型作业是指CPU处理时,需频繁地请求/O操作,导致操作完成后还要重新排队等待调度。所以CPU繁忙型作业更接近于长作业

T26、T32、T37

七、错题2.3

临界区是指并发进程访问共享变量段的代码程序 (p102-T6)

所谓临界区,并不是指临界资源,如共享的数据、代码或硬件设备等,而是指访问临界资源
的那段代码程序

共用队列可供多个进程使用,但一次只可供一个进程使用,故属于临界资源

信号量和管程区别

1、信号量可以并发,并发量是取决于s的初始值,而管程则是在任意时刻都是只能有一个。

2、信号量的P操作在操作之前不知道是否会被阻塞,而管程的wait操作则是一定会被阻塞。

3、管程的进程如果执行signal后,但是没有在这个条件变量上等待的任务的话,则丢弃这个信号。进程在发出信号后会将自己置于紧急队列之中,因为他已经执行了部分代码,所以应该优先于入口队列中的新进入的进程执行。

4、当前进程对一个信号量加1后,会唤醒另一个进程,被唤醒进程程与当前进程并发执行

p106-T44

44.【2016统考真题】使用TSL(Test and Set Lock)指令实现进程互斥的伪代码如下所示。

do{
while(TSL(&lock));
critical section;
lock=FALSE;
while(TRUE);

下列与该实现机制相关的叙述中,正确的是(B)。
A.退出临界区的进程负责唤醒阻塞态进程
B.等待进入临界区的进程不会主动放弃CPU
C.上述伪代码满足“让权等待”的同步准则
D.while(TSL(&lock)语句应在关中断状态下执行

下面主要解释A选项,为什么退出临界区的进程不是负责唤醒阻塞态进程
链接:https://www.nowcoder.com/questionTerminal/0e787cddc6324a4aa0ab36ffcd9fc82e?
来源:牛客网

TSL是互斥的硬件实现机制,同时是一种互斥锁(自旋锁),它在无法访问临界资源时会忙等(进入区中的while循环就是在忙等),既然忙等,它放弃处理机只能是被动放弃的,比如时间片用完被调度走,这是它进入了就绪态。 如果使用记录型信号量,那么可以实现让权等待,即进程发现无法访问临界资源就会调用block()自我阻塞,并将自己加入到对应信号量的等待队列中。当另一个进程访问完成临界区资源后,会在退出区调用wakeup()来唤醒处于等待队列中的进程,此时它唤醒的是阻塞态的进程。

T45

管程也能实现同步问题
信号量和管程是等价的,即信号量能实现的用管程也能实现,反之亦然。

T47

C、D选项:
管程执行wait操作一定被堵塞
而对于PV操作,执行P操作则要根据x的值判断该进程是否进入堵塞态

八、PV操作大题

1、生产者消费者问题

最简单的一类PV操作题,不需要引入int变量,条件判断之类的东西。
p108-T8和尚取水问题应该是最经典的生产者消费者问题了

2、独木桥问题(或者说南北桥问题)

同向不互斥、反向互斥

解决这类问题的关键,是设置几个整形变量counti,分别用来记录第i方向的个数,特判counti等于0,拿到该方向的独木桥行驶权

经典例题:p108-T10、p109-T14

3、读者写者问题

可以同时读,但不可以同时写和边读边写
读优先:

semaphore write=1; //互斥信号量,⽤于给写者“上锁”
semaphore rmutex=1; //互斥信号量,实现对readCount的互斥访问


int readCount=0;//读者的数量

void reader(){
    while(1){
        p(rmutex)
        if(readCount == 0){
            p(write)
        }
        readCount++;
        v(rmutex)

        read ...

        p(rmutex)
        readCount--
        if(readCount == 0){
            v(write)
        }
        v(rmutex)
    }
}


void writer(){
    while(1){
        p(write)

        writing ... 
        
        v(write)
    }
}

读写公平:

semaphore write=1; //互斥信号量,⽤于给写者“上锁”
semaphore rmutex=1; //互斥信号量,实现对readCount的互斥访问

semaphore piror=1;      // 全局优先锁

int readCount=0;//读者的数量

void reader(){
    while(1){
        p(piror)    //读进程拿到全局优先锁
        p(rmutex)
        if(readCount == 0){
            p(write)
        }
        readCount++;
        v(rmutex)
        v(piror)    // 释放

        read ...

        p(rmutex)
        readCount--
        if(readCount == 0){
            v(write)
        }
        v(rmutex)
    }
}


void writer(){
    while(1){
        p(piror)
        p(write)

        writing ... 

        v(write)
        v(piror)
    }
}

写优先:

semaphore reads=1; //互斥信号量,⽤于给读者“上锁”
semaphore writes=1; //互斥信号量,⽤于给写者“上锁”
semaphore rmutex=1; //互斥信号量,实现对readCount的互斥访问
semaphore wmutex=1; //互斥信号量,实现对writeCount的互斥访问

int readCount=0, writeCount=0; //读者、写者的数量



void reader(){
    while(1){
        p(reads)
        p(rmutex)
        if(readCount == 0){
            p(writes)
        }
        readCount++
        v(rmutex)
        v(reads)


        reading ...

        p(rmutex)
        readCount--
        if(readCount == 0){
            v(writes)
        }
        v(rmutex)
    }
}

void writer(){
    while(1){
        p(wmutex)
        if(writeCount == 0){
            p(reads)
        }
        writerCount++
        v(wmutex)

        p(writes)
        writing ... 
        v(writes)

        p(wmutex)
        writeCount--
        if(writeCount == 0){
            v(reads)
        }
        v(wmutex)
    }
}


// 只要写者一拿到读者的令牌,读者的数目就不会再增加,会一直减少
//
// 会导致读者饥饿

经典例题: P97
这种类型的问题,408的卷子上还没出现过,要格外注意

4、理发师问题

座位不够顾客跑路,座位够顾客等待,主要是对于顾客跑路这个问题的解决;
由于这种题的消费者是一次性的,因此消费者不需要while循环
一般会引入一个int型变量和if分支语句来判断是否还有座位。
经典例题: p109-T14

5、面包师叫号问题

答案有歧义啊
这个答案可以让我接受:

Semaphore buyer= 0;                //顾客人数
Semaphore seller = n;            //销售人员数
Semaphore mutex_s = 1;            //用于销售人员的互斥信号量
Semaphore mutex_b = 1;            //用于顾客的互斥信号量
int count_s = 0;                //记录取号的值
int count_b = 0;                //记录叫号的值

void Buy()                    //顾客进程
{
     进店;
    P(mutex_b);          //取号
    count_b++;
   V(mutex_b);
   V(buyer);
   P(seller);            //等待叫号
    买面包;
    离开
}

void Sell()
{
    while(true)
    {
         P(buyer);
         P(mutex_s);   //叫号
         count_s++;
         叫编号为count_s的顾客;
         V(mutex_s);
         V(seller);
    }
}
// 解决手段就是使用了与互斥无关的"号",并互斥等待取号,用号

6、哲学家进餐问题

特点: 只有一类进程,但需要占用多个资源才能运行

经典例题:p99、p112-T24
这里以p99为例

1、第一种做法(error)

semaphore chopstick[5] = {1,1,1,1,1};
void philosopher(int i){
    while(true)
    {
        /*当哲学家饥饿时,总是先拿左边的筷子,再拿右边的筷子*/
        P(chopstick[i]);
        P(chopstick[(i+1)%5]);

        // 吃饭
    
        /*当哲学家进餐完成后,总是先放下左边的筷子,再放下右边的筷子*/
        V(chopstick[i]);
        V(chopstick[(i+1)%5]);
    }
}

这种做法显然存在问题: 若哲学家全部同时拿起左边的筷子,再去拿右边的筷子就会死锁,下面进行改良
2、第二种做法

semaphore mutex = 1;    // 互斥拿筷子
semaphore chopstick[5] = {1,1,1,1,1};
void philosopher(int i){
    while(true)
    {
        /*当哲学家饥饿时,总是先拿左边的筷子,再拿右边的筷子*/
        P(mutex)
        P(chopstick[i]);
        P(chopstick[(i+1)%5]);
        V(mutex)

        // 吃饭
    
        /*当哲学家进餐完成后,总是先放下左边的筷子,再放下右边的筷子*/
        V(chopstick[i]);
        V(chopstick[(i+1)%5]);
    }
}

这里的思路是,拿筷子时互斥,这样就不会出现,同时拿左筷子的情况了,但这样的情况下,存在效率问题,比如:当一个哲学家拿完筷子吃饭时,其旁边的哲学家,再拿筷子会被mutex卡住,从而导致所有人都被mutex卡住,这样只能等这个哲学家吃完后,才能恢复。显然存在极大的效率问题
下面继续改进
3、第三种做法

semaphore mutex = 1;    // 互斥拿筷子
int chopstick[5] = {1,1,1,1,1};
void philosopher(int i){
start:      // 当哲学家左右筷子不全在时,跳转到这里,循环

    while(true)
    {
        P(mutex)
        if (chopstick[i] && chopstick[(i+1)%5] ){
            chopstick[i] = chopstick[(i+1)%5] = 0;
            V(mutex);
        }else{
            V(mutex);
            goto start;
        }
        // 吃饭        
        P(mutex);
        chopstick[i] = chopstick[(i+1)%5] = 1;  //吃完饭之后,重新设置为1
        V(mutex);
    }
}

这种方法把信号量筷子变成了int型变量,中间夹杂着条件判断语句,以实现若无可用筷子,则立即退出,避免了一直堵塞

九、产⽣死锁的必要条件

互斥条件

多个线程不能同时使⽤同⼀个资源

不剥夺条件

进程A已经拥有资源1,在⾃⼰使⽤完之前不能被其他进程获取

请求并保持条件

进程A已经有资源1,想申请资源2,但是资源2被进程B持有,进程A处于等待状态,但是进程A不释放资源1

循环等待条件

两个线程获取资源的顺序构成了环形链
注意区分循环等待与死锁的区别:
img

十、死锁的处理策略

死锁预防

1.破坏互斥条件
缺点:如打印机等临界资源只能互斥使⽤
该⽅法不太可⾏

2.破坏不剥夺条件
常⽤于状态易于保存和恢复的资源(CPU的寄存器和内存资源)
剥夺资源法

3.破坏请求并保持条件
可能会导致饥饿现象
⼀次性分配策略

4.破坏循环等待条件
可采⽤顺序资源分配法,但是编号必须相对稳定,限制了新类型设备的增加
资源有序分配策略

死锁避免

系统再进行资源分配之前,应先计算此次分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配,否则让进程等待

1、系统安全性算法

2、银行家算法
注意: 并不是所有的不安全状态都是死锁状态,当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态

死锁的检测

img
死锁的检测中有个死锁定理,如上图

死锁的解除

1.资源剥夺法
挂起某些死锁进程,并抢占它的资源

2.撤销进程法
强制撤销部分甚⾄全部死锁进程并剥夺这些进程的资源

3.进程回退法
让⼀个或多个进程回退到⾜以回避死锁的地步

十一、错题2.4

死锁的四个必要条件中,无法破坏的是互斥使用资源

所谓破坏互斥使用资源,是指允许多个进程同时访问资源,但有些资源根本不能同时访问,如打印机只能互斥使用。因此,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。

南北桥问题会发生饥饿现象

P145-T31

31.【2015统考真题】若系统S1采用死锁避免方法,S2采用死锁检测方法。下列叙述中,正确的是()。
I.S1会限制用户申请资源的顺序,而S2不会
Ⅱ.S1需要进程运行所需的资源总量信息,而S2不需要
Ⅲ.S1不会给可能导致死锁的进程分配资源,而S2会
A.仅I、II
B.仅II、Ⅲ
C.仅I、Ⅲ
D.I、II、Ⅲ

I: 在死锁预防里,在破坏循环等待条件里采用的方法就是资源有序分配策略.
而银行家算法可能更倾向于改变进程的执行顺序(严谨点应该是分配资源的顺序)

II: 这里所说的“资源总量信息”指的是银行家算法里的MAX矩阵,即这个进程在生命过程中需要申请的最大资源数量。
而资源分配图记录的是某一时刻进程正在申请(request)和已经分配(allocation)的资源的状态,和max无关。
其实银行家算法是OS站在上帝视角来看,知道所有进程需要分配的所有资源的数量;但实际哪有这么理想呢,OS怎么能知道每个进程的未来信息(即MAX矩阵)呢。

Ⅲ: 死锁检测,只有当出现死锁且被检测到时才会去处理解除
课本p140上写了:
若系统为进程分配资源时不采取任何措施,则应该提供死锁检测和解除的手段。
也就是说死锁检测只是其检测作用,并不会避免或预防死锁

posted @ 2023-09-04 15:21  TLSN  阅读(257)  评论(0编辑  收藏  举报