一、简介 LittleFS是一个小型的Flash文件系统,它结合日志结构(log-structured)文件系统和COW(copy-on-write)文件系统的思想,以日志结构存储元数据,以COW结构存储数据。这种特殊的存储方式,使LittleFS具有强大的掉电恢复能力(power-loss resilience)。分配COW数据块时LittleFS采用了名为统计损耗均衡的动态损耗均衡算法,使Flash设备的寿命得到有效保障。同时LittleFS针对资源紧缺的小型设备进行设计,具有极其有限的ROM和RAM占用,并且所有RAM的使用都通过一个可配置的固定大小缓冲区进行分配,不会随文件系统的扩大占据更多的系统资源。当在一个资源非常紧缺的小型设备上,寻找一个具有掉电恢复能力并支持损耗均衡的Flash文件系统时,LittleFS是一个比较好的选择。 LittleFS在嵌入式开发过程中经常遇到,但是如何在OpenHarmony中使用呢?本文基于OpenHarmony 3.1Release + 小凌派-RK2206开发板做LittleFS文件系统移植,现将相关移植经验发布,分享给大家。文中如有问题,请大家帮忙指正。 二、LittleFS移植过程 本文基于OpenHarmony3.1Release做LittleFS移植,小凌派-RK2206开发板内部Flash有8MB大小,其中4~8MB区间为空闲区域。我将4M~5M作为LittleFS文件系统的/data目录挂载硬件设备。具体移植过程主要如下所示: 1、hcs配置 1.1、hdf.hcs 创建/device/soc/rockchip/rk2206/hcs_config/hdf.hcs文件,具体如下:

#include "device_info/device_info.hcs"
#include "fs/fs_config.hcs"
#include "gpio/gpio_config.hcs"
#include "i2c/i2c_config.hcs"
#include "spi/spi_config.hcs"
root {
    module = "rockchip,rk2206_chip";
}

如上所述,我将在device_info/device_info.hcs添加LittleFS设备,并在fs/fs_config.hcs添加LittleFS具体信息。 1.2、BUILD.gn 新建//device/soc/rockchip/rk2206/hdf_config/BUILD.gn,具体代码如下所示:

import("//drivers/adapter/khdf/liteos_m/hdf.gni")
module_switch = defined(LOSCFG_DRIVERS_HDF)
module_name = get_path_info(rebase_path("."), "name")
hdf_driver(module_name) {
  hcs_sources = [ "hdf.hcs" ]
}

上述代码将在编译OpenHarmony3.1Rlease时,将编译hdf.hcs。 1.3、device_info.hcs 创建/device/soc/rockchip/rk2206/hcs_config/device_info/device_info.hcs文件,在文件中添加LittleFS设备,具体代码如下所示:

 device_fs :: device {
                device0 :: deviceNode {
                    policy = 0;
                    priority = 5;
                    permission = 0777;
                    moduleName = "HDF_PLATFORM_FS_LITTLEFS";
                    serviceName = "littlefs_config";
                    deviceMatchAttr = "rockchip_rk2206_fs_littlefs";
                }
            }

上述代码表示建设一个设备驱动,该驱动的模块名称(即moduleName)为“HDF_PLATFORM_FS_LITTLEFS”,OpenHamrony系统依据该名称匹配驱动程序;设备匹配信息(即deviceMatchAttr)添加小凌派开发板Flash特殊信息(比如:分区信息,挂载目录名、起始地址、结束地址等)。 1.4、fs_config.hcs 新建//device/soc/rockchip/rk2206/hdf_config/fs/fs_config.hcs文件,该文件主要写清楚设备挂载信息,具体如下:

root {
    platform {
        fs_config {
            template fs_controller {
                match_attr = "";
                mount_points = [];
                block_size = [];
                block_start = [];
                block_count = [];
            }

            fs_littefs :: fs_controller {
                match_attr = "rockchip_rk2206_fs_littlefs";
                mount_points = ["/data"];
                block_size = [4096];
                block_start = [1024];
                block_count = [256];
            }
        }
    }
}

