littlefs性能分析终极完整版

分析的目的很简单:希望支持掉电安全,或者说具有奔溃一致性特性的文件系统,他的读写速度能得到提升。如果了解了瓶颈所在,也可触类旁通。

1 概述

已支持的API接口:

  1. sysio(系统I/O)

    • 系统I/O通常指的是直接使用操作系统提供的系统调用来进行文件I/O操作。
    • 它提供了对文件描述符(file descriptors)的操作,这是Unix-like系统中的核心概念。
    • 系统I/O函数,如openreadwriteclose等,是底层I/O操作的基础,它们直接与内核交互。
    • 系统I/O通常比标准I/O更快,因为它减少了中间层,但它也要求程序员处理更多的细节,如错误处理和缓冲管理。
  2. stdio(标准I/O)

    • 标准I/O是C语言标准库提供的一组高级I/O函数,如fopenfprintffscanffclose等。
    • 它基于系统I/O,但提供了一种更方便、更高级的接口。标准I/O库会处理缓冲,减少对系统调用的直接调用,从而提高性能。
    • 标准I/O还包括对格式化输入输出的支持,这使得处理文本数据更加容易。
    • 标准I/O流(如stdinstdoutstderr)是预定义的,可以方便地用于程序的输入和输出。
  3. posixio(POSIX I/O)

    • POSIX I/O实际上并不是一个独立的I/O模型,而是指遵循POSIX标准的I/O操作。
    • POSIX是一套标准化的接口,主要用于Unix-like操作系统,旨在提高软件的源代码级别的可移植性。
    • POSIX I/O包括了系统I/O的函数,同时也包括了其他一些标准的I/O操作,如文件和目录操作、进程控制等。
    • POSIX I/O接口通常包括在unistd.h头文件中,它们确保了在不同的Unix-like系统上提供一致的I/O操作。

已支持的文件系统:

  1. vfs
  2. devfs
  3. rootfs
  4. fatfs
  5. littlefs
  6. yaffs
  7. tmpfs
  8. shmfs
  9. nfs
2 执行流程
2.1 示意图

![[fs-fk1.png]]

2.2 测试用例
#define MAX_FILE_COUNT 8
#define MAX_FILE_SIZE  (10*1024*1024)
#define FILE_BUF_SIZE  (10*1024*1024)

for (files = 0; files < MAX_FILE_COUNT; files++) {
        if (!test_create_file_speed(&start_time,&write_time)) {
            return false;
        }
    }
