《操作系统》第二章 进程管理

第二章

2.1 前趋图和程序执行

2.1.1 前趋图

前趋图是指一个有向无循环图
image

2.1.2 程序顺序执行

特点:
- 顺序性:一个程序开始执行必须要等到前一个程序已执行完成
- 封闭性:程序一旦开始执行,其计算结果不受外界因素影响
- 可再现性:程序的结果与它的执行速度无关(即与时间无关),只要给定相同的输入,一定会得到相同的结果

2.1.3 程序并发执行

特点:
- 间断性
- 失去程序的封闭性
- 不可再现性

2.2 进程的描述

2.2.1 进程的定义和特征

进程是指进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。

进程实体:程序段、相关的数据段、进程控制块PCB

2.2.2 进程的基本状态及转换

三种基本状态:就绪状态执行状态阻塞状态

  • 就绪状态:当进程已经获得了除CPU以外的所有资源,一旦得到CPU,就立即可以运行,则这些进程所处的状态为就绪状态
  • 运行状态:正在运行的进程所处的状态为运行状态
  • 阻塞/等待状态:若一进程正在等待某一事件发生(如等待输入/输出工作完成),这时即使给它CPU,它也无法运行,称该进程处于等待状态,或阻塞、睡眠、封锁状态
    image

2.2.3 进程管理中的数据结构(PCB)

进程控制块PCB的作用
(1)使操作系统能对进程进行管理。
(2)使程序并发具有可再现性

进程控制块中的信息
① 进程标识符
② 处理机状态
③ 进程调度信息
④ 进程控制信息

2.3 进程同步

2.3.1信号量机制

1.整形信号量
使用原子操作wait(S)和signal(S),这两个操作被分别称为P、V操作。

//P操作
wait(S){
    while(S<0);
    S--;
}
//V操作
signal(S){
    S++;
}

由于不满足“让权等待的原则”,后来发展为记录型信号量。

2.记录型信号量
记录型信号量的数据结构是一个两元组,包含信号量的值value和关于此信号量的阻塞队列Q,value一般反应资源的数量,只能由P、V操作改变其值。

扫描二维码关注公众号,回复: 2923004 查看本文章
//P操作
wait(S){
    S > value--;
    if(S->value < 0){
        block(S->list);
    }
}
//V操作
wait(S){
    S > value++;
    if(S->value <= 0){
        wakeup(S->list);
    }
}

以上两个操作的含义:

P操作:首先将S的value值减一,表示需要一个临界资源,如果S.value<0,说明原来S.value<=0,则表示当前没有临界资源可用,于是使用阻塞原语将该进程阻塞到信号量S相关的阻塞队列中。如果S.value<0,则|S.value|就表示阻塞队列的长度,即等待使用临界资源的长度。

V操作:首先将S的value值加一,表示释放一个临界资源,如果S.value<=0,则表示当前阻塞的进程,于是唤醒阻塞队列中的该进程。

P操作相当于“等待一个信号”,而V操作相当于“发送一个信号”。如果我们将信号量初值设置为1时可实现互斥,因为信号量表示可用资源,互斥信号只允许同时一个进程访问临界资源。设置为0或者N时可以用来实现同步。这在生产者-消费者问题中会讲解。

3.AND型信号量
AND同步机制的基本思想:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其他所有有可能为之分配的资源也不分配给它。

4.信号量集
使用记录型信号量一次只能对一个信号量进行加1或减1操作,如果一次需要N个信号量,便要进行N次P操作,显然这是很低效的。于是对于AND信号量机制进行扩充,进程对于信号量S的测试值不再是1,而是该资源允许分配的下限值,如果该资源当前的值低于下限值,则不予分配。

//S1表示资源1当前的值,t1表示资源1的下限值,d1表示资源1的需求值
Swait(S1,t1,d1,...,Sn,tn,dn);
Ssignal(S1,t1,d1,...,Sn,tn,dn);

2.3.2 信号量的应用

1.利用信号量实现进程互斥
为使多个进程能互斥地访问某临界资源,只需为该资源设置一互斥信号量mutex,并将其初值设置为1,然后将各进程访问该资源的临界区置于wait和signal操作之间。

实现进程互斥时应该注意,wait(mutex)和signal(mutex)必须成对地出现。

2.3.3 管程机制

管程:代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块。

管程的组成:
① 管程额名称。
② 局部于管程的共享数据结构说明。
③ 对该数据结构进行操作的一组过程。
④ 对局部于管程的共享数据设置初始值的语句。
image

管程和进程的区别:
① 虽然二者都定义了数据结构,但进程定义的是私有数据结构PCB,管程定义的是公共数据结构,如消息队列等;
② 进程是由顺序程序执行有关操作,管程主要是进行同步操作和初始化操作;
③ 设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题;

2.4 经典同步问题

2.4.1 生产者-消费者问题

经典的同步互斥问题,也称作“有界缓冲区问题”。具体表现为:

1.两个进程对同一个内存资源进行操作,一个是生产者,一个是消费者。

2.生产者往共享内存资源填充数据,如果区域满,则等待消费者消费数据。

3.消费者从共享内存资源取数据,如果区域空,则等待生产者填充数据。

4.生产者的填充数据行为和消费者的消费数据行为不可在同一时间发生。
image

生产者-消费者之间的同步关系表现为缓冲区空,则消费者需要等待生产者往里填充数据,缓冲区满则生产者需要等待消费者消费。两者共同完成数据的转移或传送。生产者-消费者之间的互斥关系表现为生产者往缓冲区里填充数据的时候,消费者无法进行消费,需要等待生产者完成工作,反之亦然。

