PCB中的信息:常见属性(有关于进程调度)
1)PID:(进程id)是进程中的身份标识,一个机器同一时刻,不可能有两个进程的PID相同,同一个系统的身份标识,进程的身份证号
2)一组内存指针:指名了该进程持有的一些重要数据和要执行的代码或者指令在内存中的位置这些指令依赖的数据,进程使用的内存空间是那个范围,还有一些虚拟机空间;我们运行exe文件之后,此时我们的操作系统就会把这个exe文件,加载到内存里面,就变成了进程,所以说这个进程就和我们的内存空间绑定到了一起,这期间就会把exe文件的东西加载到内存里面,所以说我们的exe可执行文件里面有什么,那么我们的内存里面就有什么数据,里面主要是进程执行的一些二进制指令,也就是说编译器生成的(编译),除了一些指令之外,还有一些中重要的数据,这些东西都会被放到我们的内存里面,数据和指令在不同的内存空间里面;
3)状态:这个状态就描述了当前这个进程正在运行态,还是正在休眠态(也叫作阻塞态,暂时不可以上CPU执行,等到恢复到就绪状态之后,在进行调度上CPU来进行执行),就绪态(随时可以上线执行),还是处于就绪状态/阻塞状态,这个叫做状态的属性就描述了这个进程接下来该如何进行调度,随叫随到,我们就根据进程的状态来进行决定接下来改进型调度哪一个进程到CPU上面
4)优先级:这个进程是优先上CPU还是放在后面执行,在采用抢占型执行的系统中,当有更高的优先级的进程正在等待时,该进程就会被迫让出CPU,就会由执行状态转变成就绪状态,假设当前我们有很多进程正在执行,这个优先级就描述了先给谁分配时间,以及后给谁分配的多,给谁分配时间的少,给谁分配的时间多,有的任务有的优先级高一些,有的优先级要低一些
5 )进程的记账信息:保存了进程在CPU执行了多长时间,不要让这个进程在CPU上霸占太久,系统分配给进程占用CPU的时间是有限的;就是统计了每一个进程,都分别的执行了多久,都分别的执行了哪一些指令,都分别的排队等了多久,这是给进程调度来提供指导依据的
6)上下文:就表示了上次进程被调度出CPU的时候,当时的程序的一个执行状态,下一次进程上CPU的时候,就可以恢复之前的状态,然后继续向下执行
存档:咱们的进程被调度出CPU之前,我们先要把CPU中的所有寄存器中的数据都保存到内存里面(PCB的上下文字段里面),相当于是存档了
读档:下一次进程再次调度上CPU的时候,就可以从刚才的这些内存中恢复这些数据到寄存器里面,就相当于读档了,就可以继续向下读档了
7)文件描述符表:在我们的程序运行的过程中,是要经常和文件打交道,文件是在硬盘的,进程每打开一个文件,就会在文件描述符表里面多增加一项;
7.1)描述了这个进程都打开了那些文件,每当系统打开一个文件的时候,其实就相当于得到了一个文件描述符,就好像得到了一个遥控器一样,因为文件数据是在磁盘上面的,代码中操作磁盘数据,不像操作内存数据这样简单与方便,咱们的文件描述符表就相当于是一个List数组,每一个文件描述符又是一个结构体,就对应着一个文件的相关信息
7.2)任意一个进程一旦进行启动,不管我们的代码中是否出现了打开/操作文件的代码,我们都会默认打开三个文件,这是系统默认打开的---标准输入(System.in),标准输出(System.out),标准错误(System.error),下标为0,下标为1,下标为2,我们的文件描述附表里面的下标,就被称之为文件描述符
一:我们说如果我们想要让一个进程进行正常工作,就需要给这个进程分配一定的系统资源
1)内存:加在exe文件到内存里面,花费一些内存资源来进行完成加载的过程
2)磁盘:打开并且操作一些文件
3)CPU:执行我们进程的一些指令
由此可知,我们如果想要让一个进程进行正常的工作,那么操作系统必须在硬件上面给予足够的硬件资源方面的支持,才可以保障进程的基本工作,才能使进程完成一些重要的工作任务
二:下面的PCB的相关信息,在上面的属性就是一些基础的属性,下面的属性都是为了可以实现进程调度
进程的状态,进程的优先级,进程的记账信息,进程的上下文
2.1)咱们现在的操作系统,一般都是多任务操作系统,就是一个系统,同一时间内只能执行一个任务,它的前身就是单任务操作系统,同一时间只能运行一个进程,单任务操作系统比如说想要运行QQ,就不能运行别的进程;
2.2)单任务操作系统,不需要考虑进程调度
像我们现在的笔记本电脑就是一个多任务的操作系统,同一时刻有很多进程在运行,在任务管理器里面,同时运行着CSDN,Idea,这些任务在同时执行
线程调度:本质上是说有限的核心执行很多很多的任务,其实就是操作系统在想着如何把CPU资源给各个进程来进行分配
进程调度:有限的CPU核数,执行多个进程,是操作系统非常巧妙的设计,只有结合PCB中的属性,才可以完成进程调度这样的事情
1)CPU(8核CPU,8个分身,同时干活);同一时刻,一个CPU只能执行一个程序的指令
2)进程调度:由于CPU计算和执行指令速度极快,我们就可以让CPU先执行进程1的指令,执行一小会,再执行进程2的指令,这也叫并发执行(非常重要)CPU的运行速度极快,虽然CPU在不停地进行切换,但是坐在电脑前的用户,是无法感知到的,就是我们的计算机操作系统对于CPU资源是如何进行分配的
2)一个机器上面只有一个CPU,但是实际上这个CPU上面有上百个进程在运行;狼多(进程),肉少(CPU),就要基于进程调度这样的方式来执行;
3)并行:多个CPU,来执行多个进程,也就是说微观上,以很小的时间维度来进行切分,两个CPU核心,在同时进行执行两个任务的代码;
4)真实的计算机,真实的操作系统在执行的时候,在进行进程调度的时候,是并行并发两种策略进行综合使用的;
5)并发:微观上,一个CPU核心,先进行执行一会任务1,在执行一会任务2,在执行一会任务1,最后再进行执行任务1,每一个任务都执行一小段逻辑,一小段逻辑执行完成之后,马上就切换到下一段任务,依次进行切换
6)操作系统何时进行进程调度,就是啥时候把进程放到CPU上面,啥时候把进程切换出CPU,这个事情对于进程本身来说是感知不到的,执行出这个进程中的任意指令,都可能产生这样的调度;
1)现在咱们的操作系统,又被称为多任务操作系统,一个系统统一时间,执行了很多任务,之前是单任务操作系统,同一时间内只能操作一个进程,比如说在我们的windows系统上面,就在执行者很多任务,同一时刻很多任务都在执行,同时运行着画图板,CCtalk,QQ音乐,idea
2)进程调度:想要让有限的核心执行很多很多的任务,我们的系统上面的任务的数量,有几十个,上百个,但是我们的CPU核数就只有那么几个
并行:微观上切换到一个很小的时间单位上面,两个CPU核心,同时执行两个任务的代码
并发:微观上,一个CPU核心,先执行一会任务1,在执行一会任务2,在执行一会任务三,只要切换的足够快,在宏观上,就好像这么多任务同时在执行;
3)咱们的并行和并发,只是在微观上有区分,在宏观上我们是区分不了的,微观上面的区分都是操作系统进行自行调度的结果,比如说现在有6个核心,同时跑20个任务,这20个任务,有些是并行的关系,有些是并发的关系,可能任务A和任务B,一会是并行,一会是并发,这些都是微观上面操作系统控制的;我们将游戏和斗音进行分屏,可能它们一会是并行执行的关系,一会是并发执行的关系;
4)并行和并发这两件事情,只是在微观上面有区别,在宏观上面我们无法进行区分,是进行并行还是并发完全取决于操作系统进行如何调度的结果;
5)正是因为在宏观上面区分不了并行并发,所以我们在写代码的时候也不用去特地的区分这两个词,实际上我们经常使用并发这个词,来进行代替并行+并发
6)咱们只是在研究操作系统的进程调度话题上面的时候,稍微做一下区分,其他场景下基本都是使用并发这一个统称来进行代替的
假设小芳是一个妹子,它长得很好看,还会说话,身材也好,这就导致它有很多的追求者,原则来说,一个女生,只能有一个男朋友,但是同一时间小芳交往了三个男朋友:
A:有钱的
B:长得帅
C:比较会舔
因此小芳就需要合理的来进行安排时间,避免同一时刻,这三个人来进行碰面,于是我就可以进行安排一个时间表:
周1:和A去逛街
周2:和B去上课
周3:和C去看电影
宏观上看起来,我好像谈了三个男朋友,但是微观上看起来,同一时刻,我只是在和一个男朋友在一起,规划时间表的过程中,也就是进程调度的过程中;
进程之间的独立性:
进程的调度,本质上来说就是操作系统在进行考虑CPU资源如何给各个进程来进行分配,但是除了CPU资源,还有内存资源,那么咱们的内存资源又是如何来进行管理的呢?
1)这就不得不谈到虚拟地址空间,由于在操作系统上面,同时运行着很多的进程,那么如果某一个进程出现BUG,进程崩溃了,那么是否会影响到其他进程呢?现代的操作系统是不会的windows,mac,能够做到这一点,就是依据了虚拟地址空间
2)在早期的操作系统里面,我们得所有进程都是访问同一个内存里面的虚拟地址空间,如果说某一个进程出现了BUG,那么会把某一个内存的数据写错了,那么就可能会引起其他进程的崩溃
我们在这里面的解决方案就是说把这个院子划分出很多的道路,这些道路之间我们彼此分割开来,每一个人走自己的道路,这个时候就没事了,也就是说每一个人都有着自己的路,一个人阳了,每一个人都不会影响其他人,虽然楼里面有很多人,但是同一时刻只有6个人出门,所以当一个人走完之后,我们就进行消杀一遍;
3)我们把进程按照虚拟地址空间的方式划分成了很多份,这个时候每一份不久只剩下一点了吗?虽然我们的操作系统里面有百八十个进程,但是从微观上来看,同一时刻,同时执行的进程就只有六个,这是我们把内存分成六份就可以了,每一个进程能够被分配的内存还是挺多的,也不是所有的进程都使用那么多的内存,比如说一个王者荣耀的游戏,要分给他的内存还是很多的,但是大部分的进程也就是说只占几M就可以了,如果这六个进程中有一个进程A退出CPU了,那么就把他对应的那份内存释放掉,再让这份内存给其他进程提供服务,各个进程之间不会相互影响,就算一个进程把自己的那一分内存写坏了,那一分内存是自己的,也不会影响到别人
4)进程和进程之间为了保证系统的稳定性,是彼此隔离开的,每一个进程有各自不同的虚拟地址空间,每一个进程用各自的内存,互不干扰,互不影响,就可以保证基本的稳定性,但是在实际工作当中,进程之间还是需要进行交互的
进程之间如何进行通信?
一方面,我们的确实要保证进程之间的独立性,保证进程的隔离,保证系统的稳定,保证一个进程出BUG之后不会影响到其他进程,但是从另一个角度来看,完成隔离也不行呀,我们还是需要进行间接交互的,我们既要完成隔离,又要进行交互
5)类似的,我们的两个进程之间,进程A可以把数据放到公共空间里面,进程B再取走这个数据,这样的话就完成了两个进程之间的交互,同时我们也是可以保证原来的隔离性不会受到影响,稳定性并不会破坏,这就叫做进程间通信;
如果说我们想创建进程就要分配资源,内存和文件,销毁进程就要释放资源,比如说内存和文件;
补充:
调度进程成本也是很高的,咱们的进程重量重量在资源申请释放,线程是包含在进程里面的,一个进程中的多个线程,是共用同一份资源(同一份内存+文件)
我们只是在创建第一个线程的时候(由于要分配资源),成本是比较高的,后续这个进程再去创建其他线程,成本会更低;
1)进程池:我们使用玩这个进程之后,并不会销毁进程,而是把这个进程放到池子里面,下次还想使用进程,直接从池子里面拿就可以了,跳过了申请内存,释放内存的过程,整体的开销就大大降低了,但是消耗资源太多了,池子里面的闲置进程还是要消耗系统资源,也是很多的,这些进程都在内存里面堆着呢,会消耗大量资源,其他程序运用的资源就变少了
2)使用线程来实现并发编程,因为线程比进程更清量,每一个进程可以执行一段任务,每一个线程可以执行一段任务,也就是一段代码,也能够并发编程,创建线程比创建进程的成本要低很多,销毁线程的成本也比销毁进程的成本要低很多,调度线程比调度进程的成本要低很多,在linux中,也把线程称之为轻量级进程
我们都知道如果我们想要通过多进程,完全是可以实现并发编程的,但是也会有一定的问题:
1)如果我们是需要进行频繁的创建进程,销毁进程,这个事情成本还是挺高的
2)如果我们想要频繁的进行线程调度,这个事情成本也是比较高的
创建进程我们就需要分配资源,CPU资源,内存资源,文件资源,销毁进程就需要释放资源,就必须要释放内存,释放文件,对于资源的申请和释放操作本身就是一个比较低效的操作,所以说创建进程,销毁进程是一个比较低效的操作
申请资源:假设我们此时要进行配置一台电脑,我就去了电脑城,然后老板给我写了一个配置单,那么我们老板就得去这些仓库里面去找,就需要去仓库里面
释放资源:我们假设把这个电脑来进行退货,那么我们就需要把这个电脑拆了,然后放到对应的货架子位置上面,CPU放在CPU的货架子上面,各种零件也要放在对应的位置
1)虽然说虚拟地址空间,让进程之间的独立性提高了,不至于相互影响使整个系统更加稳定了,但是也有一个很大的缺点,当两个进程需要进行相互配合的时候,沟通起来就会比较困难(进程之间进行通信)
2)也就是说进程一与进程二由于虚拟地址空间的(进程的独立性),导致很难互相双方进行访问对方的内存,如果想要进行更多地交流沟通,就要使用一些特殊的手段,借助双方都可以访问到的东西,来进行交互
例如:文件,管道(是内核中提供的一个队列),信号量,信号,socket,共享内存
3)我们的操作系统中提供的公共空间有很多种,并且各有特点,有的存储空间大,有的存储空间小,有的速度快,有的速度慢,所以说操作系统提供了多种多样的进程间通信机制
2.进程的特点
独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
进程的状态
运行态:进程占用CPU,并在CPU上运行;
就绪态:进程已经具备运行条件,但是CPU还没有分配过来;
阻塞态:进程因等待某件事发生而暂时不能运行;
一:进程的虚拟地址,一个进程要想运行,就要给他分配一些系统资源,其中内存就是最核心的一个资源,并不是分配了真实的物理地址,而是虚拟地址;
传统的进行分配,直接依据真实的内存地址划分成空间,分配给每个进程;物理地址:真实的内存的地址
在我们的原始的操作系统中,我们直接依据真实的内存地址划分出空间,分配给每个进程 比如说0x100-0x800分配给当前的进程1 0x900-0xc00分配给进程2
进程一:0x100-0x400
进程二0x500-0x700
下面是另外一种情况:
1)进程一:0x100-0x800
2)进程二:0x100-0x800
3)进程三:0x100-0x800
这三个地址,都是操作系统抽象出来的虚拟地址,通过中间的一个类似于哈希表的映射,映射到一块真实的内存,系统就会把这个虚拟地址转换成真实的物理地址;
这就相当于是给java100班进行编号,1-50号,再给java101班进行编号1-30号,都是1号,但属于不同的班级,属于不同的人;
为啥要搞一个虚拟地址空间呢?
1)为了一定程度上减少内存访问越界带来的后果,进程1的访问范围是0x100-0x400,如果尝试访问0x401,就访问错误,如果此时你想要访问0x401,系统就会查询这个页表,找到这个虚拟地址对应的物理地址,由于此时的0x401已经是非法地址了,页表就无法查询到,系统就感知你这是越界访问,会给进程发送一个信号,让当前这个线程进行崩溃,防止影响到其他的进程,就让进程与进程之间相互影响的可能性减少了,隔离性增加了,进程就会更加稳定了(虽然比较低效)2)假设我们访问的是真实的物理地址,访问0x401,但是有可能会真的访问成功,但是这一块内存空间并不是当前进程的分配地址呀
为什么创建进程比创建线程更重量?
1)咱们的进程重量是重量在哪里,是重量在进行申请资源释放资源(从仓库里面找东西), 线程是包含在进程里面的,一个进程中的多个线程,要共用这个线程中的同一份资源,就是同一份内存和文件
2)我们只是创建进程的第一个线程的时候,由于我们要进行分配资源,那么成本就是特别高的,后续我们在这个进程中创建其他线程的时候,这时候成本都是比较低的,省去了重复创建资源的过程
我们可以把一个进程比做成一个工厂,假设这个工厂里面有一些生产任务,假设我们要生产1W部手机
所以说我们如果说想要提高生产效率,那么就只能有两种方案
1)我们创建两个工厂,一人生产5K------->这就相当于是多创建了一个进程
2)我们还是一个工厂,我们在这个工厂里面多加一些流水线,两个生产线并行的进行生产,在这里面我们就新创建了一个线程
总结:虽然我们最终也是生产出1W个手机,花的时间好像差不多,但是最终这里面的成本就不一样了,创建工厂,你要买场地吧,你要创建工厂吧
但是如果说我们进行多增加一些线程,是不是效率就会更高呢?
一般来说是会,但是也不一定,因为如果说线程多了,那么这些线程可能会竞争同一份资源,那么此时整体的速度就收到了限制,因为咱们的整体的硬件资源是有限制的,已经达到瓶颈了,CPU,磁盘,网络带宽,内存
进程和线程之间的区别和联系:
1)进程包含线程,一个进程里面是包含很多线程的,当然,一个进程里面也是可以包含多个线程的
2)进程和线程本质上都是为了进行处理并发编程这样的场景,但是进程有问题,频繁创建和释放的时候效率比较低,相比之下,线程要更轻量一些,创建和释放的效率更高,为啥更清量,因为少了申请释放资源的过程
3)操作系统创建进程,要给进程分配资源,进程是操作系统进行资源分配的最小单位,操作系统创建的线程,是要在CPU上面进行调度执行,线程是操作系统进行调度执行的最小单位,之前咱们说是调度的是进程,但是更准确的来说,调度的是线程
4)前面的例子就是相当于是说在每一个进程里面只有一个线程了,可以视为是调度进程,但是如果说是进程里面有多个线程,更严谨的说法是,还是以线程为单位进行调度
5)进程具有独立性,每一个进程都有自己的虚拟地址空间,一个进程挂了,不会影响到其他进程,但是同一个进程的多个线程,是在用同一个内存空间,一个线程挂了,是可能影响到其他线程的,甚至可能会导致整个进程崩溃