当前位置: 首页 > news >正文

裸机嵌入式 (STM32 等)和操作系统程序 (Linux 等)程序启动对比


🚀 代码段的存放位置(.text 段)

🌟 在嵌入式裸机程序(如 STM32)

💡 代码段 .text 放在 Flash(ROM、程序存储器)

  • 因为嵌入式芯片 RAM 很小,不足以装整个程序代码。
  • Flash(或 ROM)是非易失性存储,程序烧进去就能掉电保留。
  • MCU 在上电时直接从 Flash 取指令执行。

👉 内存布局(常见 STM32):

Flash(代码区)
+---------------------+
| 矢量表 + 启动代码     |
| .text (代码)         |
| .rodata (常量)       |
| .data 初始镜像       |
+---------------------+RAM(数据区)
+---------------------+ <- SP 初始值 (栈顶)
| 栈                   |
| 堆                   |
| .bss (清零变量)      |
| .data (初始化变量)   |
+---------------------+

✅ .text 始终在 Flash,MCU 从 Flash 执行代码,不需要先搬到 RAM


🌟 在一般 GCC + 操作系统程序(如 Linux)

💡 代码段 .text 放在磁盘的可执行文件中,加载时由操作系统映射到 RAM(内存)

  • 可执行文件(如 ELF)放在磁盘。
  • 操作系统 loader(加载器)读取文件,把 .text 映射进虚拟内存(通常是物理 RAM)。
  • CPU 从 RAM(内存)取指执行(即便你看到映射自文件,本质还是在内存中执行)。

👉 内存布局(Linux 程序):

磁盘
+---------------------+
| 可执行文件 (ELF)     |
| 包含 .text/.data/... |
+---------------------+运行时内存(RAM)
+---------------------+
| .text 映射到 RAM     |
| .rodata 映射到 RAM   |
| .data 初始化到 RAM   |
| .bss 已清零 RAM 区   |
| 堆                   |
| 栈                   |
+---------------------+

代码段 .text 在运行时实际在 RAM(内存)中执行。
✅ 操作系统支持虚拟内存:可以映射、保护、共享代码段等。


🌈 两者最大区别

特性裸机嵌入式 (STM32 等)操作系统程序 (Linux 等)
代码 (.text) 存放位置存在 Flash / ROM存在磁盘文件,加载进 RAM
执行来源MCU 从 Flash 取指CPU 从内存取指(可能映射文件)
是否需要搬移代码到 RAM 才能运行❌ 不需要✅ loader 搬进内存执行
内存资源非常有限 (Flash + 小RAM)大RAM + 磁盘 + 虚拟内存
启动方式启动代码直接跑在硬件上OS loader 加载可执行文件

💡 特殊情况:嵌入式中代码段放 RAM 的场景

你也许会想到:
👉 有时嵌入式程序确实会把 .text 搬到 RAM:

  • 为了跑在高速 SRAM 里,加快速度(例如某些 STM32 内部高速 RAM)。
  • 某些代码需要在写 Flash 时运行(写 Flash 时不能同时取指)。
  • Bootloader / 特殊场景需要动态重映射。

💡 这种情况下链接脚本会明确把 .text 放 RAM,启动代码负责拷贝。

例如:

.text : 
{*(.text*)
} > RAM AT > FLASH

👉 意思是:运行时地址在 RAM,但初始值在 Flash,启动代码负责搬。


结论

✔ 操作系统程序:代码最终会映射到内存(RAM),CPU 从 RAM 取指。
✔ 裸机嵌入式程序:代码直接存在 Flash,CPU 从 Flash 执行。
✔ 区别本质:有无 OS loader + 存储介质不同 + 内存资源不同。


u-boot 和 STM32 裸机启动的相似点:

特点STM32 裸机启动ARM 的 u-boot
是否有操作系统没有没有
代码存放位置代码直接存放在 Flash 中代码也通常存放在 Flash 或 SPI/NAND 闪存中
启动方式MCU 上电后从 Flash 直接执行CPU 复位后从 Flash 或 ROM 执行启动代码
内存初始化启动文件负责复制 .data,清零 .bss启动代码负责初始化内存和拷贝数据段
堆栈设置启动代码设置栈指针启动代码设置堆栈指针
加载程序直接运行u-boot 负责加载 Linux 内核或其他程序到 RAM 并启动

具体说:

  • u-boot 是一个裸机程序(bootloader),它没有操作系统,也就是说它需要自己完成内存初始化、堆栈设置、Flash 读取等工作。
  • u-boot 代码存放在非易失存储(Flash 等),启动时 CPU 直接从 Flash 取指。
  • 它会完成类似 STM32 启动代码的工作,比如初始化 RAM,拷贝必要的数据,配置硬件,最终加载 Linux 内核或其他程序到 RAM 运行。

所以,u-boot 本质上和 STM32 裸机程序的启动流程很类似,都是“无操作系统,启动代码负责初始化内存和执行环境”,区别是 u-boot 体积更大、功能更复杂,还要支持设备驱动、文件系统、网络等功能。


U-Boot 是否使用 MMU?

