暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从零写一个文件系统-01

囧囧妹 2022-08-07
177

点击上方蓝字【囧囧妹】一起学习,一起成长!

一,开篇

在更新《一起撸linux内核系列的时候提到过在写一个文件系统,现在开始把自己写的那个文件系统简单更新下,也算是填之前留下的一个坑。

二,为什么要自己写文件系统
  • 背景
之前在开发一款国产的单片机,单片机内存分布是这样的,内部有块512KB的flash和1MB的ram,外部接一块16MB的norflash,根据业务需求我用512KB的flash来存储bin文件,1MB的ram来做运行内存,16MB的外部flash来做文件和log的存储,注意这里是块norflash,既然存储文件为什么不选用nand或者emmc呢,原因只有一个该单片机不支持,同时该单片机还要与服务器之间进行pcie通信,服务器的业务要支持虚拟化,所以单片机还要支撑服务器上层虚拟化功能。架构框图如下:

处于操作便利的考虑,在norflash上存在的虚拟机采用目录隔离,每个虚拟机都参在一个根目录,根目录下有该虚拟机所有相关文件,通过norflash的选型可以看出norflash的扇区为128KB,norflash大小为16MB,在参考了几个开源文件系统(fatfs、littlefs)后发现都有一个致命的问题就是擦除很慢,这也是norflash所决定的,而且在使用过程中频繁擦除对于norflash的寿命也会有影响,总体还是硬件受限,如果换上一片emmc或者nand其实问题都迎刃而解了。
那么如何解决这些问题呢?在思来想去后开始花了半天时间设计了一个针对现有业务的文件系统,主要解决:
1,写入之前擦除慢。
2,频繁擦除。
3,针对大块扇区norflash使用。
4,更方便管理文件,支持单片norflash上初始化多个文件系统。

  • 解决方案
首先要知道的是flash的一个特性,只能写0不能写1。flash只有在擦除时才会将所有位写1,在用户往flash上写入数据时只会将对应的位清为0,所以我们可以充分利用这个特性,将flash划分为如下:
目录表项:用于管理所有文件,每个表项包含文件名hash值及文件一级指针。
inode:文件内容存放时候以inode为单元,每个扇区为1个inode
一级指针:用于索引文件首部信息
二级指针...n级指针:用于索引文件内容


 目录表项结构体如下:
    #define HDR_LEN_IN_HARD  (32+4)
    typedef struct HDR_ST{
    u8 hash[32];
    u32 addr; //文件存放的物理地址首地址
    struct semaphore sem; //文件写操作时锁
    struct HDR_ST *next;
    }hdr_st;


    typedef struct {
    u32 dir_addr;//文件目标表基地址(缓存到ram,不写入磁盘)
    u32 count;//当前存储文件数量
    hdr_st *phdr_front;//文件目录缓存
    hdr_st *phdr_rear;//文件目录缓存
    }dir_st;
    目录表项在程序首次运行时会缓存到ram中,通过该表项就可以掌握norflash上所有的文件信息,为了快速找到某个文件我们对文件名做hash保留hash值前16字节用于快速查找文件,索引到hash值后就可以找到该hash值对应的文件一级指针,该指针会指向文件属性值所在的norflash地址,这样就可以索引到文件属性和内容。
    文件属性的定义我们也以一个inode为单元,inode定义如下:
      //inode属性值
      typedef struct {
      u8 state; //inode状态
      u8 name_len; //文件名长度-长度为0则为首块
      u16 used_len; //该inode已使用长度,只包含文件内容,不含头部和尾部的四个字节
      }attribute_st;


      typedef struct {
      attribute_st attr; //文件属性值
      u16 total_len; //文件空间大小
      u16 file_len; //文件目前的长度
      u32 next_inode_addr; //该文件下一个块的地址
      }inode_st;

      每个inode,我们以1K为单位,文件属性定义如下,我们站在flash角度在定义属性:

          开始4字节:

              (3)8bit:00-该inode已使用,ff-空闲

             (2)8bit:文件名长度(最长256字节)--(长度为0-该inode区为附属区,其它值为文件起始区)

              (1-0)16bit:当前inode区已使用长度

          偏移4字节:

              (1)16bit:文件空间总体大小

              (0)16bit:当前文件占用大小

              文件名+文件内容

      最后4字节存储下一inode区地址


      之后我们定义如下通用宏,用来兼容其它容量的norflash:
        #define FS_ONE_BLK_SIZE  (1024)  //1K字节
        #define FS_ROOM_MAX(addr) (VSM_FS_END_ADDR(addr)-VSM_FS_BASE_ADDR(addr)) //文件系统最大空间
        #define FS_BLK_COUNT(addr) ((VSM_FS_END_ADDR(addr)-VSM_FS_BASE_ADDR(addr))/FS_ONE_BLK_SIZE) //文件系统块数量
        #define FS_INODE_ROOM_MAX(addr) (FS_ROOM_MAX(addr)-FS_ONE_BLK_SIZE) //inode区空间最大值
        #define FS_INODE_CONTENT_LEN(addr) ({ \
        u16 len = 0; \
        ebc_flash_read(addr, (u8 *)&len, 2); \
        (FS_ONE_BLK_SIZE-len-4-10); \
        }) //获取inode节点内部有效数据长度最大值


        //将索引块标记为等待回收
        #define fs_set_inode_attr_invalid(inode_addr) fs_set_inode_attr(inode_addr, INODE_IS_USING, INODE_IS_UNVALID, 0, 0, 0, 0, nil);


        //sb块默认文本长度
        #define FS_ISB_INODE_LEN(len) (FS_ONE_BLK_SIZE-len-4-10)


        #define INODE_IS_USING 0x00 //
        #define INODE_IS_VALID 0xff
        #define INODE_IS_UNUSING 0xff
        #define INODE_IS_UNVALID 0x00


        #define memcpy_fromfs(to, from, n) memcpy((to),(from),(n))
        #define memcpy_tofs(to, from, n) memcpy((to),(from),(n))




        //同步磁盘时的脏数据
        typedef u32 (*file_sync_oper_st)();

            现在结构和描述都有了,接下来就来看看程序如何设计和实现。


        觉得不错,点击“分享”,“赞”,“在看”传播给更多热爱嵌入式的小伙伴吧!

        文章转载自囧囧妹,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论