【Linux深入剖析】再续环境变量 | 进程地址空间


📙 作者简介 :RO-BERRY
📗 学习方向:致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持


在这里插入图片描述


目录

  • 1.环境变量再续
    • 1.1 和环境变量相关的命令
    • 1.2 环境变量的组织方式
    • 1.3 通过代码如何获取环境变量
    • 1.4 本地变量
    • 1.5 疑问
      • 查看环境变量配置文件
  • 2.进程地址空间
    • 2.1程序地址空间
      • 验证一
      • 验证二
      • 验证三
      • 验证四
      • 验证五
    • 2.2 奇怪的现象
    • 2.3 进程地址空间
    • 2.4 什么是地址空间
    • 2.5 为什么要有地址空间+页表


1.环境变量再续

1.1 和环境变量相关的命令

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

1.2 环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

在这里插入图片描述

1.3 通过代码如何获取环境变量

  • 1.命令行第三个参数

前面讲述过main函数可以带两个参数,第一个参数是命令行参数的个数,第二个参数是存储命令行参数的指针数组
其实main函数还可以带第三个参数,那就是我们的环境变量
我们来打印一下试试看

#include<stdio.h>    
#include<string.h>    
#include<stdlib.h>    
int main(int argc,char* argv[],char *env[])
{    for(int i=0;env[i];i++){printf("---------------env[%d] -> %s\n",i,env[i]);}return 0;    
}    

运行结果:
在这里插入图片描述
可以看到这里就是我们系统的所有环境变量

  • 2.函数getenv

getenv(环境变量名)—>得到一个环境变量,根据名字获得内容

在这里插入图片描述
测试代码:

#include<stdio.h>    
#include<string.h>    
#include<stdlib.h>    
int main()
{    const char *username=getenv("USER");if(username)printf("username: %s\n",username);elseprintf("None\n");return 0;
}    

测试结果:
在这里插入图片描述
这个函数可以用来实现限制权限,使用匹配函数将USER环境变量限定为等于某个用户,如果为其他用户访问此文件则输出你没有权限访问

  • 3.通过第三方变量environ获取

测试代码:

#include <stdio.h>
int main(int argc, char *argv[])
{extern char **environ;    //extern相当于声明,也可以在命令行使用可以设置自定义环境变量 int i = 0;for(; environ[i]; i++){printf("%s\n", environ[i]);}return 0;
}

测试结果:
在这里插入图片描述


1.4 本地变量

除了环境变量,还有本地变量,可以直接在命令行上输入变量名=内容,就可以得到一个本地变量
例如:
我们定义本地变量hello,使用echo指令查看其值

在这里插入图片描述

  • 本地变量无法使用env指令去查找到

在这里插入图片描述

  • 我们可以使用指令set进行查找

set:打印出本地变量以及环境变量

在这里插入图片描述

注:

环境变量具有全局性
本地变量不具有全局性,只在bash内部可用

1.5 疑问

  • 如何消除环境变量和本地变量?

unset

在这里插入图片描述

  • 我们用set打印环境变量以及本地变量的时候,每次都密密麻麻一片,环境变量是在bash的上下文里,bash是我们的命令行解释器,我们不启动Linux时,bash就不会存在,登录后,系统才会给我们分发bash进程,那么一开始bash进程从哪里获得的环境变量呢?

每次重启xshell的时候,环境变量就会更新,我们在这里要说明的是环境变量其实是内存级的变量,也就是说当我们启动xshell的时候,环境变量就会从我们的磁盘获取这些信息,会在其中的某种脚本或者配置文件中获取,也就是说环境变量会天然以文件的方式存储在磁盘。

查看环境变量配置文件

接下来让我们来见一见我们的环境变量配置文件
名为.bash_profile的文件就是我们的环境变量配置文件
在这里插入图片描述

vim .bash_profile

在这里插入图片描述

我们可以在其中自己创建变量,重新启动xshell即可

添加变量ABCD
在这里插入图片描述

  • 重启前
    加粗样式
  • 重启后
    在这里插入图片描述

2.进程地址空间

2.1程序地址空间

我们在学C语言的时候,画过这样的空间布局图:
在这里插入图片描述
可是我们对他并不理解
今天我们来进一步对其进行了解


验证一

首先来看各个部分的空间地址
代码:

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main()
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);printf("heap addr: %p\n",heap);printf("stack addr: %p\n",&heap);return 0;
}