static bool test_create_file_speed(os_tick_t *start_time, os_tick_t *write_time)
{
    int fd = open(test_files[file_index].name, O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR|S_IWUSR);
    if (fd == -1) 
    {
        return false;
    }
    while (written != test_files[file_index].size) 
    {
        uint32_t remaining = test_files[file_index].size - written;
        test_files[file_index].checksum += fill_test_data(test_buf, remaining);
        *start_time = os_tick_get_value();
        if (write(fd, test_buf, remaining) != remaining) 
        {
            return false;
        }
        *write_time += os_tick_get_value() - *start_time;
        written += remaining;
    }
    total_written += written;
    return close(fd) == 0;
}
2.3 lfs_vfs适配层
static const struct vfs_fops _lfs_fops = {
    .f_open  = vfs_lfs_open,
    .f_write = vfs_lfs_write,
    .f_read  = vfs_lfs_read,
    .f_close = vfs_lfs_close,
    .f_lseek = vfs_lfs_lseek,
    .f_sync  = vfs_lfs_sync,
    .f_ftruncate = vfs_lft_ftruncate,
};
2.4 lfs_io适配层
int lfs_flash_write(const struct lfs_config *cfg,
                    lfs_block_t              block,
                    lfs_off_t                off,
                    const void              *buffer,
                    lfs_size_t               size)
{
    uint32_t         len;
    os_device_t     *dev;
    uint32_t         block_ct;
    uint32_t         hw_blocksize;

    LFS_ASSERT(cfg != NULL);
    LFS_ASSERT(cfg->context != NULL);
    LFS_ASSERT(buffer != NULL);

    hw_blocksize = cfg->block_size / LFS_BLOCK_SIZE_FACTOR;
    block = (block * cfg->block_size + off) / hw_blocksize;
    block_ct = size/hw_blocksize+(size%hw_blocksize?1:0);

    dev = (os_device_t *)((lfs_device_info_t *)cfg->context)->dev;
    len = os_device_write_nonblock(dev, block, buffer, block_ct);

    if (len != block_ct)
    {
        return LFS_ERR_IO;
    }
    return LFS_ERR_OK;
}
2.5 lfs_config配置
static int vfs_lfs_cfg_init(lfs_part_info_t *part_info, os_device_t *dev)
{
    int         ret;
    struct os_blk_geometry geometry;

    if(OS_FAILURE == os_device_control(dev, OS_DEVICE_CTRL_BLK_GETGEOME, &geometry))
            ret = -ENODEV;
    else
    {
        memset(&part_info->lfs, 0, sizeof(lfs_t));
        memset(&part_info->config, 0 ,sizeof(struct lfs_config));
        part_info->dev_info.dev    = (void *)dev;
        part_info->config.context = &part_info->dev_info;
        part_info->config.read    = g_lfs_dev_ops.read;
        part_info->config.prog    = g_lfs_dev_ops.prog;
        part_info->config.erase   = g_lfs_dev_ops.erase;
        part_info->config.sync    = g_lfs_dev_ops.sync;

        //从flash中读取数据的最小size,并且起始地址需要按照这个size对齐
        part_info->config.read_size        = geometry.block_size*LFS_READ_SIZE_FACTOR;
        //往flash中写入数据的最小size,并且起始地址需要按照这个size对齐
        part_info->config.prog_size        = geometry.block_size*LFS_PROG_SIZE_FACTOR;
        //可擦除块的大小,可大于物理擦除大小,但必须是读取和程序大小的倍数
        part_info->config.block_size       = geometry.block_size*LFS_BLOCK_SIZE_FACTOR;
        part_info->config.block_count      = geometry.capacity / (geometry.block_size*LFS_BLOCK_SIZE_FACTOR);
        //是用于预读的块数量,以优化读取性能
        part_info->config.block_cycles     = LFS_BLOCK_CYCLE;
        //文件系统的读写时,数据在内存中的缓存大小
        part_info->config.cache_size       = geometry.block_size*LFS_BLOCK_SIZE_FACTOR;
        //空闲块滑窗的大小(in bytes),littlefs用一个滑窗来管理所有的空闲块
        part_info->config.lookahead_size   = LFS_LOOKAHEAD_SIZE;
        part_info->config.read_buffer      = OS_NULL;
        part_info->config.prog_buffer      = OS_NULL;
        part_info->config.lookahead_buffer = OS_NULL;
        ret = 0;
        }
    return ret;
}
3 对比测试

本次分析,使用了大量的对比测试:

littlefs写(KB/S)读(KB/S)分析
lfs初始速度 read_size=512, prog_size=512, block_size=512, lookahead_size=80.76517810118.980361328初始速度
lookahead_size=25618.69552148418.400935547
更新littlefs到最新版本,lookahead_size=25618.78165039118.402720703-
lookahead_size=819234.64910546918.399744141
block_cycles从-1改为100034.79537890618.393941406-
lookahead_size=8k ,block_size=120k=240sector3729.26653441.53175需要同步更改lfs_io文件
lookahead_size=8k,block_size=1024sector5603.0857898.793
取消fal9439.2227844.319取消fal对读速度影响很大,对写没有影响
直接使用块驱动接口,不使用驱动框架9323.1667871.461-
更改测试用例11524.0758597.326
read_size=128sector, prog_size=1024sector, block_size=1024sector13287.46119012.166减小了readsize
emmc:HS20020705.88032309.614提升了emmc的时钟频率

