【Linux操作系统】进程基本概念

【Linux系统篇】进程基本概念

😊本文为小邢原创,CSDN首发
📅发布时间:2022/7/22
🙌欢迎大家👍点赞❤收藏✨加关注
🎓本文大约6400字左右
🙏博主水平有限,如有错误,还望告诉博主,万分感谢!
🚩有什么问题也可在评论区一起交流哦!

· 浅谈操作系统

操作系统是什么?

操作系统(Operator System,简称OS)简而言之就是一款专门针对软硬件资源进行管理工作的系统软件。

为什么需要操作系统?

对下管理好软硬件资源,对上给用户提供稳定的、高效的、安全的运行环境。
在这里插入图片描述

如何理解操作系统

管理

这里先给出结论:

先描述,在组织!!!

操作系统的管理就是先描述被管理对象,再通过特定的数据结构将被管理对象组织起来。

如何理解OS的管理呢?

举个栗子:

在公司内部,老板和员工是不直接打交道的,老板想要管理你,只有对你下决策。但是下决策需要依据,这个依据就是你的各种数据信息,这些数据信息就是对你的描述。但是老板和员工不直接打交道,老板是如何得到员工的数据信息的呢?通过你的组长!所以员工和老板的交流变成了你和组长的交流。当老板得到了员工的数据后,对员工的管理就变成了对员工数据的管理。公司里有很多很多的员工,老板想要高效的管理这些员工就需要一些结构将这些员工组织起来,所以需要一些特定的数据结构。则老板管理公司员工就变成了对组织员工信息的数据结构的增删查改!
在这里插入图片描述

先描述,在组织可作为我们学习操作系统的一种理念,适用于一切操作系统下的管理工作!!

提供服务

首先要说明,OS不相信任何用户,因为OS在计算机里很重要,一旦遭到一点破坏都会造成很严重的结果,所以OS不相信任何用户。那OS是怎样为用户提供服务的呢?

大家想想,日常生活中,有没有什么机构或组织不相信任何用户但又要为其用户提供服务的呢?

答案是银行,银行不相信任何用户,所以银行的安保和防盗做的就很好。他们为用户提供服务的方式就是开窗口,用户和银行通过窗口交流。

所以类比银行,OS与用户打交道就是通过系统接口来和用户打交道。

但是对于OS提供的接口,要直接使用难度较大,所以会有一些大佬将OS的调用接口进行软件封装,以第三方库的形式供我们使用。

在这里插入图片描述

· 进程

什么是进程呢?从字面意思来理解就是加载到内存里面的程序,但这样理解是不正确的。下面我们先来介绍OS是如何管理进程的,我们再来引出进程的概念。

OS如何管理进程

系统中无时无刻有大量的进程,对于这些大量的进程OS该如何管理呢?

先描述,在组织!!!

如何描述?

OS中通过一个叫做进程控制块的东西来存放了进程的一些数据信息,我们把进程控制块叫做PCB(Process Control Block)。

在语言层次上,若我们要描述一个事物或对象,我们一般通过结构体或者类来实现,所以站在语言角度,PCB就是一个结构体!

struct PCB{
   	//进程属性信息
};

在Linux中的 PCB 叫做 task_struct。

进程 VS 程序

我们知道,程序就是存放在磁盘中的文件。对于磁盘中的可执行程序要想被执行就必须加载到内存中,那加载到内存的程序真的就是进程了吗?

先给出结论:进程 = 程序文件的内容 + 相关的数据结构

刚才我们知道,OS在管理进程时不会直接对程序的内容进行管理,而是对进程的相关属性信息进行管理。所以,当一个可执行文件加载到内存时,系统会开辟一块空间作为该程序的 PCB ,当 PCB 描述好该可执行文件后,OS 管理的对象就不在是进程的内容了,而是该进程的 PCB。

在这里插入图片描述

最后我们得出结论:

系统在对进程进行管理时,先用 PCB(task_struct )来描述一个进程的所有属性信息,在对 PCB 进行组织,这样OS直接对 PCB 进行管理,而不是对相关程序做管理!

有了 PCB ,所有进程的管理任务与进程对应的程序毫无关系,与进程所对应的内核创建的PCB强相关!!!

