본문으로 이동
  • 현재 모든 문서는 작성 중입니다. 보시는 데 불편함이 있더라도 양해 부탁드립니다.

운영체제 기본/Linux의 파일시스템/ext4의 파일 읽기

~ldmsys
관리자 (토론 | 기여)님의 2026년 10월 31일 (토) 08:13 판 (ext4 버퍼드 읽기: 공통 캐시, 블록 매핑, bio 완료와 mmap 연결)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)

파일시스템 구현 목차 · 이전 VFS와 파일 객체 · 관련 procfs의 파일 읽기

Linux v6.12의 ext4 버퍼드 읽기는 공통 페이지 캐시를 사용한다. 파일 데이터를 장치에서 가져와야 하면 ext4의 블록 매핑 경로로 연결된다. 이미 파일을 열어 둔 상태에서 시작하며, 일반적인 버퍼드 읽기를 따른다. DAX 파일과 O_DIRECT 읽기는 다른 경로로 분기하므로 여기서 설명하는 페이지 캐시 경로와 구별해야 한다.

VFS에서 ext4의 읽기 함수로

ext4는 블록 장치에 파일의 내용과 메타데이터를 저장하는 파일시스템이다. inode는 파일의 크기, 종류와 데이터 위치를 관리하는 파일별 객체다. 경로를 찾아 파일을 여는 과정이 끝나면, VFS의 열린 파일 객체인 struct file이 해당 inode와 파일 연산을 가리킨다. 파일 디스크립터가 이 객체로 이어지는 과정은 VFS와 파일 객체에서 다룬다.

ext4 일반 파일의 ext4_file_operations에는 다음 읽기 함수가 등록되어 있다.[1]

.read_iter = ext4_file_read_iter,

VFS가 이 함수를 호출해도 ext4가 곧바로 디스크를 읽는 것은 아니다. ext4_file_read_iter()는 DAX와 직접 입출력 분기를 제외한 읽기를 generic_file_read_iter()에 맡긴다. 이 함수는 공통 버퍼드 읽기 함수인 filemap_read()로 이어진다. 파일시스템별 진입점과 실제 데이터 입출력 사이에 공통 페이지 캐시 계층이 있는 구조다.[1][2]

캐시에 있으면 재사용하고 없으면 채운다

페이지 캐시는 파일 내용의 일부를 메모리에 보관한다. filemap_read()는 file->f_mapping으로 접근하는 캐시에서 읽을 범위를 확보한다. 요청한 내용이 이미 준비되어 있으면 해당 데이터를 사용자 버퍼로 복사하며, 그 데이터를 얻기 위한 장치 읽기는 필요하지 않다. 캐시를 관리하는 메모리 단위인 folio의 상태와 검색 방식은 페이지 캐시에서 설명한다.

캐시에 없다고 해서 매번 read_folio만 호출되는 것은 아니다. filemap_get_pages()는 먼저 동기 선행 읽기를 시도할 수 있다. 선행 읽기인 readahead는 요청 범위와 이어질 것으로 예상되는 범위를 미리 읽는 동작이다. 이미 캐시에 있지만 읽기 작업이 진행 중이라면 완료를 기다릴 수도 있다. 필요한 데이터가 여전히 준비되지 않은 경우에는 단일 folio 읽기 경로를 사용한다. 캐시 적중 중에도 선행 읽기 표시를 만나면 뒤쪽 범위에 대한 추가 읽기를 시작할 수 있다.[2]

공통 계층은 inode의 캐시 매핑에 연결된 address_space_operations, 즉 a_ops를 통해 ext4에 데이터 공급을 요청한다. ext4는 read_folio에 ext4_read_folio()를, readahead에 ext4_readahead()를 등록한다. 일반적인 외부 데이터 블록의 읽기에서는 두 함수 모두 ext4_mpage_readpages()로 이어진다. 작은 파일의 내용을 inode 안에 담는 inline data는 별도 처리되므로 이 블록 읽기 흐름을 그대로 적용하지 않는다.[3]

파일의 위치를 장치의 위치로 바꾼다

