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 |....@...........|
解析:
magic
:0x1985
nodetype
:0x0001
(JFFS2_NODETYPE_INODE)totlen
:0x0040
(64 字节)ino
:0x00000001
4. 使用 jffs2dump 验证
jffs2dump -b jffsimage.bin
Magic: 0x1985
Node type: inode
Inode: 1
Version: 1
Data size: 32 byte