执行结果:
在这里插入图片描述

结论:低地址–>高地址
正文代码–>初始化数据–>未初始化数据–>堆–>栈


验证二

验证堆中数据存储是从低地址到高地址
栈中数据存储是从高地址到低地址

代码:

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main()
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);char *heap1=(char*)malloc(20);char *heap2=(char*)malloc(20);char *heap3=(char*)malloc(20);printf("heap addr: %p\n",heap);printf("heap addr: %p\n",heap1);printf("heap addr: %p\n",heap2);printf("heap addr: %p\n",heap3);printf("stack addr: %p\n",&heap);printf("stack addr: %p\n",&heap1);printf("stack addr: %p\n",&heap2);printf("stack addr: %p\n",&heap3);return 0;
}

执行结果:
在这里插入图片描述

结论:堆栈相向而生
堆中数据存储是从低地址到高地址
栈中数据存储是从高地址到低地址


验证三

验证命令行与环境变量

代码:

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main(int argc,char *argv[],char *env[])
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);char *heap1=(char*)malloc(20);char *heap2=(char*)malloc(20);char *heap3=(char*)malloc(20);printf("heap addr: %p\n",heap);printf("heap addr: %p\n",heap1);printf("heap addr: %p\n",heap2);printf("heap addr: %p\n",heap3);printf("stack addr: %p\n",&heap);printf("stack addr: %p\n",&heap1);printf("stack addr: %p\n",&heap2);printf("stack addr: %p\n",&heap3);for(int i=0;argv[i];i++){printf("&argv[%d]=%p\n",i,argv+i);}for(int i=0;env[i];i++){printf("&env[%d]=%p\n",i,env+i);}return 0;
}

执行结果:
在这里插入图片描述
我们在这里打印的只是环境变量以及命令行参数的表的地址!!!

结论:命令行参数表整体地址比栈区大,并且地址由小到大增长
环境变量表比命令行参数整体地址更大,且地址也是由小到大增长

验证四

验证命令行与环境变量表内数据地址

代码: 输出argv[i]以及env[i]

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main(int argc,char *argv[],char *env[])
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);char *heap1=(char*)malloc(20);char *heap2=(char*)malloc(20);char *heap3=(char*)malloc(20);printf("heap addr: %p\n",heap);printf("heap addr: %p\n",heap1);printf("heap addr: %p\n",heap2);printf("heap addr: %p\n",heap3);printf("stack addr: %p\n",&heap);printf("stack addr: %p\n",&heap1);printf("stack addr: %p\n",&heap2);printf("stack addr: %p\n",&heap3);for(int i=0;argv[i];i++){printf("&argv[%d]=%p\n",i,argv[i]);}for(int i=0;env[i];i++){printf("&env[%d]=%p\n",i,env[i]);}return 0;
}

执行结果:
在这里插入图片描述

结论:无论是表,还是表指向的项目,都是在栈的地址的上部


验证五

验证未初始化数据以及初始化数据会在进程运行期间,一直都会存在

代码: 定义了一个变量C

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main(int argc,char *argv[],char *env[])
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);char *heap1=(char*)malloc(20);char *heap2=(char*)malloc(20);char *heap3=(char*)malloc(20);int c=0;printf("heap addr: %p\n",heap);printf("heap addr: %p\n",heap1);printf("heap addr: %p\n",heap2);printf("heap addr: %p\n",heap3);printf("stack addr: %p\n",&heap);printf("stack addr: %p\n",&heap1);printf("stack addr: %p\n",&heap2);printf("stack addr: %p\n",&heap3);printf("c addr: %p\n",&c);for(int i=0;argv[i];i++){printf("&argv[%d]=%p\n",i,argv[i]);}for(int i=0;env[i];i++){printf("&env[%d]=%p\n",i,env[i]);}return 0;
}

代码二: 修改变量C为static变量

