1.操作系统:
是一类软件,是协调软件和硬件之间的一个桥梁,比如某一个软件要从硬盘上读取一个数据,此时就用到了操作系统来完成,操作系统需要协调好他们之间的操作。
对下:要管理好各种硬件:CPU,内存,硬盘等等,要协调好他们之间的操作,对上:要给软件的运行提供良好的运行环境。
操作系统本身的功能是非常复杂的,和Java密切相关的就是进程的管理。
首先介绍一下什么是进程:
进程(process)
也可以说是任务,就是我们完成某件事的过程,可以说一个运行起来的程序就是一个进程。也可以说,一个跑起来的可执行文件就是一个进程,下图中每一个可执行的文件就是一个进程。
当我们没有双击这个可执行文件,它就是一个文件,当我们双击后,此时在操作系统中就产生了一个进程。
进程的管理:
进程多了,就需要管理进程,1.首先用类/结构体来描述一个进程有哪些属性,2.然后对这些进程来用一定的数据结构来进行组织。 就像是学生管理系统,每个学生都有id和学号这样的属性,先描述出来一个学生的属性,然后使用一定的数据结构(表格)来组织这些学生对象,之后再来进行一系列的操作。
PCB:描述进程的一个结构体,(process control block)。
那这个结构体中会有哪些属性?
1.pid : 就是每一个进程需要有一个唯一的标识
2.内存指针: 就是当前这个进程使用的是内存的哪一部分(如果操作系统中有跑起来的进程,就会消耗一定的内存空间)
3.文件描述符表:首先文件就是硬盘上存储的数据,就是以文件为单位进行整理的 , 进程每次打开一个文件,就会产生一个”文件描述符“(用来标识这个文件),一个进程中可能有很多文件,此时就会产生一组文件描述符。把这些文件描述符放到一个顺序表里,就构成了文件描述符表。
所以说进程如果要跑起来,需要内存上的资源,也需要硬盘上的资源,内存指针式描述进程消耗的哪部分内存,文件描述符表式描述了进程需要消耗哪一部分硬盘上的资源,所以又可以说进程式操作系统分配资源的基本单位,此处的资源可以式内存资源,硬盘资源,CPU资源。
介绍下,我们的程序能够跑起来,就是靠CPU,cpu他能识别一系列的二进制指令。
我们经常听到8核16线程就说的是cpu中有8个物理核心,16个逻辑核心,物理核心就是相当于cpu中有8个人,16个逻辑核心指的是这8个人可以干16个人的活。
但是可以打开资源管理器看一下,我们要跑起来的线程绝对不止16个,可能是上百个线程证字啊同时执行,所以这16个核心如何执行上百个线程?
此时涉及到两个概念:1.并行:在同一时刻两个核心同时执行两个线程,此时称为并行。
2.并发:在同一时刻一个核心先执行线程1,执行一会之后,再去执行线程 2,然后再过一会之后去执行线程3. 此时 称为并发。
由于这个时间切换的很快,我们是感知不到的,所以此时看起来就是同时执行线程123.
但是是并行还是并发是由操作系统自身控制的,我们本身是感知不到的,所以可以把并行+并发统称为并发。
这个时候就引出了进程调度来完成快速的切换进程123,通过进程调度来完成一些复杂的操作。
4.描述和CPU资源相关的属性,这些属性都是辅助进行进程调度的,
(1)进程状态: 简单的认为,进程的状态主要是两个:就绪态,阻塞态。就绪态就是随时准备上CPU去执行的,阻塞态就是暂时不能上CPU去执行的, 此时cpu就可以根据进程的状态来排时间表,如果是就绪态,就往前排,如果是阻塞态就暂时往后排。
(2)进程的优先级:进程之间的调度不一定是公平的,不一定每一个进程分配的时间都是一样多的,所以有一定的优先级,就是给哪个进程先排,给哪个进程后排,有的进程优先级高。
(3)进程的上下文: 就是当前进程执行到了哪里这种的一个”存档记录“,进程在暂时离开的时候就要把当前运行的结果进行”存档“,等到进程下次回来的时候再恢复之前的存档(就是读档的过程),从这个记录的运行结果开始继续运行。
上下文具体到进程就是cpu在运行的时候,CPU内部中的一系列的寄存器的值,CPU也是可以存储数据的,只是存放的内存很小,这个存放数据的组件就是寄存器。寄存器最典型的作用就是保存当前进程执行的结果。包括进程执行到了哪一条指令。
所谓的存档就是将寄存器的有一系列的指令结果保存到PCB的上下文字段中,读档就是把上下文保存的结果恢复到寄存器中。为啥要有这两个操作,因为此时这个核心的寄存器正在执行这个指令,但是可能一会之后就去执行下一个指令了,所以如果不保存这个进程执行的指令结果,下次再回到这个进程的时候,就不知道从哪开始执行了。
(4)进程的记账信息: 就相当于一个时间表,这个表中记录了每一个进程子CPU上执行了多久了,可以通过这个时间表来作为调度的一个参考依据。比如正在并发执行两个进程,但是我一查时间表,有一个进程我给它分配的时间太短了,此时我就多分配一点时间给这个进程。
然后总结一下操作系统是如何组织这些PCB的: 一般是使用双向链表来组织pcb的,
1.创建一个进程,可以理解为创建一个链表的节点
2.销毁一个进程,就是把链表的节点进程删除的操作
3. 遍历进程列表,就是在遍历这个双向链表。
内存分配 : 操作系统中给每一个运行的进程分配的一个内存其实都不是真实的物理地址的内存,是一个虚拟地址空间。 那为啥不是内存的一个真实的物理地址呢,分配虚拟地址有什么好处呢?
上图所示:如果每一个进程分配的空间都是真实的物理地址,如果进程1出bug了,比如数组越界异常,野指针...... 那么就可能会影响到进程2,把进程2中的内容搞坏掉,进程1 bug影响到了进程2 ,此时会有很大的麻烦。
如下图:我们可以分配一个虚拟的地址空间,然后进行一个映射,页表中的这个虚拟地址会映射到一个真实的物理地址,站在两个进程的角度来看,他们的代码操作的内存地址,就是这两个虚拟地址,两个进程只能看到一个虚拟的地址,感知不到这个真实的物理地址。此时进程1再出现bug,比如数组越界,此时任对地址的操作都会经过这个页表来翻译一下,再映射到真实的物理地址,进程bug数组越界 是虚拟的地址,页表在翻译的时候没有这个越界的地址,此时也就无法修改这个真实的物理地址,不会影响进程2.
进程的独立性:一个进程是无法直接干预另一个进程的内容。
这样加一层虚拟地址来进程校验,很大程度上提高了操纵系统的稳定性。
进程之间的相互通信:
如果进程1执行计算了一个结果,进程2 需要这个结果来执行自己这个进程,此时就涉及到了进程间的通信,需要进程和进程之间的相互配合。
但是如果每个进程直接访问物理地址,就不需要进行进程之间的通信了,此时只要把进程1计算好的结果直接写到进程2即可。但是这种实现方法不是很好。
所以所谓的进程间的通信就是在隔离的前提下,找一块公共的区域,让两个进程借助这个公共的区域来 完成数据的交换。 操作系统提供进程间的通信的公共区域有很多:管道,共享内存,消息队列,信号....... 他们的本质都是一样的,都是一个公共区域来让进程之间完成数据交换的一个组件。
为什么多进程编程是比较重量级的?
主要就体现在资源的分配上
1)如果系统要给一个进程来分配一块空间,此时就需要遍历系统中的空闲空间的表,可能不只是遍历,时间会高效一点,但是总要花一定的时间。
2)如果系统要给多个进程来分配很多快空间,此时就得一个一个的分配,那后来运行起来的进程就需要等,这也是需要消耗时间的。
在Java中,主要是使用文件,socket两种方式来完成进程之间的通信,Java中使用多进程编程不是很多,主要是使用多线程编程。 而多进程编程速度慢,消耗资源多。
线程的基本含义: 一个进程中是包括多个线程的,多个线程中每个线程都是一个独立可调度执行的”执行流“, 线程和线程之间是并发的,多个线程公用同一份进程的资源,省下了分配资源的耗时时间,这就意味着对于线程来所,系统的资源是已经分配好的,创建线程就省了分配资源的时间。
如果多进程和多线程比较抽象,可以类比下:
一个可以生产东西的工厂,随着生意越来越好,此时就就需要扩大生产规模,有两种方式可以扩大生产,一种是再建一个工厂,再搞一套设备,两个工厂的机器同时生产,此时就提高了效率。但是要考虑下,建一个工厂也是需要成本的,需要时间的,如下图:
另一种就是这个工厂的地方也够大,再在这个工厂里搞一套机器,两个机器在一个工厂内也能同时进行生产,也可提高效率。
此时这两套各自独立生产的机器,就可以类比成两个独立的执行流,执行流之间本身就是并发的,只是第二种方式共用了同一个工厂,也就是公用同一份资源,就省了再建一个工厂的成本(就是分配资源的成本)。这就是多进程和多线程的区别。