操作系统概念进程基础1440

进程(同步)基础ฅฅ*(自留)

6.1 背景:

进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。
竞争条件是指多个进程并发访问和操作同一数据且执行结果与访问发生的特定顺序有关。

进程同步:有的进程之间需要相互配合地完成工作,各进程的工作推进需要遵循一定的先后顺序。

6.2 临界区问题

对临界资源的访问需要互斥地进行,即同一时间段内只能允许一个进程访问该资源。

临界区问题:设计一个以便进程协作的协议。每个进程必须请求允许进入其临界区。

解决临界区问题的进程分为进入区临界区退出区剩余区

do{
	entry section;//进入区,检查可否进入临界区,进入要“上锁”
	critical section;//临界区,访问临界资源的那段代码
	exit section;//退出区,负责“解锁”
	remainder section;//剩余区,其余代码部分
}while(TRUE);

临界区问题的解答必须满足如下三项要求:
互斥、前进、有限等待(或者空闲让进、忙则等待、有限等待、让权等待)

两种方法用于处理操作系统内的临界区问题:抢占内核非抢占内核,虽然但是非抢占内核不如抢占受欢迎,因为抢占内核更适合实时编程,且响应更快

6.3 Peterson算法

  • 算法思想:如果双方都争着想进入临界区,那可以尝试主动让对方先进入临界区。 操作系统概念进程基础1440_第1张图片操作系统概念进程基础1440_第2张图片
  • 满足互斥、前进、有限等待要求(但是不满足让权等待)

6.4 硬件同步

锁:进程进入临界区前必须得到锁,推出临界区时释放锁。

单处理器环境:

中断屏蔽方法,在修改共享变量时禁止中断出现,通常为非抢占内核采用,简单,高效,但是只适用于操作系统内核进程(权限太高)。

多处理器环境:

  • TS / TestAndSetLock / TSL指令,用硬件实现,执行的过程不允许被中断,只能一气呵成。

    C语言逻辑:old记录是否已被上锁,再将lock设为true,检查临界区是否已被上锁(若已上锁,则循环重复前几步)。
    操作系统概念进程基础1440_第3张图片
    操作系统概念进程基础1440_第4张图片

    实现简单,可以原子地执行。但是也不满足“让权等待”,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,导致忙等

  • Swap / Exchange / XCHG指令,用硬件实现,执行过程不允许被中断,从逻辑上看和TSL并无太大区别。

    c语言逻辑:
    操作系统概念进程基础1440_第5张图片

6.5 信号量

信号量是一个变量(可以是整数,也可以是更复杂的记录型变量,但是在书中讲的是整数),可以用一个信号量来表示系统中某种资源的数量

原语是一种特殊的程序段,执行只能一气呵成,不能中断,是由关中断/开中断指令实现的。

一对原语(原子操作):wait()signal(),常称为PV
操作系统概念进程基础1440_第6张图片

6.5.1 用法

  • 计数信号量:值域不受限制。二进制信号量:只能为0/1,或称互斥锁。
  • 进程需要使用资源时执行wait(),信号量计数值减一;释放资源时执行signal(),计数值加一。计数值为0之后需要使用资源的进程将被阻塞(直到计数>0)。
  • 缺点:不满足让权等待,会发生忙等
  • 这种信号量也成为自旋锁,因为进程在其等待锁时还在运行(优点:进程等待锁时不进行上下文切换,常用于多处理器系统,这样一个线程在一个处理器自旋的时候另一个线程可以在另一个处理器上在其临界区内执行)。

6.5.2 实现

  • 为了避免忙等,改写wait和signal函数,创建记录型信号量
  • 每个信号量都有一个整型值和一个进程链表。当一个进程必须等待信号量时,就加入到进程链表上。操作signal( )会从等待链表中获取一个进程以唤醒。
/*记录型信号量的定义*/
typedef struct{
	int value; //剩余资源数
	struct process *list;//等待队列
}semaphore;

void wait(semaphore *S){
	S->value--;
	//如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把它挂到S的等待(阻塞)队列中
	if(S->value<0){
		add this process to S->list;
		block();
	}
}		

