进程、线程、管程

进程

程序:指令序列

进程是程序的一次执行过程(动态) ;是进程实体的运行过程

进程实体/进程映像:(静态

  • PCB程序控制块:描述进程的各种信息;进程存在的唯一标志操作系统所需的数据都在PCB中

  • 程序段

  • 数据段


进程的特征:

  • 动态性:进程的最基本特征

  • 并发性

  • 独立性:系统资源分配的基本单位

  • 异步性

  • 结构性


进程的状态

基本状态:

  • 就绪:万事俱备,只缺CPU

  • 运行:占有CPU

  • 阻塞:因等待某一事件暂时不能运行


  • 创建态:操作系统为进程分配资源,初始化PCB

  • 终止态:操作系统回收进程资源,撤销PCB



进程控制

实现进程状态的转换 ,通过原语(一种执行期间不允许中断的特殊程序,采用开中断和关中断)实现

原语作用:

  • 更新PCB

  • 将PCB插入合适队列

  • 分配/回收资源

阻塞和唤醒原语要成对使用


进程通信

进程之间的信息交换

  • 进程与进程之间拥有相互独立的地址空间

  • 为了安全,进程之间不能直接访问

共享存储

两个进程对共享空间的访问必须是互斥


  • 基于数据结构的共享:速度慢、限制多,是一种低级通信

  • 基于存储区的共享:比基于数据结构共享速度快,是一种高级通信


消息传递

进程间的数据交换以格式化的消息为单位,通过发送/接收原语实现


  • 直接通信方式:消息直接挂到接收进程的消息缓冲队列上

  • 间接通信方式/信箱通信方式:先发到中间实体(信箱)中

管道通信

在内存中开辟了一个大小固定的缓冲区

  • 管道只能采用半双工通信

  • 各进程要互斥访问管道


  • 如果没写满,就不允许读没读空,就不允许写

  • 写满时,进程的write()系统调用被阻塞;变时,进程的read()系统调用被阻塞

  • 数据一旦被读出,就意味着被丢弃,因此,读进程最多只能有一个


进程同步、互斥

进程同步:直接制约关系 :为完成某种任务而建立的两个或多个进程,在某些位置上需要协调工作次序而产生的的制约关系

进程互斥:间接制约关系: 一个进程访问某临界资源时,另一个想访问该临界资源的进程必须等待,只有当访问结束资源释放后才能访问

  • 进入区:检查是否可以进入临界区,设置正在访问临界资源 标志(上锁)

  • 临界区:访问临界资源的代码

  • 退出区:解除正在访问临界资源 标志(解锁)

  • 剩余区:其他处理

遵循原则:

  • 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区

  • 忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待

  • 有限等待:对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)

  • 让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待

进程互斥的软件实现

单标志法

进程访问完临界区后,会把使用临界区的权限转交给另一个进程,即每个进程进入临界区的权限只能被另一个进程赋予

可以实现同一时刻最多只允许一个进程访问临界区

image

若P1在处理机运行,会因为turn!=1进入while循环直至时间片用完,切换至P0;

直到P0访问完毕将turn赋值为1,P1进程才可以访问临界区


缺:违背空闲让进原则:若P0一直不使用临界区,则P1将无法使用


双标志先检查

设置一个布尔型数组flag[],用来标记各个进程想要进入临界区的意愿, 每个进程在进入临界区之前,先检查当前有没有别的进程想进入临界区,若无,则把自身flag[]标志设置为true,并开始访问临界区

先检查,后上锁

image

由于异步性问题,①、②执行完毕切换到P1进程执行⑤、⑥

缺:违背忙则等待原则


双标志后检查

先上锁后检查

image

若按①⑤②⑥执行顺序

优: 解决了忙则等待问题

缺: 违背了空闲让进原则、有限等待原则,会造成饥饿


Peterson算法

若两个进程都想进入临界区,尝试孔融让梨,主动让对方先使用

image

  • flag[0]=true; 表示自己想进入临界区
  • turn =1; 优先让对方进入
  • while(flag[1]&&turn==1); 若对方也想进,自己则等待

优:遵循空闲让进、忙则等待、优先等待原则

缺:违背了让权等待


进程互斥的硬件实现

中断屏蔽利用开关/中断原语实现

  • 优:简单、高效
  • 缺:不适合多处理机只适用于操作系统内核进程,不适用于用户进程(开关/中断指令只能运行在内核态)

TS/TSL指令:硬件实现,执行过程不允许被中断

相比于软件实现方法,TSL指令把检查和上锁用硬件方式变成一气呵成的原子操作

  • 优:实现简单,无需检查是否像软件实现方法一样具有逻辑漏洞;适用于多处理机环境
  • 缺:违背让权等待原则