1. 什么是 MMU?

  • MMU(内存管理单元)是硬件组件,负责虚拟地址和物理地址转换,支持内存保护和分页。
  • Linux 等现代操作系统依赖 MMU 来实现虚拟内存管理。
  • 对于裸机程序和启动加载器来说,是否开启 MMU 是设计的一个选项。

2. U-Boot 默认行为

  • 在大多数平台上,U-Boot 启动时默认是关闭 MMU 的。
  • U-Boot 是一个启动加载器(Bootloader),它的任务是完成硬件初始化,加载操作系统镜像到内存,然后跳转执行操作系统。
  • 关闭 MMU 可以简化启动代码,减少初始化复杂度。

3. U-Boot 是否可以开启 MMU?

  • 是的,U-Boot 支持在启动阶段根据需要开启 MMU。
  • 对于一些需要 MMU 功能的高级平台或特定功能,U-Boot 可以配置并启用 MMU。
  • 启用 MMU 后,U-Boot 可以使用虚拟地址进行内存访问,支持更复杂的内存管理。

4. 具体情况举例

  • ARM Cortex-A 系列(带 MMU)

    • U-Boot 通常会配置页表,并在加载操作系统之前开启 MMU。
    • 这样可以为 Linux 或其他操作系统提供正确的内存映射环境。
  • ARM Cortex-M 系列(无 MMU)

    • U-Boot 不存在,因为 Cortex-M 用于裸机程序,启动代码简单,没有 MMU。

5. 总结

方面说明
默认状态U-Boot 启动时通常关闭 MMU
MMU 使用可根据平台需求配置并开启 MMU
作用为操作系统启动做好虚拟内存准备
Cortex-M 设备无 MMU,U-Boot 不适用

通常 U-Boot 在搬运(加载)Linux内核到内存时是关闭MMU的,原因和流程大致如下:


1. U-Boot启动初期 — MMU一般是关闭的

  • CPU上电复位后,MMU默认关闭,U-Boot作为第一个启动阶段的引导程序,也通常保持MMU关闭。
  • 关闭MMU时,CPU使用的是物理地址访问内存,U-Boot可以直接通过物理地址读写Flash和RAM。

2. U-Boot搬运内核(Copy Kernel)过程

  • U-Boot从Flash(或者其他存储介质)读取Linux内核镜像(二进制文件)到指定的物理内存地址。
  • 这一步是物理地址访问,MMU关闭时,U-Boot直接用物理地址操作内存,没有地址转换。
  • 这样避免了复杂的页表配置,代码简单、执行快速。

3. U-Boot开启MMU时机

  • 在完成内核加载、设备初始化等必要操作后,U-Boot才可能配置并开启MMU。
  • 开启MMU主要是为了给操作系统(比如Linux)准备好虚拟内存环境。
  • Linux内核入口通常假定MMU已经启用,或者启动代码会在早期初始化MMU。

4. 具体流程示意

CPU复位 -> MMU关闭 -> U-Boot运行↓
U-Boot关闭MMU状态下从Flash加载内核到RAM↓
U-Boot(根据平台需要)配置并开启MMU↓
跳转内核入口,内核启动(此时MMU已开启或由内核早期启动代码开启)

5. 额外说明

  • 某些特殊平台或配置下,U-Boot可能会提前开启MMU(比如需要MMU才能访问某些内存或设备),但这不是常见做法。
  • 裸机启动(无操作系统)时,MMU通常一直关闭。

Linux内核是怎么识别和管理自己可用的RAM空间,也就是区分“内核占用”与“空闲RAM”的


Linux内核识别和管理可用RAM的原理

1. 启动时内核获取物理内存信息

  • 当Linux内核启动时,启动引导程序(如U-Boot)会传递物理内存布局给内核。

  • 这通常通过如下方式传递:

    • ATAGs(旧平台)
    • Device Tree Blob (DTB)(现代ARM平台)
    • 或者通过固件接口(UEFI)等

这些结构告诉内核哪些物理地址范围是可用的RAM,哪些区域是保留的(比如MMIO,外设寄存器地址等)。


2. 内核使用内存管理子系统

  • 内核启动时,会根据启动参数和传入的内存地图(memory map)调用 memblock 子系统(早期阶段)进行物理内存管理。
  • 然后初始化 伙伴系统(buddy allocator),它管理物理页的分配和释放。

3. 内核自己代码和数据占用的内存

  • 内核本身的代码、只读数据(.text、.rodata)、已初始化数据(.data)和未初始化数据(.bss)都会被映射到物理内存的特定区域。
  • 这些内存区域在内核启动阶段就被占用,不可用于其他用途。

4. 剩余内存被标记为空闲内存

  • 除去内核自身占用的物理内存区块,剩下的内存都被内核标记为“空闲”,供内核后续动态分配(内核堆、页缓存、用户空间进程等)。
  • 这个过程在内核启动早期完成。

5. 简化流程图

引导程序传递内存地图 ---> 内核初始化memblock系统 ---> 标记内核占用区域---> 伙伴系统初始化(空闲内存管理)---> 其余内存区块标记为空闲

