linux jffs文件系统

1,JFFS 是一种经典的 Flash 文件系统,通过日志结构和垃圾回收机制,解决了 Flash 存储的诸多问题。虽然在大容量设备上逐渐被 UBIFS 取代,但在小容量嵌入式设备中仍具有重要价值。

2,rootfs.jffs文件解析查看

2.1 通用头部结构体 jffs2_raw_node_ref

struct jffs2_raw_node_ref {
    struct jffs2_raw_node_ref *next_in_ino; // 同一 inode 的下一个节点
    uint32_t flash_offset;                 // Flash 偏移
    uint32_t totlen;                       // 总长度
    uint32_t hdr_crc;                      // 头部 CRC
};

2.2 目录项节点 jffs2_raw_dirent

struct jffs2_raw_dirent {
    uint16_t magic;        // 魔数 0x1985
    uint16_t nodetype;     // 节点类型 JFFS_NODETYPE_DIRENT
    uint32_t totlen;       // 总长度
    uint32_t hdr_crc;      // 头部 CRC
    uint32_t pino;         // 父 inode 号
    uint32_t version;      // 版本号
    uint32_t ino;          // inode 号
    uint32_t mctime;       // 修改时间
    uint8_t nsize;         // 名字长度
    uint8_t type;          // 文件类型
    uint8_t unused[2];     // 对齐填充
    uint32_t node_crc;     // 节点 CRC
    uint32_t name_crc;     // 名字 CRC
    uint8_t name[0];       // 文件名(变长)
};

字段说明:

  • magic:固定值 0x1985,用于识别 JFFS 节点。
  • nodetype:标识节点类型(如目录项、文件数据)。
  • totlen:节点总长度(包括头部和数据)。
  • pino 和 ino:父 inode 和当前 inode 号。
  • name:文件名,长度由 nsize 决定。

2.3 文件数据节点 jffs2_raw_inode

struct jffs2_raw_inode {
    uint16_t magic;        // 魔数 0x1985
    uint16_t nodetype;     // 节点类型 JFFS_NODETYPE_INODE
    uint32_t totlen;       // 总长度
    uint32_t hdr_crc;      // 头部 CRC
    uint32_t ino;          // inode 号
    uint32_t version;      // 版本号
    uint32_t mode;         // 文件权限
    uint16_t uid;          // 用户 ID
    uint16_t gid;          // 组 ID
    uint32_t isize;        // 文件大小
    uint32_t atime;        // 访问时间
    uint32_t mtime;        // 修改时间
    uint32_t ctime;        // 创建时间
    uint32_t offset;       // 数据偏移
    uint32_t csize;        // 压缩后大小
    uint32_t dsize;        // 解压后大小
    uint8_t compr;         // 压缩类型
    uint8_t usercompr;     // 用户指定压缩
    uint16_t flags;        // 标志位
    uint32_t data_crc;     // 数据 CRC
    uint32_t node_crc;     // 节点 CRC
    uint8_t data[0];       // 文件数据(变长)
};

字段说明:

2.4 JFFS2 内部实现

JFFS2 在内核中通过以下mtd函数实现文件读写,并调用 MTD 接口操作 Flash:

核心操作实现

1. 模拟 Flash 读写接口

int flash_read(struct file *file, void *buf, size_t len, loff_t offset) {
    kernel_read(file, buf, len, &offset);
    return 0;
}

int flash_write(struct file *file, const void *buf, size_t len, loff_t offset) {
    kernel_write(file, buf, len, &offset);
    return 0;
}

int flash_erase(struct file *file, loff_t offset, size_t len) {
    char zero[len];
    memset(zero, 0xff, len);  // Flash 擦除后为 0xFF
    flash_write(file, zero, len, offset);
    return 0;
}

2. 挂载文件系统

int jffs2_mount(struct jffs2_sb_info *sbi, const char *filename) {
    sbi->flash_file = filp_open(filename, O_RDWR | O_CREAT, 0644);
    if (IS_ERR(sbi->flash_file)) {
        return PTR_ERR(sbi->flash_file);
    }

    INIT_LIST_HEAD(&sbi->inodes);
    INIT_LIST_HEAD(&sbi->dirents);

    // 扫描 Flash,构建内存中的文件系统树
    jffs2_scan_flash(sbi);

    return 0;
}

3. 写入文件