Swap/Exchange/XCHG指令:用硬件实现,执行过程不允许被中断

交换两个变量的值

  • 优:实现简单,无需检查是否像软件实现方法一样具有逻辑漏洞;适用于多处理机环境

  • 缺:违背让权等待原则

信号量机制

用户进程可以使用操作系统提供的一对原语(wait(S)/P(S),signal(S)/V(S))来对信号量进行操作,从而很方便进程互斥、进程同步

  • 整型信号量:用整数表示系统中某种资源的数量

    (只能进程初始化、P、V操作)

    image

    • 优:检查、上锁一气呵成,避免了并发、异步导致的问题
    • 缺:违背让权等待

  • 记录型信号量

    image

    在signal中,若S.value≤0表示等待队列中有进程在等待资源,因此使用wakeup唤醒队头进程

    优:

    • 遵循让权等待原则: 当P操作发现S.value<0,表示资源分配完毕,用block原语进行自我阻塞,主动放弃处理机

    • 实现进程同步、进程互斥

    • 实现对系统资源的申请和释放

    若未特别说明,则P、V操作中的S指的是记录型信号量


信号量机制实现进程互斥

  1. 划定临界区

  2. 设置互斥信号量 mutex 初值为1 ;不同的临界资源需要设置不同的互斥信号量

  3. 临界区之前执行P(mutex )

  4. 临界区之后执行V(mutex );P、V操作必须成对出现

image


信号量机制实现进程同步

让本来异步并发的进程相互配合,有序推进(保证一前一后执行)

  1. 设置同步信号量S,初值为0

  2. 在前操作之后执行V(S)

  3. 在后操作之前执行P(S)

例:代码4在代码2后执行

image


信号量机制实现前驱关系

  1. 为每一对前驱关系各设置一个同步变量

  2. 在前操作之后对应的同步变量执行V操作

  3. 在后操作之前对应的同步变量执行P操作


生产者-消费者

系统中有一组生产者和一组消费者,生产者每次生产一个产品放入缓冲区,消费者每次从缓冲区(临界资源:互斥访问)取出一个产品并使用

  • 临界区互斥访问
  • 空时:先在缓冲区放入产品,再从缓冲区取出产品
  • 满时:先消耗完产品,再生产产品

semaphore mutex=1; //互斥信号量,对缓冲区互斥访问

semaphore empty=n; //同步信号量,缓冲区的大小

semaphore full=0; //同步信号量,产品数量


producer(){
while(1){
​ 生产产品;
​ P(empty);
P(mutex);
​ 产品放入缓冲区; //缓冲区互斥访问
V(mutex);
​ V(full);

}
}


consumer(){
while(1){

​ P(full);
P(mutex);
​ 缓冲区取产品; //缓冲区互斥访问
V(mutex);
​ V(empty);
​ 消耗产品;

}
}


  • 实现互斥的P操作一定要在实现同步的P操作之后

  • V操作的顺序可以交换

  • 若缓冲区大小>1,则必须设置一个互斥信号量来保证互斥访问


多(类)生产者-多消费者

例:

桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。

互斥关系:对盘子的访问

同步关系:

  • 父亲在盘子放入苹果后,女儿才可以取苹果
  • 母亲在盘子放入橘子后,儿子才可以取橘子
  • 盘子为空(可由女儿/儿子触发),父亲母亲才可以放入水果

semaphore mutex = 1; //实现互斥访问盘子(缓冲区)
semaphore apple = 0 ; //盘子中有几个苹果
semaphore orange = 0; //盘子中有几个橘子
semaphore plate = 1; //盘子中还可以放多少个水果


dad(){

while(1){

​ 准备一个苹果;

​ P(plate);

​ P(mutex);

​ 把苹果放入盘子;

​ V(mutex);

​ V(apple);

}}


mom(){

while(1){

​ 准备一个橘子;

​ P(plate);

​ P(mutex);

​ 把橘子放入盘子;

​ V(mutex);

​ V(orange);

}}


daughter(){

while(1){

​ P(apple);

​ P(mutex);

​ 从盘中取出苹果;

​ V(mutex);

​ V(plate);

​ 吃苹果;

}}


son(){

while(1){

​ P(orange);

​ P(mutex);

​ 从盘中取出橘子;

​ V(mutex);

​ V(plate);

​ 吃橘子;

}}


抽烟者问题

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

互斥关系:访问桌子

同步关系:

  • 桌子上有纸+胶水,第一个抽烟者取走
  • 桌子上有烟草+胶水,第二个抽烟者取走
  • 桌子上有纸+烟草,第三个抽烟者取走
  • 抽烟者完成,供应者将材料放在桌上