#include<stdio.h>
#include<stdlib.h>int g_unval;       //未初始化数据
int g_val= 100;    //初始化数据int main(int argc,char *argv[],char *env[])
{printf("code addr: %p\n",main);printf("init data addr: %p\n",&g_val);printf("uninit data addr; %p\n",&g_unval);char *heap=(char*)malloc(20);char *heap1=(char*)malloc(20);char *heap2=(char*)malloc(20);char *heap3=(char*)malloc(20);static int c=0;printf("heap addr: %p\n",heap);printf("heap addr: %p\n",heap1);printf("heap addr: %p\n",heap2);printf("heap addr: %p\n",heap3);printf("stack addr: %p\n",&heap);printf("stack addr: %p\n",&heap1);printf("stack addr: %p\n",&heap2);printf("stack addr: %p\n",&heap3);printf("c addr: %p\n",&c);for(int i=0;argv[i];i++){printf("&argv[%d]=%p\n",i,argv[i]);}for(int i=0;env[i];i++){printf("&env[%d]=%p\n",i,env[i]);}return 0;
}

执行结果:

代码一可以看到C变量在栈上保存

在这里插入图片描述

代码二可以看到变量C并不保存在栈上了

在这里插入图片描述

这里的原因是因为:
如果在将变量加static,那么此变量就已经默认为全局变量了


2.2 奇怪的现象

