23 · datalayout = 4(CHUNK_BASED)的寻址
基线
- 内核源码:
/home/linux/erofs/linux-stable(Linux 7.3-rc5)——fs/erofs/data.c- 用户态:
erofs-utils1.9.4 ——lib/inode.c/lib/blobchunk.c/mkfs/main.c- 统一假设:
bs = 4096(blkszbits = 12)、小端序本文所有偏移与数值均为实测(
dump.erofs+od),不使用占位符。
0. 这个函数解决什么问题
datalayout = 4(EROFS_INODE_CHUNK_BASED)的文件,
数据不再要求连续存放——文件被切成固定大小的 chunk,每个 chunk 由 chunk index 数组里的一项
指向一个物理块(甚至可以指向另一个设备)。
erofs_map_chunks() 的任务:
给定逻辑地址 m_la
⇒ 算出它落在第几个 chunk
⇒ 找到该 chunk 的索引项(在元数据区)
⇒ 解出物理块地址(可能带 device_id)
⇒ 尽量向后合并连续的 chunk,一次映射尽量长
⇒ 回填 map->m_pa / m_llen / m_deviceid / m_flags
与 flat(0/2)的差别:flat 是”起始块 + 线性偏移”一步算出;chunk 是先查索引、再算物理地址的两段跳。
1. mkfs 侧:datalayout = 4 是怎么造出来的
1.1 --chunksize=N → 全局 chunkbits
/* mkfs/main.c:1268 */
cfg.c_chunkbits = ilog2(i); /* N 必须是 2 的幂,否则报 "chunksize must be a power of two" */
/* 还有联动约束(mkfs/main.c:1509 / 1567 / 1582) */
if (cfg.c_blobdev_path && cfg.c_chunkbits < mkfs_blkszbits) /* --blobdev 必须配 --chunksize */
if (cfg.c_chunkbits < mkfs_blkszbits) /* chunksize 必须大于块大小 */
1.2 未压缩文件改走 chunk 路径
/* erofs-utils/lib/inode.c:707 erofs_write_unencoded_file() */
if (cfg.c_chunkbits &&
inode->datasource != EROFS_INODE_DATA_SOURCE_REBUILD_BLOB) {
inode->u.chunkbits = cfg.c_chunkbits;
/* chunk indexes when explicitly specified */
inode->u.chunkformat = 0;
if (cfg.c_force_chunkformat == FORCE_INODE_CHUNK_INDEXES)
inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES;
return erofs_blob_write_chunked_file(inode, fd, fpos);
}
⇒ 这解释了实测现象:一旦给了 --chunksize,所有”未压缩”文件都变成 layout 4
(连 5 字节的 tiny.txt 也是),于是 flat(0) 与 inline(2) 都不再出现。
1.3 erofs_blob_write_chunked_file():切块 + 去重 + 定格式
/* lib/blobchunk.c:303 */
/* 全稀疏文件 → 直接用一个大 chunk 表示 */
if (lseek(fd, startoff, SEEK_DATA) < 0 && errno == ENXIO)
chunkbits = ilog2(inode->i_size - 1) + 1;
chunksize = 1ULL << chunkbits;
count = DIV_ROUND_UP(inode->i_size, chunksize); /* chunk 个数 */
/* ★多设备必须用 8 B 索引(因为要带 device_id) */
if (sbi->extra_devices)
inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES;
unit = (chunkformat & EROFS_CHUNK_FORMAT_INDEXES) ?
sizeof(struct erofs_inode_chunk_index) : /* 8 */
EROFS_BLOCK_MAP_ENTRY_SIZE; /* 4 */
inode->extent_isize = count * unit; /* 索引数组总字节数 */
inode->chunkindexes = malloc(count * max(sizeof(*idx), sizeof(void *))); /* 先放 blobchunk 指针 */
for (pos = 0; pos < inode->i_size; pos += len) {
/* 空洞区间 → 填 &erofs_holechunk(blkaddr = EROFS_NULL_ADDR) */
/* 有数据 → erofs_blob_getchunk(sbi, chunkdata, len)
算 hash → 查 blob_hashmap ⇒ 命中则复用已有 chunk 的 blkaddr(★去重) */
*(void **)idx++ = chunk;
}
inode->datalayout = EROFS_INODE_CHUNK_BASED; /* ★落地为 4 */
return erofs_blob_mergechunks(inode, chunkbits, ...); /* 合并小 chunk、定最终 chunkbits */
erofs_blob_mergechunks() 末尾把相对值写进格式字:
inode->u.chunkformat = (chunkbits - sbi->blkszbits) | ...
⇒ on-disk 存的是”相对 blkszbits 的差值”,不是绝对位数。这是理解内核解码的关键。
1.4 索引数组落盘
/* lib/inode.c:908 —— 写完 inode 主体 + xattr 之后,紧跟着写 */
if (inode->extent_isize) {
if (inode->datalayout == EROFS_INODE_CHUNK_BASED)
ret = erofs_write_chunk_indexes(inode, ibmgr->vf, off);
/* off = iloc + inode_isize + xattr_isize */
else /* 压缩元数据 */
...
}
/* lib/blobchunk.c:175 erofs_write_chunk_indexes() */
addrmask = _48bit ? BIT_ULL(48) - 1 : BIT_ULL(32) - 1;
startblk &= addrmask;
idx.device_id = cpu_to_le16(chunk->device_id);
idx.startblk_lo = cpu_to_le32(startblk);
idx.startblk_hi = cpu_to_le32(startblk >> 32);
DBG_BUGON(!_48bit && idx.startblk_hi); /* 非 48bit 时高位必须为 0 */
/* unit == 4 时只写低 32 位(一个 __le32) */
另:inode 占槽计算处也体现了对齐
lib/inode.c:1000if (inode->extent_isize) inodesize = roundup(inodesize, 8) + inode->extent_isize;
1.5 8 B chunk index(INDEXES 模式)的构造详解
何时启用 8 B
| 方式 | 代码 | 说明 |
|---|---|---|
| 显式强制 | mkfs/main.c:559:-Eforce-chunk-indexes → cfg.c_force_chunkformat = FORCE_INODE_CHUNK_INDEXES |
用户指定 |
| 多设备自动 | lib/blobchunk.c:if (sbi->extra_devices) inode->u.chunkformat \|= EROFS_CHUNK_FORMAT_INDEXES; |
有 --blobdev 时必然启用 |
原因很直接:只有 8 B 索引项里才有 device_id 字段——要把 chunk 指向别的设备就必须用它。
(反向选项:-Eforce-inode-blockmap ⇒ FORCE_INODE_BLOCK_MAP,强制 4 B 数组。)
mkfs 的中间结构 erofs_blobchunk
/* lib/blobchunk.c:18 */
struct erofs_blobchunk {
union { struct hashmap_entry ent; struct list_head list; };
char sha256[32]; /* ★用于跨文件去重 */
unsigned int device_id; /* ★8 B 索引才用得上 */
union { erofs_off_t chunksize; erofs_off_t sourceoffset; };
erofs_blk_t blkaddr;
};
注意它不是 on-disk 结构:inode->chunkindexes 先当作 struct erofs_blobchunk * 的指针数组使用,
落盘时才转换成真正的 erofs_inode_chunk_index。
device_id 从哪来
/* lib/blobchunk.c erofs_blob_getchunk() */
blkpos = lseek(blobfile, 0, SEEK_CUR); /* chunk 写到 blobfile 的当前位置 */
DBG_BUGON(erofs_blkoff(sbi, blkpos));
if (sbi->extra_devices)
chunk->device_id = 1; /* ★blob 设备 → 1 */
else
chunk->device_id = 0; /* 主设备 → 0 */
chunk->blkaddr = erofs_blknr(sbi, blkpos); /* 设备内块号 */
⇒ 与实测一致:device_id 恒为 1(mkfs 当前只支持一个 blob 设备)。
unit / extent_isize / 数组分配
/* erofs_blob_write_chunked_file() */
if (sbi->extra_devices)
inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES;
unit = (chunkformat & EROFS_CHUNK_FORMAT_INDEXES) ?
sizeof(struct erofs_inode_chunk_index) : /* 8 */
EROFS_BLOCK_MAP_ENTRY_SIZE; /* 4 */
inode->extent_isize = count * unit; /* 索引数组总字节数 */
inode->chunkindexes = malloc(count * max(sizeof(*idx), sizeof(void *)));
/* 8 B 模式:max(8, 8) = 8;4 B 模式:max(8, 4) = 8(指针本身也是 8 B) */
48BIT 位的判定(与 INDEXES 相互独立)
/* lib/blobchunk.c:138 erofs_inode_fixup_chunkformat() */
_48bit = inode->u.chunkformat & EROFS_CHUNK_FORMAT_48BIT;
if (_48bit) return;
for (每个 chunk) {
if (chunk->blkaddr == EROFS_NULL_ADDR) continue;
if (chunk->device_id) {
if (chunk->blkaddr > UINT32_MAX) { _48bit = true; break; }
} else if (remapped_base + chunk->blkaddr > UINT32_MAX) { _48bit = true; break; }
}
if (_48bit) inode->u.chunkformat |= EROFS_CHUNK_FORMAT_48BIT;
⇒ 只有块地址真超过 32 位时才补置 48BIT。
填索引项:8 B 与 4 B 的写入分支
/* lib/blobchunk.c:175 erofs_write_chunk_indexes() */
addrmask = _48bit ? BIT_ULL(48) - 1 : BIT_ULL(32) - 1;
startblk &= addrmask;
idx.device_id = cpu_to_le16(chunk->device_id);
idx.startblk_lo = cpu_to_le32(startblk);
idx.startblk_hi = cpu_to_le32(startblk >> 32); /* 字段是 __le16,实际只写入低 16 位 */
DBG_BUGON(!_48bit && idx.startblk_hi); /* 非 48bit 时高位必须为 0 */
...
if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE)
memcpy(inode->chunkindexes + dst, &idx.startblk_lo, unit); /* 4 B:只写低 32 位 */
else
memcpy(inode->chunkindexes + dst, &idx, sizeof(idx)); /* 8 B:写整个结构 */
...
off = roundup(off, unit); /* ★按 unit 对齐 */
return erofs_io_pwrite(vf, inode->chunkindexes, off, inode->extent_isize);
小细节:
startblk_hi在结构体里是__le16(2 B),上面用cpu_to_le32()赋值会被截断为低 16 位; 小端序下等价于cpu_to_le16(),结果正确。于是startblk_hi(16) + startblk_lo(32) = 48 位块地址, 正好对应 48BIT 模式。
on-disk 8 B 索引项的字节布局
相对偏移 0 2 4 8
+--------+--------+---------------+
|startblk| device | startblk_lo |
| _hi | _id | (le32) |
+--------+--------+---------------+
le16 le16 le32
\_______ 48 位块地址 _______/
↑
device_id 单独占 2 B(不参与块地址)
实测对照:多设备(8 B)vs 单设备(4 B)
多设备 md.erofs |
单设备 chunk.erofs |
|
|---|---|---|
| mkfs 选项 | -b4096 --chunksize=65536 -zlz4 --blobdev=blob.img |
-b4096 --chunksize=65536 |
incompat |
lz4_0padding chunked_file device_table |
chunked_file |
| inode 版本 | extended(64 B) | compact(32 B) |
| 文件 | /big.bin nid=45,layout 4 |
/big.txt nid=40,layout 4 |
chunkformat |
0x0024 = 4(delta) | 0x20(INDEXES) |
0x0004 = 4(delta) |
| unit | 8 | 4 |
| 索引起点 | iloc(1440) + 64(inode_isize) + 0(xattr) ⇒ 1504 |
iloc(1280) + 32 + 0 ⇒ 1312 |
| 索引项内容 | 00 00 01 00 00 00 00 00 ⇒ device_id=1, blkaddr=0 |
01 00 00 00 ⇒ blkaddr=1 |
| 数据落点 | blob.img 偏移 0(与 src/big.bin 前 16 B 逐字节一致 ✓) |
主镜像 block 1 |
⚠️ inode 版本会影响索引起点:compact 时 iloc + 32、extended 时 iloc + 64。
md.erofs 用 extended 是因为文件 mtime ≠ 镜像 epoch(lib/inode.c:1203 的判定),
不是因为 chunk —— 这两者是独立的选择。
与内核解码的对称关系
mkfs 写进去的三项,被内核 erofs_map_chunks() 的 8 B 分支原样读回:
addr = ((startblk_hi << 32) | startblk_lo) & addrmask; /* 48/32 位块地址 */
if (addr ^ (EROFS_NULL_ADDR & addrmask))
addr |= (device_id & device_id_mask) << 48; /* 还原 device_id */
写与读完全对称 ✓
2. on-disk 布局:chunk index 数组在哪、长什么样
2.1 布局图
iloc = meta_blkaddr*bs + nid*32
│
▼
+--------------------------------+ ← iloc
| erofs_inode_compact (32 B) |
| i_format bit1-3 = 4 | ⇒ datalayout = 4 CHUNK_BASED
| i_u 解释为 chunk_info: |
| c.format (le16) @+16 | ← chunkbits 差值 + INDEXES/48BIT 位
| c.reserved @+18 |
+--------------------------------+
| xattr ibody (xattr_isize) | 0 或 12+(n-1)*4
+--------------------------------+
| ALIGN(..., unit) | ← 按索引项大小对齐(4 或 8)
+--------------------------------+
| chunk index[0] | unit=8: device_id/startblk_hi/startblk_lo
| chunk index[1] | unit=4: 仅一个 __le32 块地址
| ... |
| chunk index[count-1] | count = ceil(i_size / chunksize)
+--------------------------------+
│ 解码后
▼
data area(或 blob 设备的)chunk 数据
2.2 unit 的选择:8 B 还是 4 B
| 条件 | unit | 索引项结构 | 能否带 device_id |
|---|---|---|---|
chunkformat & EROFS_CHUNK_FORMAT_INDEXES (0x20) |
8 | erofs_inode_chunk_index |
✅ 能 |
| 否则 | 4 | 裸 __le32 块地址数组 |
❌ 不能 |
⇒ 多设备镜像必然是 8 B(sbi->extra_devices 时 mkfs 强制开 INDEXES)。
2.3 chunkformat 的三个位域
| 位 | 名称 | 含义 |
|---|---|---|
| bit 0–4 | EROFS_CHUNK_FORMAT_BLKBITS_MASK (0x1F) |
chunksize = bs « 该值(相对值!) |
| bit 5 | EROFS_CHUNK_FORMAT_INDEXES (0x20) |
用 8 B 索引项(可带 device_id) |
| bit 6 | EROFS_CHUNK_FORMAT_48BIT (0x40) |
块地址为 48 位(>4 GiB 镜像) |
内核读取端还原(fs/erofs/inode.c:208):
vi->chunkformat = le16_to_cpu(copied.i_u.c.format);
if (vi->chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { ... -EOPNOTSUPP ... }
vi->chunkbits = sb->s_blocksize_bits +
(vi->chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK);
2.4 实测:chunk.erofs 的 on-disk 字节
镜像:/home/linux/erofs/tmp-isize/chunk.erofs(mkfs.erofs -b4096 --chunksize=65536),
文件 /big.txt:nid=40、i_size=20000、layout=4。
iloc = 40*32 = 1280
i_u(chunk_info)@1296 = 04 00 00 00
⇒ chunkformat = 0x0004
低 5 位 = 4 ⇒ chunksize = 4096 << 4 = 65536 ✓
无 INDEXES(0x20) ⇒ unit = 4
无 48BIT(0x40) ⇒ addrmask = BIT_ULL(32)-1
chunk index 起点 = ALIGN(1280 + 32(inode_isize) + 0(xattr_isize), 4) = 1312
@1312: 01 00 00 00 → le32 = 1 ⇒ 该 chunk 数据在 block 1
(chunksize 65536 > i_size 20000 ⇒ count = 1 ⇒ 索引数组只有 4 B。)
3. 内核 erofs_map_chunks() 逐段解析
位置:
fs/erofs/data.c:87;由erofs_map_blocks()在vi->datalayout == EROFS_INODE_CHUNK_BASED时调用。
3.1 完整代码
static int erofs_map_chunks(struct inode *inode, struct erofs_map_blocks *map)
{
struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
struct super_block *sb = inode->i_sb;
struct erofs_inode *vi = EROFS_I(inode);
struct erofs_inode_chunk_index *idx;
unsigned int unit = vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES ?
sizeof(*idx) : EROFS_BLOCK_MAP_ENTRY_SIZE;
erofs_blk_t addrmask = (vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) ?
BIT_ULL(48) - 1 : BIT_ULL(32) - 1;
u64 nr = map->m_la >> vi->chunkbits, chunksize = 1ULL << vi->chunkbits;
erofs_off_t pos = ALIGN(erofs_iloc(inode) + vi->inode_isize +
vi->xattr_isize, unit) + unit * nr;
/* m_llen will be clamped to EOF in the end */
erofs_off_t endpos = round_up(pos + 1, sb->s_blocksize);
u64 last, addr;
idx = erofs_read_metabuf(&buf, sb, pos, erofs_inode_in_metabox(inode));
if (IS_ERR(idx))
return PTR_ERR(idx);
map->m_la = nr << vi->chunkbits;
map->m_llen = 0;
nr = 0;
do {
if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE) {
addr = le32_to_cpu(((__le32 *)idx)[nr]);
if (addr == (u32)EROFS_NULL_ADDR)
addr = EROFS_NULL_ADDR;
} else {
addr = (((u64)le16_to_cpu(idx[nr].startblk_hi) << 32) |
le32_to_cpu(idx[nr].startblk_lo)) & addrmask;
if (addr ^ (EROFS_NULL_ADDR & addrmask))
addr |= (u64)(le16_to_cpu(idx[nr].device_id) &
EROFS_SB(sb)->device_id_mask) << 48;
else
addr = EROFS_NULL_ADDR;
}
if (!nr) {
last = addr;
continue;
}
/* expand and account the prior chunk here */
map->m_llen += chunksize;
if (last != EROFS_NULL_ADDR)
last += erofs_blknr(sb, chunksize);
} while (addr == last && pos + (++nr) * unit < endpos);
if (last != EROFS_NULL_ADDR) {
map->m_pa = erofs_pos(sb, last & addrmask) - map->m_llen;
map->m_deviceid = last >> 48;
map->m_flags = EROFS_MAP_MAPPED;
}
if (addr == last)
map->m_llen += chunksize;
map->m_llen = min_t(erofs_off_t, map->m_llen,
round_up(inode->i_size - map->m_la, sb->s_blocksize));
erofs_put_metabuf(&buf);
return 0;
}
3.2 ① 定参数、算索引项地址
nr = m_la >> chunkbits → 第几个 chunk
pos = ALIGN(iloc + inode_isize + xattr_isize, unit) + unit * nr
endpos = round_up(pos + 1, bs) → ★不允许读穿当前元数据块
idx = erofs_read_metabuf(&buf, sb, pos, in_metabox) ← 元数据通道
m_la = nr << chunkbits → 逻辑地址对齐到 chunk 起点
ALIGN(..., unit) 与 mkfs 的落盘对齐一致(mkfs 侧 roundup(..., 8),而 8 是 4 的倍数 ⇒ 两边同点)。
⚠️ 澄清:idx 到底指向第几个 chunk index?
指向第 nr 个(绝对序号 = m_la >> chunkbits)那一项,不是第 0 个。
理由:pos 里已经加了 unit * nr;而 erofs_read_metabuf() → erofs_bread() 返回的正是
「所在元数据块内、offset 处」的指针(buf->base + (offset & (bs-1))),所以 idx 落在第 nr 项上。
更要注意的是——变量 nr 在这里换了一次含义:
| 阶段 | nr 的含义 |
取值 |
|---|---|---|
算 pos 时 |
绝对序号(目标 chunk 是第几个) | m_la >> chunkbits |
循环里(执行 nr = 0 之后) |
相对 idx 的偏移量 |
0, 1, 2 … |
⇒ 循环中的 idx[nr] 实际访问的是绝对第 (原 nr + nr) 项。这是读这段代码最容易看错的地方。
多 chunk 例子(chunksize = 4096、i_size = 20000 ⇒ count = 5、unit = 4,数组起点 1312):
m_la = 12288 ⇒ 原 nr = 12288 >> 12 = 3
pos = 1312 + 4*3 = 1324 ⇒ idx 指向 @1324(第 3 项)
循环 nr=0 → 读 @1324(绝对第 3 项)
nr=1 → 读 @1328(绝对第 4 项)
注:本文 §3.6 那个实测例(
/big.txt,chunksize=65536⇒ 只有 1 个 chunk,nr = 0) 中idx恰好也落在第 0 项,特殊情形下看不出差别,不要据此认为它总是指向第 0 项。
3.3 ② 解码索引项(含 device_id)
4 B 模式:直接取 __le32,等于 (u32)EROFS_NULL_ADDR 时判为洞。
8 B 模式(关键):
addr = ((startblk_hi << 32) | startblk_lo) & addrmask; /* 块地址只占 48/32 位 */
if (addr ^ (EROFS_NULL_ADDR & addrmask)) /* 非洞 */
addr |= (device_id & device_id_mask) << 48; /* ★device_id 编到 bit48 以上 */
else
addr = EROFS_NULL_ADDR;
⇒ 索引项只有 48 位存块地址,高 16 位(bit48–63)用来带 device_id。
device_id_mask = roundup_pow_of_two(extra_devices + 1) - 1(super.c)。
3.4 ③ 向后合并连续 chunk
do {
解码 addr
if (!nr) { last = addr; continue; } /* 第一项只作为基准 */
map->m_llen += chunksize; /* 计入前一个 chunk */
if (last != NULL_ADDR) last += erofs_blknr(sb, chunksize); /* 物理地址推进 */
} while (addr == last && pos + (++nr) * unit < endpos);
addr == last:下一个 chunk 物理地址正好接上 ⇒ 连续,继续扩。< endpos:绝不读穿当前元数据块(索引数组跨块时,本次只映射块内部分,剩下的下次再映射)。
3.5 ④ 回填结果(★回退技巧)+ clamp
if (last != EROFS_NULL_ADDR) {
map->m_pa = erofs_pos(sb, last & addrmask) - map->m_llen; /* ★回退到起点 */
map->m_deviceid = last >> 48; /* 取出设备号 */
map->m_flags = EROFS_MAP_MAPPED;
}
map->m_llen = min_t(erofs_off_t, map->m_llen,
round_up(inode->i_size - map->m_la, sb->s_blocksize)); /* clamp 到 EOF */
因为循环里 last 已一路往前推进,所以要用 erofs_pos(last) - m_llen 回退到这段映射的起始物理地址;
last & addrmask 把 device_id 剥掉只留块地址,last >> 48 反过来取出设备号。
3.6 端到端实例(真实数值,可与 od 逐字节比对)
输入:inode = /big.txt (nid=40, i_size=20000, chunksize=65536, unit=4)
m_la = 0
→ nr = 0 >> 16 = 0
→ pos = ALIGN(1280+32+0, 4) + 4*0 = 1312
→ endpos= round_up(1312+1, 4096) = 4096
迭代1: 读 @1312 = le32 = 1 ;nr==0 ⇒ last = 1,continue
while: addr(1)==last(1) ✓ 且 1312 + 1*4 = 1316 < 4096 ✓ ⇒ 继续
迭代2: 读 @1316 = 00 00 00 00 → addr = 0 ;nr!=0 ⇒
m_llen += 65536 ⇒ 65536
last += erofs_blknr(4096, 65536)=16 ⇒ 1+16 = 17
while: addr(0) != last(17) ⇒ 退出
回填:
m_pa = erofs_pos(17 & 0xffffffff) - 65536 = 17*4096 - 65536 = 4096 ← block 1 ✓
m_deviceid= 17 >> 48 = 0
m_flags = EROFS_MAP_MAPPED
clamp: m_llen = min(65536, round_up(20000-0, 4096)=20480) = 20480
逻辑 [0 , 20000) 物理 block 1 起,20480 B(按块向上取整)
│ ▲
│ nr = 0 │ m_pa = 4096
▼ │
chunk index[0] @1312 = 01 00 00 00 ─────────┘
(索引项值 = 1 ⇒ block 1)
与 od -j 1312 看到的 01 00 00 00 完全一致 ✓
4. 六个要点小结
| 要点 | 说明 |
|---|---|
| unit 选 8 还是 4 | 由 chunkformat & 0x20(INDEXES)决定;多设备时 mkfs 强制 INDEXES(要带 device_id) |
| chunkbits 是相对值 | on-disk 存 chunkbits - blkszbits;内核还原为 blkszbits + (chunkformat & 0x1F) |
| device_id 藏在高 16 位 | 块地址只用低 48 位,device_id << 48 与之拼成 u64;>>48 取回、& addrmask 剥离 |
| 一次映射尽量长 | 只要 chunk 物理连续就合并;受 endpos(不跨元数据块)与 i_size(clamp)双重限制 |
| hole 处理 | addr == NULL_ADDR ⇒ 不置 MAPPED,上层按洞读零 |
| 回退技巧 | m_pa = erofs_pos(last) - m_llen(因 last 在循环中已推进) |
补充:内核这个
do-while会多读一个索引项(本例读到了数组之后的字节)。 它靠endpos限制在同一元数据块内,且值不等于last就停止扩展——只读不写,配合末尾 clamp,是安全的。
5. 关键代码引用位置
| 环节 | 位置 |
|---|---|
| chunk 映射主函数 | fs/erofs/data.c: erofs_map_chunks() |
| 分派(按 datalayout) | fs/erofs/data.c: erofs_map_blocks() |
| 多设备:统一地址 → 具体设备 | fs/erofs/data.c: erofs_map_dev() / erofs_fill_from_devinfo() |
| 读取端解析 chunkformat/chunkbits | fs/erofs/inode.c(vi->chunkformat / vi->chunkbits) |
mkfs:--chunksize → c_chunkbits |
erofs-utils/mkfs/main.c:1268(含 1509 / 1567 / 1582 的约束) |
| mkfs:改走 chunk 路径 | erofs-utils/lib/inode.c: erofs_write_unencoded_file() |
| mkfs:切块 + 去重 + 定格式 | erofs-utils/lib/blobchunk.c: erofs_blob_write_chunked_file()、erofs_blob_getchunk()、erofs_blob_mergechunks() |
| mkfs:索引数组落盘 | erofs-utils/lib/inode.c:908 → lib/blobchunk.c: erofs_write_chunk_indexes() |
| 结构体定义 | fs/erofs/erofs_fs.h:erofs_inode_chunk_info、erofs_inode_chunk_index |
| 格式位定义 | fs/erofs/erofs_fs.h:EROFS_CHUNK_FORMAT_BLKBITS_MASK / INDEXES / 48BIT;EROFS_BLOCK_MAP_ENTRY_SIZE |
参考
EROFS 官方文档 Release 0.1
erofs-utils(1.9.4)
linux-stable (93f51579e7df)