【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…

Linux补丁命令patch

1、生成补丁&#xff0c;创建两个相同的目录结构&#xff0c;修改里面文件的内容 diff -uNr old_version new_version > update.patch 2、打补丁&#xff0c;应用到一个名为 target_version/ 的目录上&#xff0c;该目录与old_version/ 文件相同。 cd target_version patc…

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

文件管理 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 基本概…

【网络】用网线连接两台电脑实现远程桌面

目录 1. 准备工作1.1 硬件要求1.2 软件要求 2. 网络连接2.1 直接连接2.2 通过路由器连接 3. 配置IP地址3.1 设置IP地址3.2 检查连接 4. 启用远程桌面4.1 启用远程桌面4.2 添加用户4.3 防火墙设置 5. 远程连接5.1 使用远程桌面连接5.2 使用快捷方式 6. 常见问题解决7. 额外建议结…

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;所以大家卡的显存…

Vue 中引入 ECharts 的详细步骤与示例

在Vue项目中引入ECharts&#xff0c;可以让我们轻松地在前端页面中展示各种图表。ECharts 是一个基于 JavaScript 的开源可视化图表库&#xff0c;它提供了丰富的图表类型和强大的配置选项&#xff0c;使得在Vue项目中集成和使用变得非常方便。 一、准备工作 创建Vue项目&…

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 …

【工具】VSCODE下载,配置初次设置

打开 settings.json 文件&#xff0c;包含了 Visual Studio Code (VSCode) 中的各种用户配置。 {"files.associations": {"*.vue": "vue","*.wpy": "vue","*.wxml": "html","*.wxss": "…

软件架构设计中的微内核架构是什么

Visual Studio Code 是一个广受欢迎的代码编辑器。它同样采用微内核架构&#xff0c;核心是一个轻量级的编辑器引擎&#xff0c;提供了基本的文本编辑、代码高亮、语法检查等功能。 通过安装不同的扩展插件&#xff0c;用户可以添加对各种编程语言的支持、调试功能、版本控制集…

React生命周期案例详解

React 组件的生命周期是指组件从创建、渲染、更新到卸载的整个过程。在 React 16 及之前的版本中&#xff0c;生命周期方法被分为几个不同的阶段&#xff1a;挂载&#xff08;Mounting&#xff09;、更新&#xff08;Updating&#xff09;、卸载&#xff08;Unmounting&#xf…

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语句 常用的打开关闭文件 # 文件关闭 # 方式…

【Python】操作列表

Python是一种功能强大的编程语言&#xff0c;它提供了丰富的操作列表的方法。列表是一种有序、可变的数据类型&#xff0c;可以存储任意类型的元素。下面是一些常用的操作列表的方法&#xff1a; 1. 创建列表&#xff1a;可以使用方括号 [] 或者 list() 函数来创建一个列表。例…

kubernetes详解

一、kubernetes的定义 Kubernetes (希腊语"舵手" 或 "飞行员") 由Joe Beda&#xff0c;Brendan Burns和Craig McLuckie创立&#xff0c;并由其他谷歌工程师&#xff0c;包括Brian Grant和Tim Hockin进行加盟创作&#xff0c;并由谷歌在2014年首次对外宣布。…

值类型和引用类型的使用

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;尤其是在云原生…

单片机原理及应用详解

单片机原理及应用详解 一、引言 单片机(Microcontroller)是集成了计算机功能的微型计算机,其内部包含CPU、内存、I/O接口等部件,广泛应用于嵌入式系统中。单片机因其体积小、成本低、功耗少等优点,成为电子产品设计中不可或缺的核心组件。本文将详细探讨单片机的原理、结…