认识冯诺依曼系统

冯诺依曼体系的五大组成部分:

输入设备

存储器:特指内存,不包括外存

计算器

控制器

输出设备

从这个架构可以看出来的是所有设备都只能直接和存储器传输数据信号。

不妨来想一下,平常我们在电脑上写的代码经过了怎么样的步骤?

硬盘 -> 内存 -> cpu -> 内存 -> 屏幕/硬盘(文件)

原因:我们写的代码是保存在某个文件中的,也就是说在硬盘上,在执行的时候,硬盘上文件的内容被拷贝到内存中,并且内存中会生成进程控制器(PCB),接着程序开始执行,并且数据信号传输到了CPU中进行处理,处理完之后的目标数据同样会先保存在内存中,接着就是看你的操作了,是要输出到屏幕上,还是输出到文件中。

这篇博客旨在讲析进程:进程由两部分组成  PCB拷贝到内存中的代码、数据

进程是由什么管理的?

所谓进程到底是什么?

不妨带着这些问题进行学习。

操作系统:任何计算系统都包含的一个基本的程序集合,称之为操作系统(OS)。笼统的理解,操作系统包括:

1)内核(进程管理、内存管理、文件管理、驱动管理)

2)其他程序(例如函数库、shell程序等等)

设计OS的意义:

1.对下,与硬件交互,管理所有软硬件资源

2.对上,为用户程序(应用程序)提供一个良好的执行环境

可以把OS想成一个管理者!

所谓正在执行的活动:程序动态执行过程中,实时变化的、记录当前运行状态的所有 CPU 上下文与运行时行为的总和

正如我们之前所说的进程由两部分组成  PCB 和 程序代码、数据,不难推理出正在执行的活动代表的就是PCB。

在下面的学习中你就把进程类比成一次实例操作。

PCB(process control block) 进程控制块

这个本质上就是一个结构体!!!

在Linux系统下,这个结构体的名字是 task_struct 

进程之间的联系就相当于PCB之间的联系,而PCB之间是通过双链表来联系的

进程的查看:

正如我们之前常说的Linux下一切皆是文件。自然也包括了进程。

1.在目录下查找进程文件

在执行进程的时候,会在/proc/目录下创建相应的文件

左边的的一个数字就代表一个进程

2.使用 top 来查找进程

退出按 q

3.使用 ps 来查找进程

ps aux 

下面我执行一个名为test的程序来进行演示.

每一秒打印一遍hello world;

我们来分析一下这个样例:

首先我执行了一个死循环的程序,可是为什么会有两个程序呢?

仔细看一看我的指令: ps aux | grep test 

grep 是不是也是一个进程!所以会打印出来,如果你看不顺眼的话,可以执行 ps aux | grep test | grep -v grep

开始分析这些数字是什么

输入指令 ps aux | head -1; ps aux | grep test | grep -v grep;

可以看到这里有两个关键词

PID :可以类比成编号,每一个进程都会被进程随机赋予一个编号(名称)

STAT:状态

一想到有编号就会想着去查一下编号为0 或者1 的是什么神仙,对吧。

但是编号只有1的,没有0的

可以看出这个东西是系统的进程

如何在代码中开一个进程?

pid_t fork()

这里先讲一下,所有进程都是由其他进程创造出来的,创造者称之为父进程,被创造者称之为子进程。

子进程的ppid 就是父进程的pid,ppid可以理解成父亲的pid

代码

可以看到的是子进程的ppid是父进程的pid。

至于fork的细则,在后面会详细讲解

在这里我们要知道的是:利用fork()能实现在代码中创建新进程

id返回值的问题:

对于父进程返回值是创建子进程的pid

对于子进程的返回值是 0;

通过返回值的不同可以分流,实现对父子进程的操控

进程状态:

状态R:

running表示运行状态,说明这个进程正在运行/或者在运行队列中(准备运行)。

状态S:

表示这个状态缺少一个SIGNAL,也就是在等着某一件事件的完成,好比scanf中等待你从键盘上输入。

状态D:

这个状态和S状态相似,唯一区别就是这个状态无法进行手动结束,只有等待目标事件完成之后,才会结束。

举个例子,对文件进行写入的时候,写入有成功和失败。设想一个情景,假设有一个进程的操作是对文件进行写入,当这个信息写入并且放回结果之前,这个状态就是D,就算是操作系统也不能结束这个进程,因为一旦强制结束,这个写入操作就会出现问题

状态T(停止状态):

对一个可停止的进程发送SIGSTOP信号来停止进程,这个状态就是 T,同样可以发送信号来重新开启这个进程。

