从环境变量到进程地址空间:Linux系统学习笔记

最近在学习Linux系统编程中环境变量与进程地址空间相关内容,把课堂笔记整理成这篇博客,方便自己复盘也分享给大家~

一、环境变量:操作系统的"全局配置参数"

环境变量是操作系统中用来指定运行环境的参数,就像程序运行时的"全局配置文件",决定了程序能在哪里找指令、用户的工作路径等核心信息。

  1. 命令行参数:程序的"动态开关"

int main(int argc, char* argv[]) 是我们接触C语言时就熟悉的入口,其中:

  • argc 是命令行参数的个数, argv 是存储参数的指针数组。

  • 它的核心作用是让同一个程序通过不同参数实现不同功能,比如 ls -a 和 ls -l ,就是通过不同的命令行参数改变程序行为,底层会被bash解析成命令行参数表。

  1. 环境变量的核心作用

环境变量最典型的例子就是 PATH ,它是系统搜索指令的默认路径集合。当我们在终端输入 ls 时,系统会遍历 PATH 中的路径,找到对应的可执行文件再执行。

除此之外,常见的环境变量还有:

  • HOME :当前用户的主目录路径

  • SHELL :当前使用的Shell程序路径

  • USER :当前登录的用户名

  1. 环境变量的特性与操作
  • 全局特性:环境变量是bash的全局变量,会被子进程继承。比如系统中如果有6个用户登陆,就会存在6个bash进程,每个bash的环境变量都会被子进程继承。

  • 本地变量与全局变量:用 export 导出的环境变量是全局的,会被子进程继承;未导出的本地变量仅在当前bash内部有效,子进程无法访问。

  • 常用操作命令:- echo 变量名 :查看指定环境变量的值,比如 echo HOME

  • env :查看所有环境变量

  • export 变量名=值 :设置全局环境变量

  • unset 变量名 :取消环境变量

  1. 代码中获取环境变量的两种方式

  2. 通过 main 函数的第三个参数 char* env[] :

这是一个存储所有环境变量的指针数组,以 NULL 结尾。不过这种方式不推荐,容易和其他参数混淆。c

#include <stdio.h>

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

(void)argc; (void)argv;

for(int i=0; env[i]; i++) {

printf("env[%d]: %s\n", i, env[i]);

}

return 0;

}

  1. 通过 getenv 函数(推荐):

可以直接指定要获取的环境变量,更安全也更灵活,适合只需要特定环境变量的场景。c

#include <stdio.h>

#include <stdlib.h>

int main() {

char* path = getenv("PATH");

if(path == NULL) return 1;

printf("PATH: %s\n", path);

return 0;

}

  1. 通过全局变量 environ :

系统提供的全局变量 environ 指向环境变量表,使用时需要声明 extern char** environ; 。c

#include <stdio.h>

int main() {

extern char** environ;

for(int i=0; environ[i]; i++) {

printf("env[%d]: %s\n", i, environ[i]);

}

return 0;

}

二、进程地址空间:每个进程的"虚拟独立世界"

进程地址空间是操作系统给每个进程提供的虚拟地址空间,它不是真实的物理内存,而是一个逻辑上的地址范围,通过页表映射到真实的物理内存。

  1. 什么是进程地址空间?

对一个进程来说,它看到的内存是一个从低地址到高地址连续的虚拟地址空间,而真实的物理内存可能是离散的,通过页表完成虚拟地址到物理地址的映射。

  • 32位系统下,进程地址空间的大小是4GB(2的32次方字节),其中低3GB是用户空间,高1GB是内核空间。

  • 每个进程都认为自己独占了整个内存,这就是进程的"虚拟内存"机制。

  1. 进程地址空间的区域划分

进程地址空间从低地址到高地址,主要分为这些区域:

区域 作用

代码段(.text) 存储程序的机器指令,只读

数据段(.data) 存储已初始化的全局变量和静态变量

BSS段(.bss) 存储未初始化的全局变量和静态变量,程序运行时初始化为0

