【Linux】进程的基本概念(已经进程地址空间的初步了解)

news2024/9/20 14:33:31

目录

一.什么是进程

进程和程序的区别

Linux查看进程 

进程的信息

fork函数

 二.进程状态

操作系统上进程状态的概念

运行

阻塞

挂起 

Linux中的进程状态

 R状态

S状态和D状态

T状态

t状态

X状态

Z状态

 三.进程的优先级

修改进程优先级

四.环境变量 

常见的环境变量

PATH

HOME

PWD

模拟pwd命令

getenv函数

main函数的两个参数

​编辑

 main函数的第三个参数

五.进程地址空间 

什么是进程地址空间 

为什么要有地址空间 

一.什么是进程

根据书本上的概念来解释,一个运行起来被加载到内存中)的程序称为进程

光这样一句话很难体会到什么是进程,所以我们来展开讲一下。

进程和程序的区别

进程被加载到内存上的,而程序是我们写好的一份二进制文件存在磁盘上的。 


那么到底什么是进程呢?

首先我们来讲一下PCB进程控制块,PCB进程控制块在Linux中就是一个名叫task_struct的结构体,在这个结构体里面,它可以存放进程的各种信息,当一个程序被加载到内存中的时候操作系统都会为这个程序生成一个PCB进程控制块,这个控制块里面存放了这个程序的各种信息,以便于维护这个进程。

所以进程可以理解成,一个二进制程序文件被加载到内存中,并且操作系统会为其构建一个PCB结构体来存放这个程序的信息,以便来维护它。

Linux查看进程 

在Linux当中,我们可以使用        ps ajx        命令查看进程。

也可以直接查看/proc文件夹。 

如下图所示,使用ps ajx来查看进程。

进程的信息

在查看进程信息中,我们可以发现有两栏:PPID、PID,这两个分别是父进程id进程id

那具体又是什么意思呢?

当我们运行一个程序的时候,它自己的进程id就叫做PID,它的父进程id就叫做PPID。


那为什么进程有会分为进程和父进程呢?

首先我们来了解一下操作系统,操作系统是一款管理软件,它可以管理好我们的软硬件资源,当我们要运行我们的程序的时候,操作系统会帮助我们去运行它,但是注意的是,操作系统不会亲自去运行该程序,而是操作系统会去派生一个子进程来运行我们的程序,为什么呢?

如果是操作系统亲自去运行我们的程序,那要是程序运行崩溃了,这就会导致我们的操作系统也一起崩溃,所以为了防止这种情况,操作系统会去派生一个子进程来运行我们的程序,即使程序运行崩溃了,最多就这个子进程崩溃了,而不会影响到我们的操作系统。

fork函数

在我们的Linux里面,提供了一个fork函数,这个函数可以在程序中创建子进程。

fork函数是去创建一个子进程,同时它会有两个返回值,这两个返回值分别给到父进程和子进程,父进程会返回子进程的id子进程会返回0,如果子进程创建失败,则返回小于0的数。所以根据这两个返回值,我们可以很好的分开父进程和子进程所运行的代码。

如下代码:

 当我们的程序运行起来的时候,会有两个进程在运行,子进程运行else if处的代码,父进程运行else处的代码。


运行结果如下所示。

 二.进程状态

知道了进程是什么后,那么每个进程都会有很多种状态,那各种状态又代表什么意思?

操作系统上进程状态的概念

 进程的状态有很多:运行,新建,就绪,阻塞,挂起等等,我们在这里挑一下讲解。

运行

有的人认为,一个进程正在被CPU处理,就叫做运行,其实这并不完全正确,因为一个进程没有被CPU处理,也可以有运行的状态。

假设内存中有多个进程,而同时又有多个进程需要被CPU处理,那么它们是怎么做的呢?

操作系统会为CPU创建一个CPU运行队列,凡是要被CPU处理的进程都会进到这个队列中,而当进程被放入到CPU运行队列中,则称为运行状态