直接调用驱动接口os_device_write_nonblock/os_device_read_nonblock

驱动接口写(KB/S)读(KB/S)分析
1024block,emmc:HS25.87 MB/s28.57 MB/s直接调用驱动接口os_device_write_nonblock
4block,emmc:HS0.25MB/s0.33MB/s-
1024block,emmc:HS20037.97 MB/s53.71 MB/s稳定性
4block,emmc:HS2000.26 MB/s0.33 MB/s

在uboot上使用命令mmcinfo

Device: sdhci@fe310000
Manufacturer ID: 45
OEM: 100
Name: DG401
Timing Interface: HS200
Tran Speed: 200000000
Rd Block Len: 512
MMC version 5.1
High Capacity: Yes
Capacity: 14.7 GiB
Bus Width: 8-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 14.7 GiB WRREL
Boot Capacity: 4 MiB ENH
RPMB Capacity: 4 MiB ENH

eMMC 5.1:最大读写速率为200MB/s(HS200模式)

linux下使用iozone测试emmc上的ext4MB/s备注
iozone -i 0 -r 1024KB -s 200M -f /w_test3 -w -I81.461写操作:Record Size 1024 kB
iozone -i 0 -r 2KB -s 200M -f /w_test3 -w -I1.805写操作:Record Size 2 kB
iozone -i 1 -r 1024KB -s 200M -f /w_test3 -w -I138.197读操作:Record Size 1024 kB
dd if=/dev/mmcblk0 of=/dev/null bs=1M count=1024190读操作,接近emmc最大读写
4 性能差异对比
4.1 修改驱动初始化

eMMC规范定义了多种操作模式,每种模式对应一个最大时钟频率。例如,eMMC的HS200模式支持高达200MHz的时钟频率,而HS400模式支持高达200MHz的时钟频率,并且是双数据速率。

在Linux内核中,mmc 结构体的 clock 成员通常是一个整数值,表示当前控制器的时钟频率。这个值是以kHz为单位的,因此如果一个 mmc 结构体的 clock 成员值为 52000,那么它表示控制器的当前时钟频率是 52 MHz。

通过在uboot上使用命令mmcinfo,发现uboot下,emmc使用的模式是HS200模式,而OneOS使用的模式是HS,即52 MHz。在rockchip_sdhci.c的接口arasan_sdhci_probe中:配置host->host_caps |= MMC_MODE_HS200;,可让读写速率提升。

4.2 一次性读取多个块

为何驱动接口一次性读取更多的block会更快?

在eMMC存储中,一次性读取更多的数据块可能会更快的因素包括:

  1. 减少命令开销:每次读取操作之前,主机需要向eMMC发送命令。如果一次性读取更多的数据块,可以减少命令的发送次数,从而降低命令传输的开销。
  2. 提高数据传输带宽利用率:eMMC接口支持高速数据传输,一次性读取更多的数据块可以更充分地利用接口的带宽,从而提高数据传输的效率。
  3. 利用eMMC的缓存:eMMC设备内部通常包含一定大小的缓存,用于优化数据传输。一次性读取更多的数据块可以更有效地利用这个缓存,减少访问实际闪存介质的次数。
  4. 减少事务处理次数:eMMC的读写操作是通过事务来管理的,每个事务包括命令、响应和数据传输。减少事务处理的次数可以提高整体的传输效率。
  5. 优化闪存操作:eMMC使用的是闪存存储技术,闪存的读取操作在处理大量数据时更为高效。一次性读取更多的数据块可以减少对闪存的编程/擦除操作,从而提高读取速度。
  6. 减少接口切换:eMMC接口支持多种操作模式,包括数据传输模式和命令模式。一次性读取更多的数据块可以减少在这些模式之间切换的次数,从而节省时间。
4.3 lfs读写操作