semaphore offer1 = 0;

semaphore offer2 = 0;

semaphore offer3 = 0;

semaphore finish = 0; //抽烟是否完成

int i=0;


provider(){

​ while(1){

​ if(i==0) 将纸+胶水放在桌上;V(offer 1);

​ else if(i==1) 将烟草+胶水放在桌上;V(offer 2);

​ else if(i==2) 将纸+烟草放在桌上; V(offer 3);

​ P(finish);i=(i+1)%3;

}

}


smoker1(){

​ while(1){

​ P(offer 1);

​ 取材料;抽烟;

​ V(finish);

}

}


smoke2(){

​ while(1){

​ P(offer 2);

​ 取材料;抽烟;

​ V(finish);

}

}


smoker3(){

​ while(1){

​ P(offer 3);

​ 取材料;抽烟;

​ V(finish);

}

}


读者写者问题

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不
会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

互斥关系:

  • 读-写进程

  • 写-写进程

    semaphore rw =1; //实现对文件的互斥访问

    int count =0; //当前有几个读进程在访问文件

    semaphore mutex=1; //对count变量的互斥访问

    semaphore w=1; //用于实现写优先


write(){

​ while(1){

​ P (w);

​ P(rw);

​ 写文件;

​ V(rw);

​ V(w);

​ }

}


reader(){

​ while(1){

​ P(w);

​ P(mutex);

​ if(count==0) P(rw); //第一个读进程进行加锁

​ count++;

​ V(mutex);

​ V(w);

​ 读文件;

​ P(mutex);

​ count--;

​ if(count==0) V(rw); //最后一个读进程进行解锁

​ V(mutex);

​ }

}

为了防止进程切换时另外的进程也进行P操作,因此P操作和count需要一起进行


哲学家进餐问题

一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

互斥关系:相邻哲学家对筷子的访问是互斥的

对五名哲学家编号0~4对应数组下标

防止死锁现象:

  • 最多允许4名哲学家同时进餐

  • 奇数号哲学家拿左边筷子,偶数号哲学家拿右边筷子;没拿到的一方会进入阻塞

semaphore chopsticks[5]={1,1,1,1,1}

semaphore mutex=1 //互斥取筷子

Pi(){

​ while(1){

​ P(mutex);

​ P(chopsticks[i]); //左边筷子

​ P(chopsticks[(i+1)% 5 ] ); //右边筷子

​ V(mutex);

​ 吃饭;

​ V(chopsticks[i]);

​ V(chopsticks[(i+1)% 5 ] );

​ 思考;

}

}


死锁

  • 死锁:在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进
  • 饥饿:长期得不到想要的资源,某进程无法向前推进
  • 死循环:某进程执行过程中一直跳不出某个循环
共同点 区别
死锁 进程无法顺利向前推进 至少有两个或两个以上进程同时发生死锁;发生死锁的进程一定处于阻塞态
饥饿 可能只有一个进程饥饿;发生饥饿的进程可能处于就绪态或阻塞态
死循环 死循环的进程可以在处理机运行(运行态)

