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

目录

一.什么是进程

进程和程序的区别

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.mzph.cn/news/870098.shtml

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

相关文章

科普文:jvm笔记

一、JVM概述# 1. JVM内部结构# 跨语言的平台&#xff0c;只要遵循编译出来的字节码的规范&#xff0c;都可以由JVM运行 虚拟机 系统虚拟机 VMvare 程序虚拟机 JVM JVM结构 HotSpot虚拟机 详细结构图 前端编译器是编译为字节码文件 执行引擎中的JIT Compiler编译器是把字节…

关于无法定位程序输入点 SetDefaultDllDirectories于动态链接库KERNEL32.dll 上 解决方法

文章目录 1. ERNEL32.dll 下载2. 解决方法 &#x1f44d; 个人网站:【 洛秋小站】 1. ERNEL32.dll 下载 Windows 7 在安装postman时报错缺少动态链接库,提示缺少.NET Framework,这是因为本地缺少相应的dll文件导致的&#xff0c;这时就需要下载ERNEL32.dll文件&#xff0c;在解…

前端/python脚本/转换-使用天地图下载的geojson(echarts4+如果直接使用会导致坐标和其他信息不全)

解决echarts4如果直接使用天地图下载的geojson会导致坐标和其他信息不全 解决方法是使用python脚本来补全其他信息&#xff1a;center&#xff0c;level&#xff0c;adcode等内容 前提是必须有一个之前使用的json文件&#xff08;需要全一点的数据供echarts使用&#xff09; …

Linux文件编程应用

目录 一、实现cp命令 二、修改程序的配置文件 三、写一个整数/结构体到文件 1.写一个整数到文件 2.写一个结构体到文件 四、写结构体数组到文件 我们学习了文件编程的常用指令以及了解文件编程的基本步骤后&#xff0c;试着来写一些程序实现某些功能。&#xff08;没有学…

记录一次微信小程序申诉定位权限过程

1 小程序接到通知&#xff0c;检测到违规&#xff0c;需要及时处理&#xff0c;给一周的缓冲时间&#xff0c;如果到期未处理&#xff0c;会封禁能力&#xff08;2023-11-17&#xff09; 2 到期后&#xff0c;仍未处理&#xff0c;封禁能力&#xff08;2023-11-24&#xff09; …

【04】微服务通信组件Feign

1、项目中接口的调用方式 1.1 HttpClient HttpClient 是 Apache Jakarta Common 下的子项目&#xff0c;用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包&#xff0c;并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 JDK 自带的 URLConnectio…

打印任务无法删除怎么办?

在删除打印任务的时候&#xff0c;你可能会遇到这样的情况&#xff0c;当我们想把打印任务取消的时候&#xff0c;却一直显示正在删除&#xff0c;而过了很久还没有取消掉&#xff0c;下面就分享一下处理这个问题的方法。 1、停止打印服务&#xff0c;按WinR键打开运行对话框&a…

Unity WebGL 嵌入前端网页并通信

1. 前言 最近在做项目时遇到需要将 UnityWebGL 嵌入到网页中去&#xff0c;且需要点击网页中的按钮 UnityWebGL 可以做出响应。新建项目部分直接略过 2. 最终效果 3. 基础设置 设置导出平台为WebGL 在Player Settings -> Publishing Settings 中勾选 Data Caching 和Deco…

《Windows API每日一练》9.1.5 自定义资源

自定义资源&#xff08;Custom Resources&#xff09;是在 Windows 程序中使用的一种资源类型&#xff0c;用于存储应用程序特定的数据、图像、音频、二进制文件等。通过自定义资源&#xff0c;开发者可以将应用程序所需的各种资源文件集中管理和存储&#xff0c;便于在程序中访…

imx6ull/linux应用编程学习(17)利用mqtt上传开发板数据,和控制开发板led(基于正点)

1.关于如何创建自己的服务器&#xff0c;可看上篇文章 imx6ull/linux应用编程学习&#xff08;16&#xff09;emqx &#xff0c;mqtt创建连接mqtt.fx-CSDN博客 2.实现任务&#xff1a;&#xff08;正点原子教程源码改&#xff09; (1)用户可通过手机或电脑远程控制开发板上的…

小白的OS Copilot 产品测评

背景 通过群友介绍才知OS Copilot 。不想错过任何优秀的AI产品。随着互联网的发展和时代的进步&#xff0c;要紧跟时代&#xff0c;了解市面上的优秀的AI科技产品。 OS Copilot 产品体验评测 1&#xff09;您的角色是什么&#xff1f;开发、运维、学生&#xff1f;如果使用O…

类和对象——【const成员】【static成员】【友元】【内部类】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件iostream的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

css文字自适应宽度动态出现省略号...

前言 在列表排行榜中通常会出现的一个需求&#xff1a;从左到右依次是名次、头像、昵称、徽标、分数。徽标可能会有多个或者没有徽标&#xff0c;徽标长度是动态的&#xff0c;昵称如果过长要随着有无徽标进行动态截断出现省略号。如下图布局所示&#xff08;花里胡哨的底色是…

若依vue集成electron实现打包exe应用程序

一、修改package.json文件,加入相关依赖和配置 {"name": "ruoyi","version": "3.8.6","description": "若依管理系统","author": "若依","license":

分层图最短路,CF 1725M - Moving Both Hands

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1725M - Moving Both Hands 二、解题报告 1、思路分析 题意看似很简单&#xff0c;就是问我们两个人分别处于1, p两个点上&#xff0c;二者同时出发&#xff0c;相遇时二者所用路径之和的最小值 不难想到求…

Git协作

文章目录 Git协作冲突冲突的发生情况解决冲突如何处理冲突 1 分支1.1 什么是Git分支1.2 创建分支 2 切换分支2.1 指向分支2.2 暂存分支切换分支与未提交更改的处理使用 Stash 临时保存更改Stash 的工作原理&#xff1a;场景设定使用 Git Stash 3 远程分支3.1 快进合并快进合并的…

Qt/QML学习-定位器

QML学习 定位器例程视频讲解代码 main.qml import QtQuick 2.15 import QtQuick.Window 2.15Window {width: 640height: 480visible: truetitle: qsTr("positioner")Rectangle {id: rectColumnwidth: parent.width / 2height: parent.height / 2border.width: 1Col…

Qt基础控件总结—多页面切换(QStackWidget类、QTabBar类和QTabWidget类)

QStackedWidget 类 QStackedWidget 类是在 QStackedLayout 之上构造的一个便利的部件,其使用方法与步骤和 QStackedLayout 是一样的。QStackedWidget 类的成员函数与 QStackedLayout 类也基本上是一致的,使用该类就和使用 QStackedLayout 一样。 使用该类可以参考QStackedL…

iPhone数据恢复篇:在 iPhone 上恢复找回短信的 5 种方法

方法 1&#xff1a;检查最近删除的文件夹 iOS 允许您在 30 天内恢复已删除的短信。您需要先从“设置”菜单启用“过滤器”。让我们来实际检查一下。 步骤 1&#xff1a;打开“设置” > “信息”。 步骤 2&#xff1a;选择“未知和垃圾邮件”&#xff0c;然后切换到“过滤…

如何将若依vue升级到springboot3.x?

为了确保项目符合要求,Spring Boot 3.x 要求Java版本为17或更高。 1、修改根目录下的pom.xml文件 <!-- java.version版本8更换为17 --> <java.version>17</java.version><!-- 新增节点 --> <mybatis-spring-boot.version>3.0.3<