为何lfs的读写操作要比直接调用驱动接口更慢?

  1. 抽象层开销:LFS作为文件系统,提供了文件和目录的抽象。这意味着在进行读写操作时,LFS需要处理文件系统的元数据,如目录结构、文件属性等,这增加了额外的开销。
  2. 缓存和缓冲:文件系统通常会使用缓存来优化对存储设备的访问。LFS可能有自己的缓存策略,这可能会增加读写操作的复杂性,尤其是在缓存失效或需要刷新缓存到存储设备时。
  3. 事务日志:LFS可能使用事务日志来确保文件系统的原子性和一致性。这意味着每次写操作可能需要额外的步骤来记录日志,这会增加写操作的延迟。
  4. 额外的写入操作:为了维护文件系统的结构,LFS可能需要进行额外的写入操作,比如更新文件系统的分配位图或者索引数据结构。
  5. 碎片处理:文件系统可能会随着时间的推移产生碎片,这需要LFS进行额外的管理工作,如动态地分配和回收存储空间,这可能会导致读写操作变慢。
  6. 同步和异步操作:直接调用驱动接口的读写操作可能更直接地控制同步和异步行为。而文件系统可能会为了数据的完整性而默认使用同步操作,这会阻塞直到数据完全写入或读取。
  7. 文件系统的一致性检查:LFS可能需要在启动或特定操作时执行一致性检查,以确保文件系统的完整性,这可能会导致额外的延迟。
  8. 系统调用开销:使用文件系统通常涉及到系统调用,这比直接在用户空间操作硬件驱动接口要慢,因为系统调用涉及到用户空间与内核空间之间的上下文切换。

总的来说,LFS提供了丰富的文件管理功能,但这些功能在提供便利的同时也带来了额外的性能开销。对于需要极高读写性能的应用,直接操作存储设备驱动接口可能会更高效,但这通常需要更复杂的错误处理和设备管理逻辑。

4.4 fal速率慢

为何fal让写速率变慢?

int fal_blk_write_block(os_blk_device_t *blk, uint32_t block_addr, const uint8_t *buff, uint32_t block_nr)
{
    int ret = OS_FAILURE;
    int count;
    struct fal_blk_device  *fal_blk_dev = (struct fal_blk_device *)blk;
    fal_part_t             *part        = fal_blk_dev->fal_part;
    const struct fal_flash *fal_flash   = part->flash;

    uint32_t start_addr = blk->geometry.block_size * block_addr;
    uint32_t end_addr   = blk->geometry.block_size * (block_addr + block_nr) - 1;
    uint32_t fal_block_start_addr = start_addr & ~(fal_flash->block_size - 1);
    uint32_t fal_block_end_addr   = end_addr & ~(fal_flash->block_size - 1);
    uint32_t fal_block_size = fal_block_end_addr - fal_block_start_addr + fal_flash->block_size;
    uint8_t *fal_block_buff = os_calloc(1, fal_block_size);

    count = fal_part_read(part, fal_block_start_addr, fal_block_buff, fal_block_size);
    memcpy(fal_block_buff + start_addr % fal_flash->block_size, buff, block_nr * blk->geometry.block_size);
    
    count = fal_part_erase(part, fal_block_start_addr, fal_block_size);
    count = fal_part_write(part, fal_block_start_addr, fal_block_buff, fal_block_size);

    ret = OS_SUCCESS;
    os_free(fal_block_buff);
    return ret;
}

在接口fal_blk_write_block中调用了fal_part_erase擦除接口,他会执行擦除操作,把所有的位都置1。

但其实对于eMMC存储设备,开发者通常不需要手动调用擦除接口。eMMC设备的控制器已经对底层的闪存操作进行了抽象和封装,包括必要的擦除操作。这意味着当应用程序写入数据到eMMC时,控制器会自动处理闪存块的管理,包括在必要时擦除和重写数据。

4.5 提高lfs_config

为何提高block_size,read_size,prog_size会让性能提升?