void signal(semaphore S){
	S->value++;
	if(S->value<=0){
		remove a process P from S->list;
		wakeup(P);
	}
}
  • 信号量的关键之处在于它们原子地执行,必须确保没有两个进程能同时对同意信号量执行操作wait和signal。两种解决方式:单处理器可以在执行wait和signal时简单地禁止中断;多处理器需要提供其他加锁技术。

6.5.3 死锁与饥饿

  • 死锁:两个或者多个进程无限地等待一个事件,而该事件只能由这些等待进程之一来产生。
  • 无限期阻塞/饥饿:进程在信号量内无限期等待。

6.6 经典同步问题

6.6.1 有限缓冲问题

  • 系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品并使用。
  • 生产者、消费者共享一个初始为空,大小为n的缓冲区。
  • 只有缓冲区没满的时候生产者才能把产品放入否则;只有缓冲区不空的时候消费者才能取出产品。
  • 缓冲区是临界资源,各进程必须互斥地访问。
  • Q:如何用信号量机制(P/V操作)实现生产者、消费者进程的这些功能?
    A:
    1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。2.整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
    3.设置信号量。设置需要的信号量并根据题目条件确定信号量初始值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少。)
    操作系统概念进程基础1440_第7张图片
    ps.实现互斥的P操作要在实现同步的P操作之前
  • 多生产者-多消费者问题:分析同步问题的时候不能从单个进程行为的角度来分析,要把“一前一后”发生的事看作是两种“事件“的前后关系。把进程行为的前后关系抽象成事件的前后关系。(好好画图就vans)

6.6.2 读者-写者问题

  • 两类进程:写进程、读进程

  • 互斥关系:写进程-写进程、写进程-读进程、读进程和读进程不存在互斥问题

  • 操作系统概念进程基础1440_第8张图片

  • 几个栗子:

    读者1→读者2:读者1进入后对w和mutex上锁,且一口气执行完if和count,再对mutex和w解锁,在此过程中就算有读者2进入也必须等到1执行完读文件之前的那一通之后,当读者2进入时重新对w上锁,而且此时count已经为1,所以会跳过对rw上锁的操作(故不会被阻塞在那里)继续执行。

    写者1→写者2:写者1进入后对w和rw上锁写文件,在此过程中如果有写者2进入即会被阻塞在P(w)处。

    写者1→读者1:读者1会被阻塞在P(w)处。

    读者1→写者1→读者1:在读者1读文件的过程中,写者1会被阻塞在P(rw)处,读者2会被阻塞在P(w)处(因为写者1已经对w上锁),所以读者1完成之后会先唤醒写者1。

    写者1→读者1→写者2:读者1和写者2都会被阻塞在P(w)处,但是读者1是先到的,所以写者1完成之后会先唤醒读者1。

  • 在这种算法中,连续进入的多个读者可以同时写文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也不是真正的”写优先“,而是相对公平的FCFS原则。

  • 核心思想在于设置了一个计数器count用来记录当前正在访问共享文件的读进程数,另外,对count变量的检查和赋值不能一气呵成导致了一些错误,如果需要实现一气呵成,应该想到用互斥信号量。

  • 读者写者问题及其解答可以进行推广,用来对某些系统提供读写锁

    读写锁在以下情况下最为有用:
    ○ 当可以区分哪些进程只需要读共享数据而哪些进程只需要写共享数据。
    ○ 当读者进程数比写进程多时。这是因为读写锁的建立开销通常比信号量或互斥锁要大,而这一开销可以通过允许多个读者来增加并发度的方法进行弥补。

6.6.3 哲学家进餐问题(死锁)

操作系统概念进程基础1440_第9张图片
哲学家进餐问题的解决方法

  • 最多只允许 4 个哲学家同时坐在桌子上。
  • 只有两只筷子都可用时才允许一个哲学家拿起它们(他必须在临界区内拿起两只筷子)。
  • 使用非对称解决方法,即奇数哲学家先拿起左边的筷子,接着拿起右边的筷子,而偶数哲学家先拿起右边的筷子,接着拿起左边的筷子。

6.7 管程

当信号量不正确地用来解决临界区问题时,会很容易产生各种类型的错误。

