linux入门(3)
GCC编译器
gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤:
step 1 预处理
- 预处理内容
- 头文件展开:将#include指令替换为对应头文件的实际内容
- 宏替换:
- 变量宏:如#define定义的常量
- 函数宏:带参数的宏定义
- 空白处理:
- 删除注释内容
- 将制表符(tab)转换为空格
- 压缩连续的空格和空行
g++ -E hello.cpp -o hello.i
- -E:指定进行预处理操作
- -o:指定输出文件名(重命名)
- 生成.i文件,为预处理文件
setp 2编译
- 核心功能:逐行检查程序中出现的语法和词法错误,所有编译过程最耗时的
- 包括:
- 错误处理:若存在编译错误则无法生成后续文件
- 函数名拼写(如exit是否写成etix)
- 关键字正确性(如return是否拼写错误)
- 简单逻辑错误(如goto语句跳转到switch-case的非法位置)
g++ -S hello.i -o hello.s
-S:编译选项,如果编译无误,生成.s汇编文件。
setp 3汇编
将.s汇编 文件中,的所有汇编指令,翻译成二进制机器码.
g++ -c hello.s -o hello.o
-c:汇编选项。 无错误检查,机械翻译。
setp 4 链接:连接器
将.o的目标文件,链接库文件、数据段合并,地址回填。生成可执行文件。
g++ hello.o -o hello
此过程无专用参数。-o不是连接过程必须使用的参数。
一步到位:
g++ hello.cpp -o hello
gcc常用选项
选项 | 作用 |
---|---|
-o file | 指定生成的输出文件名为file |
-E | 只进行预处理 |
-S(大写) | 只进行预处理和编译 |
-c(小写) | 只进行预处理、编译和汇编 |
-v / --version | 查看gcc版本号 |
-g | 包含调试信息 |
-On n=0~3 | 编译优化,n越大优化得越多 |
-Wall | 提示更多警告信息 |
-D | 编译时定义宏 |
可执行文件的虚拟进程地址空间
想象一下,你是一个城市的市长,负责管理成千上万的居民(进程)。
-
物理内存就像是城市的实际土地。土地是有限的、杂乱无章的,不同的人可能住在不同的角落。
-
直接使用物理内存就相当于让每个居民直接用经纬度坐标来定位自己的家。这非常危险且低效:一个人可能不小心把东西扔到了别人的院子里(内存越界),或者两个居民声称拥有同一块地(冲突)。管理起来是一场噩梦。
产生两个问题:
1.内存越界(“居民不小心把东西扔到别人院子里”)
2.内存冲突与管理混乱(“两个居民声称拥有同一块地”)
为了解决这个问题,你决定为每一个居民发放一张完全一样的、标准的城市地图。
-
虚拟地址空间就是这张标准的私人地图。这张地图上的地址不是真实的经纬度,而是像“人民路1号”、“中央公园”这样的人为规定的地址。
-
每个居民都相信自己是这座城市唯一的存在,认为自己独享整张地图。他们所有的指令都是基于这张地图:“去中央公园散步”、“把货物运到人民路1号”。他们完全不知道其他居民的存在。
怎么理解这张所谓的私人地图("虚拟地址空间")
“私人地址(虚拟地址空间)” 是操作系统给每个进程分配的 **“逻辑上连续的 4G 地址池”**:它不是 “存变量地址的表”,而是 “给进程的代码、变量分配虚拟地址的‘规则范围’”;并且所有进程都遵循同一个 “地址功能分区规则”(比如用户区放自己的代码变量,内核区放系统代码),这样进程不用管物理内存,只要按 “自己的虚拟地址” 做事,系统会偷偷把虚拟地址转成物理地址,最终实现进程互不干扰。
那么,如何将地图地址和真实地点对应起来呢?你建立了一个强大的城市规划局(操作系统)和地址翻译局(MMU - 内存管理单元)。
要搞懂两个关键谁来翻译(角色分工) 和 怎么翻译(翻译手册的作用)
城市场景角色 | 计算机技术组件 | 核心职责 |
---|---|---|
城市规划局 | 操作系统(内存管理器) | 给每个进程创建、维护 “翻译手册”(页表),决定 “虚拟地址对应哪个物理地址” |
地址翻译局 | MMU(内存管理单元) | 硬件层面的 “翻译员”,收到进程的虚拟地址后,查 “翻译手册”,输出物理地址 |
居民的 “绝密翻译手册” | 页表(Page Table) | 每个进程独有的 “虚拟地址→物理地址” 映射表,记录着两者的对应关系 |
居民说 “去人民路 1 号” | 进程访问 “虚拟地址” | 进程执行指令时(如读变量、写数据),给出的地址是虚拟地址 |
真实世界的经纬度 [X,Y] | 物理地址 | 最终指向物理内存(RAM)硬件的真实存储单元,只有这个地址能真正访问内存 |
“翻译手册(页表)” 是什么?为什么是 “绝密 + 专用”
“绝密”—— 防止进程作弊,保证隔离
“专用”—— 实现 “同虚拟地址,不同物理地址”
完整翻译流程:从 “居民说地址” 到 “找到真实地点”(对应计算机执行过程)
步骤 | 城市场景(居民找地址) | 计算机技术流程(进程访问内存) |
---|---|---|
1 | 居民 A 对翻译局说:“我要去人民路 1 号” | 进程 A 执行指令(如 int x = *p; ,其中 p 指向虚拟地址 0x00401000 ),向 CPU 发送 “访问虚拟地址 0x00401000 ” 的请求 |
2 | 翻译局收到请求,拿出居民 A 的 “专用翻译手册” | CPU 把虚拟地址传给硬件组件 MMU,MMU 自动找到 “进程 A 对应的页表”(操作系统已提前把页表地址告诉 MMU) |
3 | 翻译局查手册:“居民 A 的人民路 1 号 → 经纬度 [X,Y]” | MMU 查询页表,找到 “虚拟地址 0x00401000 对应的物理地址 0x12345000 ” |
4 | 翻译局带居民 A 去 [X,Y] 的真实地点 | MMU 把物理地址 0x12345000 传给物理内存控制器,内存控制器从该物理地址读取数据,返回给进程 A |
5 | 居民 A 以为自己去的是 “唯一的人民路 1 号”,不知道其他居民的存在 | 进程 A 只感知到 “自己访问了虚拟地址 0x00401000 ”,完全不知道物理地址是啥,也不知道进程 B 的存在 |
虚拟内存图
+------------------------------------+ 0xFFFFFFFF (4GB)
| 内核空间 (Kernel) |
| - 操作系统内核代码和数据 |
| - 用户进程无法直接访问 |
+-----------------------