当前位置: 首页 > news >正文

【Linux】——环境变量与进程地址空间

文章目录

  • 环境变量
    • 环境变量的概念
    • 常见的环境变量
    • PATH
    • 相关指令
  • main的三个参数
    • 前两个参数
    • 第三个参数
  • 程序地址空间
  • 进程地址空间

环境变量

环境变量的概念

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,将来会以shell的形式传递给所有进程,每个进程都会认识这些参数。
并且这些参数具有全局性,能被子进程继承。

常见的环境变量

PATH:指定命令的搜索路径
HOME:当前用户的主目录路径
SHELL:登录时,启动的是哪一个Shell,它的值通常是/bin/bash
PWD:当前所处路径
USER:当前用户

PATH

为什么我们要执行自己写的可执行程序时要 ./ 指定路径,而执行系统自带的命令则不用?
这是因为系统能够知道命令在 /usr/bin 目录下。那操作系统又是怎么知道 /usr/bin里有的呢?
在这里插入图片描述

这是因为在shell登录时,PATH就已经告诉系统了。

在这里插入图片描述
所以PATH代表的就是默认命令的查找路径。

如果我们想让我们的可执行程序也像系统指令一样使用,一种方法就是:将可执行程序拷贝到环境变量PATH的某一路径下。还要一种方法就是:将可执行程序所在的目录导入到环境变量PATH当中。

在这里插入图片描述

相关指令

env:显示所有环境变量
在这里插入图片描述

export PATH=$PATH:/新的路径:在PATH环境变量中添加新的路径。
export + 本地变量可以直接变成环境变量
在这里插入图片描述
unset:清除指定环境变量

main的三个参数

main函数是有三个参数的,但是不常用

int main(int argc,char* argv[],char* env[])

argc:代表命令行有效参数的个数。
argv : 指向命令行参数(argv[0],通常是程序的名称,后续元素则是实际的命令行参数)。
env: 指向环境变量字符串数组。

前两个参数

我们看一下前两个参数的效果

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

在这里插入图片描述
argv数组第一个有效参数是./aout,第二个和第三个分别是-a,-b,最后一个参数指向的是NULL。

第三个参数

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

在这里插入图片描述
有一个接口叫getenv,可以根据名称获取对应的环境变量

#include <stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[], char *env[])
{
    printf("%s\n",getenv("PATH"));//获取对应的环境变量
    return 0;
}

在这里插入图片描述

每个程序都会有一张环境变量表,环境变量表是一个字符指针数组,每个指针指向一个以\0结尾的环境字符串,最后一个字符指针为空。
在这里插入图片描述

程序地址空间

