第一章 命令行参数和环境变量
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 环境变量总结
- 本地 shell 变量:仅当前 shell 可见,子进程不可见
- export 导出 → 环境变量,子进程继承
- 持久化:写到
~/.bashrc、~/.bash_profile文件,登录自动加载。
第二章 程序地址空间(进程虚拟地址空间)
重点:C 语言看到的所有地址,全部是虚拟地址,不是物理内存地址。
2.1 32 位 Linux 进程虚拟地址空间布局

32 位总空间 4GB:用户空间 3G,内核空间 1G(所有进程共享)
从低地址到高地址:
- 正文代码段 (.text):程序二进制指令,只读
- 初始化数据段 (.data):已经初始化全局变量
- 未初始化数据段 (.bss):未初始化全局变量,程序加载自动置 0
- 堆 (heap) :
malloc/new向上增长 - 共享区 mmap:动态库.so、文件映射
- 栈 (stack):局部变量,函数栈帧,向下增长
- 命令行参数、环境变量
- 内核空间 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=同一个地址
核心结论:
- C 语言打印出来的地址是虚拟地址,不是物理内存地址。
- 父子进程虚拟地址完全相同,但是页表映射到不同的物理内存。
- fork 默认共享物理内存;发生修改时触发写时拷贝,复制一份物理内存给子进程。
2.3 Linux 内核如何描述进程地址空间

task_struct:PCB 进程控制块,里面有指针struct mm_struct *mmmm_struct:内存描述符,专门描述这个进程整个虚拟地址空间- 记录各个段起止地址:
start_code / end_code / start_brk / brk / start_stack - 维护虚拟内存区域两种结构:链表
mmap、红黑树mm_rb
- 记录各个段起止地址:
vm_area_struct:每一个对象代表一块独立虚拟内存区域(VMA)。- vm_start、vm_end:这块虚拟区间起止
- vm_flags:权限(读写执行、共享私有)
- vm_file:映射的磁盘文件(动态库、可执行程序)