如上所述,我将LittleFS移植所需的Flash信息分为4个: (1)mount_points:挂载目录。 (2)block_size:Flash的擦除块大小。 (3)block_start:该挂载Flash区域的起始块地址,实际Flash地址为block_size * block_start。 (4)block_count:该挂载Flash区域的存储块总数。 注意:match_attr所表示的字符串要与device_info.hcs所表示的字符串要一致。 2、hdf驱动 新建//device/soc/rockchip/rk2206/hdf_driver/fs文件夹,文件夹下创建2个文件,具体如下所示: 2.1、fs_driver.c 2.1.1、添加必要的头文件

#include <string.h>
#include "los_config.h"
#include "hdf_log.h"
#include "hdf_device_desc.h"
#include "device_resource_if.h"
#include "osal_mem.h"
#include "lfs_api.h"

2.1.2、添加HDF驱动 添加LittleFS匹配的hdf驱动,具体代码如下所示:

static struct HdfDriverEntry g_fsDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_PLATFORM_FS_LITTLEFS",
    .Bind = fs_driver_bind,
    .Init = fs_driver_init,
    .Release = fs_driver_release,
};

HDF_INIT(g_fsDriverEntry);

其中,moduleName必须要与device_info.hcs中的moduleName保持一致。

2.1.3、fs_driver_init函数 fs_driver_init为hdf驱动加载函数。OpenHarmony启动时,将根据hcs的编写匹配对应的驱动程序,并运行fs_driver_init函数。该函数负责: (1)读取hcs文件的配置参数。 (2)初始化Flash。 (3)适配LittleFS对应的read、write、erase和sync函数,并适配LittleFS相关参数。 (4)挂载LittleFS。 具体代码如下所示:

static int32_t fs_driver_init(struct HdfDeviceObject *device)
{
    int result;
    int32_t ret;
    struct FileOpInfo *file_op_info = NULL;

    if (device == NULL) {
        PRINT_ERR("device is null\n");
        return HDF_ERR_INVALID_OBJECT;
    }
    if (device->property == NULL)
    {
        PRINT_ERR("device is null\n");
        return HDF_ERR_INVALID_OBJECT;
    }

    /* Flash设备初始化 */
    FlashInit();

/* 读取hcs参数 */
    ret = fs_driver_readdrs(device->property, &m_fs_cfg[0]);
    if (ret != HDF_SUCCESS)
    {
        PRINT_ERR("%s: fs_driver_readdrs failed(%d)\n", ret);
        return ret;
    }
    
/* 适配LitteleFS对应的函数和参数 */
    for (int i = 0; i < sizeof(m_fs_cfg) / sizeof(m_fs_cfg[0]); i++) {
        if (m_fs_cfg[i].mount_point == NULL)
        {
            PRINT_LOG("m_fs_cfg[%d].mount_point is null\n", i);
            continue;
        }

        m_fs_cfg[i].lfs_cfg.read = flash_littlefs_read;
        m_fs_cfg[i].lfs_cfg.prog = flash_littlefs_write;
        m_fs_cfg[i].lfs_cfg.erase = flash_littlefs_erase;
        m_fs_cfg[i].lfs_cfg.sync = flash_littlefs_sync;

        m_fs_cfg[i].lfs_cfg.read_size = 4;
        m_fs_cfg[i].lfs_cfg.prog_size = 4;
        m_fs_cfg[i].lfs_cfg.cache_size = 256;
        m_fs_cfg[i].lfs_cfg.lookahead_size = 64;
        m_fs_cfg[i].lfs_cfg.block_cycles = 1000;

        m_fs_cfg[i].lfs_cfg.file_max = LFS_FILE_MAX;
        m_fs_cfg[i].lfs_cfg.name_max = LFS_NAME_MAX;
        
/* 准备挂载 */
        result = SetDefaultMountPath(i, m_fs_cfg[i].mount_point);
        if (result != VFS_OK)
        {
            PRINT_ERR("SetDefaultMountPath(%d, %d) failed(%d)\n", i, m_fs_cfg[i].mount_point, result);
            continue;
        }
        
/* 挂载目录 */
        result = mount(NULL, m_fs_cfg[i].mount_point, "littlefs", 0, &m_fs_cfg[i].lfs_cfg);
        printf("%s: mount fs on '%s' %s\n", __func__, m_fs_cfg[i].mount_point, (result == 0) ? "succeed" : "failed");
        if (CheckPathIsMounted(m_fs_cfg[i].mount_point, &file_op_info) == TRUE) {
            int lfs_ret = lfs_mkdir(&file_op_info->lfsInfo, m_fs_cfg[i].mount_point);
            if (lfs_ret == LFS_ERR_OK) {
                PRINT_LOG("create root dir(%s) success.\n", m_fs_cfg[i].mount_point);
            } else if (lfs_ret == LFS_ERR_EXIST) {
                PRINT_LOG("root dir(%s) exist.\n", m_fs_cfg[i].mount_point);
            } else {
                PRINT_LOG("create root dir(%s) failed.", m_fs_cfg[i].mount_point);
            }
        }
    }
    
    return HDF_SUCCESS;
}

