裸机是什么意思?从BIOS点亮屏幕到安卓原生系统刷入,从底层硬件到无OS/无预装软件的全场景解析

当你拿到一台全新出厂的未激活笔记本、刚从华强北淘到的“主板+CPU+内存条+电源”四件套、或者在开发者社区看到的“RISC-V裸机开发入门教程”时,大概率会频繁接触到“裸机”这个词,很多人对它的第一印象停留在“没有操作系统的电脑/手机”,但实际上,“裸机”的定义在消费者端、计算机硬件维修/DIY端、嵌入式/底层系统开发端有着截然不同的内涵边界;它既可以是一台无法直接运行QQ的硬件空壳,也可以是开发者手中可以精准控制每一个引脚电平的高效开发平台;甚至在智能手机领域,还存在“预装系统但无任何第三方软件的半裸机(纯净版系统设备)”和“彻底没有系统引导分区的全裸机”之分。

本文将从裸机的三重核心定义切入,结合BIOS点亮屏幕的PC裸机初始化过程、树莓派4B裸机点亮LED灯的具体代码与操作、小米10 Ultra从官方MIUI到全裸机(清空所有分区)再到刷入LineageOS原生纯净系统的完整流程三个跨领域的计算机实例,详细拆解裸机的底层逻辑、应用场景、以及它对整个信息技术产业的底层支撑意义;最后我会结合自己在嵌入式开发和PC硬件领域的10年经验,发表对“裸机开发未来趋势”和“消费电子领域‘裸机风潮’回归必要性”的个人观点,全文预计3500字,适合计算机入门爱好者、DIY硬件玩家、嵌入式开发初学者阅读。

先搞懂裸机的三重身份:跨领域的内涵差异

在开始具体的实例解析前,我们必须先明确:“裸机”从来不是一个单一、绝对的术语,它的边界完全取决于使用者的身份和使用场景的需求——这一点很多入门教程都会忽略,导致读者在不同场景下看到“裸机”时产生认知混乱,以下是我整理的、目前信息技术产业中最主流的三重定义:

1 消费者端的“伪裸机”与“半裸机”:从营销话术到用户需求

伪裸机:品牌商常玩的营销陷阱

很多智能手机、笔记本电脑品牌商在宣传新机型时,会打出“出厂自带纯净版系统,接近裸机体验”的口号——这里的“裸机体验”其实是完全误导消费者的伪裸机概念。

所谓的“纯净版系统”,本质上只是预装了少量甚至没有第三方合作APP的官方定制系统,它依然包含完整的操作系统内核(如Windows 11 Home的NT内核、iOS 17的XNU内核)、系统底层驱动、厂商自研的应用框架(如小米的MIUI Framework、华为的HarmonyOS NEXT的分布式软总线)、甚至还有无法删除的系统内置APP(如Windows的Edge浏览器、苹果的Safari浏览器、小米的小米钱包/小米商城APP——这些APP在普通消费者端无法通过正常途径卸载,但在开发者模式或Root权限下可以操作)。

半裸机:部分用户追求的“轻量使用设备”

与伪裸机相对的是部分DIY硬件玩家、商务人士、程序员追求的半裸机——它的定义是“只安装了必要的操作系统和底层驱动,没有安装任何官方定制系统之外的第三方APP、甚至没有激活系统自带的部分非必要功能”的设备。

举个我自己的例子:我用来做嵌入式开发的工作笔记本是2022款MacBook Pro 14寸 M2 Pro,刚拿到手时我做的第一件事不是激活FaceTime、iCloud这些苹果云服务,而是进入“系统设置-通用-存储空间-管理-系统数据-开发者工具”手动关闭了Xcode之外的所有iOS/MacOS模拟器、关闭了Spotlight的“ Siri建议与搜索”(用Alfred替代)、卸载了所有自带的非开发类APP(如Safari浏览器换成Chrome、Pages换成Typora+Gitbook、Numbers换成Google Sheets、Keynote换成PPT)——这台MacBook Pro在我眼中就是一台“半裸机嵌入式开发专用机”,它的启动速度比刚出厂的满装状态快了30%左右,电池续航也提升了15%左右(这是Alfred的效率和关闭非必要后台服务带来的双重效果)。

2 计算机硬件维修/DIY端的“全裸机硬件空壳”:最原始的硬件集合

这是“裸机”最早期、最原始的定义——它指的是没有安装任何操作系统、甚至没有安装任何存储介质(如硬盘、U盘、SD卡)的硬件集合体;如果是台式PC的话,它的组成一般是“主板+CPU+CPU散热器+内存条+电源+显示器连接线”(显示器本身不算裸机的一部分,它只是输出设备);如果是嵌入式开发板的话,它的组成一般是“开发板本体(包含CPU、RAM、ROM闪存、GPIO引脚、电源接口、USB接口等)”(ROM闪存里可能有出厂自带的Bootloader,但如果清空ROM的话,那就是彻底的嵌入式全裸机)。

