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

F2FS: 읽기 방식 선택에도 쓰기 상태를 확인하는 이유

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

F2FS는 플래시 저장장치를 고려해 세그먼트와 노드 구조로 파일 위치를 관리합니다. 읽기를 시작할 때도 압축 지원과 진행 중인 직접 쓰기를 확인해야 안전한 데이터를 얻을 수 있습니다. f2fs_file_read_iter 전체를 통해 파일시스템의 저장 정책이 읽기 분기와 대기 조건에 어떻게 나타나는지 살펴봅니다.

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

먼저 알아둘 개념

세그먼트와 노드

세그먼트는 블록 공간을 관리하는 단위이고 노드는 파일의 데이터 위치를 찾는 메타데이터를 담습니다. 파일 경로 이름과 물리 블록 주소는 서로 다른 정보입니다.

압축 백엔드

압축된 파일 내용을 읽으려면 그 압축 형식을 풀 수 있는 구현이 준비되어야 합니다. 저장장치에서 바이트를 읽는 것만으로 사용자에게 원래 내용을 돌려줄 수는 없습니다.

체크포인트

복구를 시작할 수 있는 파일시스템 상태를 기록한 기준입니다. 모든 read 호출이 체크포인트를 새로 기록하는 것은 아닙니다.

직접 I/O와 동시 쓰기

직접 I/O가 캐시 경로를 우회하더라도 파일의 다른 I/O와 무관하게 실행할 수 있는 것은 아닙니다. 모드와 파일 조건에 따라 진행 중인 직접 쓰기를 기다립니다.

처음 읽을 때

읽기 함수에 쓰기 대기가 등장하는 이유를 먼저 생각하십시오. 같은 파일의 저장 위치와 데이터가 바뀌는 중이라면 읽기 경로도 그 변화를 고려해야 합니다.

더 깊이 살펴볼 때

LFS 모드, 진행 중인 DIO 쓰기 계수, pinned 파일 여부, 이번 요청의 dio 선택이 AND와 OR로 어떻게 묶이는지 확인하십시오. f2fs_should_use_dio 결과를 단순히 사용자의 한 플래그와 동일시하면 안 됩니다.

그림으로 보는 변화

F2FS: 읽기 방식 선택에도 쓰기 상태를 확인하는 이유의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 읽을 수 있는 형식을 확인합니다

압축 해제 구현을 확인하고 요청의 시작 위치를 보관합니다.

화살표는 파일 속성에서 읽기 가능 조건을 판단하는 흐름입니다.

2. 동시 쓰기와 조정합니다

F2FS가 선택한 I/O 방식과 LFS 조건에 따라 진행 중인 직접 쓰기를 기다립니다.

멈춘 화살표는 동기화 대기이며 CPU가 데이터를 복사한다는 뜻이 아닙니다.

3. 읽고 통계를 남깁니다

직접 읽기 또는 페이지 캐시 읽기를 수행하고 결과를 추적합니다.

화살표는 선택된 읽기 경로와 반환 결과를 나타냅니다.

f2fs_file_read_iter를 한 줄씩 읽기

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

static ssize_t f2fs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
	struct inode *inode = file_inode(iocb->ki_filp);
	const loff_t pos = iocb->ki_pos;
	ssize_t ret;
	bool dio;

	if (!f2fs_is_compress_backend_ready(inode))
		return -EOPNOTSUPP;

	if (trace_f2fs_dataread_start_enabled())
		f2fs_trace_rw_file_path(iocb->ki_filp, iocb->ki_pos,
					iov_iter_count(to), READ);

	dio = f2fs_should_use_dio(inode, iocb, to);

	/* In LFS mode, if there is inflight dio, wait for its completion */
	if (f2fs_lfs_mode(F2FS_I_SB(inode)) &&
	    get_pages(F2FS_I_SB(inode), F2FS_DIO_WRITE) &&
		(!f2fs_is_pinned_file(inode) || !dio))
		inode_dio_wait(inode);

	if (dio) {
		ret = f2fs_dio_read_iter(iocb, to);
	} else {
		ret = filemap_read(iocb, to, 0);
		if (ret > 0)
			f2fs_update_iostat(F2FS_I_SB(inode), inode,
						APP_BUFFERED_READ_IO, ret);
	}
	trace_f2fs_dataread_end(inode, pos, ret);
	return ret;
}
static ssize_t f2fs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)

