Linux环境变量&&进程地址空间详解

2025-02-08 04:50

本文主要是介绍Linux环境变量&&进程地址空间详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,...

一、初步认识环境变量

1.1常见的环境变量

  • PATH:Linux系统下的指令命令的默认搜索路径

Linux环境变量&&进程地址空间详解

  • HOME:用户登录shell的默认主工作目录

Linux环境变量&&进程地址空间详解

  • SHELL:当前Shell,它的值通常是/bin/bash

Linux环境变量&&进程地址空间详解

为什么我们运行自己的可执行程序需要加./,而一些指令可以直接执行,这是因为存在环境变量PATH,它是Linux下指令的默认搜索路径,当运行一个指令时,操作系统会到PATH中去查找该指令的所在路径 ,当我们把我们可执行程序的路径添加到PATH中,也可像指令一样直接执行,不需要添加./

Linux环境变量&&进程地址空间详解

和环境变量相关的指令

  • 1. echo: 显示某个环境变量值
  • 2. export: 设置一个新的环境变量
  • 3. env: 显示所有环境变量
  • 4. unset: 清除环境变量
  • 5. set: 显示本地定义的shell变量和环境变

1.2环境变量的基本概念

环境变量是系统引入的一套name=value形式的变量,不同的环境变量具有不同的用途,环境变量具有全局属性

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数

有了全局属性这个概念,以下要引入命令行参数来解释这个概念

二、命令行参数

2.1通过命令行参数获取环境变量

main函数有三个参数,int  agrc ,char*argv[ ] ,char*evn[ ]

当我们在输入各种指令,运行各种程序时,本质上输入的都是一个一个的字符串,bash会根据空格将这些字符串一一划分,argc就是用来记录划分字符串的个数的,argv是一张叫作命令行参数的表(本质上是一个指针数组),里面存储的是被bash分割形成的一个个字符串的地址

为什么需要命令行参数表呢?因为这样可以为我们的指令、软件、软件等提供命令行选项支持!

命令行参数表是以NULL为结束的

实例:通过argv查看命令行参数

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

实例:通过命令行参数argc和agrv来进行选项的设置

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

main函数中还有一个参数evn,它对应是一张环境变量表,可以通过打印该表来查看系统的所以环境变量

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

通过比较可以发现,子进程的环境变量和env展示出来的环境变量一模一样!这就可以解释为什么环境变量具有全局属性了!我们所运行的进程都是子进程,bash本身在启动时会从操作系统的配置文件中读取环境变量, 形成一张环境变量表,这个表会被子进程的main函数参数接收,也就是说,子进程会继承父进程交给它的环境变量,所有建立在父进程上的子进程都会有相同的一份环境变量2.2本地变量和内建命令

本地变量:只会在本bash内部有效,不会被子进程继承

Linux环境变量&&进程地址空间详解

set可以查看所以变量(环境变量&&本地变量)

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

export可以把本地变量设置成成环境变量

Linux环境变量&&进程地址空间详解

子进程中也可以查找到一份

Linux环境变量&&进程地址空间详解

通过unsChina编程et可清除设置的环境变量

我们知道任何程序和指令在运行时都是bash的子进程,本地变量只对本bash内部有效,那么这里就有一个问题了:为什么使用echo可以显示出MY_VALUE的内容呢?

这里就要再引入一个新的概念:内建命令

  1. 常规命令:通过创建子进程完成的
  2. 内建命令:bash不创建子进程,由bash自己执行,类似bash通过调用自己内部实现的函数或者系统提供的函数,比如echo、cd都属于内建命令,比如我们在cd时改变的是bash下的工作目录,并不会去改变子进程所在的目录
  3. 模拟实现一个具有cd功能的指令--chdir()系统提供的一个改变当前工作目录的函数

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

2.3环境变量的获取

  • 第一种就是上面演示的通过命令行的第三个参数获取
  • 第二种是通过第三方变量environ获取

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

第三种是通过调用系统函数获取或者设置环境变量--getenv()和putenv()

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

三、进程地址空间

3.1进程(虚拟)地址空间的引入

观察下面代码的运行结果:可以看到子进程对全局变量g_val1进行修改,父进程和子进程输出变量的值不一样,但是地址是一样的,说明该变量所在的地址一定不是物理地址!父子进程输出的变量在物理地址上看来也不是同一个变量!我们在用C/C++语言看到的也是虚拟地址,真正的物理地址用户是看不见到,由操作系统统一管理

Linux环境变量&&进程地址空间详解

Linux环境变量&&进程地址空间详解

操作系统必须负责物理地址和虚拟地址之间的映射,那操作系统具体是如何做到的呢?

3.2进程地址空间的布局和理解

结合fork() 初步理解地址进程空间

前面已经谈到,fork()创建子进程成功,会有两个返回值,给父进程返回子进程的pid,给子进程返回0。fork()之后创建的子进程,在内存中除了有一个以父进程为模板(拷贝)的pcb数据结构,还有一个从父进程拷贝下来的mm数据结构(进程地址空间)和一个页表,页表中存着变量虚拟地址和物理地址的映射关系、权限字段、判断数据在内存还是磁盘的字段,通过映射关系,可以找到虚拟地址对应的物理地址