修改它们的同时,需要同步修改lfs_io.c文件,之间的lfs_flash_write/lfs_flash_read只能一次写入一个block,导致性能较弱。提高size大小,最终对应到os_device_write_nonblock,可以让驱动接口一次性读写多个块,从而提升性能。

直接从存储介质读取:如果数据既不在预缓存也不在读缓存中,且满足一定条件(如:大小大于等于缓存提示值并且读取位置对齐),则跳过缓存,直接从存储介质读取数据到用户缓冲区。

选择数据块:如果当前文件不在读取模式或当前块已读完,函数会找到包含文件当前位置的数据块。对于非内联文件,使用lfs_ctz_find查找数据块。
加载数据到读缓存:如果上述条件不满足,将数据加载到读缓存中,确保读取位置对齐,并读取尽可能多的数据,但不超过缓存大小和块大小。

为何提高littlefs的lookahead_size,能让性能提升?

  1. 减少查找开销:当LittleFS需要分配新的存储块时,它会使用lookahead buffer来预先检查和标记一系列可用块。一个更大的lookahead buffer意味着可以预先检查更多的块,从而减少在实际写入数据时寻找可用块所需的时间和开销。这在连续分配多个块时特别有效,因为它减少了对闪存的重复扫描。
  2. 优化块分配策略:由于lookahead buffer是按位图形式组织的,更大的尺寸意味着能够覆盖更多的物理块状态。这样,文件系统可以在更宽的范围内进行块分配选择,有助于实现更好的磨损平衡,避免某些块过早达到擦写极限,从而延长存储介质的寿命。
  3. 减少写入放大:通过更有效的块分配,可以减少不必要的擦除和重写操作(写入放大),因为文件系统可以更好地规划数据的存放位置,避免频繁地移动数据来腾出空间。这对于基于闪存的存储介质(如eMMC、NAND Flash)尤为重要,因为这些介质的擦写操作相比读取操作要慢得多且寿命有限。
  4. 提高顺序写入速度:对于连续的大块数据写入,较大的lookahead_size可以确保文件系统有足够多的连续空闲块来满足需求,从而减少碎片化,提高顺序写入的速度。
5 瓶颈总结

基于块设备的文件系统读写速率,影响其速率的瓶颈在于:

  1. 文件系统本身的配置,以littlefs为例,提高littlefs的lookahead_size,block_size,read_size,prog_size会让性能提升。
  2. 对块设备提供的接口使用:os_device_write_nonblock/os_device_read_nonblock,每次写多个sector,要比写一个快很多倍。
  3. 驱动的设置:通过更改eMMC的操作模式从HS改为HS200模式,可显著提升性能。
6 附录
6.1 dd命令

额外对照linuxdd命令:

[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=1M count=10240
10240+0 records in
10240+0 records out
10737418240 bytes (11 GB, 10 GiB) copied, 56.8519 s, 189 MB/s
[root@ok3568:/]#
[root@ok3568:/]#
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=10k count=10240
10240+0 records in
10240+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.0671878 s, 1.6 GB/s
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=100k count=10240
10240+0 records in
10240+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.431398 s, 2.4 GB/s
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=1k count=10240
10240+0 records in
10240+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.0223845 s, 468 MB/s

linux上,使用 dd 命令测试 eMMC 的读写速度时,得到的结果可能远大于预期,这可能是由于几个原因造成的:

  1. 缓存:操作系统或存储设备可能会使用缓存来临时存储数据,这样 dd 命令在读写时可能会直接与缓存交互,而不是实际的 eMMC 设备。这可能导致测试结果偏大,因为缓存的速度通常远高于存储设备的速度。
  2. 内存映射文件系统:在某些情况下,测试的文件可能会被存储在内存中,而不是直接写入 eMMC。例如,如果测试文件位于一个内存映射的文件系统(如tmpfs)上,dd 命令的测试结果将反映内存的速度,而不是 eMMC 的实际速度。
  3. 测试方法dd 命令的测试方法可能会影响结果。例如,如果测试时使用的块大小(bs选项)很小,那么可能会得到更高的速度,因为 dd 需要执行更多的I/O操作,这可能会受到系统其他活动的影响。
  4. 系统负载:如果系统在测试期间运行其他任务,这些任务可能会影响 dd 命令的性能,导致测试结果不准确。
  5. eMMC 特性:eMMC 设备可能具有不同的性能模式,例如 HS200、HS400 等。如果设备支持更高的性能模式,但在测试时没有正确配置,可能会导致测试结果低于设备的实际性能。
  6. 文件系统缓存:文件系统可能会缓存写操作,直到它们被刷新到存储设备上。如果 dd 命令在测试写操作后没有正确关闭文件描述符或同步文件系统,可能会导致测试结果偏大。
6.2 iozone命令

linux上,使用 iozone 命令测试 文件系统的读写速度时,得到的结果可能远大于预期,这和dd命令的原因非常相近,最大的问题其实是缓存,为了避免缓存影响使用 iozonedd 命令时的测试结果,可以采取以下措施:

  1. 使用 syncfsync: 在运行 iozonedd 命令之前,使用 sync 命令确保所有缓冲区和缓存中的数据都被写入到磁盘。对于 iozone,可以在测试之前使用 fsync 函数来确保文件系统缓冲区被刷新到存储设备。

  2. 使用 direct I/O: 对于 dd 命令,可以使用 conv=direct 选项来执行直接 I/O,这将绕过操作系统缓存,直接对存储设备进行读写操作。

    dd if=/dev/zero of=/path/to/file bs=1M count=1024 conv=direct
    
  3. 使用 oflagiflag 参数: 在 dd 命令中,可以使用 oflag=synciflag=sync 参数来同步 I/O,确保每次读写操作都直接与存储设备交互。

    dd if=/dev/zero of=/path/to/file bs=1M count=1024 oflag=sync
    
  4. 使用 iozone 的适当选项: iozone 命令有一些选项可以用来控制缓存的影响,例如 -I 选项可以用来执行直接 I/O 测试。确保在运行 iozone 时使用了正确的选项。

  5. 关闭文件系统的缓存: 对于某些文件系统,可以通过挂载选项临时关闭缓存。例如,在 Linux 中,可以使用 mount 命令的 sync 选项来挂载文件系统。

    mount -o remount,sync /mount/point
    
  6. 使用专用的性能测试工具: 使用专门设计用于存储性能测试的工具,如 fio,它可以提供更细粒度的控制,包括如何处理缓存和同步。

  7. 测试前清理缓存: 在运行测试之前,可以使用 echo 3 > /proc/sys/vm/drop_caches 命令(在 Linux 系统上)来清理页面缓存、目录项缓存和inode缓存。

因此,在linux下使用iozone测试emmc下的文件系统ext4:

MB/s
iozone -i 0 -r 1024KB -s 200M -f /w_test3 -w -I81.461写操作:Record Size 1024 kB
iozone -i 0 -r 2KB -s 200M -f /w_test3 -w -I1.805写操作:Record Size 2 kB
iozone -i 1 -r 1024KB -s 200M -f /w_test3 -w -I138.197读操作:Record Size 1024 kB
6.3 emmc配置分析
# fdisk -l /dev/mmcblk0

Number  Start (sector)    End (sector)  Size Name
     1           16384           24575 4096K uboot
     2           24576           32767 4096K misc
     3           32768           98303 32.0M boot
     4           98304          163839 32.0M recovery
     5          163840          229375 32.0M backup
     6          229376        12812287 6144M rootfs
     7        12812288        13074431  128M oem
     8        13074432        13139967 32.0M boot_b
     9        13139968        30777310 8611M userdata

6.4 uboot上的mmcinfo

在uboot上使用命令mmcinfo

Device: sdhci@fe310000
Manufacturer ID: 45
OEM: 100
Name: DG401
Timing Interface: HS200
Tran Speed: 200000000
Rd Block Len: 512
MMC version 5.1
High Capacity: Yes
Capacity: 14.7 GiB
Bus Width: 8-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 14.7 GiB WRREL
Boot Capacity: 4 MiB ENH
RPMB Capacity: 4 MiB ENH

在驱动的mmc_init里面添加打印mmc的结构体成员:

    const char *timing[] = {
		"Legacy", "High Speed", "High Speed", "SDR12",
		"SDR25", "SDR50", "SDR104", "DDR50",
		"DDR52", "HS200", "HS400", "HS400 Enhanced Strobe"};
    printf("-------Timing Interface: %s-------\r\n", timing[mmc->timing]);
    printf("-------mmc->capacity:%d---------\r\n",mmc->capacity);
    printf("-------mmc->high_capacity:%d---------\r\n",mmc->high_capacity);
    printf("-------mmc->bus_width:%d---------\r\n",mmc->bus_width);
    printf("-------Tran Speed: %d-------\r\n", mmc->clock);
	printf("-------Rd Block Len: %d-------\r\n", mmc->read_bl_len);

#define IS_SD(x)	((x)->version & SD_VERSION_SD)
#define IS_MMC(x)	((x)->version & MMC_VERSION_MMC)
#define EXTRACT_SDMMC_MAJOR_VERSION(x)	\
	(((u32)(x) >> 16) & 0xff)
#define EXTRACT_SDMMC_MINOR_VERSION(x)	\
	(((u32)(x) >> 8) & 0xff)
#define EXTRACT_SDMMC_CHANGE_VERSION(x)	\
	((u32)(x) & 0xff)

    printf("%s version %d.%d", IS_SD(mmc) ? "SD" : "MMC",
			EXTRACT_SDMMC_MAJOR_VERSION(mmc->version),
			EXTRACT_SDMMC_MINOR_VERSION(mmc->version));
	if (EXTRACT_SDMMC_CHANGE_VERSION(mmc->version) != 0)
		printf(".%d", EXTRACT_SDMMC_CHANGE_VERSION(mmc->version));
	printf("\r\n");

结果如下:

Timing Interface: High Speed
mmc->capacity:-1421869056
mmc->high_capacity:1
mmc->bus_width:8
Tran Speed: 52000000
Rd Block Len: 512
MMC version 5.1
littlefs 是一个用于嵌入式系统的轻量级文件系统,功能简单但高效。下面我将对其源码进行分析。 首先,littlefs 的源码结构清晰,主要包含了文件系统的核心逻辑、存储管理、文件操作等模块。在核心逻辑部分,源码实现了文件系统的初始化、格式化、挂载等功能,同时提供了基本的文件操作接口,如创建、打开、读取、写入和删除文件等。它还包含了一些实用的功能,比如目录操作、文件描述符的管理、簇(cluster)和扇区(sector)的管理等。 在存储管理方面,源码使用了位图(bitmap)来管理簇的分配情况,位图的每一位对应着簇的状态。这样,当需要分配或释放簇时,只需修改相应的位图即可。此外,源码还实现了一个缓存区(buffer)来加速读写操作,可以提高文件的访问速度。 在文件操作方面,源码使用了块(block)作为文件的基本单位,块的大小通过宏定义进行设置。它使用了文件控制块(file control block)来管理文件的元数据,包括文件名、长度、属性等。通过文件控制块,可以方便地对文件进行操作。 对于源码的性能方面,littlefs 采用了写前日志(write-ahead log)的方式来提高写入操作的性能和可靠性。在写入文件时,会先写入日志,然后再写入实际的数据块。这样即使发生意外断电等情况,也能保证数据的完整性。 总的来说,littlefs 的源码分析表明它是一个功能简单但高效的嵌入式文件系统。其清晰的结构和良好的性能使其成为嵌入式系统中的一个理想选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值