且对于进程所对应的代码和数据,是 CPU 所要关心的东西,CPU在处理完代码和数据后,OS在对进程进行管理和维护时,也不关心进程相关的代码和数据,只关心进程对应的PCB!!!

task_struct中有关进程的属性信息

Linux 操作系统下的 PCB 为 task_struct ,我们以 task_struct 为例来介绍操作系统中的 PCB。

对于 task_struct 来说,其中包含的有关进程的属性信息非常多!主要有以下属性:

标示符: 描述本进程的唯一标示符,用来区别其他进程。

状态: 任务状态,退出代码,退出信号等。

优先级: 相对于其他进程的优先级。

程序计数器: 程序中即将被执行的下一条指令的地址。

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息

我们用指令 ps 可以查看进程的一些相关属性信息:

ps axj

在这里插入图片描述

对于这些属性,现在我们先介绍标识符、进程状态、进程优先级、进程上下文数据,因为这几个属性信息对于我们理解进程有很大帮助,其他的之后会介绍。

进程标识符

标识符就是一个标识一个进程的数字或是序号,用于区别其他进程。在Linux系统中,进程标识符用 pid 表示。

我们若想获得进程的 pid 我们可以使用系统函数 getpid()。

在这里插入图片描述

这里的 pid_t 是一个有符号整数,即进程的pid。

现在我们就来使用一下这个函数:

我们写一个死循环,这样方便我们查看进程的 pid 和 ppid:

在这里插入图片描述

执行程序,我们在另一个会话中来查看进程:

查看进程指令:

ps aux

在这里插入图片描述

[ 我们查看进程时可以使用行过滤工具 grep 来查看我们想要查看的进程,其中 -v 选项是过滤掉我们不想看的信息,因为 grep 也是一个进程。]

我们发现,myproc的父进程是 bash ,这与 shell 运行原理对应。(命令行上运行的命令父进程基本都是 bash)

进程状态

首先,我们要明确进程的状态存放在 task_struct 里面,进程状态的意义在于 OS 可以通过进程的状态对进程实施特定的功能,比如调度。

Linux中,进程状态主要有:运行状态(R)、睡眠状态(S)、深度睡眠状态(D)、暂停状态(T)、追踪状态(t)、死亡状态(X)、僵尸状态(Z)。

下面介绍具体状态:

运行状态(R)

首先要问一个问题:

处于运行态的进程一定占用 CPU 资源吗?

答:不一定。

实际情况是,当一个进程处于 R 状态,则它的 task_struct 现在处于运行队列中,随时可以被执行。

在这里插入图片描述

睡眠状态(S)/ 深度睡眠状态(D)

当进程执行某些任务时,任务执行条件不具备,进程需要进行等待时,则处于 S 状态或者 D 状态。

比如,当前进程要使用磁盘或者网卡资源,但是现在磁盘和网卡正在被使用,则当前进程会进入 S 状态,在等待队列中等待。当磁盘或网卡使用完毕时,该进程就会转为 R 状态,进入运行队列等待被CPU运行,即进程等待成功。
在这里插入图片描述

[ 不要以为进程只会等待 CPU 的资源,等待 CPU 资源的队列叫作运行队列,等待其他资源的队列叫作等待队列!]

如上我们可以得到一个结论:

进程在运行时会根据运行需要,在不同的队列里!!!在不同的队列里,进程的状态是不一样的!!!

我们把从运行状态的 task_struct (run_queue)放到等待队列的过程叫作挂起(等待 / 阻塞)

从睡眠状态的 task_struct 从等待队列放到运行队列的过程叫作唤醒进程

对于 S 状态我们好理解,但是怎样理解 D 状态呢?

场景:

现在有一个进程是让磁盘写入 10G 大小的数据,磁盘说:“好,进程你在这里等着,我们现在就去写入,写好了我把是否写入成功的信息告诉你。”则这个进程就一直占用着磁盘资源。这是 OS 刚好路过,发现你这个进程啥事也不做,还占着资源,那 OS 就把你杀掉了。现在麻烦了,若磁盘写入成功,回来就找不到刚才的进程了,但至少写入成功,问题不大。但若写入失败,那就麻烦大了,进程说:“都怪OS,没事杀我干嘛!”,OS说:“谁让你啥事不干!”。所以出现了找不到谁出错的问题。

