Linux v6.6 · 개념과 코드 읽기

ext4: 읽기 요청이 캐시·직접 I/O·DAX로 나뉘는 이유

이 코드는 어떤 문제를 푸나요?

ext4 파일을 읽는다고 모든 호출이 같은 길로 디스크에 도달하지는 않습니다. 파일의 inode와 열기 옵션에 따라 페이지 캐시를 사용하는 읽기, 직접 I/O, DAX로 나뉩니다. ext4_file_read_iter를 입구로 삼아 어떤 조건을 먼저 확인하고 어느 계층에 실제 읽기를 맡기는지 설명합니다. extent와 저널은 이 함수가 직접 처리하는 대상이 아니라 후속 경로와 파일시스템 구조를 이해할 때 연결할 개념입니다.

읽을 범위: v6.6 · fs/ext4/file.c · ext4_file_read_iter 130–148행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

inode

파일의 크기·속성·블록 매핑 등 파일 자체의 상태를 나타냅니다. 같은 파일을 여러 번 열어 만든 struct file들과 구분됩니다.

extent

연속된 파일 논리 블록 구간을 연속된 저장장치 블록 구간에 대응시키는 표현입니다. 모든 파일이 단 하나의 extent로 이루어지는 것은 아닙니다.

버퍼드 I/O·직접 I/O·DAX

버퍼드 읽기는 페이지 캐시를 사용합니다. 직접 I/O는 일반 데이터 캐시 경로를 우회하려고 하며 정렬 등의 제약을 받습니다. DAX는 지원 저장장치에서 페이지 캐시를 거치지 않는 별도의 접근 방식입니다.

저널

중단 이후 메타데이터 일관성을 복구하는 데 필요한 변경을 기록합니다. read 함수가 호출될 때마다 저널에 쓰거나 모든 파일 데이터가 항상 저널에 들어가는 것은 아닙니다.

처음 읽을 때

이 함수의 분기만으로도 파일시스템 이름과 I/O 방식이 서로 다른 선택임을 알 수 있습니다. read가 ext4에 도착했다는 사실만으로 캐시 적중 여부나 실제 디스크 접근 여부를 결정할 수 없습니다.

더 깊이 살펴볼 때

DAX 분기가 직접 I/O 플래그보다 앞에 있으며, 0바이트 요청은 atime 갱신까지 건너뜁니다. 후속 함수의 잠금, 정렬 검사, 부분 읽기와 위치 갱신을 따라가면 VFS의 공통 인터페이스와 ext4 전용 제약을 구분할 수 있습니다.

그림으로 보는 변화

ext4: 읽기 요청이 캐시·직접 I/O·DAX로 나뉘는 이유의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 요청과 파일을 확인합니다

inode를 얻고 강제 종료 상태와 요청 길이를 검사합니다.

화살표는 파일 핸들에서 inode를 참조하는 관계입니다.

2. 읽기 방식을 선택합니다

DAX 지원과 inode 상태를 먼저 보고, 그다음 직접 I/O 플래그를 확인합니다.

갈라지는 화살표는 서로 다른 읽기 경로를 뜻합니다.

3. 선택한 구현에 맡깁니다

DAX·직접 I/O·일반 캐시 읽기 중 선택한 함수가 실제 처리를 이어갑니다.

화살표는 함수 호출입니다. 모든 분기가 동시에 실행되지는 않습니다.

ext4_file_read_iter를 한 줄씩 읽기

줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
	struct inode *inode = file_inode(iocb->ki_filp);

	if (unlikely(ext4_forced_shutdown(inode->i_sb)))
		return -EIO;

	if (!iov_iter_count(to))
		return 0; /* skip atime */

#ifdef CONFIG_FS_DAX
	if (IS_DAX(inode))
		return ext4_dax_read_iter(iocb, to);
#endif
	if (iocb->ki_flags & IOCB_DIRECT)
		return ext4_dio_read_iter(iocb, to);

	return generic_file_read_iter(iocb, to);
}
static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)

비동기 I/O 제어 정보 iocb와 데이터를 받을 반복자 to를 받습니다. 반환값은 읽은 바이트 수 또는 음수 오류입니다.

	struct inode *inode = file_inode(iocb->ki_filp);

열린 파일에서 inode를 얻어 파일시스템 상태와 DAX 여부를 확인할 준비를 합니다.

	if (unlikely(ext4_forced_shutdown(inode->i_sb)))

파일시스템이 강제 종료 상태인지 검사합니다. unlikely는 분기 빈도에 대한 힌트이며 오류 판단 자체를 바꾸지 않습니다.

		return -EIO;

더 읽기를 진행하지 않고 I/O 오류를 반환합니다.

	if (!iov_iter_count(to))

목적지 반복자에 남은 요청 길이가 0인지 검사합니다.

		return 0; /* skip atime */

0바이트 읽기는 바로 성공값 0을 반환해 접근 시각 갱신도 생략합니다.

#ifdef CONFIG_FS_DAX

DAX는 지원 저장장치와 파일 시스템에서 페이지 캐시를 거치지 않고 메모리처럼 직접 접근하는 기능입니다. 이 설정은 DAX 분기를 빌드에 포함할지 정하고, 실제 파일이 DAX인지 여부는 다음 IS_DAX(inode)가 별도로 검사합니다. 이 선택은 전처리 단계에서 이루어지며, CPU가 실행 중 이 줄의 조건을 검사하지 않습니다.

	if (IS_DAX(inode))

이 inode가 실제 DAX 방식으로 동작하는지 확인합니다. 빌드 지원만으로 모든 파일이 DAX가 되는 것은 아닙니다.

		return ext4_dax_read_iter(iocb, to);

DAX 전용 읽기에 동일한 요청과 목적지를 전달하고 그 결과를 반환합니다.

#endif

DAX 조건부 코드는 여기서 끝납니다.

	if (iocb->ki_flags & IOCB_DIRECT)

이 I/O 요청에 직접 I/O 플래그가 있는지 확인합니다.

		return ext4_dio_read_iter(iocb, to);

ext4의 직접 I/O 읽기로 전달합니다. 정렬 및 후속 처리는 그 경로에서 확인해야 합니다.

	return generic_file_read_iter(iocb, to);

특별 경로가 아니면 공통 파일 읽기 구현으로 넘겨 페이지 캐시를 사용하는 읽기를 진행합니다.

함께 생각해 볼 질문

0바이트 읽기에도 저장장치를 읽나요?

이 함수에서는 요청 길이가 0이면 바로 0을 반환하며 접근 시각 갱신도 건너뜁니다.

ext4이면 항상 페이지 캐시를 쓰나요?

아닙니다. DAX나 직접 I/O 조건에 따라 다른 경로를 사용합니다.

이 함수가 extent를 직접 검색하나요?

아닙니다. 읽기 방식을 선택하는 입구이며 실제 매핑은 선택된 하위 경로에서 이어집니다.

출처와 읽은 범위

Linux stable v6.6 · fs/ext4/file.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