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:映射的磁盘文件(动态库、可执行程序)
相关推荐
唐墨1232 小时前
linux kernel 比较数据大小的宏定义
linux·算法
程序开发阿元2 小时前
密码不进工具:一个独立开发者的服务器凭据管理办法
linux·ssh
拂拉氏2 小时前
【知识讲解-题目讲解】 统计满足K约束的子字符串数量2 (滑动窗口+前缀和+二分查找+分析)
算法·前缀和·二分查找·滑动窗口
AR-26710-5 小时前
Linux Day33——定时任务+lvm
linux·运维
墨痕诉清风6 小时前
SSRF漏洞实战记录+OSS桶命令执行+MCP漏洞
linux·服务器·数据库
byte轻骑兵6 小时前
实战 4:BlueZ 源码中添加自定义设备过滤规则
linux·物联网·bluez·电脑蓝牙·嵌入式蓝牙
通信小呆呆7 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
笑容掩饰我的失落7 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
瓦瓦gogo7 小时前
从一个 TypeScript 函数开始理解 MCP 协议
linux·ubuntu·typescript
Severus_black7 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl