【linux系统】进程

文章目录

  • 进程和PCB
  • linux与进程的相关命令
    • PS
  • linux下的PCB
    • 进程标识符
    • 父子进程
      • fork
    • 进程状态
      • 磁盘睡眠 -- D
      • 暂停和跟踪暂停 -- T和t
      • 僵尸进程 -- Z
        • 孤儿进程
    • 进程优先级
  • 进程地址空间
    • 再谈fork
    • 进程地址空间分布
    • 虚拟地址和页表
    • mm_struct
  • 进程控制
    • 进程终止
      • 进程退出码
      • 信号
    • 进程等待
    • 进程替换


进程和PCB

什么是进程?
课本上的定义有很多,如:进程是程序的一次执行,是加载到内存的程序,是系统进行资源分配和调度的一个独立单位

我们不必去纠结定义,只需知道2点:如何描述进程?如何管理进程?

描述=提取进程属性,管理=对进程的属性进行管理
由此首先要引出一个概念:进程的PCB

PCB(process control block) 是什么?一句话:进程属性的集合,是一个结构体。此时进程就被拆分为2个部分:属性和数据,如下图:
在这里插入图片描述

linux与进程的相关命令

linux下的进程信息存储在/proc目录下
在这里插入图片描述
大多数的进程信息也可以通过ps和top等用户级工具来获取

PS

ps命令用于显示当前正在运行的进程信息
a: 显示所有用户的进程。通常情况下,ps命令仅显示与当前终端关联的进程,但使用-a选项可以显示所有用户的进程。
j: 使用BSD风格的输出格式。这种格式下,ps命令会以进程状态、作业控制信息等形式显示进程信息。
x: 显示与终端无关的进程。通常情况下,ps命令仅显示与当前终端关联的进程,但使用-x选项可以显示与当前终端无关的进程。
在这里插入图片描述

top命令用于动态显示系统中运行的进程的相关信息,包括进程的CPU利用率、内存利用率、进程ID等
在这里插入图片描述

linux下的PCB

在linux操作系统下的PCB:task_struct(结构体)

task_struct的内容分类:

  1. 标识符: 描述本进程的唯一标示符,用来区别其他进程。
  2. 状态: 任务状态,退出代码,退出信号等。
  3. 优先级: 相对于其他进程的优先级。
  4. 程序计数器: 程序中即将被执行的下一条指令的地址。
  5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  6. 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  7. I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  8. 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  9. ……

重点介绍 标识符,状态,优先级

进程标识符

进程标识符,即PID:描述本进程的唯一标示符,用来区别其他进程。
在这里插入图片描述
这里PPID – parent PID, 即该进程的父进程的PID.

linux也提供了系统接口来获取进程的PID
在这里插入图片描述
返回值类型pid_t, 本质是整型
在这里插入图片描述

父子进程

前文在谈到进程的描述符时谈到了父进程和子进程,怎么进程还有父子关系?

所谓父子进程,就是在一个进程的基础上创建出另一条完全独立的进程,这个就是子进程。
问题来了:

  1. 如何创建?
  2. 子进程是在父进程的基础上,那么二者的PCB、代码和数据有什么不同?

fork

linux下有一个系统调用fork,它可以创建子进程。

NAMEfork - create a child processSYNOPSIS#include <sys/types.h>#include <unistd.h>pid_t fork(void);

返回值:
失败了返回-1,并且没有子进程被创建,如果成功,父进程返回子进程的PID,子进程返回0

#include <sys/type.h>
#include <unistd.h>int main()
{pid_t id = fork();if(id == 0){//子进程}else if(id > 0){//父进程}else {//创建进程失败}       }

这里就有问题了:

问题1:为什么创建子进程?
让子进程去处理其他事

问题2:为什么fork后子进程返回0?父进程返回子进程的pid
由于父只有一个,而子进程可以有很多个,因此父进程返回子进程的pid,来标识你创建好的子进程pid是多少。子进程返回0,因为子进程只有一个父亲,不需要额外标识出来。

问题3:为什么一个变量(id)有2个值?
子进程在父进程的基础上创建,这意味着父子进程具有相同的代码和数据。
那么这里的”相同“指的是

  1. 子进程复制一份父进程
  2. 子进程和父进程共享一份
    在这里插入图片描述
    实际情况是第2种:父子进程共享代码和数据
    那么父子进程岂不是使用同一个变量(id)?
    也不是,当子进程如果要更改数据时,会发生写时拷贝,即更改的数据拷贝一份,未更改的数据父子共享
    总结:代码共享,数据写时拷贝

注:为什么要写时拷贝? 为了节省资源

问题3:父子进程谁先运行?
由各自PCB的调度信息(时间片,优先级等)+调度算法自助决定

进程状态

在操作系统理论中,进程一般有3种基本状态:运行、就绪和阻塞。
在这里插入图片描述
但上面的只是操作系统理论,实际的操作系统下的进程状态更复杂。

以linux为例,解释一下:运行,阻塞,挂起
在这里插入图片描述
在计算机操作系统中,进程可能会由于各种原因而进入阻塞挂起状态,其中包括等待输入/输出(I/O)操作完成、等待系统资源分配、等待进程间通信等。

在具体的Linux系统中,进程状态有以下几种:

在这里插入图片描述

R即运行状态,S即阻塞状态,这都好理解,但有3个状态很奇怪:D、T、t、X、Z

插一个小知识:我们使用ps命令查看进程会发现有些进程的状态会带个+号,如R+
这里的 +表示该进程是在前台运行

在这里插入图片描述
如果在运行时加上&,可以让程序变为后台运行。此时如果想终止进程则需要kill -9 PID
在这里插入图片描述

磁盘睡眠 – D

进程的磁盘睡眠状态(Disk Sleep State)通常是指进程处于等待磁盘I/O操作完成的状态。这种状态通常出现在进程请求进行磁盘读取或写入操作时,但磁盘尚未完成相应的I/O操作,因此进程被阻塞,等待磁盘响应。在这种状态下,进程不会消耗CPU时间,而是被挂起,直到磁盘I/O操作完成。是阻塞挂起状态的一种形式

处于磁盘睡眠的进程,不响应操作系统的请求,直到进程完成它的I/O操作。

要想看到磁盘睡眠,需要进行高I/O操作,不容易演示。可以使用dd命令来进行,由于dd命令的操作非常强大,但同时也非常底层,因此在使用时需要特别小心,避免造成意外的数据损坏或丢失。

暂停和跟踪暂停 – T和t

Linux操作系统的有个信号kill -19, 可以使进程暂停。T状态即进程处于暂停状态。注意不要于S状态混淆,S状态一定是进程在等待某种资源,但T状态不一定在等待某种资源。
那T和t有什么区别呢?

Stopped(停止)状态:
进程处于停止状态通常是由于接收到了一个信号,例如SIGSTOP(Ctrl-Z产生的SIGTSTP信号)或者SIGTSTP(通常由shell的暂停命令引发)。这种状态下的进程被挂起,暂时停止执行,但可以通过发送SIGCONT信号来恢复执行。

Tracing Stop(跟踪停止)状态:
进程处于跟踪停止状态通常是由于调试器(如GDB)或者ptrace系统调用的作用。在这种状态下,进程被调试器所追踪,通常是因为调试器在进行单步执行、观察或者修改进程的内存等操作。这种状态下的进程暂时停止执行,直到调试器允许其继续执行。

一般认为T和t没什么区别。
在这里插入图片描述

僵尸进程 – Z

当一个进程(子进程)完成执行后,它的退出状态需要被父进程获取。如果父进程没有主动获取子进程的退出状态,那么子进程就会变成僵尸进程,相当于一个人处于生死之间。
在这里插入图片描述
下方代码实现:父进程一直运行,子进程执行3次后结束
在这里插入图片描述
结果如下:子进程的状态由S+ --> Z+, Z即处于僵尸状态
在这里插入图片描述
僵尸进程虽然不会直接对系统造成严重影响,但长时间存在的僵尸进程会对系统的正常运行产生一些间接的危害,包括:可能导致资源耗尽,影响进程管理,降低系统稳定性。因此需要父进程处理僵尸进程。
父进程通常需要调用类似于wait()或waitpid()的系统调用来等待子进程的退出,并获取其退出状态。

当然如果父进程也结束,系统会自动把子进程释放。

孤儿进程

僵尸进程是子进程结束,但父进程未结束。如何父进程先结束,子进程后结束呢?那么子进程便会变为孤儿进程,并被托孤给1号进程,即操作系统。
在这里插入图片描述

进程优先级

在这里插入图片描述
PRI(Priority):PRI 表示进程的静态优先级或调度优先级。俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高。
NI(Nice Value):NI 表示进程的 Nice 值,是一个表示进程调度优先级的数值。它的作用是改变PRI的值。

通过PRI和NI可以调整进程的优先级,计算公式如下:

PRI(new)=PRI(old)+nice

这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行所以,调整进程优先级,在Linux下,就是调整进程nice值

注意:nice 的范围在 【-20, 19】
PRI(old) 最小是80,如果原来的PRI < 80, 则会直接从80开始算:
因此:PRI 范围【60, 99】,但在计算新的PRI时,最小从80开始
举例:原来 : PRI = 60 NI = 0;
更改:令PRI = 100
结果:PRI = 99 NI = 19

那如何更改nice值呢?

在Linux系统中,nice命令用于启动一个新的进程,并设置其优先级。而renice命令用于修改已经运行的进程的优先级。

nice命令的使用:

nice [OPTION] [COMMAND [ARG]...]

nice命令通过改变进程的优先级来影响其调度。数值越大,优先级越低。默认情况下,优先级是0。

例如,运行一个命令并设置其优先级:

nice -n <优先级> <命令>

例如,将ls命令的优先级降低为10:

nice -n 10 ls

renice命令的使用:

renice [优先级] -p <进程ID> [<进程ID>...]

renice命令用于修改已经运行的进程的优先级。可以指定一个或多个进程ID来修改它们的优先级。

例如,将进程ID为1234的进程的优先级设置为10:

renice 10 -p 1234

如果你想要提高某个进程的优先级,你需要具有足够的权限。通常,只有超级用户(root)才能提高进程的优先级,而普通用户只能降低自己创建的进程的优先级。

进程地址空间

再谈fork

#include <sys/types.h>
#include <unistd.h>
#include <iostream>
int g_val = 100;int main()
{pid_t id = fork();if(id == 0){g_val = 200;printf("子进程:g_val = %d, &g_val = %p\n", g_val, &g_val);}else if(id > 0){printf("父进程:g_val = %d, &g_val = %p\n", g_val, &g_val);}
}结果:
父进程:g_val = 100, &g_val = 0x55631bc26010
子进程:g_val = 200, &g_val = 0x55631bc26010

子进程更改数据后,会发生写时拷贝,因此子进程和父进程的g_val值不一样,符合预期,但是为什么发生了写时拷贝,父子进程的g_val地址还是相同?
显然这里的地址一定不是真实的地址。

要解释这个问题,要引入一个概念:进程地址空间

进程地址空间分布

c/c++常见的地址分布图,过去我们称它为程序地址分布,但实际它真正的名字是进程地址空间
不同语言的进程地址空间大致相同,下面以c++的地址分布图为例。
在这里插入图片描述
先验证:


#include <iostream>
using namespace std;
int g_A;
int g_B = 100;
int main()
{const char* a = "ab";static int s_A = 5;int A;int B;int C;int* m_A = new int;int* m_B = new int;int* m_C = new int;printf("字符常量:a : %p\n", a);cout << "静态变量:s_A : " << &s_A << endl;cout << "全局变量:未初始化g_A : " << &g_A << endl;cout << "全局变量:已初始化g_B : " << &g_B << endl;cout << "栈区:A : " << &A << endl;cout << "栈区:B : " << &B << endl;cout << "栈区:C : " << &C << endl;cout << "堆区:m_A : " << m_A << endl;cout << "堆区:m_B : " << m_B << endl;cout << "堆区:m_C : " << m_C << endl;
}结果
字符常量:a : 0x5649d4534009
静态变量:s_A : 0x5649d4536014
全局变量:未初始化g_A : 0x5649d4536154 未初始化地址 > 已初始化地址 符合
全局变量:已初始化g_B : 0x5649d4536010
栈区:A : 0x7ffc892fb3cc 栈区的地址是增长的, 不符合
栈区:B : 0x7ffc892fb3d0
栈区:C : 0x7ffc892fb3d4
堆区:m_A : 0x5649d5eadeb0 堆区的地址是增长的,符合
堆区:m_B : 0x5649d5eaded0
堆区:m_C : 0x5649d5eadef0

怎么栈区的地址是向上增长的呢?
这里要解释一个概念:函数栈帧,当我们调用函数时,栈区会开辟一块空间给函数使用。而函数内的局部变量是在函数栈帧中开辟空间。
栈帧的分配是向下增长的,但是栈帧内部的局部变量的地址分配是由编译器的策略来决定的
在这里插入图片描述

虚拟地址和页表

每一个进程,操作系统都会分配一个进程地址空间,对于32位机器,总地址大小位4GB。每个进程都分配4GB的内存,这可能吗?不可能。
因此进程地址空间里的地址是虚拟地址,通过页表与物理地址映射。
在这里插入图片描述
回到fork里的问题:为什么父子进程不同的值有着相同的地址?因为这里的地址是虚拟地址。
子进程只需更改子进程页表。

在这里插入图片描述

mm_struct


进程控制

进程终止

当一个进程退出时,有以下3种场景:

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

对于场景1,我们无需关心。
对于场景2,结果不正确,因此我们需要查明为什么不正确。通过什么来查明? 进程的退出码。即main函数的返回值。
对于场景3,代码异常终止,此时还需要关心进程的退出码吗?不用。退出码是结果是否正确的标志。进程是否异常的标志是信号。比如ctrl + c 终止进程,就是向进程发送SIGINT信号

进程退出码

这里要介绍2个控制进程退出的函数

_exit函数#include <unistd.h>
void _exit(int status);参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
是255。exit函数#include <unistd.h>
void exit(int status);exit最后也会调用exit, 但在调用exit之前,还做了其他工作:
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit

在这里插入图片描述
我们平时控制进程退出是使用return。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。回值当做 exit的参数

信号

进程等待

进程替换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/54863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【翻译】在 Python 应用程序中使用Qt Designer的UI文件

原文地址&#xff1a;Using a Designer UI File in Your Qt for Python Application 直接上图&#xff0c;上代码 将UI文件转为Python 为了演示&#xff0c;我们使用 Qt Widgets 简单示例说明。 这个应用程序由一个源文件 easing.py、一个 UI 文件 form.UI、一个资源文件 ea…

考研笔记之操作系统(四) - 文件管理

文件管理 1. 简介1.1 前情回顾1.2 文件的属性1.3 文件内部数据的组织方式1.4 操作系统向上提供的文件功能1.5 文件应如何放在外存 2. 文件的逻辑结构2.1 无结构文件2.2 有结构文件2.2.1 顺序文件2.2.2 索引文件2.2.3 索引顺序文件2.2.4 多级索引顺序文件 3. 文件目录3.1 基本概…

1422. 分割字符串的最大得分【字符串】

文章目录 1422. 分割字符串的最大得分解题思路Go代码 1422. 分割字符串的最大得分 1422. 分割字符串的最大得分 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所…

使用3080ti运行blip2的

使用3080ti运行blip2的案例 注意&#xff01;blip2很吃显存&#xff0c;需要大于80GB显存的卡。我最后安装的所有包的版本信息&#xff08;python 3.9 &#xff09;以供参考&#xff1a; 首先&#xff0c;我在运行blip2的demo的时候显存用了80G以上&#xff0c;所以大家卡的显存…

moectf-Web题解

1、弗拉格之地的入口 2、垫刀之路01: MoeCTF&#xff1f;启动&#xff01; 3、ez_http 4、ProveYourLove 5、弗拉格之地的挑战 6、ImageCloud前置 7、垫刀之路02: 普通的文件上传 8、垫刀之路03: 这是一个图床 9、垫刀之路05: 登陆网站 10、垫刀之路06: pop base mini …

Java值传递、序列化详解

Java 值传递详解 说到参数&#xff0c;我们先来搞懂一下这两个概念 形参&实参 值传递&引用传递 形参&实参 方法的定义可能会用到 参数&#xff08;有参的方法&#xff09;&#xff0c;参数在程序语言中分为&#xff1a; 实参&#xff08;实际参数&#xff0c;…

QT实现Opencv图像处理

案例 基于QT的人脸识别 pro文件需要加以下代码 INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv2 LIBS E:/opencv/o…

D34【python 接口自动化学习】- python基础之输入输出与文件操作

day34 文件关闭 学习日期&#xff1a;20241011 学习目标&#xff1a;输入输出与文件操作&#xfe63;-46 常见常新&#xff1a;文件的关闭 学习笔记&#xff1a; 文件关闭的内部工作过程 close&#xff08;&#xff09;函数 with语句 常用的打开关闭文件 # 文件关闭 # 方式…

值类型和引用类型的使用

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApp1 {class Program{static void Main(string[] args){/****值类型****/bool test;//必须赋值,否则报错test true;Console.WriteLin…

微服务_3.微服务保护

文章目录 一、微服务雪崩及解决方法1.1、超时处理1.2、仓壁模式1.3、断路器1.4、限流 二、Sentinel2.1、流量控制2.1.1、普通限流2.1.2、热点参数限流 2.2、线程隔离2.3、熔断降级2.3.1、断路器状态机2.3.2、断路器熔断策略2.3.2.1、慢调用2.3.2.2、异常比例&#xff0c;异常数…

Observability:使用 OpenTelemetry 自动检测 Go 应用程序

作者&#xff1a;来自 Elastic Damien Mathieu 使用 OpenTelemetry 检测 Go 应用程序可以深入了解应用程序的性能、依赖项和错误。我们将向你展示如何使用 Docker 自动检测 Go 应用程序&#xff0c;而无需更改应用程序代码。 在快节奏的软件开发领域&#xff0c;尤其是在云原生…

【每日刷题】Day137

【每日刷题】Day137 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; 2. 495. 提莫攻击 - 力扣&#xf…

vrrp实验

配置Trunk和Access [SW3]int e0/0/1 [SW3-Ethernet0/0/1]p l a [SW3-Ethernet0/0/1]p d v 10 [SW3-Ethernet0/0/1]int e0/0/2 [SW3-Ethernet0/0/2]p l a [SW3-Ethernet0/0/2]p d v 10 [SW3-Ethernet0/0/2]int e0/0/3 [SW3-Ethernet0/0/3]p l a [SW3-Ethernet0/0/3]p d v 20 [S…

Linux——软件包管理

目录 rpm 包管理 基本介绍 rpm 包的查询指令 ​编辑 rpm 包的卸载和安装 yum rpm 包管理 基本介绍 rpm 包的查询指令 rpm 包的卸载和安装 yum

STM32F407寄存器操作(DMA+SPI)

1.前言 前面看B站中有些小伙伴吐槽F4的SPIDMA没有硬件可控的CS引脚&#xff0c;那么今天我就来攻破这个问题 我这边暂时没有SPI的从机芯片&#xff0c;并且接收的过程与发送的过程类似&#xff0c;所以这里我就以发送的过程为例了。 2.理论 手册上给出了如下的描述 我们关注…

【动手学深度学习】5.2 参数管理(个人向笔记+代码注释)

之前的课程中&#xff0c;我们只是通过深度学习框架完成训练的工作&#xff0c;而忽略了操作参数的具体细节。所以我们我们介绍的内容有&#xff1a; 访问参数&#xff0c;用于调试&#xff0c;诊断和可视化参数初始化在不同的模型组件间共享参数 下面是一个有单隐藏层的多层感…

如何把视频变成自己的原创?提升视频原创度的7个技巧

在短视频平台发布作品时&#xff0c;时常因为原创问题&#xff0c;而被限流。如何在海量视频中脱颖而出&#xff0c;让自己的作品具有独特性和原创性&#xff0c;是每位创作者都需要思考的问题。本文将详细介绍如何通过一系列前期准备和后期处理技巧&#xff0c;将视频素材转化…

模版进阶 非类型模版参数

一.模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将该参数当成常量来使用。 #i…

乌班图基础设施安装之Mysql8.0+Redis6.X安装

简介&#xff1a;云服务器基础设施安装之 Mysql8.0Redis6.X 安装 Docker安装 # 按照依赖 yum install -y yum-utils device-mapper-persistent data lvm2 Docker Mirror 从去年开始. hub.docker.com[1] 在国内的访问速度极慢. 当时大家主要还是依赖国内的一些镜像源: 如中科…

操作系统 | 学习笔记 | 王道 | 4.3 文件系统

4.3 文件系统 4.3.1 文件系统结构 文件系统(File system)提供高效和便捷的磁盘访问&#xff0c;以便允许存储、定位、提取数据。 用一个例子来辅助记忆文件系统的层次结构&#xff1a; 假设某用户请求删除文件"D:/工作目录/学生信息.xIsx"的最后100条记录。 用户需…