操作系统是一个搞管理的软件,它对上给各个应用程序提供稳定的运行环境;对下管理各种硬件设备。
进程
一个操作系统由内核和配套的应用程序组成。而**进程就是操作系统内核中众多关键概念中的一个。**进程通俗一点来讲就是一个已经跑起来的程序。
每个进程都会消耗一定量的系统资源(CPU,GPU,内存......)。而操作系统是按照进程来分配资源的,即:进程是系统分配资源的基本单位。
PCB
在操作系统中有一个专门的结构体 (操作系统内核是C/C++实现的)来描述进程的属性,而这个结构体被统称为 **"进程控制块"**也叫PCB(Process Control Block)(此处的pcb并不是硬件中的pcb板)。
一个进程可以使用一个或多个PCB来表示,操作系统中会使用一个类似双向链表的数据结构来管理和组织多个PCB。
- 进程的创建就是创建PCB并将其插入链表;
- 销毁进程就是将PCB从链表上删除并释放;
- 展示进程列表就相当于遍历链表。
pid
pid是进程的身份标识。每个进程都会有一个pid,程序一运行系统就会自动分配给进程一个独一无二的PID。 进程中止后PID被系统回收,可能会被继续分配给新运行的程序。
内存指针(一组属性)
内存指针描述了进程持有的内存资源是啥样的。
每个进程在创建的时候都会分配一块内存。
- 进程需要一块专门的内存来存储需要执行的指令,例如当我们打开电脑中的exe文件时系统就会读取文件内容并将其加载到内存中,之后CPU从内存中取走并执行这些指令;
- 同时还会存储一些运行时产生的临时数据。
文件描述符表
文件描述符表是一种类似于顺序表的数据结构。描述了进程持有的硬盘资源是啥样的。
每个进程都有一个自己的文件描述符表,通过文件描述符表可以知道当前程序关联了哪些文件都能操作那些文件。
PCB中还引用了一些属性,用来支持操作系统实现进程的调度。
进程的状态
就绪状态:进程时刻准备着去CPU上执行,也就是此时进程被CPU随叫随到,呼之即来挥之即去。
具体情况:
- 进程正在CPU上执行;
- 虽然此时进程没有在CPU上执行但时刻准备着去CPU执行
阻塞状态:当进程中某种执行条件不具备,而导致这个进程无法参与CPU的调度执行。比如程序在等待用户输入。
进程一共有 5 种状态,分别是创建、就绪、运行(执行)、终止、阻塞。
进程的优先级
在操作系统中对多个进程进行调度时并非是一视同仁的,有些进程操作系统会给予更高的优先级。比如:当你打游戏时游戏的优先级就高于QQ。
- 每个进程都有相应的优先级,优先级决定它何时运行和接收多少 CPU 时间。
- 进程的优先级可以动态变化 ,高优先级进程优先运行,优先级相同 的进程按照时间片轮流运行。
上下文
进程在从CPU离开之前需要保存现场,将当前CPU中的各种寄存器的状态都记录在内存中。等下次进程再回到CPU中执行时,就会将这些内存信息恢复回去让接着上次的继续执行。简单来说就是存档和读档。
记账信息
因为操作系统会通过进程优先级机制对不同优先级的进程分配不同的硬件资源。而这就有可能会导致某一个或几个进程占用了太多的资源而导致某一个或某几个进程因为分配不到资源而发生异常。
记账信息会记录当前进程所持有的CPU状况,而根据这些信息对资源进行动态的分配。
虚拟地址空间
早期的电脑并没有虚拟内存,那时操作系统为进程所分配的都是物理内存。
此时就会存在一些问题如果进程A在访问内存时发生了越界就有可能会更改进程B内存中的数据进而导致A和B两个进程都发生异常。
之后操作系统对内存进行了一层抽象,引入了"虚拟地址空间"的概念,此时操作系统分配的就不是真实的物理内存地址了而是分配的虚拟内存地址。
此时因为进程得不到真实的物理内存地址也就不会发生上述的,错误修改别的进程的内存的情况了。
但是此时又引入了一个新的问题:此时的进程与进程之间被完全的隔离了起来,之前如果两个进程之间想要实现通信就可以直接在对方的内存中进行修改,可是引入了虚拟内存之后因为无法得到真正的内存地址,所以就无法使用相同的方式进行修改。
此时进程之间的通信就需要借助一个额外的公共的内存空间进行实现:比如 A 在网上买了一台电脑,然后快递员将它送到菜鸟驿站,然后 A 再去菜鸟驿站拿到电脑。
其实此处的通信方式有很多种但整体的核心思想都是借助一个额外的公共的内存空间。
线程
此处利用工厂来举例线程和进程的关系:
此时 A 是一个工厂的老板现在他有一个工厂,但工厂里只有一条流水线 A 们每天可以赚到 100 块钱。
这时 A 想要每天赚 200 块钱那么他现在有两种解决方案,第一种再建一个工厂同样也只有一条流水线;第二种就是在旧工厂里面增加一条流水线。
这里的流水线就相当于线程;而工厂就相当于进程。
在JAVA中鼓励使用多线程 而并不鼓励使用多进程的方式来实现并发编程。
上文提到过进程是系统分配内存的基本单位 ,而分配内存对于系统来说并不轻松所以导致:进程太重量,效率不高。
- 创建一个进程消耗的时间较多;
- 销毁一个进程消耗的时间也较多;
- 调度消耗的时间也多。
而线程是比较轻量的也叫 "轻量级进程" 。就像上面举的例子新建一个工厂肯定没有新加一条流水线省时省力。
虽然线程的创建,销毁,调度都较快 但是线程却无法离开进程单独存在 ,也就是进程里面是包含线程的可以包含多个线程但至少要包含一个线程。
线程来负责执行代码,如果需要可以创建多个线程,每个线程都可以独立的执行一段代码,从而实现并发编程的效果。
线程是调度执行的基本单位。
而上文提到的PCB和线程是一一对应的,也就是说一个进程可以有多个PCB而每个PCB都对应一个线程。但是 pid,内存指针,文件描述符表 在同一个进程之间是公用的。也就是说除了第一个线程剩下的线程在创建时都不需要重新申请资源了。
而线程也不是越多越好 ,因为工厂的大小是一定的,如果线程过多效率不但不会提高可能还会降低:
线程的特点
- 每个线程都可以单独去CPU上调度执行;
- 同一个进程中的多个线程公用同一块内存空间,文件资源。
总结:
进程中包含线程(线程数至少为 1 )=> 每个PCB都对应一个线程 => 每个线程都有自己的状态,上下文,优先级,记账信息=> 每个线程都可以独立去CPU上调度执行 => 同一个进程中的多个线程公用同一块内存空间,文件资源 => 创建线程不需要重新申请资源 => 线程更轻量
- 进程是资源分配的基本单位;
- 线程是调度执行的基本单位。
进程和进程之间相互独立 ,一个进程挂了并不会影响其他的进程;而如果一个线程挂了如果没有妥善处理是会影响到其他线程的。