为什么台式PC全裸机需要这些硬件才能点亮?

很多入门DIY玩家可能会问:“我能不能只拿主板+CPU+电源就能点亮屏幕?”答案是不能——因为CPU本身没有“记忆”功能,它的启动需要从“非易失性存储介质”(如早期的BIOS ROM芯片、现在的UEFI SPI Flash芯片)中读取基本输入输出系统(BIOS) 或统一可扩展固件接口(UEFI) ,而BIOS/UEFI的运行需要依赖随机存取存储器(RAM,也就是内存条) 作为临时的工作空间——如果没有内存条,CPU就无法加载BIOS/UEFI,屏幕自然不会亮(这也是很多入门DIY玩家第一次装机时遇到“点不亮屏幕”问题的最常见原因)。

3 嵌入式/底层系统开发端的“裸机平台”:没有操作系统内核的开发环境

这是“裸机”在信息技术产业中最核心、最有技术含量的定义——它指的是开发者在没有任何操作系统内核(如Linux、FreeRTOS、RT-Thread、Zephyr)支持的情况下,直接对硬件寄存器进行操作的开发平台;在这个平台上,开发者的代码就是“唯一的软件”,它会直接在CPU的指令集上运行,没有任何中间层的干扰(这意味着代码的执行效率极高,但也意味着开发者需要自己处理所有的硬件初始化、中断响应、内存管理、外设控制等底层工作)。

举个简单的例子:有OS vs 无OS(裸机)点亮LED灯的代码复杂度差异

假设我们要在STM32F103C8T6最小系统板(这是嵌入式开发领域最常用的入门级裸机/RTOS开发板之一)上点亮一个连接在PC13引脚的LED灯:

如果用RT-Thread实时操作系统开发,只需要几行代码就能完成(因为RT-Thread已经帮我们封装好了GPIO引脚的初始化、控制等底层函数);

如果用裸机开发,则需要手动配置STM32F103C8T6的RCC时钟控制器、GPIOx端口的配置寄存器(CRH/CRL)、输出数据寄存器(ODR)等至少3个硬件寄存器,代码行数会超过30行(如果要做到“精准延时闪烁”,还需要手动配置SysTick定时器或TIMx通用定时器,代码行数会进一步增加)。

我会在本文的第三部分“裸机开发的具体实例:树莓派4B裸机点亮LED灯” 中,详细展示裸机开发的代码和操作步骤——这部分内容虽然有一定的技术门槛,但对于理解“裸机的底层逻辑”非常重要。

PC裸机的最基础操作:从BIOS点亮屏幕到进入DOS系统

在讲完裸机的三重定义后,我们先从最贴近普通用户的PC硬件维修/DIY端全裸机入手,通过一个具体的实例来理解“裸机是如何从硬件空壳变成可以输入指令的设备的”。

1 实例一:2023款联想拯救者Y9000P 2023全裸机点亮屏幕并进入DOS系统

实验背景

假设我是一名刚入职的PC硬件维修师,客户送来了一台“无法开机”的2023款联想拯救者Y9000P 2023游戏本——经过初步检测,我发现这台游戏本的SSD硬盘已经完全损坏(分区表丢失、ROM闪存颗粒报废),电池也已经完全放电;我现在需要做的第一件事是“清空所有可能的外部干扰,把它变成一台全裸机硬件空壳,然后验证主板、CPU、内存条、电源、屏幕是否正常”。

实验前的准备工作

硬件部分:

2023款联想拯救者Y9000P 2023游戏本的“核心硬件集合”:拆下来的主板(包含i9-13900HX CPU、RTX 4090 Laptop GPU、2条16GB DDR5 5600MHz内存条)、原装230W氮化镓电源适配器、原装Type-C转DP1.4a连接线、外接的27英寸4K 144Hz显示器(原装游戏本的屏幕排线我们也保留,作为备用输出)。

清空的外部干扰:完全拆下损坏的SSD硬盘、拆下原装电池、断开所有USB外设(如键盘、鼠标、U盘)。

软件部分(后续进入DOS系统需要):

一张8GB以上的空白U盘;

Rufus 4.4(这是目前最常用的U盘启动盘制作工具);

FreeDOS 1.3镜像文件(这是目前最流行的开源DOS系统,支持UEFI启动)。

实验步骤1:把核心硬件集合组装成“临时全裸机平台”并点亮屏幕

