File System · Linux v6.6 · source + diagram note

ext4

Linux v6.6 ext4의 mount 초기화와 파일별 logical→physical block mapping을 고정 원문으로 나눠 읽습니다. journal 로드와 새 block 생성은 조건부입니다.

전체 흐름

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이 아니다.

그림 1. mount와 logical→physical mapping의 두 경로

그림을 가로로 밀어 전체 흐름 보기 ↔

MOUNT: filesystem superblock 구성MAPPING: inode의 파일 논리 블록 해석fs_contextget_tree_bdevblock device 선택super.cext4_fill_superext4_sb_info 할당super.c__ext4_fill_super검사 · journal 조건성공 시s_rootroot dentryinode 요청m_lblk / m_lenlogical rangeinode.cext4_map_blocksflags에 따라 조회/생성조건부ES / extent / indirectcache 또는 tree결과매핑 / 미할당ret와 m_flags두 경로는 서로 다른 호출 문맥이다. mount 성공이 모든 파일 block mapping 완료를 뜻하지 않는다.

위 경로는 super.c의 mount 초기화, 아래 경로는 inode.c의 파일별 mapping이다.

경로입력확인하는 대상출력/경계
mountfs_context, super_blockon-disk superblock·metadata checksum·feature·journal 조건성공 시 s_root; 실패는 해당 label과 wrapper로 전달
block mappinginode, m_lblk, m_len, flagsextent 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

고정 원본: fs/ext4/super.c:5683-5705
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() 시작 (앞 함수 말미 포함)

고정 원본: fs/ext4/super.c:5202-5241
	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() — 실제 정의

고정 원본: fs/ext4/inode.c:478-516
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용 호출자

고정 원본: fs/ext4/super.c:5798-5837
	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는 거부한다.

그림 2. mount 검사와 조건부 journal·오류 분기

그림을 가로로 밀어 전체 흐름 보기 ↔

1ext4_load_super디스크 superblock 읽기2checksum / feature지원 조건 · group 검사3journal 조건NOLOAD · recovery 확인4root inodes_root 생성실패 Aout_failload_super 실패상위 wrapper도 실패실패 Bfailed_mountchecksum/feature 실패해당 label 정리조건부 실패journal 경로NOLOAD + journal recovery + RWmount 거부journal은 항상 로드되지 않는다. 오류 label과 해제 범위는 진입 단계마다 다르다.

그림의 실패 가지는 대표 조건이다. 실제 해제 대상은 고정 super.c의 각 label을 따라 확인해야 한다.

고정 소스 지점정상 경로실패·조건
ext4_load_super() L5227on-disk superblock을 읽어 s_es 준비실패 시 out_fail
ext4_init_metadata_csum() L5234metadata checksum 방식 초기화실패 시 failed_mount
ext4_check_feature_compatibility() L5283지원 기능/옵션 확인실패 시 failed_mount
journal 조건 L5373feature 있고 NOLOAD가 아니면 로드NOLOAD + journal recovery + RW이면 mount 거부
ext4_iget() L5473root 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을 저장하지는 않는다.

그림 3. mount 객체와 파일별 mapping 상태의 관계

그림을 가로로 밀어 전체 흐름 보기 ↔

VFSsuper_blocks_fs_info · s_rootext4 mountext4_sb_infos_es · s_journalon-diskext4_super_blockchecksum · featuresVFS rootdentry / inodes_root조건부journal_tNOLOAD/no journal이면 없음파일 inodeext4_map_blocksm_lblk → m_pblk파일별 logical→physical mapping은 mount의 s_root 구성과 별도다.

실선은 소유/참조 관계를 뜻한다. 떨어진 파일 inode 상자는 별도 요청 문맥이다.

객체·필드값의 출처읽을 때의 주의
super_block.s_fs_infoext4_alloc_sbi()가 만든 ext4_sb_infowrapper 실패 시 free_sbi로 정리
ext4_sb_info.s_esext4_load_super()가 읽은 superblockfeature/checksum 검사 전에는 mount 성공 아님
ext4_sb_info.s_journal조건부 journal 초기화NOLOAD/no journal 경로에 항상 존재한다고 가정 금지
super_block.s_root검증한 root inode로 만든 dentry파일의 physical block map 자체가 아님
ext4_map_blocks의 mapm_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 조회를 건너뛴다.

그림 4. extent status 조회와 CREATE 조건

그림을 가로로 밀어 전체 흐름 보기 ↔

입력m_lblk · m_lenflags: CREATE 등먼저extent status cachewritten / unwrittendelayed / holecache hit상태 재사용조건에 따라 반환cache missbacking 조회ext4_ext_map_blocks()ext4_ind_map_blocks()CREATE 경로할당/변환ext4_ext_map_blocks()ext4_ind_map_blocks()출력ret · m_flagsm_pblk 또는 미할당CREATE가 없으면 조회 결과를 반환한다. CREATE여도 이미 mapped이면 조기 반환할 수 있다.

점선은 조건부 진행이다. 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 hitwritten/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 함수 정의가 아님

불변 조건과 실패 사례

mount 경계

journal 로드는 조건부다. root inode 검증과 s_root 생성 전에는 mount 완료로 읽지 않는다.

mapping 경계

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 optionjournal load/feature 검사 내부 순서를 보여 주지 않음
stat -f -c %T /path현재 경로 filesystem type특정 파일 block의 물리 매핑은 모름
filefrag -v /path/to/fileFIEMAP으로 보고된 현재 논리/물리 extentdelayed 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 앞이다.