int jffs2_write_file(struct jffs2_sb_info *sbi, const char *filename, const void *data, size_t len) {
    struct jffs2_raw_inode inode;
    struct jffs2_raw_dirent dirent;
    loff_t offset = 0;

    // 1. 写入 inode 节点
    inode.hdr.magic = JFFS2_MAGIC;
    inode.hdr.nodetype = JFFS2_NODETYPE_INODE;
    inode.hdr.totlen = sizeof(inode) + len;
    inode.hdr.ino = get_next_ino(sbi);
    inode.hdr.version = 1;
    inode.mode = S_IFREG | 0644;
    inode.dsize = len;
    memcpy(inode.data, data, len);

    flash_write(sbi->flash_file, &inode, sizeof(inode) + len, offset);
    offset += sizeof(inode) + len;

    // 2. 写入 dirent 节点
    dirent.hdr.magic = JFFS2_MAGIC;
    dirent.hdr.nodetype = JFFS2_NODETYPE_DIRENT;
    dirent.hdr.totlen = sizeof(dirent) + strlen(filename);
    dirent.hdr.ino = inode.hdr.ino;
    dirent.pino = 1;  // 根目录 inode 号
    dirent.nsize = strlen(filename);
    dirent.type = DT_REG;
    memcpy(dirent.name, filename, dirent.nsize);

    flash_write(sbi->flash_file, &dirent, sizeof(dirent) + dirent.nsize, offset);

    // 3. 更新内存中的文件系统树
    jffs2_add_inode(sbi, &inode);
    jffs2_add_dirent(sbi, &dirent);

    return 0;
}

4. 读取文件

int jffs2_read_file(struct jffs2_sb_info *sbi, const char *filename, void *buf, size_t len) {
    struct jffs2_dirent *dirent = jffs2_lookup_dirent(sbi, filename);
    if (!dirent) {
        return -ENOENT;
    }

    struct jffs2_inode *inode = jffs2_get_inode(sbi, dirent->ino);
    if (!inode) {
        return -ENOENT;
    }

    // 从 Flash 中读取 inode 数据
    struct jffs2_raw_inode raw_inode;
    loff_t offset = 0;
    flash_read(sbi->flash_file, &raw_inode, sizeof(raw_inode), offset);
    memcpy(buf, raw_inode.data, min(len, raw_inode.dsize));

    return 0;
}

5. 删除文件

int jffs2_unlink_file(struct jffs2_sb_info *sbi, const char *filename) {
    struct jffs2_dirent *dirent = jffs2_lookup_dirent(sbi, filename);
    if (!dirent) {
        return -ENOENT;
    }

    // 写入删除标记的 dirent 节点
    struct jffs2_raw_dirent dirent_del;
    dirent_del.hdr.magic = JFFS2_MAGIC;
    dirent_del.hdr.nodetype = JFFS2_NODETYPE_DIRENT;
    dirent_del.hdr.totlen = sizeof(dirent_del) + strlen(filename);
    dirent_del.hdr.ino = 0;  // 删除标记
    dirent_del.pino = 1;
    dirent_del.nsize = strlen(filename);
    dirent_del.type = DT_UNKNOWN;
    memcpy(dirent_del.name, filename, dirent_del.nsize);

    loff_t offset = 0;
    flash_write(sbi->flash_file, &dirent_del, sizeof(dirent_del) + dirent_del.nsize, offset);

    // 从内存中删除
    jffs2_remove_dirent(sbi, dirent);

    return 0;
}

四、垃圾回收机制

JFFS 的垃圾回收用于清理无效节点,合并有效数据:

int jffs2_garbage_collect(struct jffs2_sb_info *sbi) {
    loff_t offset = 0;
    struct jffs2_raw_node_ref node;

    while (flash_read(sbi->flash_file, &node, sizeof(node), offset) == 0) {
        if (node.magic != JFFS2_MAGIC) {
            break;
        }

        // 检查节点是否有效
        if (!jffs2_node_is_valid(sbi, &node)) {
            // 无效节点,跳过
            offset += node.totlen;
            continue;
        }

        // 有效节点,复制到新位置
        void *data = kmalloc(node.totlen, GFP_KERNEL);
        flash_read(sbi->flash_file, data, node.totlen, offset);
        flash_write(sbi->flash_file, data, node.totlen, sbi->write_offset);
        sbi->write_offset += node.totlen;
        kfree(data);

        offset += node.totlen;
    }

    // 擦除旧块
    flash_erase(sbi->flash_file, 0, offset);

    return 0;
}

五、总结

完整示例:JFFS 文件系统分析流程

1. 导出 JFFS 镜像

dd if=/dev/mtdblock0 of=jffsimage.bin

2. 使用 hexdump 查找节点

hexdump -C jffsimage.bin | grep "85 19"

3. 解析节点内容

00000000  85 19 01 00 40 00 00 00  00 00 00 00 01 00 00 00  |....@...........|

解析:

  • magic0x1985
  • nodetype0x0001(JFFS2_NODETYPE_INODE)
  • totlen0x0040(64 字节)
  • ino0x00000001

4. 使用 jffs2dump 验证

jffs2dump -b jffsimage.bin
Magic: 0x1985
Node type: inode
Inode: 1
Version: 1
Data size: 32 byte

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌上花开缓缓归以

你的鼓励将是我创作的最大动力,

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值