进程状态
进程的状态有很多种模型作为参考。比较经典的是Linux的进程状态模型(笔记[[Linux 内核#Linux进程状态]]),下面是通用的进程状态模型作为参考。
三态模型
正在运行的进程一共有三种状态,分别是运行(running)态、就绪(ready)态、等待(wait)态。
就绪队列是操作系统中的一个存放就绪态进程的一个数据结构,它只是名字是队列,但是其实际的数据结构并不一定是队列,而有可能是链表或者红黑树等等。
运行态到就绪态的条件是进程的时间片用完,而到等待态是出现等待事件,这两者有什么区别呢?主要就在于资源的使用,进程会使用硬件资源:
- 如果硬件资源的使用允许的话:触发时间片轮转机制。在运行和就绪来回切换。
- 如果硬件资源在一时刻需要紧急被其他进程使用:触发阻塞,也就是等待。
五态模型
五态模型相比于三态模型增加了一个新的终止态和新建态,相对于三态模型更加全面了一些。进程在某些情况下确实会被终止,例如触发了系统的异常等等。
七态模型
进程的七态模型在三态和五态的基础上添加了进程的“挂起态”,挂起(Suspend)的本质是:“让进程不再运行”,进程经过挂起操作之后的状态又可以分为两种:
- 挂起就绪(静止就绪):具备运行条件,但是目前存在外存中。
- 挂起等待(静止阻塞):表明进程正在等待某一个事件发生且在外存中。
可以看出,挂起就绪和阻塞就绪的两个根本区别就是在于进程是否是长期挂起还是短期挂起。
状态转换
操作系统提供了一组操作进程的API用于切换进程的状态, 在计算机系统中,某些事件发生的情况下,将会自动触发这些API,导致进程状态的切换:
事件 | 原状态 | 新状态 |
---|---|---|
设备忙 | 运行中 | 阻塞(等待) |
被0除 | 运行中 | 终止态 |
对只读内存执行写操作 | 运行中 | 终止态 |
堆栈溢出 | 运行中 | 终止态 |
时间片用完 | 运行态 | 就绪态 |
需要的数据没有准备好 | 运行中 | 阻塞 |
等待外部I/O操作或进程同步 | 运行中 | 阻塞 |
参考资料
进程操作
创建进程
操作系统使用相应的系统调用创建一个进程,在Unix系统下是使用fork()
系统调用,创建一个新进程的流程如下:
- 申请一块空白的进程控制块PCB。
- 为新的进程分配资源。
- 初始化进程控制块PCB。
- 如果就绪队列能够容纳新的进程,将新的进程插入就绪队列。
fork()
是一种系统调用,是操作系统提供给用户的程序接口,fork()
的规则是:
若成功调用一次返回两个值,子进程返回0,父进程返回子进程标记。否则出错返回-1.
并发与并行
并发(Concurrency)和并行(Parallelism)是计算科学领域中常用的概念,它们描述了多个任务同时执行的方式,但它们具有不同的含义和特点:
- 并发: 并发指的是多个任务在同一时间段内进行交替执行。这些任务可以在同一个处理器上使用时间片轮转算法来切换执行,或者在多个处理器上并行执行。在并发中,任务之间可能是相互独立且不需要交互的,它们通过分时操作来共享系统资源。
- 并行: 并行指的是多个任务同时执行,每个任务在不同的处理器(核)上独立运行。这些任务可以并行地执行各自的指令,从而加快整体的处理速度。并行在硬件层面上利用多核处理器、多线程、多机集群等来实现任务的同时执行。
因此,尽管并发和并行都涉及多个任务的执行,在计算机中有明显的区别:并发透过资源共享和任务切换来提高效率,而并行通过同时执行多个任务来提高处理能力。 简单来说,可以将并发视为任务处理的组织方式,而并行则是任务执行的加速方式。并发通过任务切换和资源共享来提升效率,而并行利用多个处理单元同时执行任务来提升整体性能。
注意,并发和并行是针对于CPU而言的,程序运行于CPU之上。
并发的特性
所谓程序并发执行是指两个或两个以上程序在计算机系统中同处于已开始执行且尚未结束的状态。程序并发执行产生了一些和程序顺序执行不同的特性:
- 并发程序再执行期间具有相互制约的关系。
- 程序与计算不在一一对应。
- 并发程序执行结果不可再现。
- 并发程序的执行过程失去了封闭性。
封闭性是指程序运行结果只取决于本身,不受外界影响。
并发性失去封闭性:我的理解是一个程序当中可以创建多个进程,父子进程当中,父子进程并发运行会相互影响,具体可以参照Linux系统的folk函数的特性
进程同步
进程同步是指在多个并发执行的进程之间,通过一定的机制来确保它们按照一定的顺序执行或者协调彼此的操作,使他们有效地共享资源和相互合作。
信号量
PV操作分为P操作和V操作,是针对信号量这一特殊的变量进行的操作,简单来说,P,V操作的定义如下,假设信号量为S:
- P操作:令S自减1,即
S=S-1;
如果结果S>0,则进程继续运行。否则进程进入等待状态。一般在进入区使用。 - V操作:令S自增1,即
S=S+1
。一般在退出区使用。
每个进程都拥有一个属于自己的信号量。信号量可以同时实现进程互斥与进程同步的问题。当一个线程需要访问资源或执行操作时,它会试图减少信号量的计数。如果计数大于零,那么线程可以继续执行,并且信号量的计数减一。如果计数已经为零,那么线程将被阻塞,直到其他线程释放资源,使信号量的计数增加。
进程互斥
在操作系统中,临界资源指的是:在同一时间只允许一个进程访问的区域或者设备。例如打印机,摄像头还有许多变量、数据、内存缓冲区都属于临界资源,它们只能由一个进程进行访问。临界资源的访问离不开进程互斥这一概念与方法。
如果出现了两个或两个以上的进程对临界资源的访问,必然导致冲突。因此要避免这种情况,就要求进程之间要“互斥地”访问临界资源,
对临界资源的互斥访问,可以在逻辑上分为四个部分:
do{
entry section; //进入区(负责检查是否可进入临界区,若可,则应设置正坐在访问临界资源标志(可理解为‘上锁’),以阻止其它进程同时进入临界区)
critical section; //临界区(访问临界区的那段代码)
exit section; //退出区(负责解除正在访问临界区资源的标志(可理解为‘解锁’))
remainder section; //剩余区(做其它处理)
} while(true)
可以看出,进入区是访问临界资源的这个进程中正在访问临界资源的代码段,而进入区退出区:
- 进入区:进程中负责实现上锁的代码段。
- 退出区:进程中负责实现解锁的代码段。
锁
锁只能用于解决进程互斥的问题,可以说锁是一种特殊的信号量,它的计数器只能是 0 或 1。当计数器为 1 时,表示资源可用;当计数器为 0 时,表示资源被占用。但信号量的概念要比锁更为广泛,信号量可以有大于 1 的计数器,表示允许多个线程同时访问资源或执行操作。
进程通信IPC
进程间通信(Inter-Process Communication,IPC)是操作系统提供的一种机制,允许在运行的进程之间共享信息和数据。这种能力在多任务环境中尤其重要,因为在这种环境中,多个进程可能需要协同工作,共享资源,或者同步操作。 以下是为什么需要进程间通信的一些原因:
- 数据共享:多个进程可能需要访问和修改同一组数据。例如,一个数据库服务器可能有多个客户端进程,这些进程需要查询和修改数据库中的数据。
- 任务协作:大型任务可能需要被分割成几个子任务,这些子任务在各自的进程中运行,然后共享结果。例如,一个大型科学计算任务可能被分割为多个子任务,分布在多个处理器上运行。
- 同步和协调:在多任务环境中,一个进程可能需要等待另一个进程完成某个操作。例如,一个进程可能需要等待另一个进程完成文件写操作,然后才能开始文件读操作。
共享内存
共享内存即以共享缓冲区的方式进行通信,可以同步也可以异步。
消息机制
消息缓冲通信机制包括:
- 消息缓冲区
- 消息队列首地址
- 同步互斥信号量、发送接收消息原语
管道通信
管道通信是指两个进程,一方进程发送数据,另一方进程只能接收。这种方式只能同步。
套接字
PCB进程控制块
PCB进程控制块是每个进程的唯一标识,用于存储关于进程的一些信息,PCB的内容可以被分为两部分:
调度信息和现场信息的区别主要如下:
- 调度信息主要用来控制进程的调度顺序和策略
- 现场信息则用来保存和恢复进程的执行状态。
用户无法轻易读取进程控制块,只能由操作系统进行访问。
组织方式
进程控制块拥有三种组织方式:
- 线性方式
- 索引方式
撤销进程
撤销进程的实质是撤销PCB,当PCB消亡的时候,进程也随之消亡。具体过程:
- 找到要被撤销进程的PCB
- 将它从所在的队列中除去
- 撤销属于该进程的一切“子孙进程”
- 释放被撤销讲程所占用的的全部资源
- 消去被撤销进程的PCB。
线程管理
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈。没有内存!!!
- 进程是资源分配的最小单位,拥有自己的独立空间地址。
- 线程是进程独立调度的最小单位,不拥有资源,可以访问进程的资源,但是线程拥有自己的堆栈和局部变量。
其实线程的栈、程序计数器、本地方法区并不是真的寄存器,而是也是属于存放在进程的地址空间上去的,只能由某个特定的线程去访问。
执行流
线程对应了进程中的一个执行流,它与进程的关系如下
既然作为一个执行流,那么每个线程就应该拥有一个程序计数器来记录当前程序执行的位置,还要有堆栈保存相关变量。
线程间通信
从最根本的角度来说,只有进程才需要进行通信,因为线程之间是共享地址空间的,线程需要做的是:保护好全局变量等共享信息即可。因此有以下几种常用机制:
互斥锁
参考资料
线程操作
进程是由线程组成,那么应该有一个主线程和多个子线程组成。
join方法
如果在主线程中不使用join
方法等待子线程结束,主线程可能会在子线程执行完之前就终止。这会导致子线程没有完成任务,从而产生意外的结果。
在示例代码中,如果不使用join
方法等待子线程结束,主线程可能会先退出,而子线程还没有执行完毕。这时打印最终的共享变量值可能是不确定的,因为子线程可能还在修改共享变量。
要保证主线程等待所有子线程执行完毕,可以在创建子线程后,使用join
方法等待子线程结束。join
方法会阻塞主线程,直到被调用的线程执行完毕。
调度算法
调度算法决定了系统的响应时间,进程的调度机制,调度算法是跟页面置换算法是不一样的,页面置换管理的是内存,而调度算法管理的是进程中的线程。
严格来说:
- 进程:是资源分配的最小单位。
- 线程:是程序调度的最小单位。
轮转法
在时间片轮转法中,当时间片结束时,就强迫运行的进程让出CPU,该进程进入就绪队列,等待下一次调度。
轮转法也是一种比较公平的算法。
多级反馈队列算法
多级反馈队列算法属于一种比较综合考虑因素的算法。
先来先服务算法
先来先服务算法属于一种比较公平的算法。
假设在单道批处理操作系统中,现有A,B,C,D四个任务同时到达,每个任务的时间分别为8,4,4,4,那么平均周转时间为?
最公平性
先来先服务应当是所有进程管理中最公平的算法,其原理是:
根据进程申请的顺序,来决定先调度哪个进程。
参考资料
进程的加载
程序是存储在外存中的,而CPU不能直接访问外存,或者说访问外存(永久性存储介质)的速度过慢,因此从"程序"变到"进程",这一过程需要由"外存"到"内存"。
进程从一开始被选中执行的时候,是部分装入内存的,这是因为页式存储管理技术,导致了内存碎片化管理的方式。