目录
1.简单了解操作系统
2.进程的特性(process)
3.线程的特性(thread)
4.总结进线与线程的区别和联系
提到操作系统,就离不开冯诺依曼体系,先简单谈谈冯诺依曼体系,,
冯诺依曼体系:CPU,存储器,输入设备,输出设备
- CPU
1.CPU内部本质上是由一大堆的门电路构成。
2.CPU内部的集成程度越高,就认为计算能力越强。
3.CPU上面还包含了寄存器,可以存储一些运算的中间值。
4.CPU执行程序的过程,大概是:取指令,解析指令,执行指令。
- 存储器
内存(特点):存储空间小,访问速度快,价格贵,断电数据丢失...
外存(特点):存储空间大,访问速度慢,价格便宜,断电后数据不丢失...
说了那么多,那操作系统到底是什么呢??
由此看来, 操作系统是一个非常复杂的软件,它要管理很多其他的东西:内存管理、文件管理、设备管理、进程管理.......
【核心功能】
1.对下,要管理各种硬件设备。
2.对上,要给各种软件提供稳定的运行环境。
2.1 什么是进程/任务(task)??
计算机内部要管理任何现实事物,都需要将其抽象成一组有关联的、互为一体的数据。在 Java 语言中,我们可以通过类/ 对象来描述这一特征。(进程的唯一身份标识---pid)这样,每一个 PCB 对象,就代表着一个实实在在运行着的程序,也就是进程。操作系统再通过这种数据结构,例如线性表、搜索树等将 PCB 对象组织起来,方便管理时进行增删查改的操作。
class PCB {
// 进程的唯一标识 —— pid;
// 进程关联的程序信息,例如哪个程序,加载到内存中的区域等
// 分配给该资源使用的各个资源
// 进度调度信息(留待下面讲解)
}
2.3 进程调度
什么是进程的调度??
本质问题:我们的计算机,CPU是有限的,但是进程数量是比较多的。CPU核心数大概只有6核,而进程数量非常多,几十个,上百个 !这就是一个"狼多肉少"的问题,操作系统是要做到尽可能的公平,让每个狼都有吃肉的机会,就需要进行调度。
具体的做法就是让每个进程轮流进CPU,并且这里的轮转速度是非常快的!!
CPU主频是 1.9GHz,1s就有 19 亿个时钟周期,因此人感知不到这样的轮转。站在宏观角度来看,就好像这些进程都在同时执行一样,但是微观上并不是同时,而是"轮流"的方式占用CPU执行,这也叫做并发式的执行!!
由于CPU上有多个核心,每个核心上都可以跑一个进程,某个时刻两个进程就是在两个CPU核心上同时执行的,这就叫做并行式的执行!!
并发式执行:宏观上同时执行,微观上不是同时执行;并行式执行:宏观、微观都是同时执行;但是实际开发中,并不会对这俩概念上做明确的区分。
进程中有一些比较关键的属性(进程优先级,进程的状态,进程的记账信息,进程的上下文),用来实现进程的调度。
【进程优先级】:是指每个进程是按照优先级的顺序来占用CPU。
【进程的状态】:这里只讨论进程的就绪和阻塞两个状态,就绪状态,就有机会进入CPU,而阻塞状态就无法进入CPU。
【进程的记账信息】:操作系统在安排进程的时候,也会记录每个进程以往在CPU上执行的时间,如果发现某个进程被安排的太少了,就会适当的调整策略。(此处记录的时间不一定是以执行时间为单位,还可能以执行的指令数为单位)
【进程的上下文】:进程在调度的时候也是一样,进程很可能执行了某个操作,执行一半,就被调度走了,过一段时间,进程还要回来从上次执行到的位置继续往下执行!对于进程来说,上下文,具体指的就是 CPU 里的一堆寄存器里面的值。上下文就会在进程被切出 CPU 的时候,把寄存器的状态保存到 PCB 里(内存)。下次进程回到 CPU 上就把 PCB 里的上下文读取出来,恢复到 CPU 寄存器中。
2.4 进程的隔离性
我们希望每个进程,用自己的内存,不要互相干扰!!
但是在C语言里,有一个操作,叫做指针解引用,解引用的时候需要保证这个指针里包含的地址,指向的是咱们自己申请的合法内存地址。假设进程3中 *p = XXX,p 如果是一个野指针,就很可能会导致 进程3 指向别的进程的内存里了!!伴随着这个操作,就很可能直接把别的进程搞挂了!!这样就很难受:假如我在打电脑游戏,突然女朋友一个消息发过来,我立马切换页面去微信回复,结果这一操作直接导致我游戏崩溃,这就非常难受了!!这种行为就会让整个操作系统都很不稳定,这就违背了操作系统要给应用程序提供稳定的运行环境这一说法!!
为了让各个进程之间不要相互干扰,操作系统就引入了"虚拟地址空间",这样的概念。每个进程都只能访问到自己的地址空间,相互之间不会有影响了,哪怕你指针指错,操作系统也能及时发现,不会影响到其他的进程。就算出问题,问题也被限制到进程内部了!!
此时如果进程3里出现了野指针,并且出现了 *p = XXX 的操作,指到了错误的地址上了,也不会出现太大问题! 因为进行操作 p 指向的内存的时候需要经过 MMU 进行映射,MMU 就知道当前访问的是一个有问题的地址,于是就会像操作系统"告状",说有进程访问内存错了!!操作系统就会给对应的进程发一个"信号",告诉它,你的内存访问出错。而这里的"信号"的默认处理行为就是让进程终止运行,也就是所谓的程序崩溃!!
这样就解决了进程之间相互影响的问题,这就是进程的隔离性!!这种做法才是真正做到了操作系统给应用程序提供稳定的运行环境!!上述操作系统抽象出的虚拟地址空间可以理解为一个班的花名册,1班的19号同学对应张三,而2班的19号同学对应的是李四,互不干扰。
【虚拟地址空间过大的问题】
这时候又有问题了,你一个系统里进程有这么多,这些虚拟地址空间加到一起比物理内存大了,怎么办??
1.可能同时一时刻执行的进程其实没几个!!
2.虽然每个进程的虚拟空间很大,但是实际使用的内存只有一小部分,物理内存只需要把真实使用的这部分内存数据给表示出来即可!!(假设同时6个进程在跑,很可能每个进程只是用 1M 的内存空间)
3.极端情况下,确实同时跑的这几个进程同时吃了很多的真实内存,确实会导致物理内存不够用!!(出现这种情况,属于 bug ,因此程序猿就需要想办法优化一下内存占用,或者扩容换一个内存更大的机器)
对于以上的虚拟地址空间的做法,如果还不理解,那就再举一个例子:我们生活中的银行也是这一套做法,假如今天银行吸收1个亿的存款,银行可能明天就贷款贷出去9千万,那么银行柜台里可能实际上只有1千万,这1千万对应的是1个亿份额的储户储蓄。经验规律:这些储户不会同时来取所有的钱!!但也有极端情况,比如出现战争等重大情况,就会出现银行挤兑,导致银行破产.....
2.5 进程间通信(了解)
进程引入了隔离性,确实让系统更稳定了,但是也有别的问题,比如多个进程之间想要配合工作,就麻烦了!!为解决这一问题,操作系统又引入了"进程间通信"。操作系统提供的进程间通信的方式有很多种,但是本质都是一样的——搞一个多进程之间都能访问到的公共资源,借助公共资源来进行通信!!
进程和线程之间,存在着一定的联系!!为啥要有多个进程呢?是为了引出并发编程!!CPU单个核心已经发展到极致了,要想提升算力,就得使用多个核心!所以引入并发编程,最大的目的就是为了能够充分利用好 CPU 的多核资源。使用多进程这种编程模型,是完全可以做到并发编程,并且能够被充分利用!!
但是在有些场景下,会存在问题:频繁的创建//销毁进程,这个时候就会比较低效!!
它分为三步:
1.创建PCB;
2.分配系统资源(尤其是内存资源);(这个是在操作系统内核资源管理模块,进行一系列遍历 操作的,最耗时间的步骤)
3.把PCB加入到内核的双向链表中。
为了提高这个场景下的效率,就引入了"线程",也叫做"轻量级进程",线程其实是包含在进程中的,也就是一个进程里面可以有很多个线程。每个线程其实也有自己的PCB(一个进程里面可能就对应多个PCB),同一个进程里的多个线程之间共用一份系统资源,这就意味着进程中的最耗时间的步骤省略了,不必重新给他分配系统资源了,只需要复用之前的即可!!因此创建线程,就只需要做以上的1,3步骤。这就是线程相对于进程做出的重大改进!!也就是线程更轻量的原因!!
【举例】
线程是包含在进程内部的"逻辑执行流"(线程可以执行一段单独的代码,多个线程之间,是并发执行的)。
操作系统在调度的时候,其实也是以"线程为单位"来进行调度的!(系统内核不认进程/线程,只认PCB)。
创建线程的开销比创建进程要小,销毁线程的开销也比销毁进程要小。
另外,线程还有其他一些特性:
1.增加线程数量,可以提高效率(但是不能无限提升)
2.线程之间可能存在线程安全问题。
3.一个线程崩溃,可能导致整个进程都崩溃。
【举例】
1.进程是包含线程的,线程是在进程内部的。
2.每个进程有独立的虚拟地址空间,也有自己独立的文件描述符;同一个进程的多个线程之间,则共用这一份虚拟地址空间和文件描述符表。(进程之间的资源是独立的,同一个进程里的线程之间的资源是共享的)
3.进程是操作系统中资源分配的基本单位。线程是操作系统中,调度执行的基本单位。
4.多个进程同时执行的时候,如果一个进程挂了,一般不会影响到别的进程;同一个进程内的多个线程之间,如果一个线程挂了,很可能把整个进程带走,其他同进程的线程也就没了.
文件描述符表:内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述符就是这个表的索引。
最后还要注意的一点是,谈到多进程的时候,经常会谈到"父进程","子进程",进程A里面创建了进程B,A是B的父进程,B是A的子进程。但是在多线程里,没有"父进程","子进程"这种说法,我们认为线程之间地位是平等的!!
本篇博客就到这里了,谢谢观看!!