子进程刚创建的时候,在mm中数据的虚拟地址和父进程是一样的,我们打印看到的都是这个虚拟地址,子进程刚拷贝父进程的数据结构内容的时候,代码和数据都是共用的,并且数据在页表中的权限会被设置为只读

由于代码是共享的,那么在fork()return前子进程被创建好了,return就会被父子进程各执行一次,又由于return的实质就是在对变量进行写入,这时候就相当于要对数据进行修改

当子进程和父进程有一方要对数据进行修改,就会触发对数据的非法操作,从而发生缺页中断,此时操作系统就会重新在内存中开辟一块空间,将要修改的数据拷贝一份作修改,再重新建立映射关系,这个过程也叫写时拷贝,这样父子进程就做到了各自私有一份数据

我们上面代码所展示的结果,地址一样变量值不一样,这是因为打印出来的地址是虚拟地址,子进程拷贝了父进程的数据结构内容,所以他们的虚拟地址就是一样的;变量值不一样是因为,写时拷贝后虚拟地址在页表中映射的物理地址不一样,找到的数据当然也就并不一样了。这两个原因结合就说明了我们所看到的现象

Linux环境变量&&进程地址空间详解

3.3什么是地址空间?

地址总线排列组合形成的地址范围[0,2^32) 32根地址总线

3.4地址空间如何进行区域划分?

进程地址空间本质上是描绘进程可视范围大小,在内核上他是一个数据结构对象(mm_struct),也要被 操作系统管理,地址空间通过各个区域的star和end对区域进行线性(区域)划分,在一个区域的范围内,连续空间中,每一个最小单元都有地址,都可以被使用

区域空间的调整,本质上就是通过调整每个区域的star和end

3.5进一步理解进程和进程地址空间

目前为止,我们所说的进程,就是:进程=内核数据结构对象(pcb,mm,页表)+程序的代码和数据(可执行文件) 

为什么需要进程地址空间?

让进程以统一的视角来看待内存:如果进程直接和物理地址进行交互,那么进程的pcb数据结构中就势必要存在各个数据的物理地址。一个进程的www.chinasem.cn各个数据部分,在物理内存中实际是乱序的,但是有了地址空间(mm_struct)之后:代码就在代码段,数据就在数据段该在堆区的在堆区,该在栈区的在栈区同时,这些连续的虚拟地址再经过的页表映射到物理内存,这样,让进程数据地址从无序变有序,让进程以统一的视角看待物理内存以及各个运行区域,每个进程都会以为自己占有了整个系统的内存资源

拦截对内存的非法操作和异常访问:进程地址空间让我们在访问内存的时候有一个转化的过程,在这个过程中,如果我们对内存进行了非法操作和访问,那么就会被拦截,物理内存不会收到影响,进而保护了物理内存

有了进程地址空间和页表,就可以做到将进程管理模块和内存管理模块进行解耦合:进程在运行的时候不会关心操作系统是如何申请内存的,对进程的管理和对内存的申请都是由操作系统来完成,他们互不干扰!操作系统不做任何浪费时间和空间的事情,当一个进程申请了内存,但是它又不立即使这块内存的时候,就相当于占用了内存资源,这时候操作系统会采用惰性加载的方式:给该进程一个虚拟地址,但在页表中并没有实际映射的物理地址,也就是说进程看到的是已经开辟好的虚拟地址,但在物理内存上并没有真的申请到空间;当进程需要内存的时候,找不到映射的物理地址触发缺页中断,此时操作系统就会给它开辟空间,建立映射关系。从而说明了,pcb数据结构对象的创建先于可执行程序的加载

3.6页表的理解

页表不仅仅有虚拟地址和物理地址的映射,还有对应的编程China编程权限,当一个进程要对一个数据修改时,本质是通过虚拟地址找到对应物理内存的数据再修改,当要修改某一个数据,但是该数据在页表的所记录的权限只有rx,仅仅允许只读,那么就会修改动作就会被拦截,直接报错,程序崩溃,修改这个动作就不会被允许

这也就是为什么,一个程序崩溃时,并不会影响其他进程,因为崩溃的程序在虚拟内存页表层就已经被拦截,操作系统会直接杀掉进程,进而也就不会影响其他进程的运行

总结:页表存在CPU的cr3寄存器(物理地址),进程在被CPU调度和离开CPU的时候,都要带走寄存器里的数据 ,CPU在运行程序时,为了获取数据,就会通过cr3寄存器里的页表地址找到该进程的页表,页表中的虚拟地址通过映射得到物理地址,进而可以访问到物理内存,同时会根据虚拟地址和物理地址的映射权限(rwx)来决定是否能对该物理内存进行操作,如果非法操作,该请求会被拦截,操作系统会将此进程杀掉

三个实例体现页表的作用

进程的挂起是如何实现的?前面说到,页表中还存在一个用来判断数据是在内存还是在磁盘中的字段,进程的挂起就是将该进程所对应的数据和代码换出到外设分区中,那么这时候操作系统只需要通过修该字段就可以知道进程是否处于挂起状态