同时CPU处理运行队列是一种轮转的方式,假设现在我们的CPU只有一个核,但是这个时候我们的CPU运行队列很长,有20个进程都需要被运行,但是我们的CPU只有一个核,也就是说我们的CPU一次只能处理一个进程,但是我们在用电脑的时候,并没有因为CPU一次只能处理一个进程而导致其他的进程无法运行,那是因为我们的CPU有一个轮转的机制,对于CPU运行队列上的进程,我们的CPU会轮流去执行所有进程,且轮转的速度非常快,快到我们感受不到,所以当我们的电脑有很多进程的时候并没有感觉到其他进程卡死,那是因为CPU的轮转速度很快。

阻塞

在一个进程中,不仅会有计算的部分,还会有输出的部分,比如我们写了个代码,将程序中的所有数据写入到磁盘中,那么在这个进程中,不仅需要CPU去处理,还需要用到磁盘

那么如果此时有别的进程正在占用磁盘资源又会怎么样呢?操作系统也会给每个硬件创建一个运行队列,将需要用到此硬件资源的进程按顺序的放入队列中,此时CPU正在处理的进程而在磁盘队列中排队,就叫阻塞

挂起 

当进程的阻塞状态较多的时候,会占用非常多的内存空间,当内存空间满了的时候,为了能使时计算机正常运行,操作系统会将还没排队到的阻塞进程暂时的放入磁盘中,这个进程的状态叫做挂起。

Linux中的进程状态

上面介绍的都是大部分操作系统下的解释,那么接下来看看Linux中,状态是如何表示的。

static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

 R状态

 R状态就是对应上面的运行状态。

S状态和D状态

S状态可以对应上面的阻塞状态或者是挂起状态,至于是哪一个,由操作系统决定。

D状态时Linux下特有的,叫做深度睡眠状态,这个状态在这里不做讲解。

T状态

T状态就是暂停的意思,指这个进程停止运行了,但它还在。 

t状态

 t状态表示该进程正在被追踪,简单的理解就是,当我们使用gdb调试器对这个进程的代码进行调试打断点的时候,就是这个状态。

X状态

X状态就是死亡状态,就是简单的指该进程已经被结束掉了。 

Z状态

Z状态是僵尸状态的意思,为什么会有僵尸状态呢?

因为当一个进程结束的时候,不会立马消失,它的父进程或者操作系统会对该进程进行收集信息,简单的理解就是,走了也得有个交代,是怎么走的,要调查清楚。 

 三.进程的优先级

所谓的进程优先级就是cpu资源的分配顺序,优先级高的进程会优先使用cpu资源,反之。 

在linux系统中,我们可以使用ps -l命令来查看进程的优先级。 

 进程的优先级由两部分组成,分别是PRI和NI。

PRI就是进程的优先级,该值越小则优先级越高,反之。

而NI是该进程的优先级可修改数值,也就是说,我们可以通过效果NI值来修改进程的优先级。        

进程优先级=PRI+NI值

修改进程优先级

若要修改进程的优先级,我们可以用top命令、再按r,再输入进程的pid、最后输入NI值来修改进程的优先级。 

注意,NI的值调整只在[-20,19]这个区间中,超过了,会默认选边界的值。

四.环境变量 

对于环境变量来说,可能大部分同学都知道,比如说,要写Java的代码时,需要配置jdk的环境变量,又或者使用vscode时,也需要配置环境变量,但是环境变量到底是什么,为什么要配置环境变量? 


首先,我们来看一个现象。

我们在使用Linux时,会使用到各种各样的命令,同时,Linux也有着一个理念:Linux下一切皆文件,也就是说,Linux命令其实也是一个文件,只不过是用C\C++编写的后编译而成的可执行程序,同时,我们也可以编写自己的可执行程序。

 pwd命令的路径


但是现在有一个问题,我们在运行自己的可执行程序的时候,通常要在前面加上./,而命令不用。如下: 

 那么是什么原因导致的呢?首先,我们来看一下Linux系统中的环境变量,通过env命令查看。