在这里插入图片描述
用这段代码打印一下结果

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int g_val=100;
int main()
{
    pid_t id=fork();//创建子进程
    if(id==0)
    {
        //child
        g_val=200;
        printf("child PID:%d,PPID:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
    }
    else if(id>0)
    {
        //father
        printf("father PID:%d,PPID:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
    }
    else
    {
        // fork error
    }
    return 0;
}

在这里插入图片描述
我们发现,同一个地址上的值怎么不一样呢?
我学习fork函数时就知道,当fork创建子进程时,父子默认情况共享数据。然而修改数据时,为了维护进程独立性,会发生写时拷贝,所以父子进程的值不同,但是地址为什么会不变呢?
如果我们是在同一个物理地址处获取的值,那必定值是相同的,而现在在同一个地址处获取到的值却不同,这只能证明我们打印出的地址并不是物理地址。
所以,程序地址空间,在某种意义上也可以称作虚拟地址空间

物理地址用户一概是看不到的,是由操作系统统一进行管理的,所以即使父子进程打印的地址相同,但是物理地址可能是不同的,这也就解释了为什么地址相同,而值却不同的问题。

进程地址空间

进程地址空间是进程可以使用的全部线性地址的集合,就是程序地址空间那张图,它是抽象的,其实它屏蔽了物理存储器的实际大小和分布细节,使进程得以在一个看似连续且足够大的存储空间中存放进程映像。每个进程都有自己的内存地址范围,这样就不会与其他进程发生冲突。
就像是一个美国富豪对每个私生子说:“好好学习好好生活,以后继承我的家产”。但是私生子之间都不知道对方,以为自己真的能继承全部家产,但是平常找富豪爸爸要钱爸爸也并不会给太多。

进程地址空间本质是内存中的一种内核数据结构,在Linux当中进程地址空间具体由结构体mm_struct实现,其一般包含以下这些信息:

struct mm_struct
{
    //代码区
    unsigned int code_start;  
    unsigned int code_end;  
    //已初始化全局数据区
    unsigned int init_data_start;
    unsigned int init_data_end;
    //未初始化全局数据区
    unsigned int uninit_data_start;
    unsigned int uninit_data_end;

    //....栈区
    unsigned int stack_start;
    unsigned int stack_end;
};

在mm_struct当中,每一个的区域都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系。虚拟地址大小一般为4G,是由0x00000000到0xffffffff线性增长的,所以虚拟地址又叫做线性地址。

每个进程被创建时,其对应的进程控制块task_struct和进程地址空间mm_struct也随之被创建。而操作系统就可以通过进程的task_struct找到对应的mm_struct(因为task_struct有一个结构体指针指向的是mm_struct)。

然后我们就可以更加深入解释上面地址相同,值却不同的现象:首先父进程有自己的task_struct和mm_struct,该父进程创建的子进程也会有属于其自己的task_struct和mm_struct,父子进程的进程地址空间当中的各个虚拟地址分别通过页表映射到对应的物理内存,如下图:
在这里插入图片描述
此时若是子进程要将g_val改为200,此时为了维护进程的独立性,不影响父进程的数据,子进程就会发生写实拷贝。
在这里插入图片描述
由此诞生三个问题:
问题一:为什么数据要进行写时拷贝?

进程间具有独立性。多进程运行,需要独享各种资源,运行期间互不干扰,不能让子进程的修改影响到父进程。

问题二:为什么不在创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),提高空间利用率。

问题三:为什么要有进程地址空间?

  1. 通过添加一层隔离,降低使用进程操作的风险,从而增强了系统的安全性。
  2. 将内存申请和使用在时间上解耦,利用虚拟地址空间屏蔽底层申请内存过程,实现进程读写内存操作与操作系统内存管理在软件层面分离。(若物理内存已满而仍需申请,操作系统可执行内存管理算法,将某些进程闲置空间置换到磁盘,使进程仍能申请到内存,且用户在应用层无感知。)
  3. 站在CPU和应用层角度,进程统一使用4GB空间且各空间区域相对位置较确定。有了虚拟地址空间,CPU能以统一视角看待物理内存,不同进程通过各自页表映射到不同物理内存,同时程序代码和数据可加载到内存任意位置,大大减少内存管理负担。

相关文章:

  • ocp考试有判断题吗?多少分及格?
  • 【C++】理解 C++ 中的完美转发(Perfect Forwarding)
  • 人工智能开发中的常见问题与避坑指南
  • 列举常见算法的时间复杂度与空间复杂度
  • 【Java/数据结构】队列(Quque)
  • 【DeepSeek学C++】 effective modern C++第33条款
  • Xcode16.1使用MonkeyDev运行Tiktok报错分析
  • 分享一个精灵图生成和拆分的实现
  • 可以高效记录工作生活琐事的提醒APP工具
  • MySQL教程 基本知识(基本原理和标准语言)
  • 回溯-组合总和
  • Three.js贴图技巧:优化性能与效果
  • 算法-深度优先搜索DFS
  • Redis 在windows下的下载安装与配置
  • 质检LIMS系统在诊所的应用 诊所质检行业的最优LIMS系统
  • knowledge-微前端(多个前端应用聚合的一个应用架构体系,每个小的应用可独立运行,独立开发,独立部署上线)
  • Linux与HTTP中的Cookie和Session
  • javaFX的使用
  • 如何在linux中利用方向键快速查找之前的敲过的命令
  • 第六:go 操作 redis-go
  • 让中小学生体验不同职业,上海中高职院校提供超5万个体验名额
  • 台陆委会将欧阳娜娜等20多名艺人列入重要查核对象,国台办回应
  • 从《缶翁的世界》看吴昌硕等湖州籍书画家对海派的影响
  • 关税影响下沃尔玛想涨价,特朗普施压:自行承担,别转嫁给顾客
  • 看展 | 黄永玉新作展,感受赤子般的生命力
  • 浙江一家长称小学老师打孩子还威胁要从3楼扔下,当地警方已立案