联想拯救者Y9000P 2023的主板设计比较特殊,它的电源接口、Type-C接口、DP接口都集成在一个“IO扩展板”上——我们需要先把IO扩展板用螺丝固定在主板的对应位置,然后插入原装230W氮化镓电源适配器,最后连接外接显示器。

我们需要用“金属镊子短接主板上的Power Switch(电源开关)引脚”来启动临时全裸机平台——联想拯救者Y9000P 2023的Power Switch引脚在主板靠近键盘的一侧,标注为“PWR_SW”,一般是两个相邻的金色引脚(不同批次的标注可能略有不同,建议先查官方的维修手册)。

短接Power Switch引脚后,我们会看到以下现象(如果核心硬件正常的话):

主板上的“电源指示灯”(一般是红色或绿色的小LED灯)会亮起;

CPU散热器的风扇会开始高速转动(几秒钟后会自动降速到正常转速);

外接显示器会首先显示“Lenovo LOGO”(大概持续2-3秒),然后会进入“UEFI BIOS设置界面”(因为没有安装任何存储介质,UEFI BIOS找不到可以启动的设备,所以会自动进入设置界面)。

如果能看到UEFI BIOS设置界面,就说明这台游戏本的主板、CPU、CPU散热器、内存条、电源、屏幕(或外接显示器)都是正常的——接下来我们就可以更换新的SSD硬盘,然后安装操作系统了。

实验步骤2:制作FreeDOS U盘启动盘并进入DOS系统

虽然UEFI BIOS设置界面已经可以验证核心硬件的正常性,但如果我们想要更深入地检测硬件(比如检测内存条的兼容性、检测新更换的SSD硬盘的读写速度),就需要进入一个可以输入指令的轻量级操作系统——FreeDOS就是最好的选择。

以下是制作FreeDOS U盘启动盘并进入DOS系统的具体步骤:

插入8GB以上的空白U盘到另一台正常的电脑上;

打开Rufus 4.4,在“设备”下拉菜单中选择我们插入的空白U盘;

在“引导类型选择”下拉菜单中选择“FreeDOS”;

其他设置保持默认(分区类型选择“GPT”,目标系统选择“UEFI(非CSM)”,文件系统选择“FAT32”,簇大小选择“默认”);

点击“开始”按钮,Rufus会自动格式化U盘并安装FreeDOS系统;

制作完成后,把FreeDOS U盘启动盘插入到我们的临时全裸机平台的USB接口上;

再次用金属镊子短接Power Switch引脚启动临时全裸机平台,在显示“Lenovo LOGO”的时候快速按下“F12”键(联想拯救者系列的快速启动菜单快捷键都是F12);

在快速启动菜单中选择我们的FreeDOS U盘启动盘(标注为“UEFI: [U盘品牌型号]”),然后按下回车键;

几秒钟后,我们就会进入FreeDOS系统的命令行界面,显示的提示符是“C:>”——这时候我们就可以输入FreeDOS的指令来检测硬件了,比如输入“mem”指令可以检测内存条的容量和兼容性,输入“format D:”指令可以格式化新更换的SSD硬盘(假设新更换的SSD硬盘被识别为D盘)。

嵌入式裸机开发的具体实例:树莓派4B裸机点亮LED灯

在讲完PC全裸机的基础操作后,我们再进入最有技术含量的嵌入式/底层系统开发端裸机平台,通过一个具体的实例来理解“裸机开发是如何直接对硬件寄存器进行操作的”。

1 实例二:树莓派4B裸机点亮连接在GPIO18引脚的LED灯

实验背景

树莓派4B是目前最流行的入门级单板计算机(SBC)之一——它既可以运行Linux、Android等完整的操作系统(作为“迷你PC”使用),也可以作为“裸机开发平台”使用(因为它的CPU架构是ARMv8-A,资料非常丰富)。

很多入门级嵌入式开发教程都会用“树莓派4B运行Raspbian Linux系统点亮LED灯”作为第一个实验——但那个实验只是调用了Raspbian Linux系统的GPIO库(如wiringPi、RPi.GPIO),并没有真正接触到硬件的底层逻辑;今天我们要做的是“树莓派4B裸机点亮LED灯”,直接对Broadcom BCM2711 SoC的硬件寄存器进行操作。

实验前的准备工作

硬件部分:

树莓派4B单板计算机(任何版本都可以,我用的是8GB RAM版本);

树莓派4B官方电源适配器(5V 3A USB-C);

一张8GB以上的空白Micro SD卡;

一个红色LED灯;

一个220Ω的限流电阻(LED灯的工作电流一般是10-20mA,树莓派4B的GPIO引脚输出电压是3.3V,所以需要220Ω的限流电阻来保护LED灯和GPIO引脚);

两根杜邦线(公对母);

