Linux:环境变量 & 进程虚拟地址空间

第一章 命令行参数和环境变量

1.1 什么是环境变量

环境变量是操作系统指定运行环境的参数,具备全局特性。

例子:编译 C 程序,编译器能自动找到库文件,就是依靠环境变量完成查找。

1.2 常见环境变量

环境变量 作用
PATH 命令搜索路径,系统在这里查找可执行程序
HOME 用户家目录,~就对应 HOME 的值
SHELL 当前使用的 shell 程序,一般是/bin/bash

1.3 Linux 操作环境变量的指令

cpp 复制代码
echo $NAME      # 打印环境变量
export 变量名=值 # 设置环境变量
env             # 查看全部环境变量
unset NAME      # 删除环境变量
set             # 查看shell本地变量+环境变量
PATH 小实验

我们自己写的程序hello,必须./hello才能运行,直接敲hello找不到程序。 原因:系统只会在PATH记录的目录下搜索命令,当前目录不在 PATH。

cpp 复制代码
# 把当前目录追加进PATH
export PATH=$PATH:$(pwd)
# 之后就可以直接 hello 运行程序

1.4 程序内部获取环境变量的三种方式

cpp 复制代码
int main(int argc, char *argv[], char *env[]);

1.main 第三个参数 env :字符指针数组,每一项指向环境字符串,以NULL结尾

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

2.全局变量 environ environ没有头文件,需要 extern 声明

cpp 复制代码
#include <stdio.h>
extern char **environ;
int main(){
    int i=0;
    for(;environ[i];i++){
        printf("%s\n",environ[i]);
    }
    return 0;
}

3.系统调用 getenv () /putenv ()(推荐,只取指定变量)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *path = getenv("PATH");
    printf("PATH=%s\n", path);
    return 0;
}

4.5 环境变量的全局属性

使用 export 导出的环境变量,可以被子进程继承。

  • 直接写MYENV="123":只是 shell 本地变量,子进程看不到
  • export MYENV="123":升级成环境变量,fork 创建子进程会继承这份环境表

原理:fork 复制父进程地址空间,环境表在父进程地址空间,子进程拷贝一份,所以子进程可以拿到。

4.6 本地变量 vs 环境变量总结

  1. 本地 shell 变量:仅当前 shell 可见,子进程不可见
  2. export 导出 → 环境变量,子进程继承
  3. 持久化:写到~/.bashrc、~/.bash_profile文件,登录自动加载。

第二章 程序地址空间(进程虚拟地址空间)

重点:C 语言看到的所有地址,全部是虚拟地址,不是物理内存地址。

2.1 32 位 Linux 进程虚拟地址空间布局

32 位总空间 4GB:用户空间 3G,内核空间 1G(所有进程共享)

从低地址到高地址:

  1. 正文代码段 (.text):程序二进制指令,只读
  2. 初始化数据段 (.data):已经初始化全局变量
  3. 未初始化数据段 (.bss):未初始化全局变量,程序加载自动置 0
  4. 堆 (heap) :malloc/new向上增长
  5. 共享区 mmap:动态库.so、文件映射
  6. 栈 (stack):局部变量,函数栈帧,向下增长
  7. 命令行参数、环境变量
  8. 内核空间 1G:用户态无法直接访问

C 代码可以打印各段地址,验证布局:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int g_uninit;    // bss
int g_init=100;  // data

int main()
{
    printf("代码main地址:%p\n", main);
    printf("初始化全局:&g_init=%p\n",&g_init);
    printf("未初始化全局:&g_uninit=%p\n",&g_uninit);

    int *p = malloc(10);
    printf("堆地址 p=%p\n",p);

    int local = 10;
    printf("栈地址 &local=%p\n",&local);

    const char *s = "hello";
    printf("只读字符串 %p\n",s);
    return 0;
}

2.2 fork 写时拷贝实验,理解虚拟地址

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
int g_val = 0;
int main()
{
    pid_t id = fork();
    if(id == 0){
        //子进程
        g_val = 100;
        printf("child: g_val=%d, &g_val=%p\n",g_val,&g_val);
    }else{
        sleep(3);
        printf("parent: g_val=%d, &g_val=%p\n",g_val,&g_val);
    }
    return 0;
}

输出现象:

child: g_val=100, &g_val=0x... parent: g_val=0, &g_val=同一个地址

核心结论:

  1. C 语言打印出来的地址是虚拟地址,不是物理内存地址。
  2. 父子进程虚拟地址完全相同,但是页表映射到不同的物理内存。
  3. fork 默认共享物理内存;发生修改时触发写时拷贝,复制一份物理内存给子进程。

2.3 Linux 内核如何描述进程地址空间

  1. task_struct:PCB 进程控制块,里面有指针 struct mm_struct *mm
  2. mm_struct:内存描述符,专门描述这个进程整个虚拟地址空间
    • 记录各个段起止地址:start_code / end_code / start_brk / brk / start_stack
    • 维护虚拟内存区域两种结构:链表mmap、红黑树mm_rb
  3. vm_area_struct:每一个对象代表一块独立虚拟内存区域(VMA)。
    • vm_start、vm_end:这块虚拟区间起止
    • vm_flags:权限(读写执行、共享私有)
    • vm_file:映射的磁盘文件(动态库、可执行程序)
相关推荐
handonch1 小时前
金仓 KingbaseES 时区问题根治指南:从「总是差 8 小时」到全链路统一
linux·docker·postgresql·信创
小白起 v1 小时前
【算法学习 · 01】深入理解大 O 表示法:从「能跑就行」到「专业评估」算法性能
算法
梦帮科技1 小时前
脑机接口(BCI)与神经系统安全:EEG 神经电信号欺骗注入、神经闭环解码算法与生物黑客反制
算法·安全
老徐聊GEO1 小时前
把“安徽“写进题面之后:多平台AI推荐名单的区域扰动测量(2026)
前端·人工智能·算法·geo优化
人工智能培训1 小时前
具身智能:让人工智能拥有物理世界感知能力
大数据·人工智能·学习·算法·机器学习
果壳science1 小时前
张祥前最新研究成果登上SCI期刊!
笔记·嵌入式硬件·算法·硬件工程·几何学
Rabitebla1 小时前
【Linux 底层原理】工具篇(一):掌握 Ubuntu apt,让软件安装与管理更简单
linux·笔记·算法·ubuntu
chde2Wang1 小时前
Hadoop集群无datanode进程
linux·运维·服务器
叼烟扛炮1 小时前
Linux 学习笔记(十八):进程控制——优雅地退出与等待
linux·运维·服务器