전체 흐름
ext4_fill_super()는 mount 시 super_block과 ext4_sb_info를 준비하고 __ext4_fill_super()에 디스크 superblock 검사·조건부 journal 로드·root dentry 구성을 맡긴다. ext4_map_blocks()는 별도의 파일 inode 요청에서 logical block의 written/unwritten mapping 또는 delayed/hole 상태를 구분한다. 두 함수는 하나의 연속 I/O pipeline이 아니다.
그림을 가로로 밀어 전체 흐름 보기 ↔
위 경로는 super.c의 mount 초기화, 아래 경로는 inode.c의 파일별 mapping이다.
| 경로 | 입력 | 확인하는 대상 | 출력/경계 |
|---|---|---|---|
| mount | fs_context, super_block | on-disk superblock·metadata checksum·feature·journal 조건 | 성공 시 s_root; 실패는 해당 label과 wrapper로 전달 |
| block mapping | inode, m_lblk, m_len, flags | extent status cache와 extent/indirect 형식 | ret·m_flags·m_pblk; delayed/hole에서 물리 블록 단정 금지 |
소스 코드 위치
먼저 확인할 Linux v6.6 소스 파일
Linux v6.6: fs/ext4/super.c
Linux v6.6: fs/ext4/inode.c
Linux v6.6: fs/ext4/extents.c
Linux v6.6: fs/ext4/namei.c
설명: super.c에는 mount wrapper와 본체·journal 호출자가 있고, inode.c에는 실제 ext4_map_blocks() 정의가 있다. extent/indirect 경로는 extents.c 등과 함께 확인한다.
대표 코드
아래 범위는 각 버전의 고정 태그 원문과 일치한다. 발췌 끝 이후의 분기는 옆의 원문 링크와 도식으로 연결한다.
ext4_fill_super() — mount wrapper
static int ext4_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct ext4_fs_context *ctx = fc->fs_private;
struct ext4_sb_info *sbi;
const char *descr;
int ret;
sbi = ext4_alloc_sbi(sb);
if (!sbi)
return -ENOMEM;
fc->s_fs_info = sbi;
/* Cleanup superblock name */
strreplace(sb->s_id, '/', '!');
sbi->s_sb_block = 1; /* Default super block location */
if (ctx->spec & EXT4_SPEC_s_sb_block)
sbi->s_sb_block = ctx->s_sb_block;
ret = __ext4_fill_super(fc, sb);
if (ret < 0)
goto free_sbi;ext4_sb_info를 할당하고 __ext4_fill_super() 결과가 음수이면 free_sbi로 이동한다. 이것이 이름 그대로의 wrapper 정의다.
__ext4_fill_super() 시작 (앞 함수 말미 포함)
sbi->s_desc_per_block_bits = ilog2(EXT4_DESC_PER_BLOCK(sb));
return 0;
}
static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb)
{
struct ext4_super_block *es = NULL;
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t logical_sb_block;
struct inode *root;
int needs_recovery;
int err;
ext4_group_t first_not_zeroed;
struct ext4_fs_context *ctx = fc->fs_private;
int silent = fc->sb_flags & SB_SILENT;
/* Set defaults for the variables that will be set during parsing */
if (!(ctx->spec & EXT4_SPEC_JOURNAL_IOPRIO))
ctx->journal_ioprio = DEFAULT_JOURNAL_IOPRIO;
sbi->s_inode_readahead_blks = EXT4_DEF_INODE_READAHEAD_BLKS;
sbi->s_sectors_written_start =
part_stat_read(sb->s_bdev, sectors[STAT_WRITE]);
err = ext4_load_super(sb, &logical_sb_block, silent);
if (err)
goto out_fail;
es = sbi->s_es;
sbi->s_kbytes_written = le64_to_cpu(es->s_kbytes_written);
err = ext4_init_metadata_csum(sb, es);
if (err)
goto failed_mount;
ext4_set_def_opts(sb, es);
sbi->s_resuid = make_kuid(&init_user_ns, le16_to_cpu(es->s_def_resuid));
sbi->s_resgid = make_kgid(&init_user_ns, le16_to_cpu(es->s_def_resgid));발췌의 앞부분은 이전 함수의 끝이고, 실제 __ext4_fill_super() 정의는 L5207부터다. 그 경계를 제목과 함께 읽는다.
ext4_map_blocks() — 실제 정의
int ext4_map_blocks(handle_t *handle, struct inode *inode,
struct ext4_map_blocks *map, int flags)
{
struct extent_status es;
int retval;
int ret = 0;
#ifdef ES_AGGRESSIVE_TEST
struct ext4_map_blocks orig_map;
memcpy(&orig_map, map, sizeof(*map));
#endif
map->m_flags = 0;
ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n",
flags, map->m_len, (unsigned long) map->m_lblk);
/*
* ext4_map_blocks returns an int, and m_len is an unsigned int
*/
if (unlikely(map->m_len > INT_MAX))
map->m_len = INT_MAX;
/* We can handle the block number less than EXT_MAX_BLOCKS */
if (unlikely(map->m_lblk >= EXT_MAX_BLOCKS))
return -EFSCORRUPTED;
/* Lookup extent status tree firstly */
if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) &&
ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {
if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {
map->m_pblk = ext4_es_pblock(&es) +
map->m_lblk - es.es_lblk;
map->m_flags |= ext4_es_is_written(&es) ?
EXT4_MAP_MAPPED : EXT4_MAP_UNWRITTEN;
retval = es.es_len - (map->m_lblk - es.es_lblk);
if (retval > map->m_len)
retval = map->m_len;
map->m_len = retval;
} else if (ext4_es_is_delayed(&es) || ext4_es_is_hole(&es)) {조회 결과와 CREATE 분기 — 575-623
고정 원문 분기: fs/ext4/inode.c:575-623
found:
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
ret = check_block_validity(inode, map);
if (ret != 0)
return ret;
}
/* If it is only a block(s) look up */
if ((flags & EXT4_GET_BLOCKS_CREATE) == 0)
return retval;
/*
* Returns if the blocks have already allocated
*
* Note that if blocks have been preallocated
* ext4_ext_get_block() returns the create = 0
* with buffer head unmapped.
*/
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED)
/*
* If we need to convert extent to unwritten
* we continue and do the actual work in
* ext4_ext_map_blocks()
*/
if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN))
return retval;
/*
* Here we clear m_flags because after allocating an new extent,
* it will be set again.
*/
map->m_flags &= ~EXT4_MAP_FLAGS;
/*
* New blocks allocate and/or writing to unwritten extent
* will possibly result in updating i_data, so we take
* the write lock of i_data_sem, and call get_block()
* with create == 1 flag.
*/
down_write(&EXT4_I(inode)->i_data_sem);
/*
* We need to check for EXT4 here because migrate
* could have changed the inode type in between
*/
if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
retval = ext4_ext_map_blocks(handle, inode, map, flags);
} else {
retval = ext4_ind_map_blocks(handle, inode, map, flags);실제 정의는 inode.c에 있다. ES cache 다음에 extent 또는 indirect 형식을 조회하고, CREATE일 때 필요한 할당·변환 경로로 진행한다.
ext4_journal_bmap() — journal용 호출자
return journal_inode;
}
static int ext4_journal_bmap(journal_t *journal, sector_t *block)
{
struct ext4_map_blocks map;
int ret;
if (journal->j_inode == NULL)
return 0;
map.m_lblk = *block;
map.m_len = 1;
ret = ext4_map_blocks(NULL, journal->j_inode, &map, 0);
if (ret <= 0) {
ext4_msg(journal->j_inode->i_sb, KERN_CRIT,
"journal bmap failed: block %llu ret %d\n",
*block, ret);
jbd2_journal_abort(journal, ret ? ret : -EIO);
return ret;
}
*block = map.m_pblk;
return 0;
}
static journal_t *ext4_open_inode_journal(struct super_block *sb,
unsigned int journal_inum)
{
struct inode *journal_inode;
journal_t *journal;
journal_inode = ext4_get_journal_inode(sb, journal_inum);
if (IS_ERR(journal_inode))
return ERR_CAST(journal_inode);
journal = jbd2_journal_init_inode(journal_inode);
if (IS_ERR(journal)) {
ext4_msg(sb, KERN_ERR, "Could not load journal inode");
iput(journal_inode);
return ERR_CAST(journal);이 발췌는 ext4_map_blocks() 정의가 아니다. 앞의 return journal_inode은 직전 함수 끝이며, journal block을 m_pblk로 바꾸는 특수 호출이 이어진다.
함수별 분석
ext4_fill_super() wrapper는 ext4_sb_info 할당 뒤 __ext4_fill_super()를 호출한다. 그 본체는 super.c:L5227에서 ext4_load_super() 실패를 out_fail, metadata checksum 초기화 실패를 failed_mount로 보낸다. 그 후 feature·group metadata 검사를 진행한다. journal은 feature와 NOLOAD 조건에 따라 로드하거나 생략하며, journal recovery가 필요한 상태에서 이를 억제한 RW mount는 거부한다.
그림을 가로로 밀어 전체 흐름 보기 ↔
그림의 실패 가지는 대표 조건이다. 실제 해제 대상은 고정 super.c의 각 label을 따라 확인해야 한다.
| 고정 소스 지점 | 정상 경로 | 실패·조건 |
|---|---|---|
ext4_load_super() L5227 | on-disk superblock을 읽어 s_es 준비 | 실패 시 out_fail |
ext4_init_metadata_csum() L5234 | metadata checksum 방식 초기화 | 실패 시 failed_mount |
ext4_check_feature_compatibility() L5283 | 지원 기능/옵션 확인 | 실패 시 failed_mount |
| journal 조건 L5373 | feature 있고 NOLOAD가 아니면 로드 | NOLOAD + journal recovery + RW이면 mount 거부 |
ext4_iget() L5473 | root inode 검증 후 d_make_root() | 잘못된 root는 mount 실패 |
구조체와 필드
super_block.s_fs_info가 ext4의 ext4_sb_info를 가리키고, 그 안의 s_es는 읽어 들인 on-disk superblock이다. s_journal은 journal 경로에서만 존재할 수 있다. s_root는 mount의 root dentry다. 파일별 inode와 ext4_map_blocks 요청은 이 mount 상태를 사용하지만 s_root 자체가 모든 파일 mapping을 저장하지는 않는다.
그림을 가로로 밀어 전체 흐름 보기 ↔
실선은 소유/참조 관계를 뜻한다. 떨어진 파일 inode 상자는 별도 요청 문맥이다.
| 객체·필드 | 값의 출처 | 읽을 때의 주의 |
|---|---|---|
super_block.s_fs_info | ext4_alloc_sbi()가 만든 ext4_sb_info | wrapper 실패 시 free_sbi로 정리 |
ext4_sb_info.s_es | ext4_load_super()가 읽은 superblock | feature/checksum 검사 전에는 mount 성공 아님 |
ext4_sb_info.s_journal | 조건부 journal 초기화 | NOLOAD/no journal 경로에 항상 존재한다고 가정 금지 |
super_block.s_root | 검증한 root inode로 만든 dentry | 파일의 physical block map 자체가 아님 |
ext4_map_blocks의 map | m_lblk 요청 → m_pblk/m_flags 결과 | 0/음수 ret에서 유효 물리 블록 단정 금지 |
실행 단계와 상태 변화
inode.c:L478의 ext4_map_blocks()는 먼저 extent status cache를 살핀다. cache miss나 특수 조건이면 inode의 extent 또는 indirect 형식으로 backing mapping을 조회한다. EXT4_GET_BLOCKS_CREATE가 없으면 조회 결과로 돌아가며, 있는 경우에도 이미 mapped된 블록은 변환 요구가 없으면 바로 반환할 수 있다. v6.6에서는 EXT4_FC_REPLAY 상태이면 초기 ES cache 조회를 건너뛴다.
그림을 가로로 밀어 전체 흐름 보기 ↔
점선은 조건부 진행이다. cache hit 뒤에도 CREATE와 매핑 상태에 따라 할당·변환으로 진행한다. CACHED_NOWAIT의 조기 반환과 오류 정리는 이 요약 그림에서 생략했다.
EXT4_GET_BLOCKS_CACHED_NOWAIT는 캐시에서 결과를 얻으면 즉시 그 값을 반환하고, 찾지 못하면 backing tree를 조회하지 않고 0을 반환합니다. 따라서 이 모드의 0만으로 실제 파일 구간이 hole이라고 단정할 수 없습니다. 일반 경로의 cache hit도 CREATE와 unwritten·delayed·hole 상태 또는 변환 요구에 따라 할당·변환 단계로 이어질 수 있습니다.
| 조건 | v6.6 처리 | 독자가 확인할 결과 |
|---|---|---|
| ES cache hit | written/unwritten 또는 delayed/hole 상태를 분류 | ret와 m_flags를 함께 판독; delayed/hole은 0 |
| cache miss / 재조회 | ext4_ext_map_blocks() / ext4_ind_map_blocks() | extent 또는 indirect 형식, mapped/unwritten/unmapped |
CREATE 없음 | 조회 결과 반환 | 일반 조회에서 ret > 0 매핑 구간, 0 미할당/지연 구간, < 0 오류; CACHED_NOWAIT cache miss의 0은 미확인 |
CREATE 있음 | ext4_ext_map_blocks() / ext4_ind_map_blocks() 조건부 진입 | 할당/변환 뒤 유효성 검사 및 상태 갱신 |
journal용 ext4_journal_bmap() | ext4_map_blocks(NULL, journal_inode, &map, 0) 호출 | journal block 전용 경로; 일반 mapping 함수 정의가 아님 |
불변 조건과 실패 사례
journal 로드는 조건부다. root inode 검증과 s_root 생성 전에는 mount 완료로 읽지 않는다.
m_lblk에서 m_pblk로 바로 산술 변환하지 않는다. ES cache와 extent/indirect tree가 결정하며 hole은 물리 주소가 없다.
out_fail, failed_mount, 뒤쪽 failed_mount*는 진입 시점과 확보 자원이 다르다. 모든 오류를 같은 rollback이라고 요약하지 않는다.
v6.6에서는 extent/indirect helper를 ext4_map_blocks()에서 직접 호출하고, sysfs 등록은 orphan cleanup 앞이다.
계측과 검증
공유 tracefs 설정을 변경하지 않고 현재 mount와 파일 extent를 읽는다. 아래 결과는 관찰 시점의 상태이며, 고정 소스의 각 내부 함수 호출 순서나 과거 오류 label을 직접 증명하지 않는다.
| 읽기 전용 관찰 | 확인할 수 있는 것 | 한계 |
|---|---|---|
findmnt -T /path -no FSTYPE,OPTIONS | 대상 경로의 현재 filesystem과 mount option | journal load/feature 검사 내부 순서를 보여 주지 않음 |
stat -f -c %T /path | 현재 경로 filesystem type | 특정 파일 block의 물리 매핑은 모름 |
filefrag -v /path/to/file | FIEMAP으로 보고된 현재 논리/물리 extent | delayed allocation, unwritten extent, 권한·도구 버전에 주의 |
dmesg | grep -i ext4 | 기록된 ext4 메시지 | 로그가 없다는 사실이 실패·복구 부재를 증명하지 않음 |
findmnt -T /path -no FSTYPE,OPTIONS
stat -f -c %T /path
filefrag -v /path/to/file추가 확인 사항
- 대상 mount에서 journal feature와
NOLOAD, read-only 조건은 어떻게 결합되는가? ext4_load_super()와 checksum/feature 실패가 서로 다른 label로 가는 이유는 무엇인가?- 파일의
m_lblk범위가 ES cache에 없다면 extent/indirect 조회 결과는 mapped, unwritten, hole 중 무엇인가? CREATE요청이 있어도 새 block 할당 없이 반환되는 경우는 무엇인가?- v6.6에서는 extent/indirect helper를
ext4_map_blocks()에서 직접 호출하고, sysfs 등록은 orphan cleanup 앞이다.