把全部环境变量打印出来有很多,但不用怕,我们把我们需要的截图出来即可。

我们可以看到,在Linux系统中,有一个这样的环境变量,这里面正好有我们命令的路径。

同时在这里再引入另一个问题,当我们要运行我们的程序的时候,操作系统怎么知道我们的程序在那,它是怎么找到我们的程序的,那既然涉及到找程序,那必须要有该程序的路径,路径又有绝对路径相对路径,当我们运行我们的程序的时候,也是去找我们程序的位置,即./test中的./就是该程序的相对路径,所以操作系统会根据这个路径去找我们的程序,并运行起来。


所以当我们的操作系统要去运行pwd命令时,它也要去找该命令的路径,但是这个路径不需要我们提供,因为有一个环境变量,这个环境变量有这些命令的路径,操作系统会根据这个环境变量的路径去找到我们的命令。


所以说,这就是环境变量的意义,当我们在vscode中配置C语言的环境变量时,就是给一个路径,让我们的程序去这个路径中找到C语言的库。

常见的环境变量

接下来介绍一下常见的环境变量,以及如何查看单个环境变量。

PATH

是Linux系统中命令的路径

HOME

用户的家目录

PWD

用户所在目录


说到底,环境变量就是一堆字符串的全局变量,这些字符串的作用是,当我们要去使用某些命令或者功能时,需要通过这些环境变量去找,而且这个环境变量是内存级别的变量,当我们启动机器时,它才会被加载到内存中,关机后就会消失。

模拟pwd命令

既然环境变量中存放着我们需要用到的信息,那么我们可以使用这些环境变量来模拟实现pwd命令。

getenv函数

在Linux中,有一个函数,可以去获取我们的环境变量。 

 getenv函数,给一个环境变量的名字,返回它的环境变量的内容。使用方法如下。


我们编写的这个程序,是去PWD这个环境变量中获取我们所在的目录,并将其输出,所以我们自己写的pwd命令就简单的完成了。效果如下。

main函数的两个参数

有一些同学可能就会见过,在main函数的参数里,有两个参数。 

那么这两个参数分别又是什么呢?

char* argv[]:这是一个指针数组,每个位置里面放着一个char类型的指针

int argc:记录着argv[]这个数组的大小

那么这个指针数组里面的char指针又指向什么内容呢?我们来运行下面的代码来看看。


 

通过运行这个代码我们可以看到,当我们输入./test是它就输出./test

当我们输入./test -a -l时,它就输出./test   -a  -l。

这就是我们在使用命令时候的选项,比如说:ls -a -l

它就是通过这种方法来识别我们的选项,然后根据你所输入的选项来输出对应的信息。

 main函数的第三个参数

main函数不仅有前两个参数,还是第三个参数。

第三个参数是char* env[],这也是一个指针数组,这个数组里面每一个指针都指向一个环境变量。我们来运行看看。

所以说,要在程序中获取环境变量,除了可以使用getenv函数外,还能用char* env[]参数来获取,不过一个第一种方法用的比较多。

五.进程地址空间 

什么是进程地址空间?

在进行解释前,我们先来看一段程序。

