|
| 1 | +# 一切的源头——如何开始本项目的学习呢? |
| 2 | + |
| 3 | +我本来打算直接画一个Roadmap的,但是我收到反馈——就算画Roadmap,那些不太了解嵌入式Linux的朋友仍然会一头雾水。笔者决定,必须要把常见的问题汇总一下,整理成路线清单。聊聊我们的嵌入式Linux开发到底是什么。只有这样,学习这个项目,您才知道我们到底从那个地方开始! |
| 4 | + |
| 5 | +我需要首先说明的是——**嵌入式Linux**并不容易。绝大多数情况下,我们倾向于在**Linux开发环境**(专业的说,应当叫Linux的各个发行版,比如说UBuntu, Debian, 或者是笔者更为中意的Arch Linux等)开发嵌入式开发板的功能。 |
| 6 | + |
| 7 | +嵌入式Linux从最本质上讲,同嵌入式单片机开发有部分区别,但是又没有区别。没有区别的地方在哪里呢? |
| 8 | + |
| 9 | +1. 从驱动开发的角度上聊,都是面向硬件,在了解硬件基础特性后,使用软件代码——比如说,我们会使用C语言开发驱动(这是因为Linux提供的接口抽象只有C,近年来才慢慢有Rust)。从宏观的角度上讲,单片机的最本质的东西没有变:使用C来控制您的硬件。 |
| 10 | + |
| 11 | +2. 从应用开发的角度上聊,还是面向业务。你把手头的需求搞清楚了,比匆忙行动更加重要。这是软件工程的要义。只不过用的是C++,或者是Rust,或者是其他您手头需要的业务语言,使用特定的框架,就像您开发OLED程序可能会去用u8g2库,驱动MPU6050用其他人写的封装好的库,等等! |
| 12 | + |
| 13 | +抓住这些根,其实区别只有技术路线不同而已。这就是我们下面聊的,不一样的地方。 |
| 14 | + |
| 15 | +## 嵌入式Linux到底是个什么东西? |
| 16 | + |
| 17 | +很多人第一次接触"**嵌入式Linux**"这个词的时候,脑子里是一团浆糊。你可能玩过Arduino,玩过STM32,觉得"**嵌入式**"就是那点东西:初始化时钟、配置GPIO、写几个中断、然后在while(1)里跑你的业务逻辑。这没错,这是经典**单片机开发**。再复杂一些,也就是上个**RTOS**,让简单的Schedular(跟Linux的比真的简单到爆炸了)托管您的多任务切换细节。 |
| 18 | + |
| 19 | +但**嵌入式Linux**不一样。嵌入式Linux = 完整的**Linux操作系统** + 跑在特定硬件平台上的应用系统。它不是一个简单的"超级单片机",它就是一台真正的计算机,对!你对一个复杂度就是你的电脑差不多的东西,对他做软硬件编程! |
| 20 | + |
| 21 | +你平时在电脑上用的Ubuntu、Debian、Arch Linux,本质上和嵌入式Linux跑的东西是一样的。只不过你的电脑用的是**x86-64**处理器,而嵌入式设备用的是**ARM**、**RISC-V**这类处理器。 |
| 22 | + |
| 23 | +这下麻烦了。经典的单片机开发中,我们几乎没有硬件和软件的隔离。 |
| 24 | + |
| 25 | +没有隔离的时候,我们叫**裸机开发**。以ARM芯片为例子(RISC-V笔者只有在学习玩具操作系统XV6的时候和跟人交流CPU架构,和短暂的学习计算机架构的时候短暂的接触过,所以不下结论),您的所有逻辑,都是被前后台架构托管。任务同步的串行执行,基于状态机的跳转任务流,顶多中断打断,修改状态。可以说这样的架构本身的复杂度可以说是入门级别的。 |
| 26 | + |
| 27 | +即便有RTOS隔离,也只是被RTOS很轻的一层抽象层抽象了一下,我们还是离硬件很近。我们的代码对于单片机而言是一视同仁的。有时候眼睛一闭上,您可能都能想得到现在您的ARM芯片上跑的什么指令。 |
| 28 | + |
| 29 | +到**嵌入式Linux**变了。CPU的性能突然飙到了几百甚至1GMhz,主板硬件的内存芯片达到了几百甚至几个G的大小,片上的硬件的复杂度远远超出了您熟悉的开发板。这就要求我们使用更加复杂的操作系统控制——你知道的,就是**Linux**。 |
| 30 | + |
| 31 | +随着 **MMU** 与**虚拟内存**机制的引入,现代的操作系统根本区别于您熟悉的RTOS,它基于**页表**完成虚拟地址到物理地址的映射,并实现进程间地址空间隔离。内存管理也不再停留于单片机或轻量 RTOS 中常见的简单堆分配器,而是发展出了以 **Buddy System** 为核心的页级内存管理,并结合 **Slab/SLUB** 等对象缓存分配器提升内核分配效率与缓存局部性。 |
| 32 | + |
| 33 | +任务调度方面,系统也从裸机循环或简单优先级抢占模型,逐渐演化为 Linux 中基于 **CFS**(Completely Fair Scheduler)的通用公平调度体系,并同时保留 **SCHED_FIFO**、**SCHED_RR** 等实时调度能力,以兼顾吞吐、公平性与实时响应需求。 |
| 34 | + |
| 35 | +简单文件系统,我相信一些朋友可能玩过,对于嵌入式Linux而言,只有FAT32已经无法满足现代系统需求,**EXT4**、**Btrfs** 等现代文件系统开始提供日志恢复、权限管理、大容量支持、延迟分配以及 Copy-on-Write 等能力。与此同时,网络设备与 **TCP/IP 协议栈**的引入,也使操作系统逐渐具备完整的网络通信能力。 |
| 36 | + |
| 37 | +更重要的是,现代操作系统通过**用户态与内核态隔离**建立了严格的权限边界:应用程序无法直接访问硬件资源,而必须通过**系统调用**由内核统一完成设备管理、资源调度与安全控制。这种分层架构也使驱动、进程管理、文件系统与网络协议栈逐渐形成了高度模块化的系统设计。而在裸机或轻量 RTOS 的单片机环境中,这些东西在裸机单片机里要么没有,要么是你自己造轮子造出来的简陋版本,因此系统复杂度、抽象层级以及工程规模都与现代通用操作系统存在本质差异。 |
| 38 | + |
| 39 | +当然,这些只是景观。您要是不跟我一样喜欢看Linux内核,那无所谓,知道就得了,实际上你极少改这里的代码。 |
| 40 | + |
| 41 | +所以嵌入式Linux我注意到一些朋友跟我吐槽说不自在,其实很简单。你**是在一个已有的、成熟的操作系统框架下,去适配你的硬件,然后开发你的应用。这和单片机开发那种"一切从零开始"的思路完全不同。** |
| 42 | + |
| 43 | +## 和单片机开发到底有啥区别? |
| 44 | + |
| 45 | +这个问题我当年也纠结过好久。后来踩了一堆坑之后,我总结出这么几个关键区别: |
| 46 | + |
| 47 | +### 1. 启动链条的差异 |
| 48 | + |
| 49 | +**单片机**: |
| 50 | +```text |
| 51 | +上电 → 跳到main() → 说啥了干活呗 |
| 52 | +``` |
| 53 | + |
| 54 | +**嵌入式Linux**: |
| 55 | +```text |
| 56 | +上电 → 厂商小私货的ROM代码 → Bootloader(U-Boot) → Linux内核 → RootFS挂载 → Init进程 → 你的应用 |
| 57 | +``` |
| 58 | + |
| 59 | +看到区别了吗?单片机的启动过程简单粗暴,上电之后你的代码就开始跑了。但嵌入式Linux要经过一个完整的**启动链条**。每一个环节都可能出问题,每一个环节你都得懂。 |
| 60 | + |
| 61 | +**U-Boot**配置错了?不好意思起不来。可能串口都没消息! |
| 62 | +**内核设备树**写错了?送你一个**Kernel Panic**,再见! |
| 63 | +**Rootfs**挂载失败?还是Kernel Panic,那个挂载错误我都快背出来了。。。见太多次了! |
| 64 | + |
| 65 | +这就是为什么这个项目里有这么多关于**U-Boot**、**内核**、**Rootfs**的教程——这些东西是绕不过的坎。 |
| 66 | + |
| 67 | +### 2. 开发模式的差异 |
| 68 | + |
| 69 | +单片机开发模式下,一个工程里驱动和应用都在一个main.c里,直接操作寄存器,全局变量随便用,中断里想干啥干啥。撑死了再封装一下下,真的就是一下下了,我是没见过比丢一个函数指针和参数就能起飞一个"App"抽象更简单的应用派发代码了。 |
| 70 | + |
| 71 | +但嵌入式Linux不一样,**驱动和应用完全分离**,一个在**内核空间**,一个在**用户空间**。你不能直接操作硬件,得通过**系统调用**。内核编程有一堆限制——不能用任何传统的C标准库,不能同步的无限循环(小心被骂死哦),要考虑**并发安全**。应用开发和在PC上写程序差不多,这个分离是很多从单片机转过来的朋友最不适应的,我的私信全是问这个的,哈哈! |
| 72 | + |
| 73 | +你不能在应用里直接 `GPIO->ODR = 0x01` 这么写。你得写个驱动,然后应用通过 `/dev/xxx` **设备文件**去和驱动交互。 |
| 74 | +听起来很麻烦?确实麻烦。但这种麻烦带来的是安全性、稳定性和可维护性。 |
| 75 | + |
| 76 | +### 3. 调试方式的差异 |
| 77 | + |
| 78 | +单片机这块**J-Link/SWD**一连,单点调试走起,想看东西了?printf随便打,想在哪打在哪打!看寄存器、看内存、看变量,一切尽在掌握。但嵌入式Linux不太一样,驱动调试主要靠 `pr_*` 和 **`dmesg`**,当然也能JTag或者kgdb,但是笔者试过,麻烦程度有点小爆炸。应用调试可以用**gdb**,但跨平台调试有点折腾。你想看内存?不好意思,你只能拿到**虚拟内存地址**,背后真正的物理地址早就被**MMU**硬件和页表管理子系统托管的看不到了。日志、**/proc**、**sysfs**,信息分散在各处,很多时候你得靠"推理"和"经验"去定位问题。很多朋友第一次在内核里 `pr_*` 打了一堆日志,然后发现不知道怎么看,心态当场崩了。 |
| 79 | + |
| 80 | +### 4. 工具链的差异 |
| 81 | + |
| 82 | +单片机这块其实挺省心的,厂商IDE一键搞定,**Keil**、**IAR**、**CubeIDE**什么的,图形界面点一点就完事了。但嵌入式Linux不一样,**交叉编译工具链**得自己装,**Makefile**和**Kconfig**得自己写,很多时候你要和命令行打交道。这也是为什么我们的教程从**工具链**开始——这是绕不过的第一步,你连编译器都没有,后面的一切都无从谈起。 |
| 83 | + |
| 84 | +## 这个项目的层级结构 |
| 85 | + |
| 86 | +好,说完了嵌入式Linux是什么,以及它和单片机的区别,我们来聊聊这个项目的结构。我尽量按照"从底层到上层"的逻辑来组织这个项目,你把它想象成一栋楼,得先打地基,然后一层一层往上盖。 |
| 87 | + |
| 88 | +最底层是**工具链**,这是地基。没有工具链,你啥也干不了,**U-Boot**编不了、**内核**编不了、应用编不了。我们的教程从这里开始,你去看看[工具链安装](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/start/01_start_from_toolchain)就知道了,这一步绕不过。 |
| 89 | + |
| 90 | +再往上是**U-Boot**,你可以把它理解为嵌入式Linux的"第一个程序"。上电之后它先跑起来,负责初始化硬件——内存、串口、网络、存储这些,然后把**Linux内核**加载进来,最后把控制权交出去。这块的内容在[U-Boot教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/uboot/)里,你想了解**Bootloader**的话可以看看。 |
| 91 | + |
| 92 | +然后是**Linux内核**,这是整个系统的核心。进程调度、内存管理、文件系统、设备驱动框架、网络协议栈,全是它在管。内核这块水挺深的,我们准备了[内核教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/kernel/),从编译配置到**设备树**,一步步带你走。 |
| 93 | + |
| 94 | +内核启动后需要挂载一个**根文件系统**,这里面有**init进程**(第一个用户进程)、/bin和/sbin这些目录下的工具、配置文件、库文件等等。**RootFS**这块的内容在[Rootfs教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/rootfs/)里,**BusyBox**怎么编、**inittab**怎么写、**NFS**怎么挂,都有。 |
| 95 | + |
| 96 | +再往上是**驱动开发**,这是连接硬件和应用的桥梁。驱动负责硬件初始化、寄存器操作、向上提供统一的接口、处理中断。这块是重头戏,[驱动教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/driver/)从**字符设备**到并发机制,从硬件访问到设备树,内容相当扎实。 |
| 97 | + |
| 98 | +最顶层就是你的**应用**了,通过设备文件访问驱动,调用各种系统库,做你想做的事情。应用开发和在PC上写程序差不多,只是编译时要用**交叉编译工具链**。 |
| 99 | + |
| 100 | +## 如何开始学习? |
| 101 | + |
| 102 | +这个问题其实没有标准答案,取决于你的背景。我给几个不同的路线,你可以对号入座。 |
| 103 | + |
| 104 | +如果你是完全的新手,建议按顺序来。先搞懂**工具链**,这一步绕不过,必须先搞定。然后了解一下**U-Boot**,不需要深入,知道它是干嘛的就行。接着了解一下**内核**,同样概念层面理解即可。搞一下**RootFS**,知道根文件系统是啥、里面有什么。最后开始**驱动入门**,这里是真正开始写代码的地方。这个路径比较稳,虽然慢一点,但基础打得牢。 |
| 105 | + |
| 106 | +如果你有单片机经验,你可以跳过一些基础概念,重点关注那些不一样的点。**工具链安装**还是得看,和单片机不一样。然后重点看**内核空间基础**,这个概念在单片机里没有,但是很重要。**设备树**也得学,单片机里硬件配置在代码里,Linux里在设备树里。之后就可以直接从[字符设备驱动教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/driver/00_chardev_base/)开始了。 |
| 107 | + |
| 108 | +如果你想快速上手,想尽快看到效果,可以走"实战路线"。不喜欢折腾工具链安装的,先把**Docker环境**搭起来,看[QUICK_START.md](https://awesome-embedded-learning-studio.github.io/imx-forge/QUICK_START)就行。然后直接运行 `./scripts/release-all.sh` 构建整套系统,把镜像烧到板子上,先跑起来再说。跑起来之后,你再回头慢慢研究各个模块,这样心里有底。 |
| 109 | + |
| 110 | +## 学习路线图(时间线) |
| 111 | + |
| 112 | +最后我给一个大致的时间线参考,当然每个人情况不同,这只是我的估算。如果你想完整走一遍,大概需要**3-4周**的时间。**Docker环境**搭建和**工具链安装**一天就够了(当然可以不Docker, 你是**WSL**好还是虚拟机好,都可以,一样的。),**U-Boot基础**两三天,**Linux内核基础**三五天,**RootFS构建**两三天,**驱动开发基础**得一两周,最后是**系统整合与调试**,这块是持续的工作。如果你想快速上手,**一周**时间足够让你跑起来一个基本系统。 |
| 113 | + |
| 114 | +## 最后的一些唠叨 |
| 115 | + |
| 116 | +说实话,**嵌入式Linux**的学习曲线确实比单片机要陡峭。但这并不是因为"更难",而是因为"东西更多"。单片机开发你只需要关注你的板子和你的代码,嵌入式Linux开发你需要关注**工具链**、**U-Boot**、**内核**、**设备树**、**RootFS**、**驱动**、**应用**,每一个环节都是一个不小的topic。但好消息是,一旦你掌握了这个完整的链条,你就拥有了从零构建一个嵌入式Linux系统的能力。这意味着你不受厂商SDK的束缚,想用哪个版本的内核就用哪个版本,想怎么定制系统就怎么定制系统,真正理解"底层"是什么。这种能力的价值,我觉得远大于学习过程中的那些麻烦。实际上,这也是我开始这个项目的原因—— **用 🔥 和无数串口终端堆出来的工程。希望我们可以更方便地自定义自己的 i.MX6ULL 系统。** |
| 117 | + |
| 118 | +## 接下来去哪里? |
| 119 | + |
| 120 | +如果你已经看到这里,说明你是真的想学。那么下一步, |
| 121 | + |
| 122 | +完全新手去[工具链安装](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/start/01_start_from_toolchain) |
| 123 | + |
| 124 | +想先看效果的去看[QUICK_START.md](https://awesome-embedded-learning-studio.github.io/imx-forge/QUICK_START), |
| 125 | + |
| 126 | +想直接看驱动的去[字符设备驱动教程](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/driver/00_chardev_base/01_introduction), |
| 127 | + |
| 128 | +想了解U-Boot的去[什么是U-Boot](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/uboot/01_what_is_uboot), |
| 129 | + |
| 130 | +想了解内核的去[内核概述](https://awesome-embedded-learning-studio.github.io/imx-forge/tutorial/kernel/01_kernel_overview)。 |
| 131 | + |
| 132 | +祝你在嵌入式Linux的世界里玩得开心!如果有问题随时去[GitHub Issues](https://github.com/Awesome-Embedded-Learning-Studio/imx-forge/issues)找我们!! |
0 commit comments