出现此问题的根本原因就是进程被OS杀掉了,所以我们应该让该进程处于 D 状态,进程一旦进入 D 状态,OS都杀不掉。

暂停状态(T)/ 追踪状态(t)

当一个进程正在运行时,我们可以发19号信号,让这个进程处于暂停状态:

kill -19 [pid] :暂停

kill -18 [pid] :继续

在这里插入图片描述

可以看到,当我们跑起来一个死循环后,我们一直在执行打印语句,当我们通过 kill 指令发送19号信号时,进程就被我们暂停了,进入了T状态。

这里还有一个问题,也行大家会有疑问,为什么当前进程一直在执行,我们看到的却是 S 状态?

那是因为 CPU 太快,而显示器太慢的原因。我们的进程最终要在显示器上打印东西。在 CPU 看来,显示器(外设)的运行速率太慢,CPU已经准备好了,而显示器还在打印上一条数据,所以 CPU 只能等待显示器,故处于 S 状态。

当一个程序正在被调试时,对应的进程task_struct就处于 t 状态。

在这里插入图片描述

死亡状态(X)

当一个进程被OS杀掉后,则进程处于死亡状态,目的是为了让系统回收进程相关的内核数据结构和代码数据。

X 状态很难被演示,因为进程被杀掉后会快就被释放了。

僵尸状态(Z)

场景:

一个人在大街大街上跑步,突然就倒地不起了。你就在旁边,你靠近一看,发现这人已经没了,你立马报了警。警察来了之后封锁了现场,并让医生为死者做死亡鉴定,鉴定好后,警察通知有关部门来处理现场,把死者运走。问什么不直接运走呢?因为要了解死者死亡的原因,给社会一个交代。

所以当一个进程死后,要对该进程做死亡鉴定,找出死亡的原因。一个进程在被鉴定死亡原因的过程中,进程处于僵尸状态

要验证 Z 状态,我们可以创建一个子进程(有关子进程介绍在本博客后面部分,大家可先看创建子进程部分再回来看着一部分内容),因为一个子进程死亡后由父进程来回收,所以当我们把子进程杀掉后,不让父进程回收,则子进程进入 Z 状态:

在这里插入图片描述
在这里插入图片描述

当我们把子进程杀掉时,父进程还在睡觉,所以子进程没人来回收,所以出于 Z 状态。

孤儿进程

当子进程还没死,父进程就已经死了,此时该子进程就为孤儿进程,孤儿进程会被 1 号进程领养,1 号进程为OS。

在这里插入图片描述

[ 有一个小细节,状态后面有的加 +,有个没有。有 + 表示进程在前端运行,没有表示在后台运行,要想让一个程序在后台运行,在执行的时候在命令行后面多输入一个 & 即可。在后台运行的程序 Ctrl + c 不能终止,只有 kill -9才能杀掉。]

最后,我们将这几种状态与书本中的与之对应,这样就加深了我们对书本中的知识的理解,所以学习操作系统一定要针对特定的一款操作系统来学习才能学好!!!
在这里插入图片描述

上下文数据

在这里插入图片描述

进程的代码可能不能在很短的时间内完成,且我们的电脑一般只有一个CPU,一个时间段上CPU只能执行一个进程,但是我们在使用的时候是能感受到多个进程同时在进行的,这样的现象本质是由于CPU运行进程的快速切换实现的。CPU运行是有时间片的,即每个进程单次运行的时间是有规定的,不等超过一个规定的时间。由于时间片的时间很短,短到我们根本察觉不到,所以我们感受到的就是多个进程同时进行。

那问题来了,当前进程正在运行,CPU中的寄存器存放着当前进程的临时数据,当该进程运行到达时间片时,CPU 要切换其他进程运行,其他进程同样需要 CPU 里的寄存器资源,结果当之前的进程再被运行时,之前的临时数据已经被覆盖了。所以为了解决这个问题,task_struct 中需要上下文数据来保护和恢复上下文,暂时存放临时数据。

在这里插入图片描述

结论:

当 A 进程运行完时间片时,A 进程的 task_struct 会存放 A 进程的临时数据,当 A 进程再一次获得 CPU 资源时,A 进程的task_struct 中的上下文数据下入 CPU 的寄存器中,这样就可以接着上一次没执行完的继续执行了。

