Linux中的进程(概念,状态)

本文详细介绍了冯诺依曼体系结构的计算机组成,包括硬件单元如输入、输出设备、内存、CPU以及它们之间的连接方式。讨论了操作系统如何管理进程,进程的状态(运行、阻塞、挂起)以及Linux中的fork函数、调度器和进程状态变化。
摘要由CSDN通过智能技术生成

硬件

冯诺依曼体系

我们常见的计算机, 如笔记本, 我们不常用的计算机, 如 服务器, 大部分都是遵循冯诺依曼体系

截止现在, 我们所认识的计算机,都是由一个个的硬件组成的

  • 输入单元 如 键盘, 鼠标, 扫描仪等
  • 中央处理器(CPU), 如 运算器和控制器等
  • 输入单元, 如显示器, 打印机

在这里插入图片描述

a. 存储器指的是什么?
:内存(不是磁盘 磁盘是外部存储设备是输入输出设备)

b.输入设备是那些呢?
:键盘, 摄像头,话筒,磁盘,网卡,鼠标…

c.输出设备有那些呢?
:显示器,播放器硬件,磁盘,网卡…
输入输出设备统称为 外部设备 简称为 外设

d.运算器:对我们的数据进行计算任务(算数运算,逻辑运算)
e:控制器:对我们的计算硬件流程进行一定的控制
运算器和控制器统称为 CPU

各个硬件单元必须用“线”连接起来,1.系统总线,2.IO总线

关于冯诺依曼,必须强调几点

  • 这里的存储器指的是内存
  • 不考虑缓存情况,这里的CPU能且只能对对内存进行读写,不能访问外设,如 输入输出设备
  • 外设(输入或输出设备)要输入或者输出数据, 也只能写入内存, 或从内存中读取;
  • 一句话, 所有设备都只能直接和内存打交道;

软件

在这里插入图片描述

操作系统本质上是一款管理的软件;
: 他负责管理着上述的硬件, 同时也管理着电脑的软件

进程

一个已经加载到内存中的程序,叫做进程(任务);

ps axj 查看系统的所有进程

在这里插入图片描述

我们先随便写一段代码

在这里插入图片描述

然后运行他

在这里插入图片描述

然后我们新建一个窗口在进来, 查看一下是否存在这个代码的进程

在这里插入图片描述
不出所料, 我们查到了刚刚这段代码的进程

理解进程

先描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
系统在管理进程的时候是对进程的PCB进行管理, 而不是直接对代码和数据进行管理

再组织

在这里插入图片描述
系统对进程的各种控制, 本质上都是系统对PCB的操作而不是对代码和数据进行操作

就好比面试投递简历一样, 面试官对你的操作, 本质上是对你提交的简历进行操作(通过淘汰 ), 而不会直接对你人进行操作;

Linux中的PCB是什么样子的

在这里插入图片描述

查看进程

/proc目录

proc目录是Linux下自带的,文件化的进程, 里面的所有文件都装载了系统中正在运行的进程的基本信息;

在这里插入图片描述
在我们运行测试程序时, 我们可以查看到一个PCB id(PID)为30092的进程

在这里插入图片描述
可以看到确实是有一个文件叫30092这个目录里就记录着我们刚刚那个进程的信息

在这里插入图片描述
可以看到有一个连接文件 (exe)指向了一串路径, 可以发现这段路径就是我们代码源文件的路径, 所以这个文件就是用来帮助我们的PCB找到源文件的,这个东西我们称为内存指针

除了 exe 还有一个cwd也是连接文件, 这个我想给大家隆重介绍一下!!

cwd全称 current work director 当前的工作目录 他所指向的是当前进程的工作目录;

为什么我们在使用touch创建文件时, 不跟任何的地址, 但是系统却知道我们是想在当前目录下创建呢? 为什么我们在使用fopen时,没有传地址,只传了文件名,但是系统却能知道在当前目录下找该文件呢?

答案就是靠的这个cwd文件, 这个进程,再被号常见的时候, PCB就记录了当前进程的工作目录, 所以上述指令在默认情况下,系统能知道在哪里创建或在哪里查找文件;未来, 我们在进行很多操作中,所产生的临时文件, 也都是会自动被创建在这个目录当中;