死锁必要条件

  • 互斥条件

  • 不剥夺条件:不能由其他进程强行剥夺,只能主动释放

  • 请求和保持:已经保持了至少一个资源,又提出了新的资源请求 ,而该资源又被其他进程占有并对自己的资源保持不放

  • 循环等待:存在一种进程资源的循环等待链,链中每一个进程已获得资源的同时被下一个进程所请求

    (发生死锁时一定有循环等待,但发生循环等待未必死锁(同类资源数>1)



发生死锁时机

  • 各进程对不可剥夺的资源的竞争

  • 进程推进顺序非法

  • 请求和释放资源顺序不当

  • 信号量使用不当:互斥的P操作在同步的P操作之前


处理死锁的策略

  • (静态)预防死锁:破坏死锁的四个必要条件中的一个或多个

    • 互斥条件:使用SPOOLing技术可以将独占设备从逻辑上改成共享设备

    • 不剥夺条件:改成资源可剥夺

    • 请求和保持:

      • 静态分配方法:进程运行前一次性申请完所需的全部资源,尚未满足前不允许投入运行,资源利用率低,可能导致饥饿
    • 循环等待:

      • 顺序资源分配法:给系统中资源编号,每个进程必须按编号递增的顺序请求资源 一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源,因此有大编号资源的进程不可能逆向回来申请小编号的资源,从而就不会产生循环等待的现象;

        缺点

        • 不方便增加新的设备(可能需要重写申请编号)

        • 进程实际使用资源顺序和编号递增顺序不一致,会导致资源的浪费

        • 用户编程麻烦


  • (动态)避免死锁:用某种方法防止系统进入不安全的状态(银行家算法)

    安全序列:系统按照这种序列分配资源,每个进程都能顺利完成

    安全性算法:检查当前的剩余可用资源,能否满足某个资源的最大需求,如果可以就把该进程加入安全序列,并把该进程所具有的资源回收;不断重复看最终是否让所有进程都加入安全序列

    (处于安全状态一定不会发生死锁,处于不安全状态不一定会发生死锁


  • 死锁的检测和解除:允许死锁发生,操作系统检测到死锁发生会产生某种措施解除死锁

    • 检测:

      • 进程结点:对应一个进程

      • 资源结点:对应一类资源

      • 进程结点->资源结点:进程想申请几个资源

      • 资源结点->进程结点:为进程分配了几个资源

      若最终能消除所有边,就称这个图是可完全简化,此时一定没有死锁发生;若不能消除所有边则发生死锁


  • 消除:一旦检测出死锁的发生,就应该立即解除死锁

    • 资源剥夺法挂起某些死锁进程,并强占它的资源;应防止饥饿产生

    • 撤销进程法(终止进程法):强制撤销部分或者全部死锁的进程,并剥夺这些进程的资源

    • 进程回退法:让一个或多个进程回退到足以避免死锁的地步,


线程

  • 程序执行流的最小单位
  • 基本的CPU执行单元
  • 线程之间可以并发,提高了并发性,且线程间的并发,不需要切换进程环境,系统开销小
  • 进程:资源分配的基本单位;线程:处理机调度的基本单位
  • 几乎不拥有系统资源

  • 用户级线程ULT:应用程序通过线程库实现,所有的线程管理工作由应用程序负责,无需操作系统干预

    (只有用户看得见,对操作系统是透明的)

  • 内核级线程KLT:线程管理工作由操作系统内核核心态下完成

    (只有操作系统看得见)

因此,内核级线程才是处理机分配的单位


多线程模型

多对一模型

多个用户级线程映射到一个内核级线程,每个用户进程只对应一个内核级线程

  • 优:开销小,效率高
  • 缺:
    • 并发度不高:当一个用户级线程被阻塞后,整个进程都会被阻塞
    • 多个线程不可以在多核处理机上并行运行

一对一模型

一个用户级线程映射到一个内核级线程,每个用户进程都有与用户级线程同数量的内核级线程

  • 优:并发能力强:一个线程被阻塞后,别的线程还可以继续执行
  • 缺:线程管理成本高,开销大 ,一个用户进程占用太多内核级线程

多对多

n个用户级线程映射到m个内核级线程,每个用户进程对应m个内核级线程

克服了多对一模型并发度不高的问题;也克服了一对一模型中一个用户进程占用太多内核级线程的问题


  • 用户级线程ULT:应用程序通过线程库实现,所有的线程管理工作由应用程序负责,无需操作系统干预

    (只有用户看得见,对操作系统是透明的)

  • 内核级线程KLT:线程管理工作由操作系统内核核心态下完成

    (只有操作系统看得见)

因此,内核级线程才是处理机分配的单位


多线程模型

多对一模型

多个用户级线程映射到一个内核级线程,每个用户进程只对应一个内核级线程

  • 优:开销小,效率高
  • 缺:
    • 并发度不高:当一个用户级线程被阻塞后,整个进程都会被阻塞
    • 多个线程不可以在多核处理机上并行运行

一对一模型

一个用户级线程映射到一个内核级线程,每个用户进程都有与用户级线程同数量的内核级线程

  • 优:并发能力强:一个线程被阻塞后,别的线程还可以继续执行
  • 缺:线程管理成本高,开销大 ,一个用户进程占用太多内核级线程

多对多

n个用户级线程映射到m个内核级线程,每个用户进程对应m个内核级线程

克服了多对一模型并发度不高的问题;也克服了一对一模型中一个用户进程占用太多内核级线程的问题


管程

一种高级同步机制,让写程序时不需要再关注复杂的P、V操作


管程一种特殊的软件模块:(类)

  • 需要在管程中定义共享数据

  • 需要在管程中定义用于访问这些共享数据的入口(函数)

  • 只有通过特定的入口(封装)才能访问共享数据

  • 管程中有很多入口,但每次只能开放其中一个入口,并且只能让其中一个进程或线程进入

    由编译器负责实现各进程互斥进入管程的过程

  • 可以在管程中设置条件变量等待/唤醒操作解决同步问题

posted @   原语  阅读(149)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示