파일의 0번째 블록과 장치의 0번째 블록은 다른 위치다. ext4는 파일 안의 논리 블록 번호를 장치상의 물리 블록 번호로 변환해야 한다. extent는 연속된 논리 블록 구간과 그에 대응하는 연속된 물리 블록 구간을 묶어 표현하는 매핑이다. 블록마다 주소를 하나씩 기록하는 대신 구간 단위로 위치를 나타낸다. extent를 사용하는 파일에서는 이 정보가 extent 트리로 관리된다.[4]

ext4_mpage_readpages()는 이미 얻은 매핑을 재사용하거나 ext4_map_blocks(NULL, inode, &map, 0)을 호출해 읽을 블록의 위치를 조회한다. 이 호출은 새 데이터 블록을 할당하는 요청이 아니다. 매핑 조회 자체도 메모리에 보관된 정보를 활용하므로, 읽을 때마다 반드시 디스크의 extent 트리를 처음부터 읽는 것은 아니다.[3]

read()
  → VFS → ext4_file_read_iter()
  → generic_file_read_iter() → filemap_read()
      ├─ 준비된 캐시 데이터 → 사용자 버퍼로 복사
      └─ 데이터 준비 필요
          → a_ops의 readahead 또는 read_folio
          → ext4_readahead() 또는 ext4_read_folio()
          → ext4_mpage_readpages()
          → ext4_map_blocks()로 위치 조회 또는 기존 매핑 재사용
          → bio 구성 → submit_bio() → 블록 계층 → 저장 장치

bio는 읽을 장치 위치와 데이터를 받을 메모리 범위를 묶는 블록 입출력 요청이다. ext4는 가능한 연속 구간을 묶어 submit_bio()로 제출한다. 반면 hole은 파일의 논리 범위에 실제 데이터 블록이 없는 구간이므로 0으로 채운다. 전부 hole인 folio는 데이터 블록을 읽지 않고 완료될 수 있다. 블록 배치가 이 경로에서 묶기 어려운 경우에는 block_read_full_folio()로 내려가는 보조 경로도 있다.[5]

읽기 완료와 mmap의 연결

장치 읽기가 끝나면 bio의 완료 함수인 mpage_end_io()가 실행된다. 필요한 후처리가 없다면 __read_end_io()가 곧바로 folio_end_read()를 호출해 성공 여부를 반영하고 대기자를 깨운다. 암호화나 무결성 검증이 필요한 파일에서는 해당 후처리를 거친 뒤 완료된다. 따라서 요청을 제출한 순간과 캐시의 데이터를 사용할 수 있는 순간은 같지 않다.[5]

준비된 데이터는 filemap_read()의 copy_folio_to_iter()를 통해 읽기 대상 버퍼로 복사된다. 공통 계층은 읽은 만큼 위치를 진행시키고 바이트 수를 반환한다. EOF에서는 요청량보다 적게 읽거나 0을 반환할 수 있으며, 장치 오류가 있으면 정상 데이터처럼 취급하지 않는다.[2]

일반 ext4 파일을 mmap()으로 읽을 때도 같은 파일의 페이지 캐시를 이용한다. 다만 사용자 버퍼로 복사하는 대신 페이지 폴트를 처리하며 파일 데이터를 프로세스의 주소 공간에 매핑한다. ext4는 매핑 연산에 ext4_file_mmap()을 등록하며, 비 DAX 경로에서 ext4_file_vm_ops를 설치한다. 이 연산표의 .fault = filemap_fault가 공통 캐시 폴트 처리로 연결된다. read()와 mmap()의 진입점은 달라도 파일 내용의 캐시는 공유한다.[1]

참고 문헌

  1. ↑ 1.0 1.1 1.2 Linux v6.12 fs/ext4/file.c의 ext4_file_operations, ext4_file_read_iter, ext4_file_mmap, ext4_file_vm_ops.
  2. ↑ 2.0 2.1 2.2 Linux v6.12 mm/filemap.c의 generic_file_read_iter, filemap_read, filemap_get_pages, filemap_fault.
  3. ↑ 3.0 3.1 Linux v6.12 fs/ext4/inode.c의 ext4_read_folio, ext4_readahead, address_space_operations 정의, ext4_map_blocks.
  4. ↑ Linux v6.12 ext4 문서, The Contents of inode.i_block의 Extent Tree.
  5. ↑ 5.0 5.1 Linux v6.12 fs/ext4/readpage.c의 ext4_mpage_readpages, mpage_end_io, __read_end_io.