【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);
  else
    printf("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的速度

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

visio、ppt、office等另存图片,如何设置更清晰

visio、ppt、office等另存图片&#xff0c;如何设置更清晰 选中要另存为的部分——文件——另存为——选好位置——格式选jpg——保存——按下图设置&#xff1a;质量100%&#xff0c;分辨率选打印机&#xff0c;大小选屏幕——确定

Linux:Kubernetes(k8s)——基础理论笔记(1)

我笔记来源的图片以及共享至GitHub&#xff0c;本章纯理论。这是k8s中部分的基础理论 &#x1f447; KALItarro/k8spdf: 这个里面只有一个pdf文件 (github.com)https://github.com/KALItarro/k8spdf&#x1f446; 什么是kubernetes kubernetes 是一个开源的&#xff0c;用于管…

COMPOSER安装使用WIN下升级PHP-V

想用TP6使用phpspreadsheet但是说我PHP版本低&#xff0c;原来是PHP7.0 composer要求至少7.4 直接修改环境变量&#xff0c;把PHP目录切换到7.4 composer升级比较简单&#xff0c;在PHP目录下CMD然后官网的命令执行下即可 下面就可以在TP根目录下执行命令安装PHPSPREADSHEET…

MyBatis 学习(二)之 第一个 MyBatis 案例

目录 1 配置 MyBatis 方式 1.1 XML 配置文件 1.2 Java 注解配置 1.3. Java API 配置 2 在 MySQL 中创建一张表 3 创建一个基于 Maven 的 JavaWeb 工程 4 编写 User 实体类 5 创建 Mybatis 全局配置文件 6 编写一个 DAO 或 Mapper 接口 7 编写 SQL 映射配置文件&#…

C++的继承和多态

继承和多态 继承继承的权限继承的子父类访问派生类的默认成员函数菱形继承&#xff08;C独有&#xff09;【了解】虚拟继承什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;什么是菱形虚拟继承&#xff1f;如何解决数据冗余和二义性的继承和组合的区别&#xff1f;…

Vue3如何使用Pinia状态管理库与持久化

大家好&#xff0c;我是你们的好朋友咕噜铁蛋&#xff01;今天我将和大家分享如何在Vue3中使用Pinia状态管理库以及实现状态持久化的方法。作为一个Vue开发者&#xff0c;我们知道状态管理在大型应用程序中起着至关重要的作用。而Pinia作为Vue3推荐的状态管理库之一&#xff0c…

【论文笔记】Attention Is All You Need

【论文笔记】Attention Is All You Need 文章目录 【论文笔记】Attention Is All You NeedAbstract1 Introduction2 Background补充知识&#xff1a;软注意力 soft attention 和硬注意力 hard attention&#xff1f;补充知识&#xff1a;加法注意力机制和点乘注意力机制Extende…

HCIA-Datacom实验指导手册:6 构建基础 WLAN 网络

HCIA-Datacom实验指导手册&#xff1a;6 构建基础 WLAN 网络 一、实验介绍&#xff1a;二、实验拓扑&#xff1a;三、实验目的&#xff1a;四、配置步骤&#xff1a;1.掌握ap上线的配置方式和上线过程。ac配置验证 步骤 2 掌握隧道模式和旁挂模式下ac的配置。步骤 3 掌握查看ap…

android高级面试题2020,这套Github上40K+star面试笔记

前言 这里整理的是一些与技术没有直接关系的面试题&#xff0c;但是能够考察你的综合水平&#xff0c;所以不要以为不是技术问题&#xff0c;就不看&#xff0c;往往有时候就是这样一些细节的题目被忽视&#xff0c;而错过了一次次面试机会。 想要成为一名优秀的Android开发&…

生成服从伽马分布的随机样本np.random.gamma()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 生成服从伽马分布的随机样本 np.random.gamma() 选择题 关于以下代码输出的结果说法正确的是&#xff1f; import numpy as np import seaborn as sns a np.random.gamma(shape2,scale1.0,si…

WordPress通过宝塔面板的入门安装教程【保姆级】

WordPress安装教程【保姆级】【宝塔面板】 前言一&#xff1a;安装环境二&#xff1a;提前准备三&#xff1a;域名解析四&#xff1a;开始安装五&#xff1a;安装成功 前言 此教程适合新手&#xff0c;即使不懂代码&#xff0c;也可轻松安装wordpress 一&#xff1a;安装环…

时间管理大师速成(程序员版)

01 时间管理的重要性 管理时间有几个主要的原因&#xff1a; 时间和生活质量&#xff1a;时间是我们拥有的最宝贵的资源之一&#xff0c;管理好时间会直接影响我们的生活质量。高效的时间管理可以让我们开展日常活动&#xff0c;实现目标&#xff0c;并拥有休闲和休息的时间。 …

【虹科干货】以服务为中心的IT基础设施如何优化网络分析?

文章速览&#xff1a; 发现和识别故障实时数据分析数据包分析数据包快速捕获和解码 随着基础设施环境的快速变化和技术的不断进步&#xff0c;用户数量和IT基础设施流量迅速增加&#xff0c;服务故障的数量也相应增加。此时&#xff0c;服务中断不仅会带来直接的不便&#xf…

苍穹外卖学习 Day10 Day11 Day12

前言 用于记录苍穹外卖Day10、Day11、Day12的学习 Day10 订单状态定时处理 来电提醒 客户催单 订单状态定时处理 Spring Task Spring Task是一个任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑&#xff08;定时自动执行某段Java代码&#xff09; cron表…

小程序动态调试-解密加密数据与签名校验

前言&#xff1a; 微信小程序的加密与验签早前大多数情况&#xff0c;要么就是逆向获取源码而后拿到加密秘钥&#xff0c;要么就是逆向拿到源码后使用腾讯自带的小程序开发者功能进行动态调试模拟&#xff0c;今天介绍一款志远大佬的开源工具—WeChatOpenDevTool 工具下载地址…

01 MySQL之连接

1. 连接 1.0 基础认知 多表(主表)和一表(从表的区别): 多表一般是主表&#xff0c;一般存储主要数据&#xff0c;每个字段都可能存在重复值&#xff0c;没有主键&#xff0c;无法根据某个字段定位到准确的记录&#xff1b; 一表一般是从表&#xff0c;一般存储辅助数据&…

点云数据处理常用外部库(C++/Windows)的项目配置

一、点云数据处理常用外部库&#xff08;C版本&#xff09;的下载安装与项目配置 &#xff08;一&#xff09;PCL 基于VS2019编程平台的PCL外部库下载安装及项目配置已有大量博客&#xff0c;本文不再赘述。具体下载安装及项目配置流程可参考外部库编译配置参考资料/*1*/ 。需…

redis-RedisTemplate.opsForGeo 的geo地理位置及实现附近的人的功能

redis内部使用的是 zset 数据结构存储&#xff0c;如下 import cn.huawei.VideoApplication; import cn.huawei.domain.Jingqu; import cn.huawei.service.JingquService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired…

Redis 协议与异步方式

redis pipeline 模式 redis pipeline 是一个客户端提供的机制&#xff0c;与 redis 无关。pipeline 不具备事务性。目的&#xff1a;节约网络传输时间。通过一次发送多条请求命令&#xff0c;从而减少网络传输时间。 时间窗口限流 系统限定某个用户的某个行为在指定的时间范围…

SpringBoot项目中如何结合Mybatis进行数据库查询

在Spring Boot项目中使用Mybatis进行数据库操作是一种常见的实现方式。下面我将展示如何在Spring Boot项目中整合Mybatis。这个示例将包括几个主要部分&#xff1a;项目依赖配置、配置文件、实体类、Mapper接口及其XML配置文件、服务类、以及一个简单的控制器。 1. 项目依赖配…