
🎉作者简介:👓 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢 c + + , g o , p y t h o n , 目前熟悉 c + + , g o 语言,数据库,网络编程,了解分布式等相关内容 \textcolor{orange}{博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容} 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容
📃 个人主页: \textcolor{gray}{个人主页:} 个人主页: 小呆鸟_coding
🔎 支持 : \textcolor{gray}{支持:} 支持: 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦 \textcolor{green}{如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦} 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦👍 就是给予我最大的支持! \textcolor{green}{就是给予我最大的支持!} 就是给予我最大的支持!🎁
💛本文摘要💛
本专栏主要是讲解操作系统的相关知识 本文主要讲解 进程管理管理
清华操作系统系列文章:可面试可复习
1. 操作系统—概述
2. 操作系统—中断、异常、系统调用
3. 操作系统—物理内存管理
4. 操作系统—非连续内存分配
5. 虚拟内存管理
6. 操作系统—虚拟内存管理技术页面置换算法
7. 进程管理
8. 调度算法
9. 同步与互斥
10. 信号量和管程
11. 死锁和进程通信
12. 文件系统管理
进程:一个具有一定独立功能的程序在一个数据集合上的一次动态的执行过程
进程包括 :
进程和程序的联系 :
进程和程序的区别 :
进程是动态的, 程序是静态的 : 程序是有序代码的集合. 进程是程序的执行, 进程有核心态 / 用户态.类比
进程在进行过程中有可能被切换,CPU在执行的过程中会动态切换不同的进程,来实现不同的功能(进程的动态)
动态性 : 可动态地创建, 结果进程;并发性 : 进程可以被独立调度并占用处理机运行; (并发:一段, 并行:一时刻)独立性 : 不同进程的工作不相互影响;(页表是保障措施之一)制约性 : 因访问共享数据, 资源或进程间同步而产生制约.
如果你要设计一个OS, 怎么样来实现其中的进程管理机制?
进程控制结构

进程与PCB是一对一的关系,进程消失则PCB也消失,创建一个进程,必然有会创建唯一PCB与之对应
进程的创建 : 为该进程生成一个PCB
进程的终止 : 回收它的PCB
进程的组织管理 : 通过对PCB的组织管理来实现
PCB具体包含什么信息? 如何组织的? 进程的状态转换?
(因为内存中可能存在多个进程,需要有效的组织起来,我们希望PCB可以描述进程的状态变化)
PCB含有以下三大类信息 :
(一)进程标志信息: 如本进程的标志, 本进程的产生者标志(父进程标志). 用户标志(二)处理机状态信息保存区 : 保存进程的运行现场信息 :
用户可见寄存器. 用户程序可以使用的数据, 地址等寄存器控制和状态寄存器: 如程序计数器(PC), 程序状态字(PSW)栈指针: 过程调用, 系统调用, 中断处理和返回时需要用到它(三)进程控制信息(前面俩个是执行的状态表示)
调度和状态信息. 用于操作系统调度进程并占用处理机使用.进程间通信信息. 为支持进程间与通信相关的各种标志, 信号, 信件等, 这些信息都存在接收方的进程控制块中.存储管理信息. 包含有指向本进程映像存储空间的数据结构.(进程的的内存管理)进程所用资源. 说明由进程打开, 使用的系统资源. 如打开的文件等.(一个进程可能打开多个不同文件)有关数据结构的链接信息. 进程可以连接到一个进程队列中, 或连接到相关的其他进程的PCB.(进程之间有关系,例如父进程,子进程,A创建C,A创建B,此时A是B和C的父进程,可以通过链表管理)PCB的组织方式

进程创建
引起进程创建的3个主要事件 :
系统初始化;用户请求创建一个新进程;正在运行的进程执行了创建进程的系统调用.
进程运行
内核选择一个就绪的进程, 让它占用处理机并执行

进程等待
进程只能自己阻塞自己, 因为只有进程自身才能知道何时需要等待某种事件的发生.

进程唤醒
唤醒进程的原因 :
进程只能被别的进程或操作系统唤醒

进程结束

进程的三种基本状态 :
不同系统设置的进程状态数目不同.
三种基本状态
运行状态(Running) : 当一个进程正在处理机上运行时就绪状态(Ready) : 一个进程获得了除处理机之外的一切所需资源, 一旦得到处理机即可运行等待状态(阻塞状态 Blocked) : 一个进程正在等待某一时间而暂停运行时. 如等待某资源, 等待输入/输出完成.进程其它的基本状态
创建状态(New) : 一个进程正在被创建, 还没被转到就绪状态之前的状态结束状态(Exit): 一个进程正在从系统中消失时的状态, 这是因为进程结束或由于其它原因所导致.状态变化图