getpid函数

getpid() 可以返回调用这个函数的进程的PID

在这里插入图片描述可以看到吗用ps看到的PID跟他是一样的

getppid()函数

getppid()可以返回调用这个函数的父进程PPID

在这里插入图片描述
在这里插入图片描述
可以看到二者值是一样的

我们在不断的重启重启后, 发现程序的PPID值是不变的, 那这个父进程到底是个啥?
在这里插入图片描述
ps查看后发现其实就是命令行解释器

原来啊 我们的在执行代码时候
命令行解释器会把我们这条指令变成bash的子进程, 由子进程执行我们的命令, 而PPID不变的原因是: 我们程序的进程出问题并不会影响我们的 bash进程, 自然, PPID就不会变啦

fork(),创建进程

在这里插入图片描述

我们先写上一段代码

在这里插入图片描述
运行后我们发现, fork后面的一段代码被执行了两遍, 为什么呢?

因为我们在用fork创建进程的时候fork之前只有一个执行流, 而fork之后有了两个执行流, 也就打印了两个 after;

怎么来证明这个事呢?

我们通过man查手册, 看到fork函数会给父进程返回大于0的数, 会给子进程返回等于0的数,错误是返回小于0的数;
很奇怪的是他一个函数居然能返回两个值, 这是什么意思呢? 我们来写段代码验证他一下

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

在运行程序时, 我们发现父进程和子进程都被打印出来了, 也就是我们的一份代码里竟然id > 0 && id == 0 然而事实是这样嘛?

可以看到子进程的ppid是等于父进程的pid的, 也就是说这个打印出子进程的进程是打印出父进程的进程的字进程 而父进程的ppidbash

所以整个过程应该是这样的:
我们的bash在命令行中执行起我们的./proc 他和普通进程一样被创建出来了, 系统给这个进程赋了一个pid21272, 然后这个代码被运行, 执行到fork()时, 由fork()将进程一分为二, 一个是, 一个是, 父进程就是他自己, 而子进程就是他的新的分支, 我们的代码也就变成了两个执行流, 一个执行流中fork()返回大于0的数 另外一个返回0, 所以也就有了fork()会返回两个参数的假象

我们发现fork给父进程返回的所谓的大于0的数其实就是子进程的pid

一般而言, fork之后的代码, 父子共享

那么为什么fork要给父进程返回子进程的pid 要给子进程返回0

1 首先返回不同的返回值, 是为了区分, 让不同的执行流执行不同的代码块

2 因为一个进程可能有多个子进程, 所以需要返回子进程的pid来确定是哪一个具体的子进程, 而子进程不同, 一个子进程只会有一个父进程, 想要获得父进程的pid只需要执行getppid即可, 所以返回0与父进程进行区分(进程的pid是大于0的)

一个函数(fork)如何做到返回两次的?

看似是返回了两次, 但其实只返回了一次,分别在父进程和子进程中各返回一次

fork函数究竟在干什么? 该怎么理解?

创建了一个新的进程, 这个进程的各个属性相对于父进程略有不同(ppid, pid), 然而子进程没有自己的代码与数据, 而是与父进程共享同一份代码和数据,(这也是为什么, fork之后,父子进程共享同一份代码(但是数据不共享))
在这里插入图片描述

上面提到了, 父子进程代码共享, 而数据不共享怎么理解呢?

代码共享很简单, 就是父子进程都是用的原来的那一份代码, 而数据不共享是为什么呢? 因为进程之间是相互独立的, 如果他们共享一个数据, 那么这两进程之间就势必会互相影响, 所以他们之间是不共享数据的

那是不是操作系统就是直接将数据拷贝一份给子进程用呢?

不是的 子进程并不一定会用到所有的数据, 这样做会浪费系统的资源, 而Linux真正采用的操作是, 一开始复制共用同一份数据, 而查询到要更改数据时, 另开一个空间给你改, 这之后这段空间就单独属于你啦 (这也就是父子进程之间数据层面的写实拷贝)。

