一、进程与线程
1. 进程
程序:是静态的,就是个存放在磁盘里的可执行文件,就是一系列的指令集合。
进程(Process):是指计算机中已执行的程序,是动态的。
(1)进程的组成
当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的编号,即进程ID(Process ID,PID),操作系统此时要记录PID。除此之外,操作系统还需要记录进程所属用户ID(UID)、给进程分配的资源、进程的运行情况等。
这些信息都被保存在一个数据结构 进程控制块(Process Control Block,PCB ) 中,操作系统需要对各个并发运行的进程进行管理,但凡管理时所需要的信息,都会被放在PCB中。
PCB是进程存在的唯一标志,当进程被创建时,操作系统为其创建PCB,当进程结束时,会回收其PCB。
除了PCB,进程还需要程序段来存放程序的代码,并使用 数据段 保存进程执行时产生的数据(如程序中定义的变量)。
一个进程实体(进程映像)由PCB、程序段、数据段组成。其中程序段用来存放程序的代码(指令), 数据段 用来保存进程执行时产生的数据。
进程是动态的,进程实体(进程映像)是静态的。进程实体反应了进程在某一时刻的状态(如:x++后,x=2)
程序运行示意图:
(2)进程的特征
程序是静态的,进程是动态的,相比于程序,进程拥有以下特征。
-
动态性:进程是程序的一次执行,经历创建、活动、暂停、终止等过程,具有一定的生命周期,是动态产生、变化和消亡的基本特征。
-
并发性:多个进程实体能够同时存在于内存中,在一段时间内同时运行,提高资源利用率,并发性是进程的重要特征,也是操作系统的关键特征。
-
独立性:进程实体是一个能够独立运行、独立获取资源和独立接受调度的基本单位。未建立PCB的程序无法作为独立单位参与运行。
-
异步性:由于进程相互制约,导致执行间断性,进程按照各自独立且不可预知的速度向前推进,需配置进程同步机制以处理异步性带来的挑战。
-
结构性:每个进程通过PCB进行描述,包含程序段、数据段和进程控制块三部分组成。这种结构性有助于管理和控制进程的执行和状态转换。
(3)多个进程之间的PCB组织方式
在一个系统中,通常会存在成百上千个PCB。为了有效管理它们,需要采取适当的方式来组织这些PCB。
进程的PCB组织方式包括链接方式和索引方式。
-
链接方式:在链接方式中,每个进程的PCB包含一个指向下一个PCB的指针,形成一个链表结构。当系统需要访问某个特定进程的PCB时,可以顺着链表依次查找直到找到目标PCB。这种方式适合于需要频繁插入和删除进程的情况,但可能会增加查找时间。
-
索引方式:在索引方式中,系统维护一个索引表,其中记录了所有进程PCB的位置信息或关键字,通过索引表可以直接查找到对应进程的PCB。这种方式可以提高查找效率,适合于需要快速访问进程PCB的情况。
选择使用链接方式还是索引方式取决于系统的需求和设计考虑,不同的方式各有优劣,可以根据具体情况进行选择。
2. 进程的状态
(1)进程的五种状态
进程在操作系统中通常有五种状态,包括:
-
创建态(new):进程被创建时处于“创建态”状态,操作系统为其分配资源并初始化PCB,为进程的正常运行做准备。
-
就绪态(Ready):创建完成后,进程进入“就绪态”,表示它已经准备好运行,但由于没有空闲CPU,暂时无法执行。
-
运行态(Running):当进程获得CPU时间并开始执行对应的程序指令时,处于“运行态”,CPU会按照指令序列执行该进程。
-
阻塞态(Blocked / Waiting):在执行过程中,进程可能因等待某事件发生而无法继续执行,此时进程会进入“阻塞态”,操作系统会让其释放CPU,并等待事件发生。当CPU空闲时,操作系统会选择另一个“就绪态”进程继续执行。
-
终止态(Terminated):进程通过调用exit系统调用请求终止时,进入“终止态”,操作系统回收资源、释放内存空间等,并最终销毁该进程。一旦终止完成,进程从系统中彻底消失。
其中就绪态(Ready)、运行态(Running)、阻塞态(Blocked)为进程的三种基本状态。
(2)进程状态的转换
下面是对进程状态转换的简要描述:
-
NULL → 新建态:执行一个程序,创建一个子进程,进程从NULL状态转变为新建态。
-
新建态 → 就绪态:当操作系统完成了进程创建的必要操作,并且系统性能和虚拟内存容量允许时,进程从新建态转变为就绪态,表示已准备好运行但暂时无法执行。
-
运行态 → 终止态:当进程达到自然结束点、出现无法解决的错误、被操作系统或其他有终止权的进程终结时,进程从运行态转为终止态。
-
运行态 → 就绪态:当进程的运行时间片用完或出现更高优先级的进程时,进程从运行态转为就绪态等待重新调度。
-
运行态 → 等待态:进程等待使用资源(如外设传输),需要等待人工干预或其他事件发生时,进程从运行态转为等待态。
-
就绪态 → 终止态:在某些操作系统中,未在状态图中显示,但父进程可能允许终结子进程,此时进程从就绪态直接转为终止态。
-
等待态 → 终止态:和上一条类似,在某些操作系统中,虽未在状态图中显示,但允许父进程终结子进程,进程从等待态直接转为终止态。
-
终止态 → NULL:进程完成所有清理工作后,从终止态返回到NULL状态,整个进程生命周期结束。
3.进程的控制
进程控制的主要功能是对系统中所有进程进行有效管理,包括创建新进程、撤销已有进程和实现进程状态转换等。操作系统中通常将用于进程控制的程序段称为原语,其特点是在执行期间不允许中断,是一个不可分割的基本单位。原语的不可中断性确保了进程控制操作的完整性和一致性,防止出现竞争条件和数据不一致的情况,从而提高了系统的稳定性和可靠性。
进程的创建、终止、阻塞和唤醒是进程控制中的重要过程,它们包括以下步骤:
-
创建进程:
- 为新进程分配一个唯一的进程标识号,并为进程分配空白的PCB,并填写控制和管理信息,若PCB申请失败,则进程创建失败。
- 为进程分配资源,为新进程的程序和数据及用户栈分配必要的内存空间(在PCB 中体现)。若资源不足(如内存空间),则处于阻塞态,等待内存资源;
- 将PCB插入就绪队列,等待调度执行。
-
终止进程:
- 查找要终止的进程的PCB。
- 如果进程正在执行,立即终止其执行,并释放CPU资源。
- 如果进程有子进程,将子进程交给父进程或由1号进程(init)接管。
- 归还进程拥有的资源给操作系统,从PCB队列中删除该进程。
-
阻塞进程:
- 找到需要阻塞的进程的标识对应的PCB。
- 如果进程正在运行,保护其现场,将状态转换为阻塞状态,暂停运行。
- 将PCB插入阻塞队列中。
-
唤醒进程:
- 在阻塞队列中找到相应事件的阻塞进程的PCB。
- 将其移出阻塞队列,并设置为就绪状态。
- 将PCB插入就绪队列,等待调度程序调度运行。
进程的阻塞和唤醒是进程控制中重要的操作,确保进程间的合理执行和资源利用。进行这些操作时,需要保证相应的阻塞和唤醒语句成对出现,以确保进程状态转换的正确性。
4.进程的通信
- 管道(Pipes):是⼀种半双⼯的通信⽅式,数据只能单向流动,用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
- 共享内存(Shared memory):使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
- 消息队列(Message Queuing):消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载⽆格式字节流以及缓冲区⼤⼩受限等缺点。
- 套接字(Sockets):适⽤于不同机器间进程通信,在本地也可作为两个进程通信的⽅式。
- 信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 信号量(Semaphores):信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
5.线程
线程(Thread)通常被称为轻量级进程,因为线程是在进程内部创建和管理的,相较于整个进程来说,线程具有更小的开销和更快的上下文切换速度。线程是一个基本的CPU执行单元,也是程序执行流的最小单位。多个线程可以在同一个进程内并发执行,并且它们共享该进程的资源,如内存空间、文件句柄、网络连接等。
举例来说,当你打开微信时,可能会有多个不同的线程在后台同时执行不同的任务。例如,存在一个专门负责拉取别人给你发送的最新消息的线程,该线程定期检查服务器是否有新消息,并将其显示在你的微信聊天界面上。同时,还可能有其他线程负责处理用户的输入、更新UI界面等任务。
引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
二、进程调度
1. 调度
(1)调度的概念
进程都期望能够占用CPU进行工作,当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理,这些任务的顺序,这就是“调度”研究的问题。
这种选择进程并让其运行的功能是由操作系统中的调度程序(scheduler)完成的。调度程序负责根据一定的调度算法,决定在给定时刻哪个进程应该优先获得CPU的使用权,从而实现对系统资源的合理调度和分配。
通过调度程序的工作,操作系统能够有效地管理系统中多个进程的执行顺序和时间片分配,以确保系统资源的高效利用和任务的及时完成。不同的调度算法可能导致不同的进程调度方式,如先来先服务、最短作业优先、轮转调度等。
(2)调度的三个层次
- 高级调度(作业调度):按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次,调出一次。作业调入时会建立PCB,调出时才撤销PCB。
- 中级调度(内存调度):按照某种策略决定将哪个处于挂起状态的进程重新调入内存。一个进程可能会被多次调出、调入内存,因此中级调度发生的频率要比高级调度更高。
- 低级调度(进程调度/处理机调度):按照某种策略从就绪队列中选取一个进程,将处理机分配
给它。
调度方法 | 调度发生位置 | 发生频率 | 进程状态 | |
---|---|---|---|---|
高级调度(作业调度) | 按照某种规则,从后备队列中选择合适的作业将其调入内存,并为其创建进程 | 外存->内存(面向作业) | 最低 | 无->创建态->就绪态 |
中级调度(内存调度) | 按照某种规则,从挂起队列中选择合适的进程将其数据调回内存 | 外存->内存(面向进程) | 中等 | 挂起态->就绪态(阻塞挂起->阻塞态) |
低级调度(进程调度) | 按照某种规则,从就绪队列中选择一个进程为其分配处理机 | 内存->CPU | 最高 | 就绪态->运行态 |
2. 调度算法
- 先来先服务调度算法(First Come First Severd, FCFS):先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。
- 最短作业优先调度算法(Shortest Job First, SJF):它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
- 高响应比优先调度算法(Highest Response Ratio Next, HRRN):主要是权衡了短作业和长作业。每次进行进程调度时,先计算“响应比优先级”,然后把“响应比优先级”最高的进程投入运行。
- 时间片轮转调度算法(Round Robin, RR):每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。
- 最高优先级调度算法(Highest Priority First,HPF):调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行
- 多级反馈队列调度算法(Multilevel Feedback Queue):“时间片轮转算法”和“最高优先级算法”的综合和发展。