可能的状态变化如下 :
NULL → New : 一个新进程被产生出来执行一个程序New → Ready: 当进程创建完成并初始化后, 一切就绪准备运行时, 变为就绪状态Ready → Running : 处于就绪态的进程被进程调度程序选中后, 就分配到处理机上来运行Running → Exit : 当进程表示它已经完成或者因出错, 当前运行进程会由操作系统作结束处理Running → Ready : 处于运行状态的进程在其运行过程中, 由于分配它的处理机时间片用完而让出处理机Running → Blocked: 当进程请求某样东西且必须等待时Blocked → Ready : 当进程要等待某事件到来时, 它从阻塞状态变到就绪状态
俩种挂起状态
1. 阻塞挂起状态 : 进程在外存并等待某事件的出现
2. 就绪挂起状态 : 进程在外存, 但只要进入内存, 即可运行.
与挂起相关的状态转换
挂起 : 把一个进程从内存转到外存, 可能有以下几种情况 :
阻塞到阻塞挂起 : 没有进程处于就绪状态或就绪进程要求更多内存资源时, 会进行这种转换, 以提交新进程或运行时就绪进程.就绪到就绪挂起 : 当有高优先级阻塞(系统认为会很快就绪的)进程和低优先级就绪进程时, 系统会选择挂起低优先级就绪进程.运行到就绪挂起 : 对抢先式分时系统, 当有高优先级阻塞挂起进程因事件出现而进入就绪挂起时, 系统可能会把运行进程转导就绪挂起状态.在外存时的状态转换 :
阻塞挂起到就绪挂起 : 当有阻塞挂起因相关事件出现时, 系统会把阻塞挂起进程转换为就绪挂起进程.(但是进程本身所有的资源都还在外存中)解挂, 激活 : 把一个进程从外存转到内存; 可能有以下几种情况 :
就绪挂起到就绪 : 没有就绪进程或挂起就绪进程优先级高于就绪进程时, 会进行这种转换.(当获得资源,直接变成就绪态)阻塞挂起到阻塞 : 当一个进程释放足够内存时, 系统会把一个高优先级阻塞挂起(系统认为会很快出现所等待的事件)进程转换为阻塞进程.
OS怎么通过PCB和定义的进程状态来管理PCB, 帮助完成进程的调度过程?(具体选择哪个程序执行)

状态队列(对于任何状态而言,它存在多个进程,OS要维护一组队列)
状态表示方法

比进程更小的独立运行单位是线程(go语言还有协程)

单进程实现方法
//单进程方式
while(1){
Read();
Decompress();
Play();
}
问题: 播放出来的声音能否连贯? (因为读这个文件需要IO操作,所以CPU会等待)各个函数之间不是并发执行, 影响资源的使用效率.
多进程实现方法
//多进程
//进程1
while(1){
Read();
}
//进程2
while(1){
Decompress();
}
//进程3
while(1){
Play();
}
问题: 进程之间如何通信,共享数据?另外,维护进程的系统开销较大:
创建进程时,分配资源,建立PCB;撤销进程时,回收资源,撤销PCB;进程切换时,保存当前进程的状态信息
解决问题
因此需要提出一种新的实体, 满足以下特征:
这实体就是线程.进程拆开有俩部分组成一个是资源管理一个是线程( 进程 = 线程 + 共享资源)
线程的优点:
线程的缺点:

线程所需的资源
不同的线程需要独立的寄存器和堆栈, 共享代码,数据和文件等.
3.2.1 线程和进程的比较线程的创建时间比进程短;(直接利用所属进程的一些状态信息)线程的终止时间比进程短;(不需要考虑把这些状态信息给释放)同一进程内的线程切换时间比进程短;(同一进程不同线程的切换不需要切换页表)由于同一进程的各线程之间共享内存和文件资源, 可直接进行不通过内核的通信.(直接通过内存地址读写资源)
进程创建需要创建线程和相关的资源,所以所需时间长,而进程直接就可以使用线程的资源,因此创建线程所需要的时间更少线程具有同一个地址空间也就是同一个进程的页表,切换时不需要切换内存管理的页表,而对于进程切换时,需要切换页表,开销大(会涉及到访问的地址空间,缓存等都不一样,都是无效需要重新加载)
主要有三种线程的实现方式:
用户线程 : 在用户空间实现; POSIX Pthreads, Mach C-threads, Solaris threads内核线程 : 在内核中实现; Windows, Solaris, Linux轻量级进程: 在内核中实现,支持用户线程; Solaris(LightWeight Process)用户线程与内核线程的对应关系

用户线程库完成了对用户线程的管理,线程控制块(TCB)就在这个库里面实现,对于OS它看不到TCB只能看到进程信息,但是进程里面的线程只有线程管理库可以看到,线程OS看不到
用户线程
操作系统看不到的是用户线程,用户线程操作系统看不到由应用线程的库来管理操作系统只能看到进程, 看不到线程, 线程的TCB在线程库中实现在用户空间实现的线程机制, 它不依赖于操作系统的内核, 由一组用户级的线程库来完成线程的管理, 包括进程的创建,终止,同步和调度等.
用户线程的缺点:
理解
1.(因为OS看不到线程,他只能看到进程,当线程因调用发生阻塞,它以为是进程发出的,所以他会阻塞进程,进而导致所有线程都阻塞);
2.用户态的线程库无法主动打断用户线程执行(无特权),但是OS可以,OS会管理中断,一旦产生中断,CPU控制权全部交给OS,OS进行处理,例如时钟中断,OS会强制切换,把当前线程停止下来,执行其他线程
内核线程
只要内核线程完成一次切换,就要完成一个从用户态到内核态变化,相比前面用户线程开销大(用户线程都是在用户态进行切换的)
操作系统管理起来的。能够看到的线程是内核线程,内核是由OS管理- 操作系统能够看到进程也可能看到线程,线程在内核中实现;
内核线程是在操作系统的内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建,终止和管理.
轻量级进程

