Linux 进程:地址空间、页表、进程控制、程序替换 📅 发布时间:2026/7/5 22:25:08 👁️ 浏览次数: 一、Linux 里 “进程” 到底是什么进程 内核态的进程控制块PCB 用户态的程序代码 / 数据 地址空间 页表映射。PCBLinux 里是task_struct内核管理进程的 “身份证”存 PID、状态、优先级、文件描述符等。地址空间给进程画的 “虚拟大饼”让每个进程都觉得自己独占内存。页表连接 “虚拟地址” 和 “物理内存” 的桥梁内核的 “翻译官”。先记住核心结论Linux 中每个进程都有独立的虚拟地址空间通过页表映射到物理内存进程之间互不干扰。二、进程地址空间不是 “真实内存”是 “虚拟蓝图”1. 虚拟地址空间的本质新手最容易踩的坑以为0x7fxxxx这样的内存地址是物理内存地址其实是虚拟地址。32 位 Linux 系统中每个进程的虚拟地址空间大小是 4GB内核会把它分成两部分内核空间高 1GB0xC0000000 ~ 0xFFFFFFFF所有进程共享映射到物理内存的内核区域。用户空间低 3GB0x00000000 ~ 0xBFFFFFFF每个进程独立通过页表映射到不同的物理内存页。64 位系统虚拟地址空间更大通常用 48 位寻址但核心逻辑一致。2. 虚拟地址空间的布局用户空间从低地址到高地址用户空间分为 7 个区域按用途划分低地址 → 高地址 1. 代码段.text存放程序编译后的二进制指令只读可执行 2. 数据段.data存放已初始化的全局变量、静态变量 3. 未初始化数据段.bss存放未初始化的全局变量、静态变量运行时初始化为0 4. 堆heap动态内存分配区域malloc/mmap向上增长 5. 共享库存放动态链接库比如libc.so 6. 栈stack函数调用、局部变量、临时数据向下增长 7. 命令行参数/环境变量存放argv、envp等。验证写个简单 C 程序打印各区域的地址直观感受布局#include stdio.h #include stdlib.h int g_val 10; // 已初始化数据段 int g_unval; // 未初始化数据段 const char* str abc; // 只读数据段代码段 int main() { int a 20; // 栈 char* p malloc(10); // 堆 printf(代码段str%p\n, str); printf(数据段g_val%p\n, g_val); printf(BSS段g_unval%p\n, g_unval); printf(堆p%p\n, p); printf(栈a%p\n, a); printf(环境变量%p\n, environ); // environ是全局变量存环境变量地址 free(p); return 0; }输出示例代码段str0x55f8b7a7d004 数据段g_val0x55f8b7c7e010 BSS段g_unval0x55f8b7c7e014 堆p0x55f8b949c2a0 栈a0x7ffeef7a89dc 环境变量0x7ffeef7a9c88能明显看到栈地址 堆地址 数据段地址 代码段地址符合 “栈向下、堆向上” 的规律。3. 为什么要有虚拟地址空间隔离性每个进程的虚拟地址空间独立一个进程崩溃不会影响其他进程。安全性用户空间无法直接访问内核空间需通过系统调用防止恶意程序篡改内核。内存复用物理内存不足时内核可将不常用的页换出到磁盘swap需要时再换入。方便链接编译时程序不用关心物理内存地址只需要按虚拟地址布局即可。三、页表虚拟地址和物理内存的 “翻译官”1. 页表的核心作用虚拟地址不能直接访问物理内存必须通过页表Page Table映射。内核会把虚拟地址和物理内存都分成固定大小的 “页”Linux 默认 4KB页表就是记录 “虚拟页号 → 物理页框号” 的映射关系表。2. 映射流程CPU 给出虚拟地址分为 “虚拟页号VPN” 和 “页内偏移”。MMU内存管理单元根据 VPN 查页表找到对应的物理页框号PFN。物理页框号 页内偏移 物理地址访问物理内存。3. 关键特性页表共享父子进程会共享页表的只读部分比如代码段写时复制Copy On WriteCOW—— 父进程 fork 出子进程后数据段 / 堆 / 栈默认共享只有当某一方修改数据时内核才会复制对应的页保证独立性。缺页中断如果虚拟页没有映射到物理页比如首次访问、页被换出到 swapCPU 会触发缺页中断内核负责分配物理页、更新页表。页表层级64 位系统用多级页表四级避免页表占用过多内存。实战点fork 子进程后子进程修改全局变量父进程不受影响就是 COW 机制在起作用#include stdio.h #include unistd.h int g_val 10; int main() { pid_t pid fork(); if (pid 0) { // 子进程 g_val 20; printf(子进程g_val%d, 地址%p\n, g_val, g_val); } else { // 父进程 sleep(1); // 等子进程修改 printf(父进程g_val%d, 地址%p\n, g_val, g_val); } return 0; }运行输出子进程g_val20, 地址0x55e8d8772010 父进程g_val10, 地址0x55e8d8772010注意地址相同虚拟地址但值不同 —— 因为修改时触发 COW子进程复制了对应的物理页页表映射到新的物理页虚拟地址不变但物理内存地址是发生了改变的。四、进程控制fork/exec/wait/exitLinux 下控制进程的核心是四个系统调用fork创建、exec程序替换、wait等待子进程、exit退出。1. fork创建子进程1原理fork()调用后内核会复制父进程的 PCBtask_struct生成子进程的 PCBPID 不同。父子进程共享页表COW。父进程返回子进程的 PID子进程返回 0出错返回 - 1。2fork 的基本用法#include stdio.h #include unistd.h #include sys/wait.h #include stdlib.h int main() { pid_t pid fork(); if (pid 0) { // 出错 perror(fork error); exit(1); } else if (pid 0) { // 子进程 printf(子进程PID%d, 父PID%d\n, getpid(), getppid()); exit(0); // 子进程退出 } else { // 父进程 // 等待子进程退出避免僵尸进程 wait(NULL); printf(父进程子进程PID%d\n, pid); } return 0; }3关键问题僵尸进程 / 孤儿进程僵尸进程子进程退出后父进程未调用wait/waitpid回收子进程的 PCB 仍留在内核中占资源。解决僵尸进程需要父进程调用wait/waitpid或用信号SIGCHLD处理。孤儿进程父进程先退出子进程被 initPID1收养不会变成僵尸进程。2. exit/_exit进程退出exit(int status)用户态退出会刷新缓冲区、关闭文件描述符最终调用_exit。_exit(int status)内核态退出直接终止进程不刷新缓冲区。区别验证#include stdio.h #include unistd.h #include stdlib.h int main() { printf(hello); // 没有\n缓冲区不刷新 // exit(0); // 会刷新缓冲区输出hello _exit(0); // 不刷新缓冲区无输出 }3. wait/waitpid等待子进程退出wait(int *status)阻塞等待任意子进程退出回收资源status 存退出状态waitpid(pid_t pid, int *status, int options)更灵活可指定 PID、非阻塞WNOHANG。获取子进程退出状态#include stdio.h #include unistd.h #include sys/wait.h #include stdlib.h int main() { pid_t pid fork(); if (pid 0) { exit(5); // 子进程退出状态码5 } int status; waitpid(pid, status, 0); if (WIFEXITED(status)) { // 判断是否正常退出 printf(子进程正常退出状态码%d\n, WEXITSTATUS(status)); // 输出5 } return 0; }五、进程程序替换exec 系列函数1. 为什么需要程序替换fork创建的子进程和父进程运行相同的程序若想让子进程运行另一个程序需要用exec系列函数 —— 替换进程的代码段、数据段、堆、栈保留 PID 不变。2. exec 系列函数函数说明execl列表传参llistexeclp找 PATH 环境变量ppathexecle传环境变量eenvexecv数组传参vvectorexecvp数组传参 找 PATHexecve系统调用其他都是封装3. 核心用法1execlp执行系统命令比如 ls#include stdio.h #include unistd.h #include stdlib.h int main() { pid_t pid fork(); if (pid 0) { // 替换成ls命令-l是参数NULL表示参数结束 execlp(ls, ls, -l, NULL); // 若替换成功下面的代码不会执行 perror(exec error); exit(1); } wait(NULL); return 0; }2execv执行自定义程序数组传参// 先写一个自定义程序test.c #include stdio.h int main(int argc, char *argv[]) { for (int i0; iargc; i) { printf(argv[%d]%s\n, i, argv[i]); } return 0; } // 编译gcc test.c -o test // 主程序main.c #include stdio.h #include unistd.h #include sys/wait.h #include stdlib.h int main() { pid_t pid fork(); if (pid 0) { char *argv[] {./test, hello, world, NULL}; execv(./test, argv); // 替换成自定义程序 perror(exec error); exit(1); } wait(NULL); return 0; }运行主程序输出argv[0]./test argv[1]hello argv[2]world4. 关键特性替换只会改变用户空间的代码 / 数据内核的 PCBPID、文件描述符等不变。若替换失败才会执行后续代码需处理错误。六、实战forkexecwait 组合这是 Linux 下创建新进程运行另一个程序的标准流程#include stdio.h #include unistd.h #include sys/wait.h #include stdlib.h int main() { // 1. 创建子进程 pid_t pid fork(); if (pid 0) { perror(fork error); exit(1); } if (pid 0) { // 2. 子进程执行程序替换 printf(子进程执行ps命令...\n); execlp(ps, ps, aux, NULL); // 替换失败才会走到这里 perror(exec error); exit(2); } else { // 3. 父进程等待子进程 int status; waitpid(pid, status, 0); if (WIFEXITED(status)) { printf(子进程退出状态%d\n, WEXITSTATUS(status)); } } return 0; }总结进程地址空间是虚拟的每个进程独立分为内核 / 用户空间用户空间按代码段 / 数据段 / 堆 / 栈等布局。页表映射虚拟地址和物理内存父子进程共享只读页写时复制COW是核心优化。进程控制fork 创建子进程共享页表exit 退出wait 回收子进程防僵尸。程序替换exec 系列函数替换进程的代码 / 数据保留 PID是实现多程序运行的核心。
协议森林 瑞士军刀 (ICMP协议) 头薪妇忍一、自回归推理的瓶颈与 KV Cache 的诞生 在 Transformer 的自注意力机制中,第 个位置的输出需要与历史所有位置进行交互: 其中 , , 。 训练与推理在计算模式上存在根本差异。训练阶段采用并行计算:整个序列一… 2026/5/17 9:25:55
在 Asp.NET MVC 中使用 SignalR 实现推送功能 爸影鲁椎开源神器 Infisical:一站式解决秘密管理、PKI、KMS 等难题! Infisical 是一个开源的密钥管理、PKI 和 SSH 访问平台。简单讲,它帮助团队安全地存储和管理敏感信息(如密码、证书、密钥),并控制谁可以… 2026/7/3 19:49:15
XML-RPC 实现C++和C#交互 捞骨背岩JetBrains 再放大招!继 JetBrains RustRover、CLion、Rider、WebStorm 和 RubyMine 之后,其专业数据库管理工具 DataGrip 也正式面向非商业用途免费开放。无论你是学生、开源贡献者,还是出于个人兴趣探索数据库技术,现在都… 2026/7/5 17:35:36
Hugging Face与Flair默认情感分析管道深度对比 1. 项目概述:为什么“开箱即用”的情感分析模型值得较真?你是不是也经历过这样的场景:项目时间紧,老板说“先跑个情感分析看看用户评论倾向”,你火速打开 Hugging Face 的pipeline,一行代码搞定;… 2026/7/5 22:24:52
移动端实时AI换脸部署实战:模型量化与跨平台优化 1. 项目概述:当实时AI换脸遇上移动端最近在折腾一个挺有意思的项目,叫Deep-Live-Cam。简单说,它是个开源的实时人脸替换工具,你给它一张目标人脸图片,它就能用你的摄像头实时把画面里的人脸换成目标脸,效果… 2026/7/5 22:22:51
KOLLMORGEN CP310250伺服驱动器技术解析与应用指南 1. 产品定位与核心特性解析 KOLLMORGEN CP310250伺服驱动器是工业自动化领域的一款高端驱动解决方案,专为对动态响应和精度要求严苛的应用场景设计。这款额定功率3kW的驱动器采用了模块化架构,支持多种反馈接口(包括EnDat 2.2、BiSS-C、Resol… 2026/7/5 22:22:51
蒙特卡洛方法在SIR模型中的3个关键应用:从参数估计到干预策略评估 蒙特卡洛方法在SIR模型中的3个关键应用:从参数估计到干预策略评估引言:当概率遇上流行病学想象你是一位公共卫生决策者,面对一种新型传染病的爆发,需要回答三个关键问题:病毒传播速度有多不确定?如果实施社… 2026/7/5 22:20:51
Three.js 中国旗帜教程 中国旗帜 China Flag ▶ 在线运行案例 案例合集: 三维可视化功能案例(threehub.cn)开源仓库github地址: https://github.com/z2586300277/three-cesium-examples400个案例代码: 网盘链接 你将学到什么 RawShaderMaterial 手写… 2026/7/5 22:18:51
App渠道追踪实战指南:iOS、Android与鸿蒙多平台实现与避坑 1. 项目概述:为什么渠道追踪是App增长的“生命线”在移动互联网的下半场,流量红利见顶,每一分市场预算都变得弥足珍贵。作为开发者或市场运营,你是否曾面临这样的灵魂拷问:我们投放在抖音、小红书、知乎、应用商店的广… 2026/7/5 22:18:51
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/5 0:01:32
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/5 0:01:32
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/5 0:05:36
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/5 0:01:32
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/5 0:01:32
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/5 0:05:36