堆(Heap) 动态内存分配的区域,从低地址向高地址增长,比如 malloc 分配的内存就在这里

栈(Stack) 存储局部变量、函数参数、返回地址等,从高地址向低地址增长

共享库区域 存储动态链接库的代码和数据

内核空间 操作系统内核的地址空间,用户进程无法直接访问

  1. 虚拟地址 vs 物理地址

C/C++中使用的所有指针地址,都是虚拟地址,不是真实的物理内存地址。操作系统通过页表,将虚拟地址转换为物理地址,这样做有几个好处:

  1. 内存隔离:每个进程的虚拟地址空间相互独立,一个进程的错误不会影响其他进程。

  2. 内存保护:操作系统可以通过页表设置权限,比如代码段只读,防止进程修改指令。

  3. 内存管理:可以将不常用的内存数据交换到磁盘(swap分区),缓解物理内存不足的问题。

  4. 地址无关代码:程序不需要知道自己会被加载到哪个物理地址,方便操作系统动态加载和重定位。

  5. 进程地址空间的核心特性

  • 进程独立性:每个进程都有自己独立的地址空间,修改一个进程的变量不会影响其他进程,即使变量的虚拟地址相同,映射的物理地址也不同。

  • 写时拷贝(Copy-On-Write):当子进程被创建时,不会立即复制父进程的整个地址空间,而是和父进程共享同一份物理内存,只有当进程需要修改数据时,才会复制一份副本,这就是写时拷贝,也是子进程具有独立性的底层支撑。

三、学习中的关键思考与疑问

  1. 环境变量的来源:环境变量最开始从哪里来?答案是用户的相关配置文件,比如 ~/.bashrc 、 /etc/profile 等,用户登陆时bash会读取这些文件初始化环境变量。

  2. 本地变量与全局变量的区别:本地变量仅在当前bash进程中有效,不会被子进程继承;而用 export 导出的全局环境变量会被子进程继承,这也是为什么我们在子shell中设置的变量,父shell看不到的原因。

  3. 页表的作用:页表不仅完成虚拟地址到物理地址的映射,还能通过权限位控制内存的读写执行权限,比如代码段的只读保护,防止恶意修改指令。

  4. 地址空间的实现:进程地址空间本质上是操作系统通过 mm_struct 结构体管理的,里面包含了各个区域的起始地址、结束地址和权限信息,内核通过这个结构体来管理进程的内存。

四、学习总结

这次学习的环境变量和进程地址空间,是Linux系统编程的核心基础:

  • 环境变量是进程运行的"配置中心",理解它的继承机制和操作方式,能帮我们更好地理解进程间的环境传递。

  • 进程地址空间是操作系统给进程的"虚拟隔离舱",虚拟地址、页表映射、区域划分这些概念,是理解进程管理、内存管理的关键。

后续还要深入学习页表的具体实现、进程创建时的地址空间复制,以及动态链接库在地址空间中的加载机制,把这些知识点串起来~

相关推荐
亿元程序员2 小时前
海外这个新游好玩?手把手带你实战一个!
前端
M ? A2 小时前
Vue slot 插槽转 React:VuReact 怎么处理?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
a1117762 小时前
演唱会3D选座网页(HTML 开源)
前端·3d·html
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day10(交互升级与接口溯源)
前端·javascript·3d·自动化·交互
恋猫de小郭2 小时前
WasmGC 是什么?为什么它对 Dart 和 Kotlin 在 Web 领域很重要?
android·前端·flutter
新酱爱学习2 小时前
从一次 OpenClaw 请求抓包,聊聊 Skill 的运行原理
前端·人工智能·mcp
慕斯fuafua2 小时前
CSS——弹性盒子
前端·css
M ? A2 小时前
Vue Transition 组件转 React:VuReact 怎么处理?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
小江的记录本2 小时前
【分布式】分布式一致性协议:2PC/3PC、Paxos、Raft、ZAB 核心原理、区别(2026必考Raft)
java·前端·分布式·后端·安全·面试·系统架构