必须在切换之前存储许多部分的进程上下文必须能够在之后恢复他们,所以进程不能显示它曾经被暂停过必须快速(上下文切换时非常频繁)寄存器(PC,SP...),CPU状态等信息一些时候可能会费时,所以我们应该尽可能避免
这部分切换都是与硬件联系,所以使用汇编代码写的

fork()的简单实现
在99%的情况下,我们在调用fork()之后调用exec()
vfork()
exec是让当前进程执行新的程序,所有的地址空间和代码全部变成新的exec后当前地址空间的代码段已经覆盖了,则printf的内容已经被新的程序calc覆盖了,就不会打印了.如果打印出来,就意味着exec执行失败pid > 0意味着执行空间在父进程,wait(pid)父进程要等待子进程,wait返回意味着子进程结束pid = 0 fork失败int pid = fork(); //创建子进程
if(pid == 0) { //子进程
exec_status = exec("calc", argc, argv0,argv1,...);
printf("Why would I execute?");
} else if(pid > 0) { //父进程
printf("Whose your daddy?");
...
child_status = wait(pid);
}
执行exec代码和数据都复制了一份,原来的代码发生了变化,被新代码覆盖了

内存中的布局图

exec()加载程序取代当前运行的进程exec()调用允许一个进程"加载"一个不同的程序并且在main开始执行(事实上 _start)argc)和它字符串参数数组(argv)它是相同的进程但是它运行了一个不同的程序stack,heap重写fork执行开销,因为在执行的过程中会创建很多的子进程所以要使fork效率高
fork是把父进程空间完全复制一份到子进程,内存的大拷贝(代码段和数据段等),但是做exec时你之前做的拷贝全都被覆盖了(因为要加载新的程序)解决办法
vfork:对父进程只进行一小部分复制,一个fork一个vfork增加编程人员开销COW技术:通过虚存管理,写的时候在进行复制(当父进程创建子进程使用的COW技术,则在复制时,只是复制父进程一些源数据(页表),他们指向同一块空间,当父进程或者子进程在写的时候,会触发异常使得这个页复制成俩份,分别不同的地址,如果只读则不需要)
父进程创建子进程后,最重要的一个是等待子进程的结束
问题1::当子进程结束直接调用exit不就完事了,当子进程执行完exit后是否子进程需要的资源全部回收到OS中
操作系统会对当前退出的子进程,回收地址空间、文件系统及其资源,但是对于当前进程的进程控制块(PCB)不会被操作系统回收- 当操作系统释放完这些资源,它无法回到用户空间在执行,但是在内核中还有相应的资源例如PCB,它是很难释放的
- 当子进程执行完
exit最后时会返回,会通过OS通知父进程,如果父进程在执行wait,那正好这俩个对上,父进程会帮助子进程把它内存中的资源释放掉(PCB)- 父进程
wait配合子进程的exit完成对子进程所有地址空间的及其资源的回收
wait()系统调用是被父进程用来等待子进程的结束
wait()系统调用担任这个要求
它使父进程去睡眠来等待子进程的结束当一个子进程调用exit()的时候,操作系统解锁父进程,并且将通过exit()传递得到的返回值作为wait调用的一个结果(连同子进程的pid一起)如果这里没有子进程存活,wait()立刻返回 当然,如果这里有为父进程的僵尸等待,wait()立即返回其中一个值(并且解除僵尸状态)
- 当子进程执行完
exit,且父进程还没有执行完wait的时候(没有把子进程PCB回收这段时间),这中间有一段时间
将这程序的"结果"作为一个参数关闭所有打开的文件,连接等等释放内存释放大部分支持进程的操作系统结构检查是否父进程是存活着的:
清理所有等待的僵尸进程僵尸状态
exit执行完毕但是wait还没有执行完毕,此时子进程就处于僵尸状态,什么也做不了就只能等待被父进程回收
问题2:父进程先于子进程死亡退出了,那么就不可能有一个父进程在wait了,也就意味着子进程一直处于僵尸状态,无法回收相应的PCB,那么它就会一直留在OS中>
解决办法进程都会有父子关系,最早的进程称为root进程,它会定期扫描进程控制块列表,看是否有进程处于僵尸状态,如果有进程处于僵尸状态,他会代替进程的父进程完成回收操作,使得操作系统中不会有过多的僵尸进程存在

执行exec时,进程可能处于不同的状态,例如当从磁盘加载一个新程序时,进程可能从运行到阻塞,然后再运行新程序
exec: 加载新程序和运行新程序