面包板(可选,用来方便地连接LED灯、限流电阻和杜邦线)。

软件部分(需要在另一台正常的电脑上安装,我用的是MacOS Ventura 13.6.7,Windows和Linux的安装步骤类似):

ARM GCC交叉编译工具链(用来把我们写的C语言裸机代码编译成树莓派4B可以识别的ARMv8-A机器码);

GNU Make(用来自动化编译过程);

balenaEtcher(用来把我们编译好的裸机镜像文件烧录到Micro SD卡上);

一个文本编辑器(比如Visual Studio Code、Sublime Text,我用的是Visual Studio Code)。

实验步骤1:硬件连接

我们需要把LED灯、限流电阻和树莓派4B的GPIO引脚连接起来——树莓派4B的GPIO引脚编号有两种:物理引脚编号(从左上角开始数,1、2、3...40)和BCM编号(Broadcom公司给BCM2711 SoC的GPIO引脚定义的编号);我们在裸机开发中必须使用BCM编号,因为硬件寄存器的操作是基于BCM编号的。

以下是具体的硬件连接方式:

把红色LED灯的长脚(阳极) 连接到220Ω限流电阻的一端;

把220Ω限流电阻的另一端连接到树莓派4B的BCM18引脚(物理引脚编号是12);

把红色LED灯的短脚(阴极) 连接到树莓派4B的GND引脚(物理引脚编号是14)。

硬件连接完成后,我们可以先检查一下连接是否正确——如果有条件的话,可以用万用表测量BCM18引脚和GND引脚之间的电压(树莓派4B未启动时,电压应该是0V)。

实验步骤2:安装必要的软件工具

(1)安装ARM GCC交叉编译工具链和GNU Make

MacOS用户:可以用Homebrew安装,打开终端输入以下指令:/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 如果已经安装了Homebrew,可以跳过这一步

brew install arm-none-eabi-gcc make

