进程 / 任务
进程就是操作系统提供的一种软件资源。也就是说,进程是系统分配资源的基本单位。
多任务操作系统是当下的主流。
我们现在所用的系统都属于 多任务操作系统 ,同时可以运行多个任务,比如我用着画图板和浏览器,还在使用qq音乐听歌。这些运行中的程序,就可以称为任务,也叫做进程。
当然,也有单任务操作系统,例如“老人机”。单任务操作系统不能后台运行,也就是说:“要想执行另一个程序,就需要先退出前一个程序”。
我们目前使用的智能手机,能够来回切换,其实也是有在后台运行的。
对于多任务操作系统来说,进程的概念就非常重要了。
可以认为,计算机中的每一个进程在运行时,都需要给它分配一定的系统资源。
进程是系统分配资源的基本单位。
进程在系统中是如何管理的呢?
我们操作系统的进程管理,其实就分为两步:
- 描述:使用类 / 结构体这种方式,把一个实体熟悉给列出来。
- 再组织:使用一定的数据结构,把这些对象 / 结构体串到一起。
例如,学校要对学生进行统一管理,那么就需要:
- 先描述:把一个学生的各种属性都给列出来;
- 再组织:使用一定的数据结构,把所有学生的信息整合到一起。
再细分:
描述
操作系统一般是使用 C/C++ 实现的,例如 Linux,因此就可以使用结构体来表示进程信息,我们给它起个名字,就叫 PCB(进程控制块) 吧。(注意,这里的 PCB 是 Process Control Block,不是你想到的那个)
PCB 是操作系统学科的通用概念(不管在 Windows 上,还是在 Linux 上,都适用)。在 Linux 上,PCB 就是一个 struct task_struct
这样的结构体。
组织
以 Linux 为例,我们使用链表这样的数据结构来把若干个 task_struct
(前面讲的 PCB),给串起来的。
当我们看到任务管理器中的进程时,意味着系统内部就在遍历这些链表,并打印每个节点的相关信息。
如果运行一个新的程序,于是就在系统中多出一个进程,多的这个进程就需要构造一个新的 PCB,并添加到链表上。
如果某个运行的程序退出了,操作系统就需要把对应的进程的 PCB 从链表中删除掉,并销毁对应的 PCB 资源。
学过数据结构应该会对这些很清楚。
PCB核心属性
PCB 这个结构体,是一个非常庞大的结构体(有上百个属性),不过这里我们只需要关注一些重要属性。
pid 属性
进程身份标识,通过一个简单的不重复的整数来进行区分的,系统会保证 pid 的唯一性,针对某个进程来操作,就可以通过 pid,pid 一般是从 1 开始累加的。
一般来说 pid 是一个 32 位整数
我们想要结束系统中正在运行的某个进程,就先选中某个进程,然后点击结束任务,此时任务管理器获取你选中的这个进程的 pid,然后调用系统 api,把这个参数(pid)传进去,从而完成结束进程的操作。
为了描述进程使用哪些内存,我们需要引入内存指针的概念:
内存指针
什么是内存指针?
通常是一组,进程在运行过程中需要消耗一些系统资源,其中内存就是一个重要的系统资源。整个系统的内存不是我们决定的,而是操作系统给我们分配的。
可以把内存想象成一个旅馆,这个旅馆里面有很多个房间,所谓的申请内存,就是你跟老板说开一个房间,这时候就是申请到了一个空的房间号(这个房间号是我们不能确定的)。
那么,如果房间没申请,我们能进去嘛?
一方面,你没申请你也进不去,另一方面,你强行破门而入,后果不可预知。
每个进程都必须使用自己申请到的内存
内存指针就是为了描述这个进程都能使用哪些内存。
一个进程润(run)起来时,需要有指令和数据,这些都是要加载到内存中的,进程需要知道哪里是存的指令,哪里是存的数据。
指令和数据是在进程跑起来之前加载的
比如我们双击运行一个 exe 文件,就是运行进程,这个过程中,就是系统先把指令和数据加载到内存中,然后再创建进程,让进程执行。
指令一般不会很大,但是数据是有可能会非常大的,比如一个服务器程序,启动过程至少要消耗 10 分钟,这十分钟里要加载 100GB 的数据到内存中。
虽说需要加载到内存,但是总有断电的时候,那么我们该如何操作呢?这就需要引入 文件描述表 了。
文件描述表
我们的进程经常要访问硬盘,操作系统将硬盘进行了封装,比如 Linux 的 /dev/
目录下,我们可以找到 nvme0n1
,或是 sda
、sdb
等。
我们所说的存储器,就是内存加外存,内存断电会丢失数据,其中外存包括不限于,硬盘、软盘、光盘和 U 盘等,断电不会丢失数据。
一个进程想要操作一个文件,就需要先“打开文件”,就是让进程在文件描述符表中分配一个表项(构造一个结构体),表示这个文件的相关信息。
进程是操作系统分配资源的基本单位。内存和外存这些都在 PCB 中体现。
CPU资源
CPU 是最复杂的,一个进程消耗的 cpu 资源,一般都是指 CPU 占用率,每个 CPU 的核心(一个 CPU 可以有多个核心)就好比一个舞台,进程要执行的指令就是演员,演员要上台才能进行表演。
我们在资源管理器所看到的 cpu 的百分数,其实就是你的进程在 CPU 舞台上消耗的时间的百分比。
如果有一个进程把 CPU 吃满(100%)了,就意味着这个进程把 CPU 霸占了。
在一个核心中,同一个时刻,同一个舞台上,只能有一个演员
分时复用
我的这台电脑上的 CPU,就有 16 个逻辑核心。
我系统上的进程远远不止 16 个!!!但是只有 16 个逻辑核心,该怎么分呢?
这就需要让多个演员轮流上台,(假设我们现在的电脑只有一个核心),如果有多个核心,就可以不同舞台同时刻安排演员。
这就是我们所说的 分时复用(并发)
注意,这里讲的和我们之前所说的同时运行是不冲突的,
(CPU 频率往往都是 GHz,一秒钟能够执行几十亿条指令的,由于执行速度太快,我们肉眼感知不到)
进程的状态
用于描述某个进程是否能上 CPU 执行。
比如通过 Scanner 等待用户输入的内容。用户什么时候输入,完全不可控。
进程有很多种状态:
- 就绪状态:随时准备上 CPU 执行。一呼即应。
- 阻塞状态:这个进程当前不方便上 CPU 工作,不应该调度它。直观上理解就是进程卡住了。
通过上述两种状态,操作系统就可以达到 等待 和 挂起 的效果。
优先级
多个进程等待系统调度,谁先调度和谁后调度,谁调度的时间长,谁调度的时间短,这都是操作系统可以进行调配的。
总结
现代计算机的执行,往往是 并行 + 并发 同时存在。
多个 进程 是 并行执行 还是 并发执行,都是看系统调度,系统如何调度取决于系统的调度器模块,这我们干预不了。
因此,我们往往就把 “并行” 和 “并发” 统称为 “并发”,后续相关的编程方式,我们一般称为 “并发编程”。