6. 内核如何避免占用冲突?

  • 内核链接脚本中定义了内核镜像在物理内存中的位置(起始地址和大小)。
  • 启动代码中,符号(如_start, _end)告诉内核自己占用内存范围。
  • 内核memblock系统会把这块内存标记为“保留”,不分配给其它用途。

7. 举例

假设系统RAM是0x80000000 ~ 0x80800000(8MB),

  • 内核代码占用0x80000000 ~ 0x80040000 (256KB)
  • 其余的7.75MB标记为空闲

总结

步骤说明
内存地图传递U-Boot等引导程序传递物理内存可用范围
内核占用内存范围由链接脚本和启动代码符号确定内核占用内存区块
空闲内存管理memblock 和 buddy allocator 管理剩余可用内存

🌟 大致流程

1️⃣ U-Boot 提供可用 RAM 信息给内核

  • U-Boot 在启动 Linux 时会传递 物理内存信息(RAM 的起始地址、大小)。

  • 具体形式:

    • Device Tree (DTB) 文件:现代 ARM 系统通用。
    • ATAGs:老式 ARM 系统。
  • 内核启动代码解析这些结构,知道哪些物理地址范围是 RAM,哪些是保留或外设地址。


2️⃣ 内核初始化物理内存管理

  • 内核早期阶段(还没开启 MMU 时),使用 memblock 子系统 管理物理内存范围。

  • memblock 记录:

    • 哪些物理内存区块可用。
    • 哪些区块已被内核自己占用(内核代码、BSS、数据段等)。
    • 哪些区块是保留或不可用的。
  • 内核自己的内存区块会标记为 保留,防止后续分配覆盖它。


3️⃣ 内核设置 MMU 和虚拟地址空间

  • 内核会建立页表,把物理内存映射到内核虚拟地址空间(比如 ARM 上虚拟地址从 0xC0000000 开始)。
  • 这时候 MMU 开启,内核代码从物理地址跳到虚拟地址执行。

4️⃣ 内核管理 ELF 程序(用户态进程)

  • 当 Linux 内核加载用户程序(比如 ELF 文件)时:

    • 内核会在虚拟内存空间(由MMU提供)为进程分配地址空间。
    • 页表把用户进程的虚拟地址映射到实际的物理内存(也就是 memblock 和伙伴系统管理的那片空闲RAM)。
    • 内核负责加载 ELF 各段到合适的物理内存,并建立虚拟到物理的映射。

🌈 简化流程图

U-Boot 提供内存地图(DTB / ATAGs)↓
内核 memblock 记录物理内存分布(哪些可用,哪些保留)↓
内核构建页表,启动 MMU,映射内核虚拟地址↓
内核加载用户 ELF,给进程分配虚拟内存,并映射到空闲物理页

http://www.lqws.cn/news/493561.html

相关文章:

  • 前端依赖升级完全指南:npm、pnpm、yarn 实践总结
  • Android 编译和打包image镜像流程
  • 小程序 顶部栏标题栏 下拉滚动 渐显白色背景
  • 华为HN8145V光猫改华为蓝色公版界面,三网通用,xgpon公版光猫
  • 多智能体协同的力量:赋能AI安全报告系统的智能设计之道
  • 创客匠人洞察:2025 创始人 IP 打造六大趋势与知识变现新路径​
  • 【入门级-基础知识与编程环境:3、计算机网络与Internet的基本概念】
  • Flutter ListTile 徽章宽度自适应的真正原因与最佳实践
  • 开启游戏新时代:神经网络渲染技术实现重大跨越
  • HarmonyOS 5 双向滚动课程表:技术实现与交互设计解析(附:源代码)
  • 谷歌地图的3d街景使用的是什么数据格式?
  • Java 程序设计试题​
  • 常见JavaScript 代理模式应用场景解析
  • 6.23_JAVA_RabbitMQ
  • 2025年中科院三区全新算法,恒星振荡优化器:受自然启发的元启发式优化,完整MATLAB代码免费获取
  • hive集群优化和治理常见的问题答案
  • 综述AI生成工具推荐:高效自动化生成学术综述
  • 网络安全之某cms的漏洞分析
  • MocapApi 中文文档 和github下载地址 NeuronDataReader(以下简称 NDR)的下一代编程接口
  • 1 Studying《Systems.Performance》7-13
  • Maven 多模块项目调试与问题排查总结
  • SpreadJS 迷你图:数据趋势可视化的利器
  • Web基础 -SpringBoot入门 -HTTP-分层解耦 -三层架构
  • HTML语义化标签
  • 最近小峰一直在忙国际化项目,确实有点分身乏术... [特殊字符] 不过! 我正紧锣密鼓准备一系列干货文章/深度解析
  • [HTML]iframe显示pdf,隐藏左侧分页
  • Python异步爬虫编程技巧:从入门到高级实战指南
  • 从本地到云端:通过ToolJet和cpolar构建远程开发环境实践过程
  • ​​FFmpeg命令全解析:三步完成视频合并、精准裁剪​​、英伟达显卡加速
  • systemd[1]: Failed to start LSB: Bring up/down networking