Linux 环境变量是程序运行的'隐形配置',程序地址空间是进程访问内存的'底层规则',这两类知识点是打通 Linux 系统底层逻辑的关键纽带。理解它们不仅能解释'为什么命令能直接运行''进程如何安全访问内存'等核心问题,更能为编写灵活的命令行工具、排查内存相关问题提供底层支撑。
环境变量
概念: 一般是指在操作系统中用来指定操作系统运行环境的一些参数。不同的环境变量有不同的用途,通常具有全局属性(这里的全局属性意思是可以被子进程继承)。
Bash 本身在启动的时候,会从操作系统的配置文件中读取环境变量信息。每次启动,环境变量都会被重置;父进程的环境变量变了,子进程会跟着变。
常见的环境变量
- PATH: 指定命令的搜索路径
- HOME: 指定用户的主工作目录
- SHELL: 当前 shell,它的值通常是/bin/bash
- HISTSIZE:
history能保存之前使用指令的最大条数 - SSH_TTY: 终端设备的路径(SSH 远程登陆的话会有)
- USER 和 LOGNAME: 代表当前登录的用户是谁(这俩没啥区别)
- PWD: 表示当前的路径 (修改这个不会改变现在在哪个路径哈)
- OLDPWD: 通过
cd命令切换目录时,标记上一次的路径
和环境变量相关的指令
echo: 显示某个环境变量值export: 设置一个新的环境变量env: 显示所有环境变量 (想只看一个的话就grep那样)unset: 清除环境变量set: 显示所有本地变量和环境变量- 修改环境变量:eg:
PATH=$PATH:/home/renshen
引申:本地变量只在当前 bash 进程里有效。本地变量转变成环境变量:export 变量名 (不用加=值)。
通过系统调用获取或设置环境变量
系统调用:getenv, putenv。
引申:chdir 这个系统调用可以改路径。
main 函数的命令行参数
其实 main 函数原型为 int main(int argc, char *argv[], char *env[])。
- 第一个参数是接受到的命令行参数的总个数 (包括程序名)
- 第二个参数是命令行参数(最后一个位置会放一个
nullptr),命令行的指令当字符串传过来的 - 第三个参数是环境变量数组
在进程进行时,都会被传入两张核心向量表——命令行参数表和环境变量表。所以,不要 char *env[] 也行,直接在代码里 extern char **environ。env 怎么用,这个也怎么用,eg:environ[0]。这个全局变量指向环境变量表 (不用头文件)。
main 函数这样设置的目的——为指令、工具、软件等提供命令行选项的支持。
常规命令和内建命令
- 常规命令: 通过 bash 创建子进程执行
- 内建命令: bash 不创建子进程,自己亲自执行——类似 bash 调用系统提供的函数,比如
echo
程序地址空间
进程地址空间

这是进程地址空间的分配图。最下面地址是 00000000,最上面是 FFFFFFFF(32 位的话)。
Linux 里面这个叫做线性地址或者虚拟地址 (线性地址和虚拟地址不是一个东西哈)。
- 堆里面存放的是动态开辟的空间
- 栈里面存放的是局部变量等
- 未初始化全局变量和已初始化全局变量共同组成了全局数据区
- 栈区的上方还有命令行参数环境变量区 (这个是低地址向高地址长的)
- 引申:
static修饰的局部变量在编译的时候会被编译到全局数据区
这里的存放并非真实的存放哈,只是存的地址映射,真正的东西存放在物理内存里面。在调试的时候查看内存就是看的这个虚拟内存的地址;物理地址用户是看不到的。
关于进程地址空间的大小:(32 位计算机的话),操作系统给每个进程都假装许诺给他 4GB 的内存——但是其实上是进程需要多少空间,操作系统才给多少 (其实进程全部加起来一共只有 4GB 能用)。
问题:
- 什么是地址空间: 本质是一个描述进程可视范围的大小的内核的一个数据结构对象,跟 PCB 差不多 (也是先描述再组织)。
- 如何理解地址空间上的区域划分: 其实就是搞了个结构体,里面存这个地址范围的 begin end;对空间的区域增大减少其实也就是把 begin 和 end 改了。在这个地址范围内,这个连续的空间中的每一个最小单位都可以有地址,这个地址可以被这部分直接使用。
- 32 位地址总线为什么能支持最大 4GB 内存寻址: 32 位地址总线一共 32 根,每根的状态只有 0 或 1,所以就是 2 的 32 次方,然后一个地址对应着 1 个字节的空间——所以就是 4GB。所以操作系统给进程地址空间画饼的大小是 4GB 的内存。
进程地址空间的意义:
- 让进程以统一的视角看待内存
- 有了进程地址空间可以让我们在访问内存时,增加一个转换的过程——在这个转换过程中,可以对寻址请求进行审查,所以进行异常访问的话,就会被直接拦截,这个请求就不会到物理内存那里——保护了物理内存的安全
- 有地址空间和页表的存在,将进程管理模块和内存管理模块进行了解耦合 (就是降低依赖关系),进程具有独立性的原因
虚拟地址和物理地址间的关系

- 每一个进程都有自己的一个进程地址空间
- 父进程和子进程的进程地址空间也是分开的——但是页表部分是一样的,但是如果进行了修改操作的话,页表的虚拟地址和物理地址的对应关系就变了
- (这其实也是父子进程代码可以共享的原因)在父进程没有分出子进程时,页表里面可读可写的部分会在分出子进程之后全部变成只读部分——eg: 父进程如果想改值,也会触发写时拷贝
问题: 进程怎么找到自己的页表?
CPU 里面有个 cr3 寄存器会存储当前进程的页表地址 (操作系统写入的,这里存的是物理地址哈)。上一个进程的页表可以通过进程控制块的 mm_struct 找到。
所以对进程的认知进一步加深了:
- 进程的内核数据结构包括
task_struct和mm_struct和页表 - 进程在被创建的时候是先创建的内核数据结构,再加载对应的可执行程序
作业部分
不属于冯诺依曼体系结构必要组成部分是:(B)A.CPU//运算器与控制器 B.Cache//缓存(一种技术)//是为了缓解 CPU 与内存之间的速度差异,属于体系结构的优化补充,而非必须的核心组成部分 C.RAM//属于存储器 D.ROM//属于存储器
冯诺依曼体系结构计算机的基本原理是 (D) A.信息存储 B.存储智能 C.数字控制 D.存储程序和程序控制
系统调用函数的执行过程应该是在用户态 (错) 应该是系统调用的运行过程是在内核态完成的
系统感知进程的唯一实体是 (B) A.进程 id B.进程控制块 C.进程管理器 D.进程名
在抢占式多任务处理中,进程被抢占时,哪些运行环境需要被保存下来?(ACD)[多选] A.所有 cpu 寄存器的内容//cpu 上正在处理的数据 B.全局变量 C.页表指针//程序切换时会将页表起始地址加载到寄存器中 D.程序计数器
关于僵尸进程,以下描述正确的有(A)A.僵尸进程必须使用 waitpid/wait 接口进行等待 B.僵尸进程最终会自动退出 C.僵尸进程可以被 kill 命令杀死 D.僵尸进程是因为父进程先于子进程退出而产生的
以下关于孤儿进程的描述正确的有 (AC) A.父进程先于子进程退出,则子进程成为孤儿进程 B.孤儿进程会产生资源泄漏 C.孤儿进程运行在系统后台 D.孤儿进程没有父进程
以下描述错误的是 (D) A.守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。B.僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有 wait/waitpid 子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。C.孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工作)D.精灵进程:精灵进程退出后会成为僵尸进程 引申:守护进程和精灵进程其实是一个东西
使用 shell 时,默认的环境变量放在哪里 (A) A.~/.bash_profile B.~/.bash C./etc/profile.d D.~/bash
以下描述正确的有 [多选](AC) A.子进程默认会复制拥有与父进程相同的环境变量 B.环境变量使 shell 运行环境配置变的更加复杂//可以使运行环境的配置更加灵活简单 C.环境变量可以使用 export 命令设置 D.删除一个环境变量可以使用 unset 和 rm 命令
在 CPU 和物理内存之间进行地址转换时,(B)将地址从虚拟(逻辑)地址空间映射到物理地址空间 A.TCB//线程控制块 B.MMU//内存管理单元 C.CACHE//高速缓冲存储器 D.DMA//直接内存访问 (不经过 CPU)