通过上下文数据的保存和恢复上下文,我们知道进程执行是会被切换的。

进程优先级

进程的优先级,从字面上理解就是进程被执行的顺序,即 CPU 分配资源的先后顺序。

为什么进程的 task_struct 中要有优先级呢?

主要还是 CPU 资源缺乏,引入优先级本质就是一种资源分配的方式。

我们可以使用指令来查看进程的优先级:

ps -al :可以查看进程优先级

在这里插入图片描述

在Linux操作系统中,PRI 表示进程的优先级,PRI 值越小,进程的优先级越高。且进程优先级是可以被修改的,修改进程优先级是通过NI(nice)值来修改的。

PRI(new)= PRI(old)+ NI

老的 PRI 值一般都是80,每次修改老的 PRI 都是80,在80的基础上修改

我们一般很少自己去修改进程的优先级,但为了测试修改优先级,我们可以用 top 指令来修改指令的优先级。

下面我们写了一个死循环,便于我们测试:

在这里插入图片描述

当我们使用 top 指令修改优先级时,具体步骤如下:

1)先输入 top,回车

2)输入 r,之后输入进程 pid 回车

3)输入 NI 值回车,修改成功

在这里插入图片描述

其实我在修改 NI 值的时候输入的是100,我想让该进程的优先级变为180,但是 PRI 确实 99。这说明 NI 值是有范围的,即优先级是有范围的。NI 值的范围为[-20 ~ 19],一共40个级别

那为什么 NI 值是一个相对较小的范围?那是因为若 NI 范围过大,每个进程享受 CPU 资源可能就没有那么均衡,最终会导致进程饥饿问题。

fork创建子进程

fork函数的功能就是创建一个子进程。

在这里插入图片描述

举个栗子

下面的代码中只有一条打印语句,但是最终会有两条输出:

在这里插入图片描述
在这里插入图片描述

理解fork

在OS角度上,fork创建进程与OS创建进程没有差别,fork的本质就是让系统中多了一个进程(数据代码 + PCB)。

对于子进程,它的数据代码是什么呢?默认情况下,子进程会继承父进程的代码,内核的 task_struct 会以父进程为模板,初始化子进程的 task_struct,即 fork 之后,父子进程的代码是共享的(fork语句之前的代码也共享),父子代码只有一份。

对于数据,默认情况下也是共享的,不过要考虑把数据被修改的情况,因为子进程被创建出来后,与父进程所干的事不一定相同,所对应的数据也必然不同。当父子进程数据没有被修改的情况下,父子进程对应的数据是共享的,当数据被修改时,会发生写时拷贝!!写时拷贝保证了进程之间的独立性。

fork 返回值

我们创建子进程的目的是实现多线程程序,这就需要 fork 的返回值来实现。

当 fork 创建子进程失败,返回一个小于 0 的值;

当 fork 创建子进程成功,给父进程返回子进程的 pid ,给子进程返回0。

在这里插入图片描述
在这里插入图片描述

[ fork 之后父子进程谁先执行不确定 ]

如何理解 fork 有两个返回值?

我们先解释一下 fork 创建子进程成功后返回的两个返回值,为什么返回父进程其子进程的pid,返回子进程0呢?因为对于父进程来说,子进程会有很多个,得到子进程的 pid,父进程就能够找到子进程。对于每一个子进程来说,他只有一个父进程,要找到父进程只需要调用函数 getppid() 即能找到父进程。

一个函数最后的 return 语句一次只能返回一个值,那为什么 fork 函数能返回两个值?

我们知道,fork 的功能是创建子进程。对于一个函数,其核心功能代码肯定在 return 语句之前。也就是说,当 fork 函数执行到 return 语句时,子进程已创建成功,两个进程都执行一次 return 语句,所以 fork 有两个返回值。而且两个返回值是不相同的,所以 fork 返回的时候会发生写时拷贝!

好啦,如果你认真的读到了这里,还望点个赞收藏加关注,你的支持是我最大的动力!😘😘
【本文为作者原创,未经允许禁止私自转载,抄袭,一经发现,将会受法律责任】

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的小邢~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值