6.7.1 使用(封装思想)

  • 管程是一种特殊的软件模块(类似于面向对象编程的),组成部分:
  1. 局部于管程的共享数据结构的说明。
  2. 对该数据结构进行操作的一组过程
  3. 对局部于管程的共享数据设置初始值的语句。
  4. 管程有一个名字。
  • 管程的基本特征:
  1. 局部于管程的数据只能被局部于管程的过程所访问。
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据。
    (1.2 类似于封装)
  3. 每次仅允许一个进程在管程内执行某个内部过程。(因此,程序员不需要显示地编写同步代码)
  • 引入管程的目的无非就是要更方便地实现进程互斥和同步:
  1. 需要在管程中定义共享数据(比如生产者消费者问题的缓冲区)。
  2. 需要在管程中定义用于访问这些共享数据的”入口“,入口其实就是一些函数(比如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,另一个函数用于取产品这样子)。
  3. 只有通过这些特定的入口才能访问共享数据。
  4. 管程有很多”入口“,但是每次只能开放其中一个入口,而且只能让一个进程或者线程进入(ps.这种互斥特性是由编译器负责实现的,程序员不用关心)。
  5. 可在管程中设置条件变量以及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出入口);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。

6.7.2 哲学家进餐问题的管程解决方案 && 6.7.3 基于信号来给你的管程实现

  • 对每个管程,都有一个信号量mutex(初始化为1),进程在进入管程之前必须执行wait(mutex),在离开管程之后必须执行signal(mutex)。
  • 详细做法案例参考网页:管程求解哲学家就餐问题

6.7.4 管程内的进程重启

  • 简单解决重启顺序的方案:FCFS
  • 复杂:条件等待构造x.wait(c),其中 c 是整数表达式,需要在执行操作wait()时进行计算。c的值称为优先值 (priority number),会与悬挂进程的名称一起存储。当执行x.signal()时,与最小优先值相关联的进 程会被重新启动。

6.8 同步实例

6.8.1 Solaris同步

  • 为了控制访问临界区, Solaris 提供了适应互斥、条件变量、信号量、读写锁和十字转门。

6.8.2 Windows XP同步

6.8.2 Linux同步

6.8.4 Pthread同步

  • PthreadAPI 为线程同步,提供互斥锁、条件变量、读写锁。

6.9 原子事务

6.9.1 系统模型

  • 执行单个逻辑功能的一组指令或操作称为事务(transaction) 。己成功完成执行的终止事务称为提交(committed) ,否则,称为撤销(aborted)。 由于被中止的事务可能己改变了它所访问的数据,这些数据的状态与事务在原子执行情况下是不一样的。被中止的事务必须对其所修改的数据不产生任何影响,以便确保原子特性。因此,被中止的事务所访问的数据状态必须恢复到事务刚刚开始执行之前,即这个事务己经回退 (rolled back)。确保这一属性是系统的责任。
  • ○ 易失性存储:驻留在易失性存储土的信息通常在系统崩溃后不能保存。内存和高速缓存就是这种存储的例子。对易失性存储的访问非常快,这是由于内存访问本身的速度以及易失性存储内的任何数据项都可以直接访问。
    ○ 非易失性存储:驻留在非易失性存储上的信息通常在系统崩溃后能保存。磁盘和磁带就是这种存储介质的例子。磁盘比内存更为可靠,磁带比磁盘更为可靠。然而,磁盘和磁带也会出错,从而导致信息遗失。当前,非易失性存储要比易失性存储慢几个数量级,因为磁盘和磁带设备为机电的且要求物理运动以访问数据。
    ○ 稳定存储:驻留在稳定存储上的信息绝不会损失("绝不"应该打个折扣,因为从理论上来说这样的保证是不成立的)。

6.9.2 基于日志的恢复

(略)

(•͈ᴗ•͈ૢૢ)❊⿻* e.g.

操作系统概念进程基础1440_第10张图片

(1)设置资源信号量Empty:=100,表示阅览室中最初有100个空位。设置互斥信号量Mutex:=1,表示登记表初始时可用的。
(2)按顺序填入:P(Empty); P(Mutex); V(Empty);

操作系统概念进程基础1440_第11张图片
操作系统概念进程基础1440_第12张图片

(1)设置资源信号量empty:=2;同步信号量full:=0;
(2)P(empty); V(full); P(full); V(empty);

你可能感兴趣的:(操作系统)