一、进程同步
什么是同步?同步就是说一个任务要等另一个执行完毕才能继续执行,而不是同时执行。我们都知道,进程有异步性,这种性质会导致操作系统的混乱。进程同步,指的是进程之间的执行次序的管理,就是为了解决进程异步性的这种混乱。
(1)直接制约和间接制约。
进程之间有两种制约关系。分别是直接制约和间接制约。直接制约指的是进程间的合作,即一个进程需要另一个进程的配合,否则会阻塞。如输出缓冲区为空的时候,输出进程就会阻塞,输出进程依赖输入进程不断的输入。间接制约指的是对于某种资源,同时只能有一个进程占用,你用的时候,别人就不能用。
(2)同步机制应遵循的规则。
这是所有的同步机制所需要遵循的规则:
1)空闲让进。资源空闲的时候,允许进程访问。
2)忙则等待。资源被占用的时候,进程必须等待。
3)有限等待。应保证进程有限时间能访问到资源,不能无限等待。
4)让权等待。运行中的进程不能访问指定资源时,应释放处理机。
二、PV操作
PV操作的鼎鼎大名,想必很多人都听说过。它就是经典的实现最基本的进程同步机制的一对操作。为什么叫PV操作呢?它是鼎鼎大名的计算机学家狄克斯特拉用荷兰语定义的,在荷兰文中,通过叫passeren,释放叫vrijgeven。P操作又叫Wait(S),本质是使用资源,V操作又叫Sign(S),本质是释放资源。PV操作都是原子操作,要么全做,要么全不做,并且PV操作是成对的。我们来详细看看PV操作的原理,是怎么实现进程同步的。PV操作跟信号量是分不开的,先看看什么是信号量。
(1)什么是信号量?
信号量是一种数据结构。包括整形信号量、记录型信号量、AND型信号量和信号量集等。不同的信号量对应不同的数据结构,也对应不同的PV操作。信号量和操作它的PV操作构成了对
应的信号量机制,用来控制进程同步。
(2)整型信号量。
顾名思义,整形信号量的数据结构就是一个简单的整形,一般用整形S来表示。其上的PV操作如下:
(这里的程序代码都是Pascal代码)
wait(S): while S <= 0 do no-op; s := s - 1sinal(S): s := s + 1
如上,S代表的是资源数目。对于wait(S)操作,当资源数目小于等于0的时候,就一直等待。若有资源,就跳出循环,使用一个资源。
对于sinal(S)操作,每次执行都释放一个资源。
(3)记录型信号量。
这个信号量比整形信号量增加了一个标识进程指针的属性,指向所有等待的进程链表。
PV操作与整形信号量的区别在于,wait()时,若s<=0,那么阻塞自身,放弃处理机。signal()后,判断若s<=0,就唤醒一个进程。它的好处是当进程请求不到资源的时候,不会无限等待。
(4)AND型信号量。
AND型信号量是针对多个临界资源而言的。即将进程运行中所需要的所有资源一次性分配给进程,进程运行完毕后释放所有资源。相当于把进程所需的所有资源捆绑在一起了。做法就是在wait操作中增加一个AND条件:只有当进程所需的所有资源处于空闲状态时,进程才能继续操作。
(5)信号量集。
信号量集指的是一次性对N个某类资源的请求处理。上面的AND型信号量指的是对多个不同类型资源的处理,而信号量集指的是对同类的多个资源的处理,也相当于AND型信号量的特殊情况。
(6)管程
由于进程对某一个资源进行操作的时候,都需要自带一对PV操作,为了避免这种情况,把某个资源和进程对其进行的操作包装起来,这样的一个模块称为管程。它是操作系统中的一个资源管理模块,供进程调用。可以看到,管程实现了面向对象的思想。
(7)条件变量
在上面的进程同步的实现中都有一个很严重的隐含问题,那就是,如果某个进程一直不释放某个资源,其他进程就只能无止休地等待。条件变量的意义在于,除了原本的资源空闲就让进、处理完就释放这样的逻辑外,还有其他的条件。例如:资源空闲且XX条件,就让进。处理完成或XX条件就释放资源。这些额外的条件,就叫条件变量。
三、进程通信
上面的通过信号量进行的进程同步,其本质是一种低级的通信机制。进程之间无法大量交换信息。那么两个进程之间想要实现大量的、频繁的信息交换,该怎么做呢?这就是高级通信机制了。高级通信机制有三大类:
(1)共享存储器系统。
存储器即内存,共享存储器,顾名思义,就是通信的两个进程通过共享的一块内存区域来通信,一个负责读一个负责写。而实际上面的信号量也是一种共享存储器系统,只不过进程间共享的是一个数据结构,并用PV操作对数据结构进行操作。
(2)消息传递系统。
进程间通过指定格式的消息进行通信。消息格式通常就是一个包含地址的头和一个包含内容的body。这种格式也叫做协议。我们常见的网络协议也是这种方式。消息传递系统分为直接通信方式和间接通信方式。直接通信方式即通信的进程双方都知道对方的存在,并在消息头中携带了自身和对方的地址信息。间接通信方式即进程间的消息传递不是直接传递给对方,而是有一个中间实体暂存、并转发,这样避免了进程双方接收、发送数据的速率不统一导致的进程阻塞。
(3)管道通信。
管道是一种连接读进程和写进程的共享文件——pipe文件,其本质是固定大小的缓冲区,这个缓冲区将2个进程连接起来,这两个进程对管道是互斥的访问,且写进程写入数据后便阻塞,直到读进程取走所有数据才被唤醒继续写数据。这种一次性的读操作和写操作,虽然会导致进程堵塞,但是在读写的过程中无须维护读写指针,效率非常高。
四、线程
线程是什么?线程就为了使操作系统能够有更好的并发而创建的,相当于只拥有少量资源的进程——轻型进程。在这种多线程操作系统中,进程是拥有系统资源的基本单位,包含多个线程,为其提供资源,而进程本身不再作为可执行的实体,当进程执行的时候,实际上是其中的某个线程在执行。
(1)线程执行的本质。
理解线程就必须深入理解并发。并发的本质就是单处理机系统永远都是线性执行任务的。而线程的本质就是将原本为实现进程的时间片划分的更细,假设在某个单处理机操作系统中,时间片为20毫秒,即一个进程的单次执行时间为20毫秒,在这个进程内有50个线程在执行,那么划分后,平均每个线程的执行时间肯定小于0.4毫秒。不过对于大部分任务而言,这仍旧是足够的。线程无非就是这样。
(2)线程的类型和实现。
1)用户级线程(User Level Thread)——ULT。
这种线程的实现非常简单,对于处理器而言,它仍旧是在进行进程切换,并不知道有线程的存在。如果每个进程相当于一个车子,那每个线程都相当于一个司机,线程切换就是不断在换司机。
那么在进程内部如何分割出各种多线程的呢?进程中有一个函数集合,专门用来管理和控制线程的执行。这个函数集合被称为运行时系统。进程执行时就是执行它的运行时系统,对其中的线程进行切换管理。线程的运行时信息——线程控制块TCB存放在各自的堆栈中,每次切换的时候,运行时系统就从线程的堆栈中取得对应的运行时信息,设置CPU的寄存器中,之后便可以运行。值得注意的是,线程是不能直接调用系统资源的,线程需要系统资源时,需要由运行时系统来调用分配。
2)内核支持线程(KernelSupported Thread)——KST。
这种线程的创建、撤销、切换就不是依赖进程,是直接像进程调度一样由内核控制,由于线程基本不用有资源,所以这种调度也很快。内核支持线程的线程优先级通常比用户级线程要高很多。
那么内核支持线程如何实现的呢?创建一个进程时,系统为之分配一个任务数据区(Per Task Data Area),其中包含若干线程控制块TCB,这些TCB并非存放在进程资源内存中,而是保存在CPU的寄存器中。然后进行跟PCB非常类似的由处理器切换控制。
3)组合方式,由轻型进程(Light weight process)——LWP实现。
内核支持多KST线程的创建,同时支持ULT线程的创建,这种支持是通过轻型进程LWP实现的。轻型进程LWP的本质就是一个KST进程,它的特点就是能够让ULT连接,当ULT连接它的时候,就相当于在调用KST,可以实现KST的所有功能。所以一般LWP都是用线程池来实现的。可以看到LWP的目的就是为了让用户级线程ULT直接能调用系统资源。
参考:《计算机操作系统(汤子瀛)》