一个变量 id为什么会有两个值呢?

因为父子进程之间是相互独立的, 父子进程写实拷贝, 所以, 虽然看着是同一个变量, 但是其数据其实是不同的。

具体是如何实现的, 我们等到学地址空间的时候就能明白了

如果父子进程被创建即(调用完fork) 父进程 与 子进程 谁先运行呢?

谁先运行有调度器决定, 所以谁先运行时不确定的

什么是调度器

我们的系统中通常会有多个进程, 调度器就是觉得将哪一个进程放进CPU里运行的那个东西, 调度器的调度原则一般都是要尽可能的平均或则公平的;

管理进程

kill -9 PID

干掉该PID所对应的进程
在这里插入图片描述
可以看当我们现在有一个PID为26071的进程

在这里插入图片描述
当我们向他发送9号信号后就会发生…
在这里插入图片描述
我们正在运行的程序就被干掉

进程状态

介绍一下一般的操作系统学科中的进程状态(提一下),运行, 阻塞, 挂起

在这里插入图片描述

运行状态

凡是在运行队列中的进程,我们称为运行态(R), 表示该进程已经准备好了, 可以随时被调度。

一个程序只要把自己放在CPU上运行了,是不是一直要执行完毕,才把自己放下来?

答案显然是不是的
比如我写一个while(1)如果是的话,那当我运行这段代码时, 其他的进程就都无法运行了;
为了防止这种情况,每一个进程都会有一个叫做时间片的概念

所以就会有大量的把进程拿上CPU在拿下来的操作 – 哦们称为进程切换

阻塞状态

在这里插入图片描述

比如我们这个进程需要从键盘中读取数据, 但是我们键盘一直没有输入数据, 那么这个进程就在键盘的等待队列总待着, 同样如果后续又有进程需要从键盘中读取数据, 也一样去等待队列待着,, 当进程处于的这种状态我们称为阻塞状态, 之前提到的等待队列其实也就是阻塞队列在系统中, 阻塞队列可以有很多个

挂起状态

加入有一大堆的进程都处于阻塞队列中, 是的操作系统的内部资源严重不足了,操作系统需要再保证正常运行的情况下,省出来内存资源,
就会把一些处于阻塞队列的进程的代码与数据返还到外设中, 只保留pcb在内存中, 等未来pcb就绪了 处于运行状态了, 才考虑又将代码搬进内存(这个过程称为内存数据的换出和换入)。一般只有迫不得已操作系统才会这样做。

一个进程, 他的代码和数据被换出了 , 只有pcb在内存中, 我们把这个进程称之为挂起状态

具体的Linux中的状态是如何维护的?

我们来看看``Linux的源代码

在这里插入图片描述

r状态 running
top指令, 实时查看系统的进程

在这里插入图片描述

在这里插入图片描述
可以看到虽然我们的程序在疯狂的跑, 但是状态却不是r而是s, 这是为什么呢?

我们死循环里的代码注释掉, 再来运行一次试试

在这里插入图片描述
可以看到进程的状态变成r了, 这是为什么呢?

因为我们之前一直在运行printf函数, 而这个函数是需要去访问显示器设备的, 所以,进程就很大部分时间处于等待的状态,即阻塞状态, 所以Linix中的s就对应了阻塞状态;

我们可以看的状态不仅是r 还有个 + 号, 这个意思就是该进程处于前台运行, 也就是当这个进程在运行的时候, 我们的bash命令行就不会运行了, 如果在执行代码的时候在后面加一个&,就可以让我们的进程处于后台状态,这样就不会影响我们bash命令行的运行了

处于前台的进程我们可以直接ctrl c结束掉, 而处于后台的进程, 我们只能采用kill -9 pid的方式结束

s状态 sleeping

在这里插入图片描述
为了便于我们看到s状态, 我们将代码进行略微调整
在这里插入图片描述
可以看到进程是处于休眠状态的, 因为他在等待io设备就绪

我们再换一个代码, 更加直观的看到休眠状态
在这里插入图片描述

