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

news2024/9/19 10:52:18

目录

一.什么是进程

进程和程序的区别

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/1918237.html

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

相关文章

行列视(RCV)报表是如何产生的?

首先看一下对于生产型企业来说&#xff0c;生产数据特点是什么样的&#xff1a; 生产数据可以理解为是生产制造企业&#xff0c;在生产过程中从车间现场设备上通过自动化传感器采集到的全面感知数据。这类数据一般包括设备状态、设备管道运行参数、各种仪表参数等。具有持续、…

【Linux操作系统-测试】第三节.Linux 系统、网络信息、用户权限命令总结

文章目录 前言一、Linux 系统相关信息命令 1.1 df 命令--查看磁盘剩余 1.2 ps 命令--查看进程 1.3 top 命令--显示进程运行状态 1.4 kill 命令说明 -- 杀死进程二、Linux 网络信息命令 2.1 ping 命令--检查网络是否连通 2.1 ifconfig--显示网络设…

便携式(手持)气象仪:野外气象监测的得力助手

在气象监测领域&#xff0c;随着科技的不断发展&#xff0c;一种新型的便携式&#xff08;手持&#xff09;气象仪正在逐渐崭露头角。便携式&#xff08;手持&#xff09;气象仪以其高度集成、低功耗、可快速安装以及便于野外使用的特性&#xff0c;成为了气象观测领域的一颗新…

微信小程序切换商户号

1.登录微信公众平台小程序 2.功能->微信支付 3.关联成功后会志一关联商户号列表显示 4.登录你需要切换的商户号 在下面选择你需要开通的产品服务 5.切换到账户中心的api安全里面 只需要改变当前下面的配置即可切换小程序的收款商户号 申请API证书按照官方的指引即可解…

Redis+Caffeine 实现两级缓存实战

RedisCaffeine 实现两级缓存 背景 ​ 事情的开始是这样的&#xff0c;前段时间接了个需求&#xff0c;给公司的商城官网提供一个查询预计送达时间的接口。接口很简单&#xff0c;根据请求传的城市仓库发货时间查询快递的预计送达时间。因为商城下单就会调用这个接口&#xff…

如何将一个2D数组切分成多个块

要将一个2D数组切分成多个块&#xff0c;可以考虑使用以下几种方法&#xff0c;具体取决于如何定义块的划分规则和需求。如果你希望将2D数组均匀地切分成固定大小的小块&#xff0c;可以使用简单的循环和切片操作。 1、问题背景 Python 中, 如果有一个 raw 数据文件&#xff0…

SAP与税控系统集成案例

一、项目背景 重庆润通控股有限公司成立于2007年&#xff0c;是一家集合汽柴油动力及终端、摩托车、储能电源、汽车零部件、金融服务等产业的多元化集团公司。 大量订单数据导致订单业务会很复杂&#xff0c;为提供订单完成质量&#xff0c;引入税控系统服务商进行订单开票…

docker笔记2

docker笔记2 一、阿里云镜像配置二、docker基本原理1.docker是如何启动一个容器的2.docker的底层原理 三、镜像命令总结 一、阿里云镜像配置 配置镜像的目的 由于Docker Hub等公共镜像仓库的服务器可能位于国外&#xff0c;直接从中拉取镜像时可能会遇到网络延迟或不稳定的问…

硬盘模式vmd怎么改ahci_电脑vmd改ahci模式详细步骤

最近有很多网友问&#xff0c;我新买的电脑安装原版win10或win11找不到驱动器呀&#xff0c;进入第三方pe又找不到硬盘&#xff0c;找到硬盘安装后又出现安装蓝屏的情况&#xff0c;新机器怎么回事呀&#xff1f;这位网友内心有点崩溃&#xff0c;不知道啥原因。其实这些都是由…

magma软件许可优化解决方案

Magma软件介绍 MAGMA在90年代初通过代理进入中国市场。为了更好快速的服务中国的客户、带来更便捷的、中文语言的技术支持和培训&#xff0c;2009年&#xff0c;MAGMA德国在中国正式设立分公司&#xff0c;即迈格码&#xff08;苏州&#xff09;软件科技有限公司&#xff08;以…

Nodejs 第八十章(Kafka高级)

kafka前置知识在前几章章讲过了 不再复述 Kafka集群操作 1.创建多个kafka服务 拷贝一份kafka完整目录改名为kafka2 修改配置文件 kafka2/config/server.properties 这个文件 broker.id1 //唯一broker port9093 //切换端口 listenersPLAINTEXT://:9093 //切换监听源启动zooKe…

MySQL DDL

数据库 1 创建数据库 CREATE DATABASE 数据库名 CREATE DATABASE IF NOT EXISTS 数据库名;&#xff08;判断是否存在) CREATE DATABASE 数据库名 CHARACTER SET 字符 2 查看数据库 SHOW DATABASES; 查看某个数据库的信息 SHOW CAEATE DATABASE 数据库名 3 修改数据库 …

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 找单词(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

AIDL通讯机制

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Android ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 1、接口定义 2、数据传输 3、Binder机制 4、使用场景 5、线程安全 我的其他博客 正文 AIDL通讯机制 它是android平台上用于实现…

vue创建项目失败(Error: EPERM: operation not permitted)

项目报错&#xff0c;not permitted是不允许、没有权限&#xff0c;解决方法就是需要改一下node所在盘下面的权限 npm ERR! code EPERM npm ERR! syscall mkdir npm ERR! path C:\Program Files\nodejs\node_cache\_cacache npm ERR! errno -4048 npm ERR! Error: EPERM: opera…

【python算法学习1】用递归和循环分别写下 fibonacci 斐波拉契数列,比较差异

问题&#xff1a; fibonacci 斐波拉契数列&#xff0c;用递归和循环的方法分别写,比较递归和循环的思路和写法的差别 最直接的思路&#xff0c;是写递归方法 循环方法的稍微有点绕&#xff0c;我觉得问题主要是出在&#xff0c;总结循环的通项公式更麻烦&#xff0c;难在数学…

2008年下半年软件设计师【下午题】真题及答案

文章目录 2008年下半年软件设计师下午题--真题2008年下半年软件设计师下午题--答案 2008年下半年软件设计师下午题–真题 2008年下半年软件设计师下午题–答案

FPGA上板项目(一)——点灯熟悉完整开发流程、ILA在线调试

目录 创建工程创建 HDL 代码仿真添加管脚约束添加时序约束生成 bit 文件下载ILA 在线调试 创建工程 型号选择&#xff1a;以 AXU9EG 开发板为例&#xff0c;芯片选择 xczu9eg-ffvb1156-2-i 创建 HDL 代码 注意&#xff1a;由于输入时钟为 200MHz 的差分时钟&#xff0c;因此…

安卓 APK 安装过程详解

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Android ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 1. 开机后连上网线 2. 查看网线的IP地址 3. 检查ADB连接 4. 修改文件权限 步骤 结语 我的其他博客 前言 在安卓设备上安装…

商家为何疯狂送钱?用户如何省钱?一篇文章带你了解!

大家好&#xff0c;我是你们的电商策略伙伴吴军。今天&#xff0c;我将带大家走进一个颠覆传统、充满活力的商业模式——循环购模式。你是否曾听说过“消费1000&#xff0c;回馈2000”的诱人宣传&#xff1f;又或者&#xff0c;每天动动手指就能领钱&#xff0c;而且这些钱还能…