下面是这段程序的代码。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int global=10;//定义一个全局变量
  5 
  6 int main()
  7 {
  8     pid_t id=fork();//创建子进程
  9     if(id<0)
 10     {
 11         printf("创建子进程失败\n");
 12         return 1;
 13     }
 14     else if(id==0)//说明是子进程
 15     {
 16        int cnt1=3; 
 17        while(cnt1--)
 18        {
 19            printf("我是子进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 20            sleep(1);
 21        }
 22 
 23        global=20;//在子进程中改变global的值
 24 
 25        int cnt=3;                                                                                                                                                                             
 26        while(cnt--)                                                                  
 27        {                                                                             
 28            printf("我是子进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 29            sleep(1);
 30        }              
 31     }                 
 32     else//说明是父进程
 33     {               
 34        int cnt2=6;                  
 35        while(cnt2--)                                                                 
 36        {                                                                             
 37            printf("我是父进程,全局变量global:%d,它的地址是:%p\n",global,&global);
 38            sleep(1);                
 39        }                            
 40     }                               
 41     return 0;
 42 }

在这段代码中,我做的事情是:创建了一个全局变量global,且创建了一个子进程,在子进程和父进程中,刚开始我都进行了相同的操作,将全局变量的值和地址都进行一个输出,3秒后,我在子进程中将全局变量global的值进行改变,再进行一个输出。

现在我们来看一下运行的结果。 

在前3秒的运行结果中,我们可以看到,父进程和子进程都进行了相应的输出,它们打印出来的global的值都是相同的,这应该没什么问题,且它们打印出来的地址也是相同,这样看起来也确实没什么问题。

但是当在子进程中将global的值进行改变的时候,只有子进程的global改变了,而父进程的global没有改变,这看起来也很合理,但是这个时候发现,父进程和子进程的global的地址竟然也是相同的???


看到这个现象难免会觉得很奇怪,我们来分析一下:

我们知道,当一个程序运行起来的时候,它会被加载到内存中,相应的它所创建的变量也会在内存中,同时我们知道内存在电脑中其实是一个硬件,即内存条,就是说这些变量和程序都是放在内存条中的。

这时我们在看上面的代码结果,发现父进程和子进程global这个变量的地址是相同的,但是值不同,我们在把这个结果套入我们的内存条中分析,它们的地址相同,说明在这个内存条上,父进程和子进程的global的位置是相同的,但是值不同,说明在这个位置的内存上的值不同,也就是说相同的位置却又两个不同的值

这看上去显然不合理,所以说上面结果所打印出来的值其实不是真实的地址,而是虚拟的

什么是进程地址空间 

上面的问题,我们先放一放,我们来解释一下什么是地址空间。 


在学习C/C++的时候,应该都见过这个图, 

 

为什么这个图叫做C/C++的内存分布,而不是内存条的内存分布?

那既然有C/C++的内存分布,那是不是说明还有其他语言的内存分布,为什么同一块内存条上,不同的语言会有不同的内存分布呢?

那是因为这个C/C++内存分布其实是虚拟地址,这个内存分布的地址不是真实的,这个虚拟地址就叫做进程的地址空间


那么进程的地址空间到底是什么?

在linux中,进程的地址空间其实就是一个结构体变量,当一个程序被运行起来的时候操作系统会给它创建这个结构体的变量,在这个结构体变量中,它记录了栈区、堆区、未初始化数据区,初始化数据区的起始位置和终止位置,也就是虚拟地址,且当程序运行起来的时候,会通过页表将这个结构体变量上的虚拟地址映射到物理内存中,光看这句话很难理解,我们来看一下图。

 

当我们把一个程序运行起来的时候,会将这个程序加载到内存中,同时,操作系统会为这个进程创建一个PCB进程控制块,同时也会创建这个进程的地址空间,最后通过页表将地址空间中的虚拟地址映射到物理内存中。

为什么要有地址空间 

知道了什么是地址空间后,那么为什么要有地址空间呢?

那是因为要保证进程的独立性,同时也是为了安全考虑的,如果没有地址空间,让进程直接去访问物理内存,那要是越界了怎么办,有了地址空间的存在,要是越界访问了能很好的进行判断并阻止。

那么进程的独立性又是如何保证的?

我们接着来看开始那段程序:


首先我们要先知道,父进程是如何创建子进程的。

当我们调用fork函数去创建子进程的时候,操作系统会帮我们把父进程的数据和代码全盘拷贝一份,同时PCB进程控制块进程的地址空间页表也会全盘拷贝下来。这个时候子进程就创建好了,这对于前3秒的运行结果来看,没什么问题,但是当我们更改全局变量global的时候,这里会发生一个写时拷贝,操作系统会为子进程的global变量开一块新空间,将新的值20写进去,写完后,再改变子进程页表的映射关系来保证进程的独立性,使父进程和子进程的global变量互不影响。

光看这段看也很难理解,我们来看一下图:

 

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

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

相关文章

夏日智启:我的Datawhale AI夏令营探索之旅

前言 最近几年&#xff0c;AI&#xff08;人工智能&#xff09;的发展呈现出了前所未有的迅猛势头&#xff0c;其影响力和应用范围不断扩大&#xff0c;深刻地改变着我们的生活、工作和社会结构。尤其是AI大模型技术&#xff0c;国内外可谓是“百模大战”&#xff0c;百舸争流…

【Python 基础】控制流 - 1

控制流 你已经知道了单条指令的基本知识。程序就是一系列指令。但编程真正的力量不仅在于运行&#xff08;或“执行”&#xff09;一条接一条的指令&#xff0c;就像周末的任务清单那样。根据表达式求值的结果&#xff0c;程序可以决定跳过指令&#xff0c;重复指令&#xff0…

线性代数|机器学习-P22逐步最小化一个函数

文章目录 1. 概述2. 泰勒公式3. 雅可比矩阵4. 经典牛顿法4.1 经典牛顿法理论4.2 牛顿迭代法解求方程根4.3 牛顿迭代法解求方程根 Python 5. 梯度下降和经典牛顿法5.1 线搜索方法5.2 经典牛顿法 6. 凸优化问题6.1 约束问题6.1 凸集组合 Mit麻省理工教授视频如下&#xff1a;逐步…

实验-ENSP实现防火墙区域策略与用户管理

目录 实验拓扑 自己搭建拓扑 实验要求 实验步骤 整通总公司内网 sw3配置vlan 防火墙配置IP 配置安全策略&#xff08;DMZ区内的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9: 00- 18:00)可以访问&#xff0c;生产区的设备全天可以访问&#xff09; 配置nat策…

26.Labview波形图、XY图、强度图使用精讲

我们如何使用Labview显示曲线或者制作出下面这种我们想要的曲线并随着我们输入值的变化而变化呢&#xff1f; 本文详细讲解一下每种波形图的使用方式&#xff0c;帮助大家深入了解波形图的使用技巧。 文章中的所有程序已上传到下面链接中&#xff0c;下载地址(建议先转存)&am…

利用量子信息推进计算

利用量子信息推进计算 编译 李升伟 我们重点介绍 2024 年美国物理学会 3 月会议上关于量子计算和量子算法的热烈讨论&#xff0c;并邀请能够显著推动量子信息科学领域向前发展的论文提交。 美国物理学会 (APS) 三月会议可以说是世界上最大的年度物理学会议之一&#xff0c;今…

N32G45XVL-STB之lvgl的应用实例

目录 概述 1 硬件介绍 1.1 ST7796-LCD 1.2 MCU IO与LCD PIN对应关系 1.3 MCU IO与Touch PIN对应关系 2 N32G45x移植 LVGL 2.1 移植步骤 2.2 注意点 2.2.1 UI刷新函数 2.2.2 主函数中调用 3 LVGL的应用Demo 3.1 功能描述 3.2 代码实现 3.3 测试 N32G45XVL-STB之lv…

为Linux设置GRUB密码

正文共&#xff1a;999 字 11 图&#xff0c;预估阅读时间&#xff1a;1 分钟 我们前面介绍了如何恢复root密码&#xff08;CentOS 7.9遗忘了root密码怎么办&#xff1f;&#xff09;&#xff0c;虽然简单好用&#xff0c;但是可能会被不法分子利用&#xff0c;造成root密码以及…

Elasticsearch文档_id以数组方式返回

背景需求是只需要文档的_id字段&#xff0c;并且_id组装成一个数组。 在搜索请求中使用 script_fields 来整理 _id 为数组输出&#xff1a; POST goods_info/_search?size0 {"query": {"term": {"brand": {"value": "MGC"…

防火墙图形化界面策略和用户认证(华为)

目录 策略概要认证概要实验拓扑图题目要求一要求二要求三要求四要求五要求六 策略概要 安全策略概要&#xff1a; 安全策略&#xff08;Security Policy&#xff09;在安全领域具有双重含义。宏观上&#xff0c;安全策略指的是一个组织为保证其信息安全而建立的一套安全需求、…

通过图像高频信息保留图像细节,能保留多少细节-Comfyui

&#x1f9e8;前情提要 如果还不了解comfyui中图像高频信息保留细节的内容&#xff0c;可以参考上一篇文章&#xff1a; 图像中高频信息、低频信息与ComfyUI中图像细节保留的简单研究-CSDN博客 这次主要是简单测试下保留图像细节&#xff0c;能保留到什么程度&#xff1b; …

自建搜索引擎-基于美丽云

Meilisearch 是一个搜索引擎&#xff0c;主程序完全开源&#xff0c;除了使用官方提供的美丽云服务&#xff08;收费&#xff09;进行对接之外&#xff0c;还可以通过自建搜索引擎来实现完全独立的搜索服务。 由于成本问题&#xff0c;本博客采用自建的方式&#xff0c;本文就…

HybridCLR原理中的重点总结

序言 该文章以一个新手的身份&#xff0c;讲一下自己学习的经过&#xff0c;大家更快的学习HrbirdCLR。 我之前的两个Unity项目中&#xff0c;都使用到了热更新功能&#xff0c;而热更新的技术栈都是用的HybridCLR。 第一个项目本身虽然已经集成好了热更逻辑&#xff08;使用…

【排序 - 冒泡排序】

当我们谈论经典的排序算法时&#xff0c;冒泡排序&#xff08;Bubble Sort&#xff09;往往是最先被提及的一种。尽管它在实际应用中不太常见&#xff0c;但冒泡排序的简单易懂&#xff0c;有助于理解排序算法的基本原理和思想。 冒泡排序的基本原理 冒泡排序是一种基础的交换…

Git的基本知识点 + GitBash安装Pacman + Git命令含有中文,终端输出中文乱码

Git的基本知识点&#xff1a;整理自以下作者的文章繁华似锦Fighting的文章https://www.jianshu.com/nb/49854893另外还补充了git ls-file、.gitignore 等内容&#xff0c;涉及具体操作&#xff0c;还有命令总结。简略版可以看以上作者的文章&#xff0c;详细版可以看网盘里面的…

【企业级监控】源码部署Zabbix与监控主机

Zabbix企业级分布式监控 文章目录 Zabbix企业级分布式监控资源列表基础环境一、LNMP环境搭建&#xff08;在zbx主机上&#xff09;1.1、配置Yum仓库1.1.1、下载阿里云的仓库文件1.2.2、安装PHP7的仓库1.2.3、生成Mariadb10.11的仓库文件1.2.4、快速重建Yum缓存 1.2、安装PHP7.4…

Golang | Leetcode Golang题解之第228题汇总区间

题目&#xff1a; 题解&#xff1a; func summaryRanges(nums []int) (ans []string) {for i, n : 0, len(nums); i < n; {left : ifor i; i < n && nums[i-1]1 nums[i]; i {}s : strconv.Itoa(nums[left])if left < i-1 {s "->" strconv.It…

数学建模美赛经验小结

图片资料来自网络所听讲座&#xff0c;感谢分享&#xff01;

《C++设计模式》状态模式

文章目录 一、前言二、实现一、UML类图二、实现 一、前言 状态模式理解最基本上的我觉得应该也是够用了&#xff0c;实际用的话&#xff0c;也应该用的是Boost.MSM状态机。 相关代码可以在这里&#xff0c;如有帮助给个star&#xff01;AidenYuanDev/design_patterns_in_mode…

Hadoop-22 Sqoop 数据MySQL到HDFS(全量) SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce

章节内容 上节我们完成了&#xff1a; Sqoop 介绍Sqoop 下载配置环境等Sqoop 环境依赖&#xff1a;Hadoop、Hive、JDBC 等环境补全 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 VM 虚拟机…