F2FS 파일 읽기 요청과 목적지 반복자를 받습니다. 결과는 바이트 수 또는 음수 오류입니다.

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

파일에서 inode를 얻어 F2FS 속성과 동시 I/O 상태를 확인합니다.

	const loff_t pos = iocb->ki_pos;

읽기 전 시작 위치를 보관합니다. 이후 위치가 이동해도 추적 기록에 원래 값을 쓸 수 있습니다.

	ssize_t ret;

읽기 결과를 저장할 부호 있는 변수입니다. 음수 오류도 담아야 합니다.

	bool dio;

이번 읽기가 직접 I/O를 사용할지 기록할 논리값입니다.

	if (!f2fs_is_compress_backend_ready(inode))

해당 파일을 읽는 데 필요한 압축 구현이 준비되어 있는지 확인합니다.

		return -EOPNOTSUPP;

준비되지 않았다면 지원하지 않는 작업이라는 오류를 반환합니다.

	if (trace_f2fs_dataread_start_enabled())

읽기 시작 추적 이벤트가 활성화됐을 때만 경로 추적 정보를 계산합니다.

		f2fs_trace_rw_file_path(iocb->ki_filp, iocb->ki_pos,

파일과 현재 위치를 추적 함수에 전달합니다.

					iov_iter_count(to), READ);

요청 바이트 수와 읽기 방향 READ도 함께 전달합니다.

	dio = f2fs_should_use_dio(inode, iocb, to);

파일과 요청 조건을 함께 검사해 F2FS가 사용할 직접 I/O 여부를 결정합니다.

	if (f2fs_lfs_mode(F2FS_I_SB(inode)) &&

파일시스템이 LFS 모드인지를 대기 조건의 첫 항목으로 검사합니다.

	    get_pages(F2FS_I_SB(inode), F2FS_DIO_WRITE) &&

동시에 진행 중인 직접 쓰기가 있는지를 두 번째 조건으로 검사합니다.

		(!f2fs_is_pinned_file(inode) || !dio))

pinned 파일의 직접 읽기 예외를 고려합니다. pinned가 아니거나 이번 읽기가 직접 I/O가 아니면 앞 조건들과 함께 대기합니다.

		inode_dio_wait(inode);

이 inode의 진행 중인 직접 I/O가 끝날 때까지 기다립니다.

	if (dio) {

앞서 결정한 직접 I/O 사용 여부에 따라 읽기 경로를 나눕니다.

		ret = f2fs_dio_read_iter(iocb, to);

F2FS 직접 읽기를 수행해 결과를 ret에 저장합니다.

	} else {

직접 I/O를 선택하지 않은 요청은 아래 캐시 읽기 경로로 갑니다.

		ret = filemap_read(iocb, to, 0);

페이지 캐시를 이용하는 filemap 읽기에 요청을 넘깁니다.

		if (ret > 0)

실제로 양수 바이트를 읽었을 때만 읽은 양을 통계에 더합니다.

			f2fs_update_iostat(F2FS_I_SB(inode), inode,

F2FS 파일시스템과 inode를 통계 갱신 함수에 전달합니다.

						APP_BUFFERED_READ_IO, ret);

애플리케이션 버퍼드 읽기 종류와 실제 읽은 바이트 수를 전달합니다.

	trace_f2fs_dataread_end(inode, pos, ret);

원래 시작 위치와 최종 결과로 읽기 종료 추적 이벤트를 남깁니다.

	return ret;

실제 읽은 길이, 0 또는 오류를 호출자에게 반환합니다.

함께 생각해 볼 질문

캐시를 쓰지 않으면 다른 쓰기를 기다릴 필요도 없나요?

그렇지 않습니다. 파일 데이터와 위치의 일관성을 위해 직접 I/O 사이에도 동기화가 필요할 수 있습니다.

압축 기능이 준비되지 않은 파일을 읽으면 어떻게 되나요?

이 함수는 -EOPNOTSUPP를 반환해 지원되지 않는 상태를 알립니다.

시작 위치를 왜 별도 변수에 보관하나요?

읽는 동안 iocb의 현재 위치가 바뀔 수 있으므로 추적 종료 이벤트에 원래 요청 위치를 사용할 수 있게 합니다.

출처와 읽은 범위

Linux stable v6.18.37 · fs/f2fs/file.c

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

맨 위로 ↑