演示代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int g_val = 100;int main()
{pid_t id =fork();if(id == 0){//子进程int cnt = 0;while(1){printf("child,pid: %d,ppid %d,g_val: %d,&g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(2);cnt++;if(cnt == 5){g_val = 200;printf("child change g_val: 100->200\n");}}}else{//父进程while(1){printf("father,pid: %d,ppid %d,g_val: %d,&g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(2);}  }
}

演示结果:
在这里插入图片描述

如上可以看到,我们父子进程互相具有独立性,子进程将值改为200,但是父进程依然只有100,但是我们可以发现一个现象,那就是g_val的地址是相同的,但是其值不同!!!同一个地址打印出不同的值

如果这个地址是内存里的地址,我们对同一个地址读取出两个不同的值,这是绝对不可能的,所以这里打出来的地址绝对不是物理地址!!!

引入一个概念:

这个地址叫做:虚拟地址/线性地址

结论:
能得出如下结论:
1.变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
2.但地址值是一样的,说明,该地址绝对不是物理地址!
3.在Linux地址下,这种地址叫做 虚拟地址
4.我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理


2.3 进程地址空间

有了上面的铺垫,我们来真正进入到进程地址空间
先直接说结论:

在Linux中是具有虚拟地址的,在Linux里使用的地址都是虚拟地址,虚拟地址空间和真实地址有一个映射关系,这个映射关系是由操作系统维护的一个表来记录的,子进程在继承父进程的时候同样会继承父进程的存储信息,映射关系表,对这些会重新进行拷贝操作, 所以会和父进程里的变量指向同一块地址空间,在这里注意,这里是两个虚拟地址空间指向的同一块内存地址,如上方,我们的子进程修改了g_val的值,按道理说应该改变的是物理地址的值,但是OS为了保证各个进程的独立性,所以OS会在物理空间重新给你开辟一个空间,修改子进程的映射关系表,看上去是指向同一块物理地址,实际上是两块物理地址。

虚拟地址空间以及映射关系表均在操作系统内部

在这里插入图片描述

上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了
不同的物理地址!

2.4 什么是地址空间

进程地址空间,每一个进程,都会存在一个进程空间,32【0,4GB】

进程地址空间的本质是数据结构,具体到进程中,就是特定的数据结构的对象,里面存储的是我们的虚拟地址,由操作系统提供。进程地址空间本质是进程看待内存的方式,抽象出来的一个概念,内核中用一个结构体mm_struct表示,这样每个进程都认为自己独占系统内存资源。

我们的地址空间,不具备对我们的代码和数据的保存能力!在物理内存中存放的!
将地址空间上的地址(虚拟/线性)转化到物理内存中,操作系统给我们的进程提供了一张映射表—页表

在进程控制块task_struct中有一个mm_struct结构体指针,指向一个mm_struct结构体,这个结构体里面完成对各个数据区域的划分,然后通过页表映射到物理内存上。

在这里插入图片描述

区域划分:将线性地址空间划分成为一个一个的area | [start, end]

struct area
{int start;int end;
}

在[start, end]之间的各个地址叫做虚拟地址。


2.5 为什么要有地址空间+页表

  • 将物理内存从无序变有序,让进程以统一的视角看待内存
  • 将内存管理和进程管理进行解耦合
  • 地址空间+页表是保护内存安全的重要手段

如果进程直接访问物理内存,那么我们看到的地址就是物理地址。c语言中可以用指针访问地址,如果指针越界了,有可能直接访问到另一个进程的代码和数据,这样的话进程的独立性无法保证。 因为物理内存暴漏,有可能有恶意程序直接通过物理地址进行内存数据的篡改。所以虚拟地址存在的第一个意义是保护物理内存,不受任何进程的直接访问,这样操作系统就可以在虚拟到物理之间转化的时候方便进行合法性校验。

【扩展】
malloc/new申请内存

1.申请的内存,你会直接在里面使用吗?
不一定
2.申请内存,本质在哪里申请?
进程的虚拟地址空间中申请

操作系统需要为效率和资源使用率负责
1.充分保证内存的使用率,不会空转
2.提升new或者malloc的速度

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

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

相关文章

FX110网:外汇交易中的隔夜利息该如何计算?

在交易过程中&#xff0c;我们经常能够听到 “隔夜利息”这个词&#xff0c;但不少新手依然不是很明白这个专业名词的意思。今天小编帮助大家理解这个概念。“隔夜利息”的含义 顾名思义&#xff0c;是根据持仓总数计算的每日可赚取或需支付的利息。每个货币都有他们自己的基准…

贝叶斯优化双向门控循环单元BO-BIGRU时序预测的matlab实现【源代码】

贝叶斯优化双向门控循环单元简介&#xff1a; 贝叶斯优化双向门控循环单元&#xff08;BO-BIGRU&#xff09;是一种结合了贝叶斯优化和双向门控循环单元&#xff08;BIGRU&#xff09;的神经网络模型。BIGRU是一种改进的循环神经网络&#xff08;RNN&#xff09;&#xff0c;它…

现代信号处理学习笔记(二)参数估计理论

参数估计理论为我们提供了一套系统性的工具和方法&#xff0c;使我们能够从样本数据中推断总体参数&#xff0c;并评估估计的准确性和可靠性。这些概念在统计学和数据分析中起着关键的作用。 目录 前言 一、估计子的性能 1、无偏估计与渐近无偏估计 2、估计子的有效性 两个…

Python入门到精通(九)——Python数据可视化

Python数据可视化 一、JSON数据格式 1、定义 2、python数据和JSON数据转换 二、pyecharts 三、折线图 四、地图 五、动态柱状图 一、JSON数据格式 1、定义 JSON是一种轻量级的数据交互格式。可以按照JSON指定的格式去组织和封装数据JSON本质上是一个带有特定格式的字符…

嘴尚绝卤味传统与创新的完美结合

在当下这个美食文化丰富多彩的时代&#xff0c;卤味作为一种深受大众喜爱的食品&#xff0c;不仅承载着传统的烹饪智慧&#xff0c;更在不断创新中展现出新的魅力。嘴尚绝卤味&#xff0c;作为卤味市场中的佼佼者&#xff0c;凭借其独特的优势&#xff0c;正逐渐成为消费者心中…

java高级——动态代理

目录 动态代理介绍明星代理案例实现案例分析动态代理应用场景 动态代理介绍 用一个明星的案例来解释动态代理的流程。 假设现在有一个明星坤坤&#xff0c;它有唱歌和跳舞的本领&#xff0c;作为明星是要用唱歌和跳舞来赚钱的。但是每次做节目&#xff0c;唱歌的时候要准备话…

阿里云2024年服务器2核4G配置评测_CPU内存带宽_优惠价格

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

Linux磁盘性能方法以及磁盘io性能分析

Linux磁盘性能方法以及磁盘io性能分析 1. fio压测1.1. 安装fio1.2. bs 4k iodepth 1&#xff1a;随机读/写测试&#xff0c;能反映硬盘的时延性能1.3. bs 128k iodepth 32&#xff1a;顺序读/写测试&#xff0c;能反映硬盘的吞吐性能 2. dd压测2.1. 测试纯写入性能2.2. 测试…

抖音小店新店没有体验分怎么办?怎么从零做体验分?新手商家速看

大家好&#xff0c;我是电商花花。 新手开店的体验分都不是很高&#xff0c;我们想要做店铺体验分都要从零开始做。 如果新手开店不需要怎么出体验分&#xff0c;不知道怎么提高店铺体验分的&#xff0c;都可以看一下今天的文章&#xff0c;教大家怎么做店铺的体验分。 首先&…

基于springboot + vue实现的前后端分离-汽车票网上预定系统(项目 + 论文)

项目介绍 系统是一个B/S模式系统&#xff0c;采用Spring Boot框架&#xff0c;MySQL 数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得汽车票网上预订系统管理工作系统化、规范化。本系统的使用使管理人…

JVM——JVM与Java体系结构

文章目录 1、Java及JVM简介1.1、Java是跨平台的语言1.2、JVM是跨语言的平台 2、Java发展里程碑3、Open JDK和Oracle JDK4、虚拟机与JVM4.1、虚拟机4.2、JVM 5、JVM整体结构6、Java代码执行流程7、JVM的架构模型7.1、基于栈式架构的特点7.2、基于寄存器架构的特点 8、JVM的生命周…

cRIO9040中NI9871模块的测试

硬件准备 CompactRIO9040NI9871直流电源&#xff08;可调&#xff09;网线RJ50转DB9线鸣志STF03-R驱动器和步进电机 软件安装 参考&#xff1a;cRIO9040中NI9381模块的测试 此外&#xff0c;需安装NI-Serial 9870和9871扫描引擎支持 打开NI Measurement&#xff06;Automa…

Docke相关命令总结

docker systemctl 相关 commanddetailsudo systemctl start docker启动dockersudo systemctl stop docker停止dockersudo systemctl restart docker重启dockersudo systemctl status docker查看docker状态 镜像相关 commanddetaildocker search 镜像名称搜索镜像docker pull …

jetson nano——编译安装opencv-python==4.3.0.38

目录 1.下载源码&#xff0c;我提供的链接如下&#xff1a;2.解压文件3.安装依赖scikit4.安装opencv-python5.查看opencv-python版本 系统&#xff1a;jetson-nano-jp451-sd-card-image ubuntu 18.04 1.下载源码&#xff0c;我提供的链接如下&#xff1a; 链接&#xff1a;http…

网络:IPv6

1、由于IPv4地址资源枯竭&#xff0c;所以产生了IPV6。 版本长度地址数量IPv432 bit4 294 967 296IPv6128 bit340 282 366 920 938 463 374 607 431 768 211 456 2、IPv6的基本报头在IPv4报头基础上&#xff0c;增加了流标签域&#xff0c;去除了一些冗余字段&#xff0c;使报…

docker (十二)-私有仓库

docker registry 我们可以使用docker push将自己的image推送到docker hub中进行共享&#xff0c;但是在实际工作中&#xff0c;很多公司的代码不能上传到公开的仓库中&#xff0c;因此我们可以创建自己的镜像仓库。 docker 官网提供了一个docker registry的私有仓库项目&#…

Zookeeper基础入门-2【ZooKeeper 分布式锁案例】

Zookeeper基础入门-2【ZooKeeper 分布式锁案例】 四、ZooKeeper-IDEA环境搭建4.1.环境搭建4.1.1.创建maven工程&#xff1a;zookeeper4.1.2.在pom文件添加依赖4.1.3.在项目的src/main/resources 目录下&#xff0c;新建文件为“log4j.properties”4.1.4.创建包名com.orange.zk …

Neoverse S3 系统 IP:机密计算和多芯片基础设施 SoC 的基础

第三代Neoverse系统IP Neoverse S3 产品推出了我们的第三代基础设施特定系统 IP&#xff0c;这是下一代基础设施 SOC 的理想基础&#xff0c;适用于从 HPC 和机器学习到 Edge 和 DPU 的各种应用。S3 机箱专注于为我们的合作伙伴提供 Chiplet、机密计算等关键创新以及 UCIe、DD…

(Linux学习一):Mac安装vmWare11.5,centOS 7安装步骤教程

一。下载vmware 官网地址&#xff1a;下载地址 由于我的电脑系统是Mac 10.15.6版本系统&#xff0c;我下载的是VMware Fusion 11.5版本&#xff0c;13是最新版本不支持安装需要系统在11以上。 百度网盘下载地址: VMware Fusion 11 VMware Fusion 12 VMware Fusion 13 下载需要…

matlab实现不同窗滤波器示例

1 汉明窗低通滤波器 &#xff1a; 在Matlab中使用汉明窗设计低通滤波器可以通过fir1函数实现。汉明窗通常用于设计滤波器&#xff0c;可以提供更突出的频率特性。 下面是一个示例代码&#xff0c;演示如何在Matlab中使用汉明窗设计低通滤波器&#xff1a; % 定义滤波器参数 fs …