- 进程和线程(二)
- 进程概念
- 进程的状态和组成
- 进程的基本状态及其转换
- 进程描述
- 进程队列
- 进程管理
- 进程创建
- 进程终止
- 进程阻塞
- 进程唤醒
- *** 作系统如何切换进程?
- 线程
- 线程概念
- 线程的组成
- 线程和进程的关系
- 用户级线程和核心级线程
- 进程的同步和互斥
- 临界区和临界资源
- 互斥实现方式
- 信号量
- 经典进程同步问题
- 管程
- 管程概念
- 管程特性
- 进程通信
- 进程通信的方式
- 消息传递系统
- 客户-服务器系统的通信
由于多道程序并发执行时共享系统资源,共同决定这些资源的状态,因此系统中各程序在执行过程中出现相互制约的新关系,程序的执行出现“走走停停”的新状态,这些都是在程序的动态过程中发生的。而程序本身是机器能够翻译或执行的一组动作或指令,它或者写在纸面上,或者存放在磁盘等介质上,是静止的。很显然,直接从程序的字面上无法看出它什么时候运行、什么时候停顿,也看不出它是否影响其他程序或者一定受其他程序的影响。
综上所诉,用程序这个静态概念已经不能如实反映程序并发执行过程中的这些特征。为此,人们引入“进程(Process)”这一概念来描述程序动态执行过程的性质
所谓进程就是程序在并发环境中的执行过程
进程的基本特征:
- 动态性:进程是程序的执行过程,它有生、有亡,有活动,有停顿,可以处于不同的状态
- 并发性:多个进程的实体能够存在于同一内存中,在一段时间内都得到运行。这样使得一个进程的程序与其他进程的程序并发执行。
- 调度性:进程是系统中申请资源的单位,也是被调度的单位。 *** 作系统中有很多调度程序,它们根据各自的策略调度合适的进程,为其运行提供条件。
- 异步性:各进程向前推进的速度是不可预知的,即异步方式运行。这造成进程间的相互制约,使程序失去再现性。为保证各程序的协调运行,需要采取必要的措施。
- 结构性:进程有一定的结构,它由程序段、数据段和控制结构(如进程控制块)等组成。程序规定了该进程所要执行的任务,数据是程序 *** 作的对象,而控制结构中含有进程的描述信息和控制信息,是进程组成中最关键的部分。
进程映像:进程通常由程序、数据集合、栈和PCB这四部分组成。进程的这四部分构成进程在系统中存在和活动的实体,有时也称“进程映像”。
进程控制块
- 进程控制块含有进程的描述信息和控制信息,是进程动态特性的集中反映,是系统对进程施行识别和控制的依据。
- 进程控制块一般包含:进程名、特征信息、进程状态信息、调度优先权、通信信息、现场保护区、资源需求分配和控制方面的信息、进程实体信息、族系信息和其他信息等。
- 进程控制块的作用
- 每个进程有唯一的进程控制块。
- *** 作系统根据PCB对进程实施控制和管理。
- 进程的动态、并发等特征是利用PCB表现出来的。若没有进程控制块,则多道程序环境中的程序是无法实现并发的。
- PCB是进程存在的唯一标志。当系统创建了一个新进程时,为它建立一个PCB;当进程终止后,系统回收其PCB,该进程在系统中就不存在了。
系统中有许多进程。处于就绪状态和处于阻塞状态的进程可以分别有多个,而阻塞的原因又各不相同。为了对所有进程进行有效的管理,常将各个进程的PCB用适当的方式组织起来。一般来说,有线性方式、链接方式和索引方式。
进程管理 进程创建一个进程可以动态地创建新进程,前者称作父进程,后者称作子进程。引发创建进程的事件通常时调度新的批作业,交互式用户登录, *** 作系统提供服务和现有进程派生新进程。
创建进程时要执行创建进程的系统调用fork,其主要 *** 作分为如下四步:
- 申请一个空闲的PCB:从系统的PCB表中找出一个空闲的PCB项,并指定唯一的进程标识号PID(即进程内部名)。
- 为新进程分配资源:根据调用者提供的所需内存的大小,为新进程分配必要的内存空间,存放其程序和数据及工作区。
- 将新进程的PCB初始化:根据调用者提供的参数,将新进程的PCB初始化。这些参数包括进程名(外部标识符)、父进程标识符、处理机初始状态、进程优先级、本进程开始地址等。一般将新进程状态设置为就绪状态。
- 将新进程加到就绪队列中:一个进程派生新进程后,有两种可能的执行方式:一是父进程和子进程同时(并发)执行,二是父进程等待他的某个或全部子进程终止。建立进程的地址空间也有两种可能的方式:一是子进程复制父进程的地址空间,二是把程序装入子进程的地址空间。
导致进程终止的原因很多,可以分为如下三种情况。
- 正常终止。当一个进程完成自己的任务后,使用exit系统调用,要求 *** 作系统删除它
- 异常终止。在进程运行过程中,如果出现某些错误或故障,会导致进程终止。原因包括:运行超时、内存不足、越界错误等。
- 外部干扰。外部干扰包括 *** 作员或 *** 作系统的干预,如死锁, *** 作员或 *** 作系统终止该进程,父进程终止等。
一旦系统中出现要求终止进程的事件后,便执行终止进程的系统调用exit。通常,终止进程的主要 *** 作过程如下:
- 从系统的PCB表中找到指定进程的PCB。若它正处于运行状态,则立即终止该进程的运行。
- 回收该进程所占用的全部资源。
- 若该进程有子孙进程,则还要终止其所有子孙进程,回收它们所占用的全部资源。
- 将被终止进程的PCB从原来队列中摘走,以后由父进程从中获取数据,并释放它。
正在运行的进程通过调用阻塞原语(sleep),主动把自己阻塞,进程的阻塞过程如下:
- 立即停止当前进程的执行
- 将现行进程的CPU现场送到该进程的PCB线程保护区中保存起来,以便重新运行时恢复此时的现场。
- 把该进程PCB中的现行状态由“运行”改为“阻塞”,把该进程插到具有相同事件的阻塞队列中。
- 转到进程调度程序,重新从就绪队列中挑选一个合适进程投入运行
阻塞的进程并不能唤醒自己,当阻塞的事件满足时由与该事件相关的进程调用唤醒原语(wakeup),唤醒原语执行过程如下:
- 把阻塞进程从相应的阻塞队列中摘下
- 将现行状态改为就绪状态,然后把该进程插入就绪队列中
- 如果被唤醒的进程比当前运行线程的优先级更高,则设置重新调度标志。
当一个进程启动磁盘读写时,将当前CPU现场保存到PCB现场保护区中,将该进程设置为阻塞状态,放到阻塞队列中。调用schedule()函数,从就绪队列中找到下一个进程并获取下一个进程的PCB(调用getNext函数),把新的PCB信息放到CPU中运行,切换地址空间的映射表。
线程 线程概念在传统的进程概念中,体现了进程的两个属性:资源分配的单位和调度运行的单位。由于进程是资源的拥有者,所以它的负载很重,因而在实施进程的创建、删除和切换过程中要付出较大的时空开销。这样,就限制了系统中进程的数目和并发活动的程度。
在很多现代 *** 作系统中把上诉两个属性分别赋予不同的实体:进程只作为资源的拥有者,而调度和运行的属性赋予新的实体——线程。
线程(Thread)是进程中实施调度和分派的基本单位,且一个进程中的多个线程共享进程的资源。保留了并发的优点,避免了进程切换的代价。
线程的组成线程,有时也称轻载进程(LWP)。每个线程有一个thread结构,即线程控制块,用于保存自己私有的信息,主要由以下四个基本部分组成:
- 一个唯一的线程标识符
- 描述处理器工作情况的一组寄存器(如程序计数器、状态寄存器、通用寄存器等)的内容。
- 每个thread结构有两个指针。一个指向核心栈,一个指向用户栈。当用户线程转变到核心态方式下运行时,就是使用核心栈;当线程在用户态下执行时,就使用自己的用户栈。
- 一个私有存储区,存放现场保护信息和其他与该现场相关的统计信息。
- 一个进程可以有多个线程,但至少要有一个线程;并且一个线程只能在一个进程的地址空间内活动。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
- 处理机分配给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
用户级线程:在用户态下运行的线程,不能被 *** 作系统看到,只能自己调度自己销毁,提高了CPU的利用率,但是不能充分发挥多核CPU的特性。
核心级线程:从用户态到核心态线程的切换,因为CPU看不到用户级线程,所以当用户级线程发生系统调用并阻塞时,CPU就会等待,不会调度到其他用户级线程造成资源的浪费。
进程的同步和互斥同步。逻辑上相关的两个或多个进程为完成一项任务,通过协调活动来使用同一资源,而产生的执行时序的约束关系,称为同步。
互斥。逻辑上相互无关的两个或多个进程由于争用同一资源而发生的相互制约关系称作互斥。
临界区和临界资源一次仅允许一个进程使用的共享资源称为临界资源。在每个进程中访问临界资源的那段程序叫做临界区。
临界区只允许单个进入,独自占用,尽快退出,败落让权。
互斥实现方式- 禁止中断。当进程进入临界区后立即关闭所有的中断,在它离开临界区之前才重新开放中断。这种方式很可能会导致系统崩溃,而且在多处理器的计算机系统中,这种方法并不奏效。
- 专用机器指令。很多计算机都有一条名为TSL的指令:TSL RX,LOCK。它把内存字LOCK的内容读到寄存器RX中,然后在该地址单元中存入一个非零值。读数和存数的 *** 作是不可分割的,即在这条指令完成之前,其他线程不能访问该单元。
- 原语。所谓原语(Primitive)是机器指令的延伸,它是为完成某些特定功能而编制的一段系统程序,它在执行时不可分割、不可中断。
- 为解决进程互斥进入临界区问题,可为每类临界区设置一把锁,该锁有打开和关闭两种状态,进程执行临界区程序的 *** 作按以下步骤:
- 关锁。先检查锁的状态,若为关闭状态,则等待其打开,当拿到锁时关闭
- 执行临界区程序
- 开锁。将锁打开,退出临界区
信号。是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。
信号量。信号量是一个特殊的变量,他的本质是计数器,信号量里面记录了临界资源的数目,有多少数目,信号量的值就为多少,进程对其访问都是原子 *** 作(P,V *** 作,P:测试,V:增加)。它的作用就是,协调进程对共享资源的访问,让一个临界区同一时间只有一个进程访问它。
void P(semaphore S){ S.value--; if(S.value < 0){ 把这个进程加到S.list队列; block(); //挂起当前线程 } } void V(semaphore S){ S.value++; if(S.value <= 0){ 从S.list队列移除进程Q; wakeup(Q); //恢复被阻塞的线程Q } }经典进程同步问题
-
生产者-消费者问题
-
在每个程序中必须先做P,后做V,二者要成对出现。夹在二者中间的代码段就是该进程的临界区。
-
对同步信号量full和empty的P,V *** 作同样必须成对出现,但它们分别位于不同的程序中。
-
无论在生产者进程中还是在消费者进程中,两个P *** 作的次序不能颠倒:应先执行同步信号量的P *** 作,然后执行互斥信号量的P *** 作。否则可能会造成死锁。
//生产者进程Producer while(TRUE){ P(empty); P(mutex); 产品送往buffer(in); in = (in + 1) mod N; V(empty); V(mutex); } //消费者进程 while(TRUE){ P(full); P(mutex); 从buffer(out)中取产品; out = (out + 1) mod N; V(mutex); V(empty); }
-
-
读者-写者问题
-
设置两个信号量:读互斥信号量rmutex和写互斥信号量wmutex。另外设立一个读计数器readcount,它是一个整型变量,初值为0。
-
rmutex:用于读者互斥地访问readcount,初值为1.
-
wmutex:用于保证一个写者与其他读者/写者互斥地访问共享资源,初值为1。
//读者 while(TRUE){ P(rmutex); readcount = readcount + 1; if(readcount == 1){ P(wmutex); } V(rmutex); //执行读 *** 作 P(rmutex); readcount = readcount + 1; if(readcount == 0){ V(wmutex); } V(rmutex); //使用读取的数据 } //写者 while(TRUE){ P(wmutex); //执行写 *** 作 V(wmutex); }
-
-
哲学家进餐问题
typedef struct { //定义结构型信号量 int value; struct PCB *list; } semaphore; semaphore chopstick [5] = {{1},{1},{1},{1},{1}}; //信号量数组初始化 semaphore count = {4}; //允许同时拿筷子准备就餐的人数 int I; //第几位哲学家 第I个哲学家进程Process I: while(TRUE){ Think; //哲学家在思考问题 P(count); //试图拿左边筷子 P(chopstick[I]); //试图拿右边筷子 Eat; //进餐 V(chopstick[I]); //左边筷子放回原处 V(chopstick[(I+1) mod 5]); //右边筷子放回原处 V(count); }
-
打瞌睡的理发师问题
-
设立三个信号量:
custmoers:用来记录等候理发的顾客数,初值为0。
barbers:等待顾客的理发师数,初值为0。
mutex:用于对waiting变量的互斥 *** 作。
#define CHAIRS 5 typedef struct { int value; struct PCB *list; } semaphore; semaphore customers = {0}; semaphore barbers = {0}; semaphore mutex = {1}; int waiting = 0; void barber(void){ while(TRUE){ P(customers); //如果没有顾客,理发师打瞌睡 P(mutex); //互斥进入临界区 waiting--; V(barbers); //一个理发师准备理发 V(mutex); //退出临界区 cut hair(); //理发 } } void customer(void){ P(mutex); //互斥进入临界区 if(waiting < CHAIRS){ waiting++; V(customers); V(mutex); P(barbers); get haircut(); }else { V(mutex); } }
-
一个管程定义一个数据结构和能为并发进程在其上执行的一组 *** 作,这组 *** 作能实现进程同步和改变管程中的数据。
管程特性- 管程内部的局部数据变量只能被管程内定义的过程所访问,不能被管程外面声明的过程直接访问。
- 进程要想进入管程,必须调用管程内的某个过程。
- 一次只能有一个进程在管程内执行,而其余调用该管程的进程都被挂起,等待该管程成为可用的。即管程能有效地实现互斥。
进程通信是指进程间的信息交换。各进程在执行过程中为合作完成一项共同任务,需要协调步伐,交流信息。进程间交换的信息量少,少者仅是一个状态或数值,多者可交换成千上万字节的数据。
进程的互斥和同步也是进程通信的一种,只不过由于它交换的信息量太少,所以被归结为低级进程通信。
进程通信的方式-
共享存储器方式
- 共享存储器方式是在内存中分配一片空间作为共享存储区。需要进行通信的各个进程把共享存储区附加到自己的地址空间,然后,就像正常 *** 作一样对共享区中的数据进行读或写。如果用户不需要某个共享存储区,可以把它取消。通过对共享存储区的访问,相关进程间就可以传输大量数据。
-
消息传递方式
-
消息传递方式以消息为单位在进程间进行数据交换。它有如下两种实现方式:
(1)直接通信方式。发送进程直接将消息挂在接收进程的消息缓冲队列上,接收进程从信息缓冲队列中得到消息。
(2)间接通信方式。发送进程将消息送到称作信箱的中间设施中,接收进程从信箱中取得消息。这种通信方式也称信箱通信方式。
-
-
管道文件方式
- 管道文件也称管道线,它是链接两个命令的一个打开文件。一个命令向该文件中写入数据,称作写者;另一个命令从该文件中读取数据,称作读者。
消息传递系统的功能是允许进程彼此进行通信,而不必借助与共享数据。这种方式既可实现进程的同步,又可实现在协作进程间交换信息。另外,消息传递既可以在单CPU系统中实现,又可以在共享内存的多CPU系统中实现。
消息传递系统有多种形式,通常都提供两个原语,即send和receive。它们与信号量相似,而与管程不同。它们是系统调用,而不是编程语言的构造。
在设计消息传送系统时涉及同步、寻址、格式和排队规则等多项问题。
客户-服务器系统的通信- socket
- socket也称套接字或插口,是更为一般的进程通信工具,既使用于同一台计算机上的进程通信,也适用于网络环境下的进程通信。socket好像一条通信线两端的接插口——只要通信双方都有插口,并且中间有通信线路相连,就可以互相通信了。socket是一种低级通信方式,socket只允许在通信线程间交换无结构的字节流,必须由客户或服务器应用程序负责对数据加以构造
- 远程过程调用
- 远程过程调用(RPC)是远程服务的一种最常见的形式。RPC是网络连接系统之间采用的抽象过程调用机制。
- 由于调用者和被调用者运行在不同的机器上,所以它们在不同的地址空间中执行时会出现复杂的情况。为此,RPC要解决如下问题:服务器的启动和功能定位,参数的定义和传送,故障处理,安全问题处理,服务器寻找及系统间数据替换处理等。
socket也称套接字或插口,是更为一般的进程通信工具,既使用于同一台计算机上的进程通信,也适用于网络环境下的进程通信。socket好像一条通信线两端的接插口——只要通信双方都有插口,并且中间有通信线路相连,就可以互相通信了。socket是一种低级通信方式,socket只允许在通信线程间交换无结构的字节流,必须由客户或服务器应用程序负责对数据加以构造
- 远程过程调用
- 远程过程调用(RPC)是远程服务的一种最常见的形式。RPC是网络连接系统之间采用的抽象过程调用机制。
- 由于调用者和被调用者运行在不同的机器上,所以它们在不同的地址空间中执行时会出现复杂的情况。为此,RPC要解决如下问题:服务器的启动和功能定位,参数的定义和传送,故障处理,安全问题处理,服务器寻找及系统间数据替换处理等。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)