进程的独立性:进程的独立性表现在,每个进程都有自己私有的一份数据,以及每个进程都有自己的一份mm_struct进程地址空间,这就保证了每个进程只能访问自己的进程地址空间,相互之间不得访问!也就是说,如果进程直接和物理内存打交道,那么就可能访问到其他进程的数据,但是由于进程地址空间的存在,非法qsTtjPyXP访问在页表层面就会被拦截,确保了更个进程之间不会非法访问和篡改对方的数据

代码和字符常量区的数据为什么是只读的?如果这写数据本身是只读的,那么它就不可能从磁盘加载到内存中!这边的只读是在页表层面上的只读!它在页表中的权限被设置成只读,当一个进程试图修改该部分的数据,一样会被拦截

四、Linux内核进程调度队列

4.1优先级

  • 普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
  • 实时优先级:0~99(不关心)

4.2活动队列

  • 所有时间片还没结束的进程都被放在活动队列
  • 本质上是一个指针数组,数组的下标就表示优先级,从100开始
  • 调度过程
  1. 从[0,140)开始遍历,找到第一个优先级最高且非空的队列
  2. 从该队列的第一个进程开始调度运行
  3. 但由于逐一遍历数组的效率太低下了,为了提高查找非空队列的效率,可以采用位图的思想,用5*32个比特位来队列是否为空

4.3过期队列

  • 过期队列的结构和活动队列一样
  • 过期队列上放的都是时间片过期的队列
  • 当活动队列的进程都调度结束了,那么swap交换两个队列的指针,就可以对过期队列的进程进行时间片的重新计算,等待调度运行

4.4active指针和expired指针

  • active指针指向活动队列
  • expired指针指向过期队列
  • 当活动队列的进程全都调度完毕,swap交换两个指针,就相当于有了新的活动队列

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Linux环境变量&&进程地址空间详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153338

相关文章

Java导出Excel动态表头的示例详解

《Java导出Excel动态表头的示例详解》这篇文章主要为大家详细介绍了Java导出Excel动态表头的相关知识,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录前言一、效果展示二、代码实现1.固定头实体类2.动态头实现3.导出动态头前言本文只记录大致思路以及做法,代码不进

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语

Rsnapshot怎么用? 基于Rsync的强大Linux备份工具使用指南

《Rsnapshot怎么用?基于Rsync的强大Linux备份工具使用指南》Rsnapshot不仅可以备份本地文件,还能通过SSH备份远程文件,接下来详细介绍如何安装、配置和使用Rsnaps... Rsnapshot 是一款开源的文件系统快照工具。它结合了 Rsync 和 SSH 的能力,可以帮助你在 li

一文详解Java Condition的await和signal等待通知机制

《一文详解JavaCondition的await和signal等待通知机制》这篇文章主要为大家详细介绍了JavaCondition的await和signal等待通知机制的相关知识,文中的示例代码讲... 目录1. Condition的核心方法2. 使用场景与优势3. 使用流程与规范基本模板生产者-消费者示例

Linux部署jar包过程

《Linux部署jar包过程》文章介绍了在Linux系统上部署Java(jar)包时需要注意的几个关键点,包括统一JDK版本、添加打包插件、修改数据库密码以及正确执行jar包的方法... 目录linux部署jar包1.统一jdk版本2.打包插件依赖3.修改密码4.执行jar包总结Linux部署jar包部署

开启mysql的binlog日志步骤详解

《开启mysql的binlog日志步骤详解》:本文主要介绍MySQL5.7版本中二进制日志(bin_log)的配置和使用,文中通过图文及代码介绍的非常详细,需要的朋友可以参考下... 目录1.查看是否开启bin_log2.数据库会把日志放进logs目录中3.查看log日志总结 mysql版本5.71.查看

C++实现获取本机MAC地址与IP地址

《C++实现获取本机MAC地址与IP地址》这篇文章主要为大家详细介绍了C++实现获取本机MAC地址与IP地址的两种方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实际工作中,项目上常常需要获取本机的IP地址和MAC地址,在此使用两种方案获取1.MFC中获取IP和MAC地址获取

C/C++通过IP获取局域网网卡MAC地址

《C/C++通过IP获取局域网网卡MAC地址》这篇文章主要为大家详细介绍了C++如何通过Win32API函数SendARP从IP地址获取局域网内网卡的MAC地址,感兴趣的小伙伴可以跟随小编一起学习一下... C/C++通过IP获取局域网网卡MAC地址通过win32 SendARP获取MAC地址代码#i

deepseek本地部署使用步骤详解

《deepseek本地部署使用步骤详解》DeepSeek是一个开源的深度学习模型,支持自然语言处理和推荐系统,本地部署步骤包括克隆仓库、创建虚拟环境、安装依赖、配置模型和数据、启动服务、调试与优化以及... 目录环境要求部署步骤1. 克隆 DeepSeek 仓库2. 创建虚拟环境3. 安装依赖4. 配置模型