目录
1. 进程
1.1 PCB
1.1.1. PID
1.1.2. 内存指针
1.1.3. 文件描述符表
1.1.4.进程调度相关的属性
1.2 进程的虚拟地址空间
1.3 进程间的通信
2. 线程
2.1 线程与进程之间的联系
2.2 多线程与多进程
1. 进程
在了解线程之前,我们首先要了解进程:对于一个跑起来的程序,就是一个进程,没运行起来的,只能称作程序,进程是操作系统资源分配的基本单位。进程是一个重要的“软件资源”是由操作系统内核负责管理的,我们也知道,操作系统,对下要管理硬件设备,对上,要给软件提供稳定的运行环境。而操作系统对进程的负责管理,可以理解成:描述+组织
描述:也就是讲清楚都有哪些属性特征,它是使用C中的结构体来描述进程的属性,而用来描述进程的这个结构体,又称为 PCB,下面再对PCB进行详细讲解。
组织:用数据结构,把这样的多个基本单位串联起来,这个数据结构便是双向链表,但对于内部两说,这可能不是一个单纯的双向链表,将多个PCB串联起来,而创建一个进程,本质上就是在双向链表中插入一个新创建的 PCB 结构体对象;销毁一个进程,本质上就是把链表上的对应 PCB 结构体对象删除。通常我们在任务管理器中看到的进程列表,本质上就是在遍历这个进程链表。
1.1 PCB
在PCB中,很多内容都在描述进程的特征。
1.1.1. PID
PID是进程的身份标识符。(是唯一的数字)
1.1.2. 内存指针
内存指针,指向了自己的内存位置,描述了进程持有了哪些资源。
1.1.3. 文件描述符表
同样是描述了进程持有了哪些资源,指的是硬盘上的文件等其他资源。
1.1.4.进程调度相关的属性
首先我们要理解硬件资源,内存,硬盘,网卡,这些对于多个进程来说,都比较好分,但是CPU资源不好分,进程有上百个,多核CPU是没有办法进行分配的。因此,为了解决这种狼多肉少的局面,也就引入了两个重要的概念:并行和并发。
并行: 从微观上来看,在同一时刻,两个核心上的进程,是同时执行的;
并发:从微观上看,同一时刻,一个核心上只能执行一个进程,但是在这个进程上,它是能够对进程快速的进行切换的。例如:先运行一下音乐程序,再运行一下视频程序,切换的速度足够快,宏观上是感知不到的,也就让我们感觉是多个进程在同时进行。(3.3GHz,也就是每秒运行33亿条指令)
我们往往也把并行和并发统称为并发。
因此为了给进程分配CPU资源,操作系统里也就有了一个重要的模块:调度器,就负责让有限的 CPU 来调度执行这么多的进程。那么实现这个调度器,也就需要PCB的支持,通过进程调度相关的属性,来完成调度。
4.1 进程的状态
1.就绪状态:随叫随到,进程随时可以到CPU上执行
2.运行状态:正在CPU上运行
3.阻塞状态:简单理解为暂时无法到CPU上执行,暂时无法参与调度,例如,进程正在sleep,或者正在进行密集的IO操作,读写数据。
(这里说明一下IO:IO是对于文件系统或者磁盘的数据进行一个读取,也可能是读取寄存器里的数据,存到磁盘,也就是外存,其实就是一个对数据的读取)
4.2 优先级
进程的优先级也就是在CPU上执行的先后顺序,或者说是执行的频率。(先后顺序,多少)
要理解的就是操作系统进行调度并不是一碗水端平的。
4.3 上下文
操作系统在进行进程切换的时候,就需要把进程执行的"中间状态"记录下来保存。下次再去CPU上运行的时候,就可以恢复到上次的状态,继续执行。而上下文,本质上就是CPU中各个寄存器的值,寄存器是CPU中内置的存储数据的模块,保存的是程序运行过程中的中结果。
保存上下文:就是把这些CPU寄存器的值,记录保存到内存中(PCB中)。
恢复上下文:就是把内存中的这些寄存器值恢复回去。
4.4 记账信息
操作系统,统计每个进程在CPU上占用的时间,执行的指令数目,根据这个来决定下一阶段如何调度。
PCB这里包含的属性是非常多的,这里列举的只是一些比较核心的属性。
1.2 进程的虚拟地址空间
进程的虚拟地址空间,解决的是进程之间相互影响的问题,引入虚拟地址空间,在地址越界的时候,可以及时发现错误。
我们需要知道,在程序中我们一般获取的内存地址,都并非真实的物理内存地址,而是经过虚拟出来的。正如C中的指针,指针指向的内存空间,就是虚拟的内存地址,并非真实的物理内存地址。之所以需要虚拟地址,是因为如果在代码出现问题的时候,导致访问越界,就会有多个线程受到影响。例如下图所示(图中的内存是真实物理内存):进程1的访问地址如果变成了0x1000到0x7000,这时,是进程1出bug了,却会把进程2给搞崩了。正如你的QQ音乐正在运行,但是你的网易云音乐崩了,QQ音乐也得受之印象,所以这就需要引入虚拟地址空间。
所以,就针对进程使用的内存空间,进行“隔离”引入了虚拟空间地址,代码里不再直接使用真实的物理地址了,而是使用虚拟地址。然后由操作系统和专门的硬件设备来负责进行虚拟地址到物理地址的转换。如下图所示:
此时一旦进程的访问越界了, 在硬件设备上进行转换的时候,也就会转换失败,并且反馈错误,这样就避免了一个进程出错。影响到另一个进程的正常运行了。
1.3 进程间的通信
在上述的讲解中,我们也知道了进程间是相互隔离操作的,但有些时候,进程之间是需要进行数据交互的,所以就需要搞一个"公共空间",基于这个公共空间来进行数据交互即可。
而这主要基于两种方式:使用文件,网络。这个在后续文章中也会进行讲解。
2. 线程
在上述的讲解中,我们也知道了引入"进程"这个概念,最主要的目的就是为了解决 "并发编程"这个问题(这里的并发指的是:并发+并行) ,也就是要提高程序的执行效率,充分去利用 CPU 的多核资源。
从理论上来讲,多进程编程,已经可以解决并发编程的问题了,已经可以利用起来CPU的多和资源了,但是,从效率上来看还是没那么高效:创建一个进程,销毁一个进程,调度一个进程,这都会造成比较大的开销,也就是进程消耗资源多,速度较慢。
于是,线程就应运而生,线程也称为 "轻量级进程",轻量表现在:线程把申请资源,释放资源的操作给省下了,在解决并发编程的前提下,让创建,销毁,调度的速度更快。
2.1 线程与进程之间的联系
进程和线程的关系:一对多的关系。进程包含线程,一个进程可以包含多个线程,也可以包含一个线程,然后只有在第一个线程启动的时候,开销是比较大的,因为在同一个进程里,线程之间是共用进程的同一份资源的。(包括 内存 和 文件描述符表 )也就是在一个进程中,线程1 new的对象,线程2 3 4是可以调用的,线程1 打开的文件,线程2 3 4 也是可以进行使用的。
因此,在上文中讲到的进程,也就相当于每个进程里面只有一个线程。
一个进程中有多个线程,每个线程都是独立在CPU上进行调度的,每个线程都有自己的执行逻辑,一个CPU核心上执行的是一个线程,所以,线程是操作系统调度执行的基本单位。
同时,一个线程也是通过一个PCB来描述的。所以说一个进程里面可能是对应一个PCB也可能是对应多个PCB,因此,对于上文介绍的PCB中的状态,优先级,上下文,记账信息,每个线程都有自己所对应的,但在一个进程中的PCB之间,PID都是一样的,内存指针和文件描述符表都是一样的。
所以,可以认为:进程专门负责资源分配,线程来接管和调度相关的一切内容。
2.2 多线程与多进程
对于多线程和多进程的理解,我们也可以举个例子:一个厂房中有一台机器,想要提高生产效率,一种方法就是再购买一块地方作为厂房,然后再引入机器进行生产,另一种方法就是再原来的厂房里,去引入更多的机器。
从中我们也可以看出,多线程相比于多进程而言,是要更高效的。
但是增加线程的数量,也不是一直可以提高运行效率的,CPU 核心的数量是有限的。线程太多,核心数目有限,又会在线程调度上浪费很多的开销。
在每一个进程中,CPU,内存,带宽等这些资源都是多个线程共享的,而创建太多的线程,就会导致资源耗尽,或者多个线程同时抢占同一资源,造成线程安全问题,这也是多线程的弊端。而在多进程中,一个进程只有一个线程,就不会出现这样的情况。
多线程还有一个弊端:如果在一个进程中,一个线程抛出异常,但是没有处理好,就可能导致整个线程挂了,也就是其他线程也无法正常工作了,也会造成刚刚说的现象:QQ音乐正在运行,然后出错了,结果把网易云音乐和其他程序也整挂了。
所以说,多线程虽然提高了运行速度,但也是需要付出一定代价的。
多进程在我们日常中也很常见:Chrome浏览器使用多进程编程,每一个页面就对应一个进程,目的就是为了防止一个页面挂了,导致其他页面也挂了。
从而我们也可以看出,对于安全问题,发生情况一般都是:多个执行流都在访问同一个共享资源。
线程模型:天然就是资源共享的,多个线程同一个资源,就很容易会触发安全问题。
进程模型:天然是资源隔离的,单个进程访问单个资源,就不容易触发安全问题。但也并不是不会触发,在进程间进行通信的时候,多个进程就需要访问同一个资源,这个时候也是可能会触发安全问题的。