2.2、BUILD.gn BUILD.gn负责将fs_driver.c编译到内核中,具体源代码如下所示:

import("//drivers/adapter/khdf/liteos_m/hdf.gni")
module_switch = defined(LOSCFG_SOC_SERIES_RK22XX) && defined(LOSCFG_DRIVERS_HDF_PLATFORM) && defined(LOSCFG_FS_LITTLEFS)
module_name = get_path_info(rebase_path("."), "name")
hdf_driver(module_name) {
  sources = [
    "fs_driver.c",
  ]
  include_dirs = [ "." ]
}

3、测试程序 我在main函数中添加一个任务,负责每隔5秒读写文件。具体代码如下所示:

/* 文件系统测试 */
static void file_rw()
{
    static unsigned int cur = 0;
    char file_name[] = "/data/a.txt";
    int fd_w, fd_r;
    unsigned char buffer[256];

    /* 写操作 */
    fd_w = open(file_name, O_WRONLY | O_CREAT);
    if (fd_w == -1)
    {
        printf("write: %s open failed!\n", file_name);
        return;
    }
    memset(buffer, 0, sizeof(buffer));
    snprintf(buffer, sizeof(buffer), "Hello World and %d\n", cur++);
    printf("write: %s", buffer);
    write(fd_w, buffer, strlen(buffer));
    close(fd_w);

    /* 读操作 */
    fd_r = open(file_name, O_RDONLY);
    if (fd_r == -1)
    {
        printf("read: %s open failed!\n", file_name);
        return;
    }
    lseek(fd_r, 0, SEEK_SET);
    memset(buffer, 0, sizeof(buffer));
    read(fd_r, buffer, sizeof(buffer));
    printf("read: %s", buffer);
    close(fd_r);
}

static void IotProcess(void *arg)
{
    static const unsigned int SLEEP_MAXSEC = 5;
    
    while (1)
    {
        printf("%s: sleep %d sec!\n", __func__, SLEEP_MAXSEC);
        
        /* 文件系统测试 */
        file_rw();

        
        LOS_Msleep(SLEEP_MAXSEC * 1000);
    }
}

三、实验结果   程序编译烧写到开发板后,按下开发板的RESET按键,通过串口软件查看日志如下:

[MAIN:D]Main: OpenHarmony start schedule...
Entering scheduler
IotProcess: sleep 5 sec!
write: Hello World and 0
read: Hello World and 0
IotProcess: sleep 5 sec!
write: Hello World and 1
read: Hello World and 1
IotProcess: sleep 5 sec!
write: Hello World and 2
read: Hello World and 2
......

想了解更多关于开源的内容,请访问:

51CTO 开源基础软件社区

https://ost.51cto.com/#bkwz