一、进程描述
📖 1、进程的定义:
一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程
📖 2、进程的组成:【包含了一个正在运行的一个程序的所有状态信息】
- 程序的代码
- 程序处理的数据
- 程序计数器中的值,指向下一条将运行的指令
- 一组通用寄存器的当前值,堆、栈
- 一组系统资源(如打开的文件)
📖 3、进程与程序的联系:【多对多】
- 程序是产生进程的基础
- 程序每次运行可能构成不同的进程【处理的数据可能不一样】
- 进程是程序功能的体现【功能都是一致的】
- 通过多次执行 >> 一个程序可以对应多个进程;通过多次调用 >> 一个进程可以包含多个程序
📖 4、进程与程序的区别:
- 程序是静态的,进程是动态的【程序是有序代码的集合,进程是程序的执行,分为核心态、用户态】
- 程序是永久的,进程是暂时的【程序可以长期保存,进程是一个状态变化的过程】
- 程序与进程的组成不同【进程由程序 + 数据 + 进程控制块(进程状态控制信息PCB)组成】
- 案例分析:
📖 5、进程的特点:
-
动态性:可以动态的创建、结束进程
-
并发性:进程可以被独立调用并占用处理机运行【并发:一段时间内多个进程、并行:同一时刻多个进程(需要多个CPU)】
-
独立性:不同进程的工作不互相影响【操作系统为会不同的进程分配不同的页表】
-
制约性:因访问共享数据/资源或进程间同步而产生制约
📖 6、进程的控制结构:
操作系统为每个进程都维护了一个PCB,用来保存与该进程有关的各种状态信息。
那么什么是进程控制块呢?
- 操作系统管理控制进程运行所用的信息集合。
- 操作系统用PCB来描述进程的基本情况以及运行变化的过程
- PCB(进程控制块)是进程存在的唯一标志
- 通过对PCB的组织管理来实现进程的组织管理【创建进程就会为其分配一块PCB,释放进程就会回收其PCB】
📖 7、PCB包含哪些信息?
(1)进程标识信息
父进程标识、本进程标识、用户标识
(2)处理机状态信息保存区【保存进程的运行现场信息】
-
用户可见寄存器【程序可以使用的数据、地址寄存器等】
-
控制和状态寄存器【程序计数器(PC)、程序状态字(PSW)等】
-
栈指针【过程调用/系统调用/中断处理和返回时需要使用】
(3)进程控制信息【对进程的管理】
📖 8、PCB的组织方式:【如果进程处于稳定状态就选择索引表、如果经常变换就选择链表】
- 链表:同一状态的进程其PCB成一链表,多个状态对应多个不同的链表【就绪链表、阻塞链表等】
- 索引表:同一状态的进程归入一个index表(由index指向PCB),多个状态对应多个不同的index表【就绪索引表、阻塞索引表】
二、进程状态
$ 进程的生命期管理
- 进程产生到结束的整个过程为进程的生命周期
❤️ 1、什么情况下会去创建进程?【3个主要事件】
系统初始化、用户请求、正在运行的进程执行力创建进程的系统调用
❤️ 2、进程创建后就能运行吗?
实则不然,内核会选择一个就绪的进程,让它占用处理机并执行【当进程创建后获得运行的必备资源后就会处于就绪状态】
❤️ 3、什么情况下进程会暂停下来?【自身阻塞自己,阻塞状态】
- 请求并等待系统服务,无法马上完成
- 启动某种操作,无法马上完成
- 需要的数据没有到达
❤️ 4、什么情况下被阻塞的进程会被唤醒?【被其他的进程或操作系统唤醒】
- 被阻塞进程需要的资源可被满足
- 被阻塞进程等待的事件到达
- 将该进程的PCB插入到就绪队列
❤️ 5、进程结束的原因?
- 正常退出(自愿)
- 错误退出(自愿)
- 致命错误(强制)
- 被其他进程杀死(强制)
$ 进程变化模型
💚 6、进程有哪几种基本状态:【不同操作系统设置的进程状态数目不同】
- 就绪状态:一个进程获得了除处理机之外的一切所需自愿。一旦获得处理机即可执行
- 运行状态:当一个进程正在处理机上运行时
- 等待状态(阻塞状态):一个进程正在等待某一事件而暂停运行时。
-
进程还具有两种其他基本状态:
(1)创建状态:一个进程正在被创建,还没有被转到就绪状态之前的状态
(2)结束状态:一个进程正在从系统中消失时的状态,这是因为进程结束或由其他原因所导致的
从创建到就绪状态不会很久;
时间片结束让出处理机由操作系统来完成;
阻塞的进程等到了需要的事件
$ 进程挂起模型
💙 7、什么叫进程挂起?【把一个进程从内存转到外存的过程为挂起】
-
进程没有占用内存空间,我们称之为进程挂起,处于挂起状态的进程映像存储在磁盘上
-
挂起状态分为两类:
(1)阻塞挂起状态:进程在外存并等待某事件的出现
(2)就绪挂起状态:进程在外存,但是只要进入内存,即可运行
-
与进程挂起相关的进程状态转换:
- 挂起进程的激活/解挂:把一个进程从外存转到内存
(1)就绪挂起到就绪:没有就绪进程或挂起就绪进程的优先级高于就绪进程时
(2)阻塞挂起到阻塞:当进程释放足够内存时,系统会把一个高优先级阻塞挂起进程转化为阻塞进程
- 问题:OS怎么通过PCB和定义的进程状态来管理PCB,帮 助完成进程的调度过程?
💚 8、如何来维护不同状态的进程控制块?
- 操作系统通过一组队列来表示系统中所有进程的当前状态
- 不同的状态对应不同的状态队列
- 每个进程的PCB根据自身的状态加入到相应的队列中
三、线程
- 线程的定义:比进程还小的、可以独立运行的基本单位
🌔 1、为什么使用线程?【通过案例来分析】
- 使用一个进程来实现:
main(){
while(TRUE){
Read();
Decompress();
Play();
}
}
// 三个函数分别对应上述的三个核心模块
Read(){
pass;
}
Decompress(){
pass;
}
Play(){
pass;
}
- 采用上述实现方法可能会出现一些问题:
(1)播放出来的声音可能不连贯【在读文件时速度很慢】
(2)各个函数之间不能并发执行,影响资源的使用效率
- 改用多进程的方法实现:
采用多进程也会存在一些问题:
(1)进程间如何通信、共享数据?
(2)维护进程的系统开销比较大
- 为了解决上述的这些问题,提出了一种新的实体:【需要具有以下两个特点】
(1)实体之间可以并发的执行
(2)实体之间共享相同的地址空间
🌔 2、什么是线程?
- 定义:进程当中的一条执行流程
- 有了线程那么如何表述进程呢?
(1)从资源组合角度
进程把一组相关的资源组合起来,构成一个资源平台,包括地址空间、打开的文件等
(2)从运行的角度
代码在这个资源平台上的一条执行流程(线程)
🌔 3、线程的优缺点:
(1)优点
- 一个进程可以同时存在多个线程
- 各个线程之间可以并发的执行
- 各个线程之间可以共享地址空间和文件等资源
(2)缺点
一个线程崩溃,会导致其所属进程的所有线程崩溃
🌔 4、不同操作系统对线程的支持情况不同:
早起MS-DOS为单进程单线程
到Unix时已经为多进程单线程
现在Windows NT 已经支持多进程多线程
🌔 5、对于一个进程中的多个线程所需的资源:
- 分为独享资源和共享资源
- 要有各自的寄存器、堆栈和控制流
🌔 6、线程与进程的比较:
- 进程是资源分配单位,线程是CPU调度单位
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈
- 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间转换关系
- 线程能减少并发执行的时间和空间开销:
(1)线程的创建时间比进程短,线程的终止时间比进程短
(2)同一进程内的线程切换时间比进程短
(3)由于同一进程的各线程间共享内存和文件资源。可直接进行不通过内核的通信
🌔 7、线程是如何实现的呢?
- 用户线程:在用户空间实现【操作系统看不到,用户线程库来完成统一的管理】
- 内核线程:在内核中实现【操作系统管理起来的线程】
- 轻量级线程:在内核中实现,支持用户线程
- 用户线程与内核线程之间的对应关系可以为:一对一、一对多、多对多
🌔 8、用户线程:
在用户空间实现的线程机制,由一组用户级的线程库函数来完成线程的管理。
-
用户线程的维护由相应进程通过线程库函数来完成
-
每个线程都要有一个私有的线程控制块(TCB),用来记录线程的状态信息,TCB也有线程库函数来维护
-
用户线程的切换也是由线程库函数来完成
-
允许每个进程拥有自定义的线程调度算法
-
用户线程的缺点:
(1)如果一个线程发起系统调用而阻塞,则整个进程在等待
(2)当一个线程开始运行后,除非它主动的交出CPU的使用权,否则它所在的进程当中的其他线程将无法运行
(3)犹豫时间片分配给进程,故与其它进程比,在多线程执行时,每个线程得到的时间片较少,执行会比较慢
🌔 9、内核线程【TCB存储在内核中】
- 在支持内核线程的操作系统中,由内核来维护进程和线程的上下文信息(PCB和TCB)
- 因为线程的创建、终止、切换都有系统调用或内核函数来完成,所以系统开销比较大
- 在一个进程中,如果某个内核线程发起系统调用而被阻塞,并不会影响其他内核线程的运行
- 时间片分配给线程,多线程的进程获得更多的CPU时间
- Windows NT 和 Windows 2000/XP 支持内核线程
🌔 10、轻量级进程【内核支持的用户线程】
一个进程可以有一个或多个轻量级进程,每个量级进程由一个单独的内核线程来支持。
🌔 11、进程的上下文切换:
- 什么是上下文切换?
停止当前运行进程(运行态 >> 其他态)并且调度其他进程(转变为运行态)
- 上下文切换有哪些要求?
(1)必须在切换之前存储许多部分的进程上下文
(2)能够在之后恢复它们,所以进程不能显示它曾经被暂停过
(3)必须快速
- 通过什么来存储上下文:寄存器
四、进程控制
$ 创建进程
🐱 1、利用fork和exec创建一个新的进程【执行一个新的映像】
int pid = fork();
if(pid == 0){
exec("program", argc, argv0, ...);
}
(1)fork系统调用会返回两个进程,一个为要创建的进程,一个为其父进程
子进程返回值为0,父进程返回的为子进程id(PID)【内存和CPU状态是完全复制的】
(2)exec可以加载一个指定程序,并给出它的参数列表
(3)fork地址空间的复制过程
$ 加载和执行进程
🐱 2、系统调用exec()加载新程序取代当前运行进程
main()
...
int pid = fork(); //创建新进程
if(pid == 0){
//子进程
exec_status = exec("calc", argv0, argv1, ...)
printf("Why would I execute?");
}else{
//父进程
printf(Whose your daddy?"");
...
child_status = wait(pid);
}
if(pid < 0){//Error}
- 如果exec执行成功,那么子进程中的代码全部被替换,包括文件地址,所以不会执行printf部分的代码
wait(pid)
的作用是等待子进程结束- Exec() 调用允许一个进程“加载”一个不同的程序并且在main开始执行
- 运行为要调用的进程传递一个参数列表,虽然进程被替换掉了,但是线程还是原来的那个线程
- 如果exec调用成功,那么代码、栈、堆都会被替换
🐱 3、优化fork的无效内存复制:
$ 等待和终止进程
🐱 1、为什么要通过wait系统调用让父进程去等待子进程结束?
子进程通过exit来退出释放资源时,进程控制块不会被释放掉,所以需要父进程去帮助子进程完成所有资源的回收
🐱 2、wait系统调用的作用:
- 子进程向父进程返回一个值,所以父进程必须接受这个值并处理
- 当子进程调用exit()的时候,操作系统就会解锁父进程,并将exit()传递得到的返回值作为wait调用的一个结果
- 也存在两种其他情况:
(1)执行wait时,没有子进程处于存活状态,wait()立刻返回
(2)如果系统检测到了父进程处于僵尸等待,wait(0立刻返回其中一个值来解除僵尸等待
🐱 3、进程的状态变换: