Linux进程(2)

📅 2026/7/13 16:26:26
Linux进程(2)
一、命令行参数和环境变量1. 命令行参数1.1 什么是命令行参数在命令行中启动程序时跟在程序名后面的字符串就是命令行参数。例如./myprogram -l -a test.txt其中-l、-a、test.txt就是命令行参数。1.2 main 函数的参数详解在 C/C 语言中main函数可以有多种写法其中最完整的形式是int main(int argc, char *argv[], char *env[])参数类型说明argcintargument count命令行参数的个数包括程序名本身argvchar *argv[]argument vector字符串数组存放每个参数的内容envchar *env[]environment环境变量数组存放所有环境变量可选argc和argv是最常用的两个参数env是 POSIX 标准扩展也可以用全局变量environ替代。1.3 argc 和 argv 详解argc—— 参数个数值至少为1因为argv[0]始终是程序名示例./myprogram hello world时argc 3argv—— 参数数组argv[0]程序名可能是完整路径或相对路径argv[1]~argv[argc-1]实际传入的参数argv[argc]固定为NULL作为数组结束标记内存结构示意图argv[0] → ./myprogram\0 argv[1] → hello\0 argv[2] → world\0 argv[3] → NULL1.4 三种 main 函数写法对比写法适用场景能否获取命令行参数能否获取环境变量int main(void)简单程序不需要任何输入❌❌int main(int argc, char *argv[])需要处理命令行参数✅❌int main(int argc, char *argv[], char *env[])需要同时获取参数和环境变量✅✅ 如果只需要环境变量也可以使用extern char **environ替代env参数。1.5 常见陷阱与注意事项argv[0]不一定是程序名攻击者可以通过exec系列函数伪造argv[0]所以不要依赖它做安全判断。参数中包含空格怎么办使用引号包裹./myprogram hello world此时argv[1] hello world作为一个整体参数个数为 0 不可能即使不传任何参数argc也是 1只有程序名argv和env的内存是连续的在内存布局中环境变量紧跟在命令行参数之后两者都以NULL结尾。2. Bash中内部的两张核心表在 Bash Shell 中为了管理程序的运行环境内部维护了两张重要的表表名英文名称存储内容作用范围生命周期环境变量表Environment Table键值对KEYvalue全局可被子进程继承从 export 开始到 Shell 结束命令行参数表Argument Table程序启动时传入的字符串数组仅限当前程序程序运行期间这两张表虽然都是字符串数组但生命周期、作用范围和传递方式完全不同。环境变量表Environment Variable TableBash 在启动时会从父进程继承环境变量并维护一张全局的环境变量表。特点以KEYvalue形式存储可被子进程继承通过export所有进程共享环境变量表的概念但内容是独立的副本查看当前 Shell 的环境变量表env # 显示所有环境变量 set # 显示所有变量包括本地变量 declare -p # 显示所有变量的详细属性在 Bash 中的存储方式Bash 内部用哈希表来存储环境变量以便快速查找。当执行export命令时export MYENVhello worldBash 会在哈希表中查找是否已存在MYENV如果存在则更新值不存在则插入新条目将该变量标记为可继承添加到环境变量表内存结构示意环境变量表字符指针数组 ───────────────────────────────────── [0] → PATH/usr/local/bin:/usr/bin:/bin\0 [1] → HOME/home/akaedu\0 [2] → SHELL/bin/bash\0 [3] → USERakaedu\0 [4] → LANGzh_CN.UTF-8\0 ... [?] → NULL ← 结束标记 ─────────────────────────────────────在 C 程序中如何访问#include stdio.h extern char **environ; // 全局变量指向环境变量表 int main() { for (int i 0; environ[i] ! NULL; i) { printf(environ[%d] %s\n, i, environ[i]); } return 0; }命令行参数表Command-Line Argument Table当你在 Shell 中执行一个命令时Bash 会解析输入行将其拆分成多个参数构建一张命令行参数表然后传递给程序的argv。特点以参数字符串数组形式存储不会被继承每个程序独立拥有第一个元素始终是程序名构建过程示例$ ls -l /home --colorautoBash 解析后生成的命令行参数表命令行参数表 ───────────────────────────────────── argv[0] → ls\0 argv[1] → -l\0 argv[2] → /home\0 argv[3] → --colorauto\0 argv[4] → NULL ← 结束标记 ─────────────────────────────────────在 C 程序中如何访问#include stdio.h int main(int argc, char *argv[]) { for (int i 0; i argc; i) { printf(argv[%d] %s\n, i, argv[i]); } return 0; }两张表的关系与区别对比维度环境变量表命令行参数表存储位置environ全局变量argv参数结束标记NULL指针NULL指针数据格式KEYvalue纯字符串无特定格式数据来源继承自父进程 Shell 配置用户命令行输入是否可被子进程继承✅ 是export后❌ 否典型用途系统配置、默认值、路径程序行为控制、输入文件修改方式export VARvalue、setenv直接修改命令行查看命令env、setecho $Shell 内Bash 内部的处理流程当你输入一条命令时Bash 内部的处理流程如下┌─────────────────────────────────────────────────────────┐ │ 用户输入./myprogram -v --config test.conf │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 1. 词法解析Tokenization │ │ 将输入行拆分成[ls, -l, /home] │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 2. 构建命令行参数表argv 数组 │ │ argv[0] ./myprogram │ │ argv[1] -v │ │ argv[2] --config │ │ argv[3] test.conf │ │ argv[4] NULL │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 3. 准备环境变量表environ 数组 │ │ 继承自当前 Shell 的已导出环境变量 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 4. 调用 fork() execve() 加载程序 │ │ 将两张表传递给新进程 │ └─────────────────────────────────────────────────────────┘3. 环境变量什么是环境变量环境变量是操作系统中一组具有全局特性的参数用来指定系统的运行环境。常见环境变量PATH指定命令的搜索路径HOME用户的主工作目录SHELL当前使用的 Shell 类型如何通过代码获取环境变量方法一使用getenv推荐#include stdio.h #include stdlib.h int main() { printf(PATH: %s\n, getenv(PATH)); return 0; }方法二使用main的第三个参数int main(int argc, char *argv[], char *env[]) { for (int i 0; env[i]; i) { printf(%s\n, env[i]); } return 0; }方法三使用全局变量environ#include stdio.h extern char **environ; int main() { for (int i 0; environ[i]; i) { printf(%s\n, environ[i]); } return 0; }环境变量的特性环境变量最开始从系统的相关配置文件(bash)中来的环境变量可以被子进程继承环境变量具有全局特性bash会记录两套变量一个是本地变量一个环境变量本地变量不会被子进程继承同时还有内建命令这种命令不用创建子进程而是让bash自己执行。二、程序地址空间1. 进程地址空间布局当Linux加载一个可执行程序时会为其创建一个独立的虚拟地址空间。在32位系统下典型的布局如下0xFFFFFFFF ------------------ | 内核空间 | (用户态不可访问) 0xC0000000 ------------------ | 栈 (向下) | | ↓ | | ↑ | | 堆 (向上) | | 未初始化数据 | | 已初始化数据 | | 代码段 | 0x08048000 ------------------ | 保留区 | 0x00000000 ------------------各区域作用代码段(.text)存放程序指令只读数据段(.data/.bss)全局变量和静态变量堆动态分配的内存(malloc/new)栈函数调用、局部变量2. mm_struct进程地址空间的“总司令”mm_struct不仅描述虚拟地址空间的布局还承载了内存管理相关的全部元数据。核心成员详解struct mm_struct { struct vm_area_struct *mmap; // VMA 链表头 struct rb_root mm_rb; // VMA 红黑树根节点 unsigned long task_size; // 进程虚拟地址空间大小 // 代码段 / 数据段 / 堆 / 栈 的边界 unsigned long start_code, end_code; unsigned long start_data, end_data; unsigned long start_brk, brk; unsigned long start_stack; // 命令行参数和环境变量区域 unsigned long arg_start, arg_end; unsigned long env_start, env_end; pgd_t *pgd; // 页表根 atomic_t mm_users; // 引用计数多个线程共享 atomic_t mm_count; // 内核中暂时引用 spinlock_t page_table_lock; // 保护页表并发访问 struct rw_semaphore mmap_sem; // 保护 VMA 操作 };task_struct-mm指向所属进程的mm_struct。线程轻量级进程共享同一个mm_struct因此可以共享地址空间。3. VMA地址空间的“功能区”vm_area_struct描述一段连续的虚拟地址区域如代码区、堆区、栈区、内存映射区等。struct vm_area_struct { unsigned long vm_start, vm_end; // 虚拟地址范围 unsigned long vm_flags; // 权限VM_READ, VM_WRITE, VM_EXEC, etc. struct mm_struct *vm_mm; // 所属 mm_struct struct file *vm_file; // 映射的文件文件映射 unsigned long vm_pgoff; // 文件偏移 const struct vm_operations_struct *vm_ops; // 操作函数如缺页处理 };一个mm_struct下可以有几十甚至上百个 VMA通过链表或红黑树快速查找。4. 页表页表是操作系统用来将虚拟地址翻译为物理地址的数据结构由 CPU 的 MMU内存管理单元硬件配合使用基本思路内存被分成固定大小的页通常 4KB虚拟页号 → 物理页框号的映射记录在页表中两级页表示例32 位4KB 页虚拟地址 (32位) [ 页目录索引(10位) | 页表索引(10位) | 页内偏移(12位) ]查找过程从cr3寄存器获得页目录基址页目录索引 → 找到页表地址页表索引 → 找到物理页框号物理地址 物理页框号 × 4KB 页内偏移虚拟地址 → MMU → 页表 → 物理地址5. 页表与进程地址空间的关系进程 A 虚拟地址空间 ↓ 页表 A独立 ↓ 物理内存 进程 B 虚拟地址空间 ↓ 页表 B独立 ↓ 物理内存每个进程有独立的页表cr3不同内核空间在所有进程的页表中映射相同物理页共享用户空间完全隔离但可通过mmapMAP_SHARED共享物理页