Windows用户:可以从ARM官方网站下载GNU Arm Embedded Toolchain(https://developer.arm.com/downloads/-/gnu-arm-embedded-toolchain),然后安装到默认路径(C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10);GNU Make可以从MinGW-w64官网下载(https://www.mingw-w64.org/),或者直接安装Visual Studio的“C++桌面开发” workload(里面包含了GNU Make)。

Linux用户:可以用系统自带的包管理器安装,打开终端输入以下指令:# Ubuntu/Debian用户

sudo apt update

sudo apt install gcc-arm-none-eabi make

# CentOS/RHEL用户

sudo yum install gcc-arm-none-eabi make

(2)安装balenaEtcher

可以从balenaEtcher官方网站下载(https://www.balena.io/etcher/),然后安装到默认路径即可。

实验步骤3:编写裸机代码

树莓派4B的裸机代码需要包含三个部分:

启动汇编代码(start.S):用来初始化CPU的栈指针、设置CPU的异常向量表、然后跳转到C语言的main函数;

硬件寄存器定义头文件(gpio.h):用来定义BCM2711 SoC的GPIO相关硬件寄存器的地址和位掩码;

主程序代码(main.c):用来初始化GPIO18引脚为输出模式、然后循环点亮和熄灭LED灯。

以下是三个部分的具体代码:

(1)启动汇编代码(start.S)

/* 启动汇编代码:start.S

* 功能:初始化CPU栈指针、设置异常向量表、跳转到C语言main函数

* 架构:ARMv8-A (AArch64)

*/

/* 定义栈顶地址:树莓派4B的RAM起始地址是0x00000000,我们把栈顶设置在0x80000(512KB)的位置 */

.equ STACK_TOP, 0x80000

/* 定义代码段入口:树莓派4B的裸机代码必须从0x80000的位置开始执行 */

.section .text.boot

.global _start

_start:

/* 1. 初始化所有CPU核心的栈指针:树莓派4B有4个CPU核心,我们只使用核心0,其他核心进入无限循环 */

mrs x0, mpidr_el1 /* 读取MPIDR_EL1寄存器,获取当前CPU核心的编号 */

and x0, x0, #0xFF /* 只保留MPIDR_EL1寄存器的最低8位(Aff0),也就是核心编号 */

cbz x0, core0_init /* 如果核心编号是0,跳转到core0_init标签 */

core_hang: /* 其他核心进入无限循环 */

b core_hang

core0_init:

/* 2. 初始化核心0的栈指针:把栈顶地址设置到SP_EL1寄存器 */

ldr x1, =STACK_TOP

mov sp, x1

/* 3. 跳转到C语言的main函数 */

bl main

/* 4. 如果main函数返回,进入无限循环 */

b core_hang

(2)硬件寄存器定义头文件(gpio.h)

/* 硬件寄存器定义头文件:gpio.h

* 功能:定义BCM2711 SoC的GPIO相关硬件寄存器的地址和位掩码

* 参考资料:BCM2711 ARM Peripherals Documentation(https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf)

*/

#ifndef GPIO_H

#define GPIO_H

/* BCM2711 SoC的外设寄存器起始地址:树莓派4B启动后,外设寄存器会被映射到0xFE000000的位置 */

#define PERIPHERAL_BASE 0xFE000000

/* GPIO寄存器的偏移地址:从PERIPHERAL_BASE加上这个偏移地址就是GPIO寄存器的实际地址 */

#define GPIO_BASE (PERIPHERAL_BASE + 0x200000)

/* GPIO功能选择寄存器(GPFSELn)的偏移地址:

* 每个GPFSELn寄存器可以控制10个GPIO引脚的功能(输入、输出、或者其他复用功能)

* GPFSEL0控制GPIO0-GPIO9,GPFSEL1控制GPIO10-GPIO19,以此类推

*/

#define GPFSEL1 (GPIO_BASE + 0x04) /* 我们要控制的GPIO18在GPFSEL1寄存器中 */

/* GPIO输出置位寄存器(GPSETn)的偏移地址:

* 向GPSETn寄存器的某一位写1,可以把对应的GPIO引脚置为高电平

* 写0没有任何效果

*/

#define GPSET0 (GPIO_BASE + 0x1C)

/* GPIO输出清零寄存器(GPCLRn)的偏移地址:

* 向GPCLRn寄存器的某一位写1,可以把对应的GPIO引脚置为低电平

* 写0没有任何效果

*/

#define GPCLR0 (GPIO_BASE + 0x28)

/* GPIO18引脚的位掩码:

* 我们要控制的GPIO18在GPFSEL1寄存器的第24-26位(每个引脚占3位)

* 在GPSET0和GPCLR0寄存器的第18位(每个引脚占1位)

*/

#define GPIO18_FUNC_MASK (0x7 << 24) /* 清零GPFSEL1寄存器的第24-26位 */

#define GPIO18_FUNC_OUT (0x1 << 24) /* 把GPFSEL1寄存器的第24-26位设置为001(输出模式) */

#define GPIO18_BIT (0x1 << 18) /* 控制GPSET0和GPCLR0寄存器的第18位 */

/* 软件延时函数的声明:在main.c中实现 */

void delay(unsigned int cycles);

/* 硬件寄存器的指针定义:

* 我们需要用volatile关键字来修饰这些指针,因为编译器不知道硬件寄存器的值会在什么时候变化

* 如果不用volatile关键字,编译器可能会优化掉一些对硬件寄存器的读写操作

*/

#define REG32(addr) (*(volatile unsigned int *)(addr))

#endif /* GPIO_H */

(3)主程序代码(main.c)

/* 主程序代码:main.c

* 功能:初始化GPIO18引脚为输出模式、然后循环点亮和熄灭LED灯

*/

#include "gpio.h"

/* 软件延时函数:

* 功能:通过循环执行空指令来实现延时

* 参数:cycles - 循环的次数(次数越多,延时越长)

* 注意:这是一个非常粗糙的软件延时函数,延时时间不准确;如果要实现精准延时,需要配置SysTick定时器或TIMx通用定时器

*/

void delay(unsigned int cycles)

{

while (cycles--)

{

asm volatile("nop"); /* 执行一条空指令,消耗一个CPU周期 */

}

}

/* C语言的main函数:

* 功能:程序的入口点

*/

int main(void)

{

/* 1. 初始化GPIO18引脚为输出模式 */

REG32(GPFSEL1) &= ~GPIO18_FUNC_MASK; /* 先清零GPFSEL1寄存器的第24-26位 */

REG32(GPFSEL1) |= GPIO18_FUNC_OUT; /* 再把GPFSEL1寄存器的第24-26位设置为001(输出模式) */

/* 2. 循环点亮和熄灭LED灯 */

while (1)

{

REG32(GPSET0) = GPIO18_BIT; /* 把GPIO18引脚置为高电平,点亮LED灯 */

delay(0x800000); /* 延时一段时间(大概1秒) */

REG32(GPCLR0) = GPIO18_BIT; /* 把GPIO18引脚置为低电平,熄灭LED灯 */

delay(0x800000); /* 再延时一段时间(大概1秒) */

}

/* 3. 程序永远不会到达这里 */

return 0;

}

实验步骤4:编写Makefile自动化编译过程

为了避免每次编译都要输入一大堆指令,我们可以编写一个Makefile来自动化编译过程——Makefile是GNU Make工具的配置文件,它可以定义“目标文件”、“依赖文件”和“编译指令”之间的关系。

以下是具体的Makefile代码:

# Makefile:自动化编译树莓派4B裸机代码

# 架构:ARMv8-A (AArch64)

# 定义交叉编译工具链的前缀

CROSS_COMPILE = aarch64-none-eabi-

# 定义编译器、汇编器、链接器、目标文件复制工具的名称

CC = $(CROSS_COMPILE)gcc

AS = $(CROSS_COMPILE)as

LD = $(CROSS_COMPILE)ld

OBJCOPY = $(CROSS_COMPILE)objcopy

# 定义编译选项

CFLAGS = -Wall -Wextra -O2 -ffreestanding -nostdlib -nostartfiles -march=armv8-a -mtune=cortex-a72

# -Wall -Wextra:开启所有警告

# -O2:开启二级优化

# -ffreestanding:告诉编译器这是一个独立的程序,不需要依赖标准库

# -nostdlib:不链接标准库

# -nostartfiles:不链接启动文件(我们自己写了start.S)

# -march=armv8-a:指定CPU架构为ARMv8-A

# -mtune=cortex-a72:指定CPU调优为Cortex-A72(树莓派4B的CPU核心是Cortex-A72)

# 定义链接选项

LDFLAGS = -T linker.ld -nostdlib -nostartfiles

# -T linker.ld:指定链接脚本为linker.ld

# 定义目标文件

OBJS = start.o main.o

# 定义最终的裸机镜像文件名称

TARGET = kernel8.img

# 默认目标:编译出kernel8.img

all: $(TARGET)

# 编译kernel8.img的规则:先链接出elf文件,再用objcopy转换成二进制镜像文件

$(TARGET): $(OBJS) linker.ld

$(LD) $(LDFLAGS) $(OBJS) -o kernel8.elf

$(OBJCOPY) -O binary kernel8.elf $(TARGET)

# 编译start.o的规则:把start.S汇编成start.o

start.o: start.S

$(AS) -o $@ $<

# 编译main.o的规则:把main.c编译成main.o

main.o: main.c gpio.h

$(CC) $(CFLAGS) -c -o $@ $<

# 清理目标:删除所有编译生成的文件

clean:

rm -f $(OBJS) kernel8.elf $(TARGET)

# 声明伪目标:这些目标不是文件,只是一些操作

.PHONY: all clean

实验步骤5:编写链接脚本(linker.ld)

链接脚本(linker.ld)是GNU LD链接器的配置文件,它可以定义“代码段”、“数据段”、“BSS段”等内存段的位置——树莓派4B的裸机代码必须把代码段放在0x80000的位置,否则无法启动。

以下是具体的链接脚本代码:

/* 链接脚本:linker.ld

* 功能:定义内存段的位置

* 架构:ARMv8-A (AArch64)

*/

/* 定义内存区域:树莓派4B的RAM起始地址是0x00000000,我们只使用前512KB(0x00000000-0x00080000) */

MEMORY

{

ram (rwx) : ORIGIN = 0x00000000, LENGTH = 0x00080000

}

/* 定义内存段的位置 */

SECTIONS

{

/* 代码段(.text):从0x80000的位置开始 */

.text 0x80000 : ALIGN(4)

{

*(.text.boot) /* 先放启动汇编代码的.text.boot段 */

*(.text) /* 再放其他代码的.text段 */

} > ram

/* 数据段(.data):紧跟在代码段后面 */

.data : ALIGN(4)

{

*(.data)

} > ram

/* BSS段(.bss):紧跟在数据段后面,初始化为0 */

.bss : ALIGN(4)

{

__bss_start = .;

*(.bss)

__bss_end = .;

} > ram

/* 堆段(.heap)和栈段(.stack):紧跟在BSS段后面(我们在start.S中已经设置了栈顶地址) */

}

实验步骤6:编译裸机代码并烧录到Micro SD卡上

现在我们已经完成了所有的代码编写工作,接下来需要编译裸机代码并烧录到Micro SD卡上:

打开终端,进入我们存放所有代码的文件夹(比如我存放在~/Desktop/RPi4B_Blink/文件夹下);

输入“make”指令,GNU Make会自动编译所有的代码,生成kernel8.img镜像文件;

输入“make clean”指令可以删除所有编译生成的文件(如果需要重新编译的话);

打开balenaEtcher,点击“Flash from file”按钮,选择我们刚刚生成的kernel8.img镜像文件;

点击“Select target”按钮,选择我们插入的空白Micro SD卡;

点击“Flash!”按钮,balenaEtcher会自动格式化Micro SD卡并烧录kernel8.img镜像文件;

烧录完成后,把Micro SD卡从另一台正常的电脑上拔下来,插入到树莓派4B的Micro SD卡插槽中;

把树莓派4B的官方电源适配器插入到USB-C接口中,启动树莓派4B——几秒钟后,我们就会看到连接在GPIO18引脚的LED灯开始循环点亮和熄灭(大概每秒一次)。

如果LED灯没有亮,我们可以检查以下几个方面:

硬件连接是否正确(LED灯的长脚和短脚是否接反了、BCM18引脚和GND引脚是否接对了、限流电阻是否接好了);

Micro SD卡是否烧录正确(有没有烧录成kernel7.img或者其他镜像文件、有没有格式化Micro SD卡为FAT32文件系统);

代码是否正确(有没有修改硬件寄存器的地址、有没有修改GPIO引脚的位掩码)。

消费电子领域的裸机延伸:小米10 Ultra从官方MIUI到全裸机再到刷入LineageOS原生纯净系统

在讲完PC全裸机和嵌入式裸机开发的实例后,我们再回到消费者端,通过一个具体的智能手机实例来理解“全裸机”和“半裸机(纯净版系统设备)”在消费电子领域的应用。

1 实例三:小米10 Ultra从官方MIUI到全裸机再到刷入LineageOS原生纯净系统

实验背景

我有一台2020年购买的小米10 Ultra 5G手机——这台手机的硬件配置非常强大(骁龙865 Plus CPU、12GB LPDDR5 RAM、512GB UFS 3.0 ROM、120倍潜望式长焦镜头),但官方MIUI系统(MIUI 14)的广告太多、内置APP太多、后台服务太多,导致手机的启动速度变慢、电池续航变短、甚至偶尔会出现卡顿的情况。

我现在想要做的是:清空小米10 Ultra的所有分区(变成彻底的全裸机),然后刷入LineageOS 20原生纯净系统(变成一台半裸机)——LineageOS是目前最流行的开源Android原生定制系统之一,它没有任何广告、内置APP非常少(只有电话、短信、相机、设置等基本APP)、后台服务非常少,所以运行速度非常快、电池续航也非常长。

实验前的准备工作

硬件部分:

小米10 Ultra 5G手机(必须已经解锁Bootloader,否则无法刷入第三方Recovery和第三方系统);

一根原装USB-C转USB-A数据线;

一台正常的电脑(Windows、MacOS、Linux都可以,我用的是Windows 11 Home)。

软件部分:

小米10 Ultra的官方USB驱动程序(https://www.miui.com/unlock/download.html);

ADB和Fastboot工具(可以从Android官方网站下载Platform Tools:https://developer.android.com/studio/releases/platform-tools);

小米10 Ultra的第三方Recovery(TWRP 3.7.0_12-0:https://twrp.me/xiaomi/xiaomimi10ultra.html);

小米10 Ultra的LineageOS 20原生纯净系统镜像文件(https://download.lineageos.org/cmi);

小米10 Ultra的Google Apps(MindTheGapps-13.0.0-arm64-20230808.zip:https://wiki.lineageos.org/gapps/,如果不需要Google服务的话可以跳过)。

实验步骤1:备份手机上的所有重要数据

在清空所有分区之前,我们必须先备份手机上的所有重要数据(比如照片、视频、联系人、短信、微信聊天记录等)——因为清空所有分区后,手机上的所有数据都会永久丢失,无法恢复。

备份数据的方法有很多种:

可以用小米官方的“小米云服务”备份(需要开通小米云会员,否则存储空间只有5GB);

可以用“微信电脑版”备份微信聊天记录;

可以用ADB工具备份整个手机的数据(适合高级用户);

可以把手机上的照片、视频等文件复制到电脑上。

实验步骤2:进入Fastboot模式并清空所有分区

备份完所有重要数据后,我们需要进入Fastboot模式并清空所有分区:

把小米10 Ultra手机关机;

同时按住“音量减键”和“电源键”,直到手机屏幕上显示“FASTBOOT”的兔子图标,然后松开手;

用原装USB-C转USB-A数据线把手机连接到电脑上;

在电脑上打开命令提示符(Windows)或终端(MacOS/Linux),进入存放ADB和Fastboot工具的文件夹(比如我存放在C:\platform-tools\文件夹下);

输入“fastboot devices”指令,检查电脑是否识别到了手机——如果显示了手机的序列号,就说明识别成功了;

输入“fastboot erase boot”指令,清空Boot分区;

输入“fastboot erase system”指令,清空System分区;

输入“fastboot erase vendor”指令,清空Vendor分区;

输入“fastboot erase product”指令,清空Product分区;

输入“fastboot erase system_ext”指令,清空System_ext分区;

输入“fastboot erase odm”指令,清空Odm分区;

输入“fastboot erase cache”指令,清空Cache分区;

输入“fastboot erase userdata”指令,清空Userdata分区(这是最重要的一步,会清空手机上的所有个人数据);

输入“fastboot erase metadata”指令,清空Metadata分区;

输入“fastboot erase dtbo”指令,清空Dtbo分区;

小米10 Ultra手机已经变成了彻底的全裸机——没有任何引导分区、没有任何系统分区、没有任何个人数据分区,无法启动,只能进入Fastboot模式或Recovery模式。

实验步骤3:刷入第三方Recovery(TWRP)并格式化Data分区

清空所有分区后,我们需要刷入第三方Recovery(TWRP)并格式化Data分区:

把下载好的TWRP 3.7.0_12-0镜像文件(twrp-3.7.0_12-0-cmi.img)复制到存放ADB和Fastboot工具的文件夹下;

在命令提示符或终端中输入“fastboot flash recovery twrp-3.7.0_12-0-cmi.img”指令,刷入TWRP Recovery;

刷入完成后,输入“fastboot reboot recovery”指令,重启手机进入TWRP Recovery模式;

第一次进入TWRP Recovery模式时,会显示“Unmodified System Partition”的提示——选择“Keep Read Only”,然后滑动滑块确认;

进入TWRP Recovery的主界面后,点击“Wipe”按钮;

点击“Format Data”按钮;

输入“yes”,然后滑动滑块确认格式化Data分区(这一步是为了解除Data分区的加密,因为LineageOS原生纯净系统默认不支持小米官方的Data分区加密);

格式化完成后,点击“Back”按钮返回Wipe界面;

点击“Advanced Wipe”按钮;

勾选“Dalvik / ART Cache”、“Cache”、“System”、“Vendor”、“Data”(注意不要勾选“Internal Storage”,因为我们后面需要把系统镜像文件复制到Internal Storage中),然后滑动滑块确认擦除;

擦除完成后,点击“Back”按钮返回TWRP Recovery的主界面。

实验步骤4:复制系统镜像文件到Internal Storage并刷入

格式化Data分区和擦除其他分区后,我们需要复制系统镜像文件到Internal Storage并刷入:

在TWRP Recovery的主界面点击“Mount”按钮;

勾选“Internal Storage”,然后点击“Enable MTP”按钮——这样电脑就可以识别到手机的Internal Storage了;

把下载好的LineageOS 20原生纯净系统镜像文件(lineage-20.0-20231001-nightly-cmi-signed.zip)和Google Apps镜像文件(MindTheGapps-13.0.0-arm64-20230808.zip)复制到手机的Internal Storage中;

复制完成后,点击“Disable MTP”按钮,然后点击“Back”按钮返回TWRP Recovery的主界面;

点击“Install”按钮;

选择我们刚刚复制到Internal Storage中的LineageOS 20原生纯净系统镜像文件;

滑动滑块确认刷入;

刷入完成后,如果需要刷入Google Apps的话,点击“Add More Zips”按钮,选择Google Apps镜像文件,然后滑动滑块确认刷入;

刷入完成后,点击“Wipe Cache/Dalvik”按钮,滑动滑块确认擦除;

擦除完成后,点击“Reboot System”按钮,重启手机;

第一次启动LineageOS 20原生纯净系统需要较长的时间(大概5-10分钟),因为系统需要初始化所有的硬件和应用——耐心等待即可;

启动完成后,按照提示设置语言、地区、Wi-Fi、Google账号(如果刷入了Google Apps的话)等——小米10 Ultra手机已经变成了一台半裸机LineageOS原生纯净系统设备,没有任何广告、内置APP非常少、后台服务非常少,运行速度非常快、电池续航也非常长(我用了之后,电池续航从原来的一天一充变成了一天半一充)。

个人观点:裸机开发的未来趋势与消费电子领域“裸机风潮”的回归必要性

作为一名在嵌入式开发和PC硬件领域有10年经验的从业者,我想结合自己的经验发表两个个人观点:

1 个人观点一:裸机开发不会被RTOS/嵌入式Linux取代,反而会在“特定场景”下迎来新的发展机遇

很多人可能会问:“现在RTOS(如FreeRTOS、RT-Thread、Zephyr)和嵌入式Linux已经这么成熟了,为什么还要学裸机开发?裸机开发会不会被取代?”

我的答案是:裸机开发永远不会被取代,反而会在“低功耗、高实时性、极低成本、极小存储空间”的特定场景下迎来新的发展机遇——原因如下:

低功耗场景:RTOS/嵌入式Linux需要运行很多后台服务和任务调度算法,这会消耗大量的电能;而裸机开发没有任何中间层,代码的执行效率极高,电能消耗也极低——比如智能手表的心率传感器、体温

Back to top: