存储介质与分区基础

大家好,我是你们的讲师。这一章咱们聊聊嵌入式开发里最基础、也最绕不开的话题——存储介质和分区。说白了,就是你的代码到底存在哪、怎么存、怎么组织。我见过不少新手,上来就烧录,结果板子变砖,十有八九是没搞懂这层。

Flash存储器原理

Flash存储器,你可以把它想象成一块电子黑板。写数据就像用粉笔写字,擦除就像用板擦擦掉。但有个关键区别:Flash只能把1写成0,不能把0写成1。想改回1?必须整块擦除。

为什么会这样?这跟Flash的物理结构有关。每个存储单元是一个浮栅晶体管,写入时注入电荷,擦除时释放电荷。电荷注入是逐位操作的,但释放必须整块来。嗯,这里要注意,这个“块”就是后面要说的“擦除块”,大小通常是64KB或128KB。

我在项目中遇到过一个问题:某款IoT设备频繁写日志,结果Flash提前报废了。原因就是没考虑Flash的擦写寿命——一般NOR Flash能撑10万次,NAND Flash只有几千到几万次。所以,别把日志直接往Flash上怼,要用磨损均衡算法。

NAND vs NOR Flash

这两种Flash,就像卡车和跑车,各有各的赛道。我做个表格,大家一看就明白。

特性 NOR Flash NAND Flash
读取速度 快(支持随机读取,类似RAM) 较慢(只能按页读取)
写入速度 慢 快(批量写入效率高)
擦除速度 慢 快
密度 低(最大几百MB) 高(可达几TB)
可靠性 高(坏块少) 低(出厂就有坏块,需要ECC)
典型用途 代码存储、启动引导 大容量数据存储
成本 高 低

我个人习惯:NOR Flash用来放Bootloader和关键代码,因为可以XIP(片上执行),CPU直接读,不用先拷到RAM。NAND Flash则用来放文件系统、用户数据。你想想看,手机里的存储芯片基本都是NAND,但它的启动ROM还是NOR。

避坑指南: 我曾经在一个项目里用NAND Flash直接存启动代码,结果发现启动时CPU读NAND需要初始化控制器,而控制器代码又在NAND里……死循环了。后来加了个小容量的NOR Flash做启动引导,才解决。

分区表概念

分区表,就是一张地图,告诉系统:哪块区域是干嘛的。没有分区表,Flash就是一块大荒地,你都不知道哪里能种菜、哪里能盖房。

分区表通常放在Flash的固定偏移位置,比如偏移0处。它包含每个分区的:

  • 名称:比如boot、system
  • 起始地址:在Flash中的偏移
  • 大小:分区占用的字节数
  • 属性:是否可读、可写、可执行

下面是一个典型的分区表结构图,我用SVG画出来,大家感受一下。

典型Flash分区布局 典型Flash分区布局 分区表 (Partition Table) Bootloader (启动引导) Boot (内核/启动镜像) System (系统分区) Cache (缓存分区) Data (用户数据分区) Flash地址递增方向 注意 分区表通常占用 Flash前几个扇区 修改分区表需要 重新擦写整个Flash

这张图展示了一个典型的嵌入式Linux分区布局。注意,分区表本身也占一个分区,而且通常放在最前面。我建议你设计分区时,给每个分区留点余量,别算得死死的。我曾经因为分区大小刚好够,结果系统更新时新固件大了几KB,直接刷爆了。

常见分区类型

咱们逐个看看这些分区是干嘛的。

Boot分区

这个分区存放的是启动引导程序,比如U-Boot、GRUB。它负责初始化硬件、加载内核。我一般把它放在NOR Flash里,因为启动时CPU直接就能读。如果放在NAND里,Bootloader自己得先初始化NAND控制器,有点蛋生鸡的问题。

System分区

系统分区,存放操作系统内核、驱动、核心库。在Android设备里,这就是/system目录。这个分区通常是只读的,防止用户误删系统文件。我遇到过有人手贱删了/system下的某个so文件,结果系统直接起不来。

Data分区

用户数据分区,存放应用、配置、用户文件。这个分区是可读写的,也是磨损最严重的。我建议你在这个分区上启用文件系统日志,比如ext4的journal,防止突然断电导致数据损坏。

Cache分区

缓存分区,存放临时数据,比如系统更新包、应用缓存。这个分区可以随时清空,不影响系统运行。嗯,这里要注意,Cache分区的大小不要太大,一般几十MB就够了。我见过有人给Cache分了1GB,纯属浪费。

核心要点: 分区设计要遵循“最小权限原则”——能只读就别读写,能小就别大。Boot和System尽量只读,Data和Cache才开放读写权限。

分区表代码示例

下面是一个实际的分区表定义,来自某个嵌入式Linux项目。我用的是设备树(Device Tree)格式。

// 分区表定义 (设备树格式)
partitions {
    compatible = "fixed-partitions";
    #address-cells = <1>;
    #size-cells = <1>;

    partition@0 {
        label = "bootloader";
        reg = <0x000000 0x100000>;  // 1MB
        read-only;
    };

    partition@1 {
        label = "boot";
        reg = <0x100000 0x200000>;  // 2MB
        read-only;
    };

    partition@2 {
        label = "system";
        reg = <0x300000 0x500000>;  // 5MB
        read-only;
    };

    partition@3 {
        label = "cache";
        reg = <0x800000 0x100000>;  // 1MB
    };

    partition@4 {
        label = "data";
        reg = <0x900000 0x700000>;  // 7MB
    };
};

这段代码定义了5个分区,总大小16MB。注意看地址是连续的,从0开始。我习惯把Bootloader放在最前面,因为CPU复位后默认从地址0开始执行。

警告: 修改分区表后,一定要重新烧录整个Flash,不能只烧某个分区。因为分区表变了,所有分区的地址都偏移了。我曾经只烧了System分区,结果Bootloader还在老地址找内核,直接死机。

好了,这一章的内容就到这里。Flash的原理、NAND和NOR的区别、分区表的概念、常见分区类型,这些是后续烧录实战的基础。你把这些搞懂了,后面烧录的时候就不会一脸懵了。