我们看看, 如果我们运行程序后, 一直不输入, 进程会处于什么样的状态?

在这里插入图片描述

可以看到,进程在等待我们的输入, 所以是处于s状态的;

所以 Linux中的阻塞状态就是s状态

D状态 disk sleep

也是一种阻塞状态, 不过在Linux中, 这个状态称为深度睡眠状态, 而s状态也就是浅度睡眠。

浅度睡眠是可以响应变化的, 比如可以被kill指令停掉

背景
在我们的内存空间严重不足, 即使操作系统挂起很多进程也无法解决的时候, 操作系统是会动手干掉进程

D状态下, 仍何人都无法杀死这个进程包括操作系统, 即使重启系统也无法杀死他, 除非直接断电, 这是因为处于d状态的进程, 虽然在休眠, 但是都是在进行一些重要的内容, 比如在进行向磁盘写入内容, 此时虽然进程处于休眠状态但是是非常重要的一旦进程被杀死, 那么数据就极有可能会丢失!!

由于D状态的特殊性, 一般情况下不会维持太长的时间

T状态 stop

;

在这里插入图片描述
我们先让程序进入死循环

kill -l 查看信号

在这里插入图片描述
我们已经用过看9好信号。 即杀死进程的型号, 这次我们需要用到19号信号, 即展厅进程的信号和18号信号, 让停止的进程重新运行起来

在这里插入图片描述
可以看到, 我们刚刚创立的进程现在处于了T状态

在这里插入图片描述
再传递18号信号后, 进程又恢复了原本的状态

t状态tracing stop

在我们使用gdb调试代码时, 当代码运行到断点处就会自动停止, 为什么呢? 就是因为此时,该进程处于t状态

在这里我们暂时不做`t 和 T 的区别`
X状态 dead

当进程运行结束后就会处于这个状态, 此时操作系统会将其放入类似垃圾回收站的地方, 并将其所占有的空间释放掉

Z状态 zombie

当一个进程在结束时,他不会立即进入X状态而是会先进入一个状态, 叫做Z状态

为什么Linux操作系统要维护这个一个状态呢?

维持给其父进程和与这个进程有关的进程看, 一旦这个进程的信息被读取到了,这个进程的资源才会被释放;

在这里插入图片描述

我们用这份代码来验证僵尸进程

在这里插入图片描述
可以看到当子进程退出后, 父进程未对子进程的信息做处理, 子进程就会处于僵尸状态(Z状态)

在这里插入图片描述
如果父进程一直不回收子进程的信息,那么这段资源就会一直被Z进程占用, 我们把这种, 因为僵尸进程而导致自身资源一直被占用而不释放所引发的问题叫做内存泄漏

当我们使用ctrl + c终止进程时, 为什么父进程没有处于僵尸状态(Z状态)?


不是不处于僵尸状态, 而是很快的被bash(他的父进程)处理了

当我们使用ctrl + c终止进程时, 为什么子进程也没了

在这里插入图片描述
我们让父进程运行一会就退, 让子进程多运行一会, 看看会怎么样;

在这里插入图片描述
我们可以看到, 当父进程退出前, 子进程的父进程是父进程, 而当父进程退出后, 子进程的父进程变成了1, 那这个1是什么呢?

在这里插入图片描述
我们使用top命令, 确实是查到了一个叫做pid为1的叫做systemd的进程
在这里插入图片描述
其实这个pid为1的进程就是我们操作系统本身;

孤儿进程

所以我们就知道了:
父子进程中,父进程先退出, 子进程的父进程会被改为1号进程(操作系统)
这种父进程是操作系统(1号进程)的进程 – 孤儿进程
这种行为我们称为该进程被系统所领养

;

为什么操作系统要领养它?
原因很简单: 父进程已经退出了, 也就意味着子进程未来没有进程来回收他的信息了, 为了防止内存泄漏, 所以就将该进程托付给系统;

答案
到这里我们就可以解决这个问题了, 当我们父进程被终止时, 子进程的父进程就变成了系统, 然后被系统回收了;

Linux中进程状态的变化

在这里插入图片描述

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值