既然了解了互斥与同步关系,那么我们就来设置信号量:

由于有互斥关系,所以我们应该设置一个互斥量mutex控制两者不能同时操作缓冲区。此外,为了控制同步关系,我们设置两个信号量empty和full来表示缓冲区的空槽数目和满槽数目,即有数据的缓冲区单元的个数。mutex初值为1,empty初值为n,即缓冲区容量,代表初始没有任何数据,有n个空的单元,类似的,full初值为0.

下面进行生产者-消费者行为设计:

//生产者
void Productor() {
    while(1) {
        //制造数据
        P(empty);
        P(mutex);
        //填充数据
        V(mutex);
        V(full);
    }
}

//消费者
void Consumer() {
    while(1) {
        P(full);
        P(mutex);
        //消费数据
        V(mutex);
        V(empty);
    }
}

2.4.2 读者-写者问题

第二个经典问题是读者-写着问题,它为数据库的访问建立了一个模型。规则如下:

1.一个进程在读的时候,其他进程也可以读。

2.一个进程在读/写的时候,其他进程不能进行写/读。

3.一个进程在写的时候,其他进程不能写。

我们来分析他们的关系,首先,这个问题没有明显的同步关系,因为在这个问题里,读和写并不要合作完成某些事情。但是是有互斥关系的,写者和写者,写者和读者是有互斥关系的,我们需要设置一个mutex来控制其访问,但是单纯一个信号量的话会出现读者和读者的互斥也出现了,因为我们可能有多个读者,所以我们设置一个变量ReadCount表示读者的数量,好,这个时候,对于ReadCount又要实现多个读者对他的互斥访问,所以还要设置一个RC_mutex。这样就好了。然后是行为设计:

void Reader() {
    while(1) {
        P(RC_mutex);
        rc = rc + 1;
        if(rc == 1) P(&mutex);  //如果是第一个读者,那么限制写者的访问
        V(RC_mutex);
        //读数据
        P(RC_mutex);
        rc = rc - 1;
        if(rc == 0) V(&mutex);  //如果是最后一个读者,那么释放以供写者或读者访问
        V(RC_mutex);
    }
}

void Writer() {
    while(1) {
        P(mutex);
        //写数据
        V(mutex);
    }
}

2.5 进程通信

进程通信指进程之间的信息交换。

2.5.1 进程通信的类型

1.共享存储器系统
(1)基于共享数据结构的通信方式
- 适合于传递相对少量的数据,通信效率低下,属于低级通信。

(2)基于共享存储区的通信方式
- 属于高级通信。需要通信的进程在通信前,先向系统申请获得共享存储区的一个分区,并附加到自己的地址空间中,便可对其中数据进行正确读、写,读写完成或不再需要时,将其归还给共享存储区。

2.管道通信系统
- 向管道提供输入的进程(称写进程),以字符流的形式将大量数据送入管道,而接受管道输出的进程(读进程)可从管道中接收数据。

3.消息传递系统
- 直接通信:发送进程直接把消息发送给接收者,并将它挂在接收进程的消息缓冲队列上。接收进程从消息缓冲队列中取得消息。
- 间接通信:发送进程将消息发送到某种中间实体中(信箱),接收进程从中取得消息。

4.客户机-服务器系统

2.6 线程

2.6.1 线程的引入

引入目的:减少程序在并发执行时所付出的时空开销。

线程与进程的比较

1.调度的基本单位
进程:作为独立调度和分派的基本单位,能独立运行。
线程:在引入线程的OS中,将线程作为独立调度和分派的基本单位,能独立运行。

同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程的线程中,必然会引起进程的切换。

2.并发性
在引入线程的OS中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可以并发执行,甚至还允许在一个进程中所有线程都能并发执行。

3.拥有资源
进程可以拥有资源,并作为系统中拥有资源的一个基本单位。
线程本身并不拥有系统资源,而是仅有一点必不可少的、能保证独立运行的资源。

4.独立性
同一个进程中的不同线程之间的独立性比不同进程之间的独立性要低得多。同一个进程中的不同线程往往是为了提高并发性以及进行相互之间的合作而创建的,它们共享该进程的内存地址空间和资源。

5.系统开销
系统创建和撤销进程时的开销明显大于创建以及撤销线程时的开销。

5.支持多处理系统

2.7 线程的实现

2.7.1 线程的实现方式

1.内核支持线程KST(Kernel Supported Threads)
无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,是与内核紧密相关的。

2.用户级线程ULT(User Level Threads)
用户级线程是在用户空间中实现的。对线程的创建、撤销、同步与通信等功能,都无需内核的支持,即用户级线程是与内核无关的。

使用用户级线程方式的优点
(1)线程切换不需要转换到内核空间
(2)调度算法可以是进程专用的
(3)用户级线程的实现与OS平台无关

使用用户级线程方式的缺点
(1)系统调用的阻塞问题
在基于进程机制的OS中,大多数系统调用将使进程阻塞,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞。而在内核支持线程方式中,则进程的其他线程仍然可以运行。

(2)在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点。内核每次分配给一个进程的仅有一个CPU,因此,进程中仅有一个线程能执行,在该线程放弃CPU之前,其他线程只能等待。

猜你喜欢

转载自blog.csdn.net/swpu_ocean/article/details/80443970