正因为你有能力跨越这个考验才会降临目录1. 初始环境变量2. 环境变量表与命令行参数表3. 环境变量的查看4. 虚拟地址空间4.2 写时拷贝COW4.2 缺页异常1. 初始环境变量环境变量是操作系统运行环境的一些参数env可以查看所有的环境变量unset删除环境变量export添加环境变量echo$查看某一个环境变量。其中env是系统级的命令echosetexport是shell内置的命令getenvsetenv等是语言层的标准定义。常见的环境变量有PATH可执行文件的搜索路径列表。HOME当前用户主目录路径。PWD当前工作目录的绝对路径。还有USER,SHELL等等。父进程的环境变量可以被子进程继承当进程退出时内存中的数据被清理新增的环境变量自动被回收这也进一步说明进程是独立的2. 环境变量表与命令行参数表每个进程都要维护两张表命令行参数表和环境变量表。父进程创建子进程时子进程会继承这两张表。命令行参数表是一个字符串数组以NULL结尾通常有两个参数argv和argc。argv是数组名argc是数组大小数组存储进程运行时指令与配置。环境变量表传递全局上下文信息给子进程使用。主函数main其实也是有argv和argc这两个默认参数的当执行文件./test时argv[0]存储的就是./test3. enviorn可以用getenv查看用const char*的变量接收也可以用environ访问environ是一个全局指针变量指向一个以NULL结尾的字符串数组使用时需要加上extern声明environ可以遍历整个数组拿到完整的“keyvalue”#include stdio.h extern char **environ; // ⚠️ 必须手动声明 int main() { for (char **ep environ; *ep ! NULL; ep) { printf(%s\n, *ep); // 输出格式: KEYVALUE } return 0; }每个进程都有自己的environ指向各自独立的内存区域并且会进行实时更新envp不会进程在创建好后会让environ直接指向内核在进程启动时放置在栈上的那块原始内存。┌─────────────────┐ ← 高地址 │ envp strings │ ← 内核 execve() 放在栈上的原始数据 │ PATH/bin │ │ HOME/root │ ├─────────────────┤ │ argv pointers │ ├─────────────────┤ │ argc │ ← main(argc, argv, envp) 的参数来源 └─────────────────┘ ← 低地址 ↑ environ envp[0] ← 全局指针直接指向这里零拷贝setenv首次修改时会malloc一块新的堆内存将现有环境变量拷贝进去加上新变量然后更新environ指针指向这块新的内存后续修改直接在堆上realloc/调整putenv和setenv的不同当putenvchar* string时ibc 内部大致执行以下逻辑扫描当前environ数组查找是否存在同名 KEY。如果找到直接将environ[i] string;替换旧指针。如果没找到将string追加到environ数组末尾必要时realloc扩容environ指针数组本身然后补上NULL终止符。所以putenv是很危险的如果原字符串指针作用域结束或者free了会产生未定义行为setenv内部是malloc和strcpy深拷贝更加的安全建议优先使用setenv4. 虚拟地址空间我们平时说的栈堆等程序内存空间都不是物理内存而是虚拟地址空间一个进程一个虚拟地址空间一套页表页表用来做虚拟和地址的映射4.2 写时拷贝COW写时拷贝是一种资源优化管理策略当多个调用者请求相同资源时系统会让他们共享同一份资源当某个调用者试图修改资源时系统才会为他单独复制一份副本修改当父进程创建子进程时 内核仅复制页表让父子进程指向同一块物理内存并标记为只读。一旦某方尝试写入CPU 触发缺页异常内核才分配新内存并复制数据程序申请内存大概分为3步1.在虚拟地址空间中申请指定大小的空间2.加载程序申请物理内存3.页表进行映射所以本质上物理内存是没有0x112233这样一串的数字的一切的一切都是操作系统为了用户使用方便而包装设计的。虚拟地址的区域划分实际上就是一个结构体struct mm_struct { long code_start; long code_end; long init_start,init_end; long unit_start,unit_end; ... }虚拟地址空间的意义1. 将地址从“无序”变“有序”2.进程管理和内存管理进行一定程度的解耦合3. 地址转换的过程对地址和操作判定保护物理内存。这一步主要是通过页表权限和缺页异常来实现的我们都知道字符串常量不能修改是因为页表对应的权限为只读所以无法访问物理内存进行写操作缺页异常是操作系统的一种机制当CPU试图访问一个虚拟地址时如果MMU内存管理单元在页表中找不到有效的物理映射或者找到了映射但权限不足CPU就会抛缺页异常4.2 缺页异常缺页异常分软异常和硬异常软异常通常就是COW硬异常指页面不在物理内存而在磁盘上需要从磁盘上读入页面。CPU 执行访存指令 ↓ MMU 查页表 → 发现无效/权限不足 ↓ CPU 抛出缺页异常陷入内核态 ↓ OS 检查异常原因 虚拟地址 ↓ ┌──────────────┬──────────────┬──────────────┐ │ COW 写入 │ 页面在磁盘 │ 非法访问 │ │ │ │ │ │ 分配新物理页 │ 分配物理页 │ 发送 SIGSEGV │ │ 复制原页内容 │ 从磁盘读入 │ 杀死进程 │ │ 更新页表W1 │ 更新页表 │ │ └──────────────┴──────────────┴──────────────┘ ↓ 返回用户态重新执行刚才失败的指令缺页异常也是虚拟内存实现所有功能按需分页写时拷贝内存交换内存映射文件动态栈增长的基础缺页异常也是有代价的硬缺页需要数百万个CPU周期高性能程序要尽量避免硬缺页希望你能有所收获 Thanks♪(ω)
【 linux 】理解环境变量和虚拟地址空间
正因为你有能力跨越这个考验才会降临目录1. 初始环境变量2. 环境变量表与命令行参数表3. 环境变量的查看4. 虚拟地址空间4.2 写时拷贝COW4.2 缺页异常1. 初始环境变量环境变量是操作系统运行环境的一些参数env可以查看所有的环境变量unset删除环境变量export添加环境变量echo$查看某一个环境变量。其中env是系统级的命令echosetexport是shell内置的命令getenvsetenv等是语言层的标准定义。常见的环境变量有PATH可执行文件的搜索路径列表。HOME当前用户主目录路径。PWD当前工作目录的绝对路径。还有USER,SHELL等等。父进程的环境变量可以被子进程继承当进程退出时内存中的数据被清理新增的环境变量自动被回收这也进一步说明进程是独立的2. 环境变量表与命令行参数表每个进程都要维护两张表命令行参数表和环境变量表。父进程创建子进程时子进程会继承这两张表。命令行参数表是一个字符串数组以NULL结尾通常有两个参数argv和argc。argv是数组名argc是数组大小数组存储进程运行时指令与配置。环境变量表传递全局上下文信息给子进程使用。主函数main其实也是有argv和argc这两个默认参数的当执行文件./test时argv[0]存储的就是./test3. enviorn可以用getenv查看用const char*的变量接收也可以用environ访问environ是一个全局指针变量指向一个以NULL结尾的字符串数组使用时需要加上extern声明environ可以遍历整个数组拿到完整的“keyvalue”#include stdio.h extern char **environ; // ⚠️ 必须手动声明 int main() { for (char **ep environ; *ep ! NULL; ep) { printf(%s\n, *ep); // 输出格式: KEYVALUE } return 0; }每个进程都有自己的environ指向各自独立的内存区域并且会进行实时更新envp不会进程在创建好后会让environ直接指向内核在进程启动时放置在栈上的那块原始内存。┌─────────────────┐ ← 高地址 │ envp strings │ ← 内核 execve() 放在栈上的原始数据 │ PATH/bin │ │ HOME/root │ ├─────────────────┤ │ argv pointers │ ├─────────────────┤ │ argc │ ← main(argc, argv, envp) 的参数来源 └─────────────────┘ ← 低地址 ↑ environ envp[0] ← 全局指针直接指向这里零拷贝setenv首次修改时会malloc一块新的堆内存将现有环境变量拷贝进去加上新变量然后更新environ指针指向这块新的内存后续修改直接在堆上realloc/调整putenv和setenv的不同当putenvchar* string时ibc 内部大致执行以下逻辑扫描当前environ数组查找是否存在同名 KEY。如果找到直接将environ[i] string;替换旧指针。如果没找到将string追加到environ数组末尾必要时realloc扩容environ指针数组本身然后补上NULL终止符。所以putenv是很危险的如果原字符串指针作用域结束或者free了会产生未定义行为setenv内部是malloc和strcpy深拷贝更加的安全建议优先使用setenv4. 虚拟地址空间我们平时说的栈堆等程序内存空间都不是物理内存而是虚拟地址空间一个进程一个虚拟地址空间一套页表页表用来做虚拟和地址的映射4.2 写时拷贝COW写时拷贝是一种资源优化管理策略当多个调用者请求相同资源时系统会让他们共享同一份资源当某个调用者试图修改资源时系统才会为他单独复制一份副本修改当父进程创建子进程时 内核仅复制页表让父子进程指向同一块物理内存并标记为只读。一旦某方尝试写入CPU 触发缺页异常内核才分配新内存并复制数据程序申请内存大概分为3步1.在虚拟地址空间中申请指定大小的空间2.加载程序申请物理内存3.页表进行映射所以本质上物理内存是没有0x112233这样一串的数字的一切的一切都是操作系统为了用户使用方便而包装设计的。虚拟地址的区域划分实际上就是一个结构体struct mm_struct { long code_start; long code_end; long init_start,init_end; long unit_start,unit_end; ... }虚拟地址空间的意义1. 将地址从“无序”变“有序”2.进程管理和内存管理进行一定程度的解耦合3. 地址转换的过程对地址和操作判定保护物理内存。这一步主要是通过页表权限和缺页异常来实现的我们都知道字符串常量不能修改是因为页表对应的权限为只读所以无法访问物理内存进行写操作缺页异常是操作系统的一种机制当CPU试图访问一个虚拟地址时如果MMU内存管理单元在页表中找不到有效的物理映射或者找到了映射但权限不足CPU就会抛缺页异常4.2 缺页异常缺页异常分软异常和硬异常软异常通常就是COW硬异常指页面不在物理内存而在磁盘上需要从磁盘上读入页面。CPU 执行访存指令 ↓ MMU 查页表 → 发现无效/权限不足 ↓ CPU 抛出缺页异常陷入内核态 ↓ OS 检查异常原因 虚拟地址 ↓ ┌──────────────┬──────────────┬──────────────┐ │ COW 写入 │ 页面在磁盘 │ 非法访问 │ │ │ │ │ │ 分配新物理页 │ 分配物理页 │ 发送 SIGSEGV │ │ 复制原页内容 │ 从磁盘读入 │ 杀死进程 │ │ 更新页表W1 │ 更新页表 │ │ └──────────────┴──────────────┴──────────────┘ ↓ 返回用户态重新执行刚才失败的指令缺页异常也是虚拟内存实现所有功能按需分页写时拷贝内存交换内存映射文件动态栈增长的基础缺页异常也是有代价的硬缺页需要数百万个CPU周期高性能程序要尽量避免硬缺页希望你能有所收获 Thanks♪(ω)