其实你在你执行的程序中按下的 CTRL  + Z ,就是使这个程序进入 T 状态。

状态X:

死亡状态,这个表示这个进程已经结束了并且内存已经释放了,你无法在任务列表中看到这个状态

状态Z(僵尸状态):

这个状态就是X之前的状态,可以理解成在进程结束的时候,操作系统要知道这个进程为什么结束,此时该进程还没有被释放的状态称之为僵尸状态,这个状态直到读取了返回状态之前,都是Z状态。

但是要是这个状态一直没有读取,就会造成内存泄漏

孤儿进程:

这个进程表示如果父进程提前退出,子进程进入Z状态 ,此时子进程的状态称之为 孤儿进程 此时如何处理。

这个进程的问题就是,只要没有读取子进程的返回信号,就会一直处于这个状态,但要是父进程都结束了,就没有进程来读取子进程的返回信号,不加以处理就一定会造成内存泄漏,所以一旦事情变成这样,就需要给这个孤儿进程重新认领一个父亲。

处理方案是 孤儿进程会被1号进程 init/systemd进程领养,后续有该进程进行回收操作

进程优先级:

前提知识:cpu是有限的,应当对要占用cpu资源的操作按优先级来执行,也就是cpu资源分配的先后顺序就是进程的优先级(priority)

UID 表示进程的执行者,你也可以查看一下自己的uid

ll -n 表示按照数字来列举出来,因为计算机也只认知二进制

PRI 就是进程优先级默认是80,NI 表示进程可被执行的优先级的修正数据,nice值的取值范围是 -19 - 20 

PRI(new) = PRI(默认) + NI

当nice值为负数的时候,优先级值变小,优先级变高;同理,当nice值为正数的时候,优先级值变大,优先级变低。

所以我们在修改优先级的时候,都是修改nice的值!!!

修改nice值的方法:

top

进入top后按 “ r "  ——r 代表renice ,表示改变nice值

输入进程pid ——表示指定进程

输入修改的值

进程切换:

进程在运行的时候不是说一定要把某一个进程给运行完才能运行下一个进程,否则你就无法在刷视频的时候能够使用qq/wechat。所有的进程都有一个时间片(存储运行事件)。

那为什么我们感知不到进程一直切换?因为太快了!

既然你说每个进程在执行的时候不一定是执行完的,那运行中产生的数据,以及下一次从哪里开始继续执行进程又如何知道?

其实是通过寄存器来进行记录,可以看到下面pcb结构中保存了很多寄存器。

接下来讲这些进程如何进行切换?

linux中所有要运行的进程都会被放进一个叫调度队列的双链表中!

这个是内核进程O(1)的调度队列

我们先看到蓝色框框的queue,这个数组队列的特性是first in first out,所以先进去的就会先执行,这个就是进程优先级的值!queue的下标就是进程优先级的体现,可是前面讲的PRI(new) = PRI(默认)+ NI;ni的值属于  [-19 - 20].,这里可以看到我们能够进行操控的优先级只有40种,那为什么这里queue却有140个空间?

这里需要仔细分析一下优先级:

1. 实时优先级  0 ~ 99

2.普通优先级 100 ~ 139

实时优先级用户没办法进行分配,我们也不关心。为什么?以智能汽车为例,如果前面预测会发生车祸,汽车就不会执行放音乐、空调等进程,而是直接执行预警的进程,总不能等你都撞上了才预警你。

所以我们不用去关心实时优先级,可是又存在一个问题 我们的pri的值是属于 [ 61,100] 的,如何实现对应下标是 100 - 139 ?

说白了这就是一个映射61 -> 100 ... 100 ->139..

从我们前面优先级的图片可以看出来进程存在很多相同的优先级,那这个如何安排?

和哈希桶类似,相同优先级的会以双链表的形式相互连接。

接着看到bitmap,这个就是位图,标记queue哪个优先级下标存在与不存在,以一个bite 的0 ,1 来表示是否存。这样就提高查找非空队列是否为空,极大提高了查找效率。

从上面的图片中我们看出来prio_array_t 有两个元素 array[0] 和 array[1],前者位活跃进程,后者位过期进程,爱无疑问这两个东西指向的结构完全相同(红色框和蓝色框的东西完全相同),那问什要弄完全相同的结构?思考一下,当一个进程的时间片结束后,我们是不是运行完一轮之后还要运行这个进程,那这个进程放在哪里?对就是放在另一个进程中!等所有事件片都耗尽了,把array[0] 和 array[1] 进行交换,这样就是实现了进程交换!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