本章将会介绍进程的一些概念:冯诺伊曼体系结构、进程是什么,怎么用、怎么表现得、进程空间地址、物理地址、虚拟地址、为什么存在进程空间地址、如何感性得去理解进程空间地址、环境变量是如何使用的。
目录
1. 冯诺伊曼体系结构
1.1 是什么
这个体系结构在我们计算机如笔记本、服务器,都是遵守这个体系结构。主要就是三部分 CPU 、 存储器(里面存放的是物理地址)、IO设备。
1.2 结论
通过理解了这个结构,我们需要知道,我们的 CPU 在进行计算的时候不是直接与输入输出设备进行连接交互,是与存储器进行交互。所有的外设,有数据的载入,只能载入到内存当中。
1.3 操作系统
概念:任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。简单来说就是进行软硬件结合的软件。对上服务用户(提供一个良好的执行环境),对下管理好各种硬件。
1.4 搞管理的软件 + 计算机的结构
如何理解他是搞管理的软件?,举一个例子:在学校里面,我们见不到校长,但是校长会对我们进行管理,如何理解这个管理?就是虽然没有见面,但是校长手里有我们的各种数据、例如:学号、成绩、宿舍号、专业、挂了几科,这些数据是校长进行决策的一个依据。校长就是根据这些数据来进行决策,如果是挂的科比较多了,就要警告,处理。所以我们就相当是各种硬件(被管理者),然后校长(管理者)就是 CPU 进行决策。他是怎么拿到数据的就是通过辅导员拿到数据。
那么管理者如何拿到数据,通过辅导员,在计算机系统中相当于驱动。
然后我们操作系统对于用户想要进行的操作需要经过 shell(因为操作系统不相信我们用户是安全的),提供的接口来调用系统内部的函数(系统调用接口,在 LINUX 就是 c 式的接口)。对内保护自己,对外提供服务。
1.5 总结
通过上面的例子,我可以推断出。校长进行管理一个学生的时候需要对他的信息继续描述变成一个结构体,然后将结构体通过链表或者是其他的数据结构连接到一起。所以对于同学的操作就变成对于结构体的操作。我们计算机也是需要这样的。
所以!计算想要管理硬件需要两步:1. 首先进行描述,将相关的内容变成(struct)结构体的形式存在 2.随后进行组织,使用链表,其他高效的数据结构存放起来。
2. 进程的基本概念
2.1 什么是进程
进程是:一个运行起来(加载到内存)的程序/内存当中的程序。我们先来理解一下程序,就是写在磁盘里的二进制程序。执行就要加载到内存当中。
一个cpu计算机可能许多的程序需要被执行,所以就需要进行描述 + 组织!继续描述时需要通过 PCB(进程控制块)struct task_struct{} 。在内存当中会对于没有个程序提供一个进程控制块。
通过上面的描述,我们对于进程的管理,转化成对于链表的增删查。进程 = 内核数据结构(表示当前进程的属性,例如:挂起、阻塞、等待等状态) + 进程对应的磁盘代码以及数据(就是相关内容,拷贝到内存)。
2.2 为什么会有进程
一方面:需要进行管理,一个 cpu 只能执行一个进程,所以需要进行管理继承。另外一方面
PCB 的作用就像一本进程的“身份档案+进度卡,让操作系统了解进程到达了程度了。
2.3 见见进程
简单的进程,在Window里面。在Linux里面可以使用 ps ajx | grep '文件名'可以查看我已经打开的进程。
在Linux 中我先书写一个进程,然后再Xshell中显示一下,如下图所示:可以见简单的看到有一个进程正在执行。
2.4 通过系统调用查看
可以使用两个系统的函数,来查看当前进程的标识符 pid(当前进程进程) 与 ppid(父进程); pid 很好理解,我们为什么需要有 ppid ,因为一个进程做的事情怎么样了,肯定是需要有一个 leader 知道他干的怎么样了,如何就需要给当前进程一个 ppid(父进程)。
2.5 fork()函数
fork()是调用系统函数,实现创建一个子进程。他有两个返回值一个,对应不同的进程有不同的返回值,例如:如果是子进程返回0,对于父进程返回他的子进程(为什么要这样?因为父亲就一个爹,所以不需要特别的说明,但是父进程有好几个孩子,所以需要返回一个孩子的pid,说明是自己的大儿子,还是二儿子过来然我抱一下);
代码表示的结果为:
#include <sys/types.h>
#include <unistd.h>
int main()
{
// printf("pid: %d\n", getpid());
// printf("ppid: %d\n", getppid());
int ret = fork();
if(ret == 0)
{
printf("ret:%d\n", ret);
printf("我是子进程,我的pid为:%d, ppid:%d\n",getpid(), getppid());
}
else
{
printf("ret:%d\n", ret);
printf("我是父进程,我的 pid 为:%d, ppid为:%d\n",getpid(), getppid());
}
return 0;
}
到这里很多的同学,可能会有点疑惑为:为什么一个变量ret,会同时进行两次打印,也就是为什么会有两个结果。这个就跟进程有很大关系。
可以这么理解,我们的子进程,在创建的时候会进行写时拷贝,开辟一模一样一样的空间,将父进程的部分内容拷贝过来,然后进行调用,通过管理PCB(struct结构体),经过页表,就会单独的进行子进程的代码与数据。
3. 进程的状态
3.1 为什么?
想要了解正在运行着的进程是什么意思?就要明白进程的状态。我们为什么有要对进程有状态的规定呢?
因为一个cpu只能进行一个进程其他的进程就需要进行等待的操作,所以其他的进程就要处于等待或者是其他的状态。对于这个进程我们要能够区分两个概念,就是一个是操作系统哲学当中的进程状态的概念,以及真正的在 Linux 当中使用的两个概念。
3.2 需要了解的三个状态
3.2.1 运行状态
一个 cpu 就有一个运行状态,一个进程以 PCB 的形式存放在这个cpu的运行队列里面。
3.2.2 阻塞状态
CPU的运行队列中,在运行一个程序的时候不只是需要访问 CPU 还需要访问外设,但是外设又因为速度相比 CPU 比较慢所有需要在外设中重新开辟一个队列,将接下来可能会运行的程序存放到这个队列里面,这样的操作就叫做堵塞状态。
3.2.3 进程状态的本质
本质就是进程 PCB 结构体在不同的队列之中。我们所说的进程是运行状态不是进程正在运行,而是存放在 Running 这个结构体里面(这个结构体有head*指针,指向进程控制块)。在其他的状态也是这样。
3.2.4 挂起状态
由于内存的空间是有限的所以,遇到内存空间爆满的时候就需要,将一些 PCB 对应的核心代码放到存盘。等到需要的时候再将它取出(概括来说就是数据的换入、换出。)。
3.3 在Linux下的进程状态
3.3.1 规定的几种状态
为了方便好看,这里就不再进行码字,直接放上这张图片。Linux 里面的进程相当于是对于操作系统里面进程的详细化,实例化。
还有的状态为:+R,+S。有没有 + 号的区别在于 有 + :表示前台进程,输入其他命令不会显示的命令行上,无 + : 表示后台进程,当进程在进行的时候,可以在命令行上使用其他的指令。(相当于有 + 之后变成了高级模式不能执行其他的指令,个人理解可能有错🐱)
3.3.2 进程的查看与孤儿进程
我们想要查看进程可以使用 ps axj | grep。进行查看状态。接下来我会写一个程序来体现进程状态。我写一个持续打印的代码,其中的状态是 S+,阻塞状态,为什么会这样?因为使用外设打印的非常的慢,所以当 CPU 发出指令的时候外设的打印对列布满了进程,每个进程都处于等待的状态之中。
我们先来感性的理解一下什么是孤儿进程:从字面意思可以看出,孤儿就是没有父亲,也就是没有ppid,这里的没有是指父亲创建子进程后父进程被杀掉了,但是他的子进程还在运行中等待着子进程的回收。但是我们的进程是由 PID = 1(是由操作系统提供的) 的所以这个子进程会被 PID = 1 进程进行领养(使用 kill -9 杀死父进程)。从图中可以看到父进程消失了,然后子进程的ppid 变成 1,被领养了。此时这个子进程就变成了孤儿进程。
3.3.3 僵尸进程
是什么:感性 + 理性理解:僵尸进程就是父进程创建了一个子进程,然后子进程去执行操作,但是子进程退出了,但是父进程没有回收子进程的信息,也就是 Z 状态。好比:父母让我们去买酱油,最后父母是需要知道我到底买没买上,我最后的结果是什么,如果不告诉的化就会造成僵尸进程。解决为使用 wait(),来回收子进程,判断子进程的信息,比如是正常退出,还是有异常退出,信息都存放在wait对应的参数列表里面。
这个僵尸状态的解决,到后面会进行讲解,此处不进行过多描述。从图片中可以看出进入了僵尸状态。
4.总结
以上是对于进程状态的回顾,下一章将会讲解进程的优先级以及进程的切换🥵🥵🥵。这个文章用于我的学习记录,如果是有其他的错误还请批评指正。如果对你有帮助还请给我点个赞👍👍👍。