Study · 운영체제 · 파일 읽기 · 파일과 디렉터리

읽기의 핵심은 파일 시스템이 아니라 메모리 관리에 있다

장면

같은 파일을 두 번 읽으면 두 번째가 빠르다. 두 번째에는 어디까지만 갔을까?

$ time cat big.log > /dev/null   # time 은 걸린 시간을 잰다. > /dev/null 은 읽은 내용을 화면에 뿌리지 않고 버린다는 뜻이다
$ time cat big.log > /dev/null   # 같은 파일을 한 번 더 읽는다 — 두 번째가 빠르다
$ free -h                        # 메모리 현황을 본다. buff/cache 칸이 커널이 캐시로 들고 있는 양이고, 그 값이 늘어 있다

아래 재생기의 ▶를 누르면 한 스텝씩 진행된다. 금색 테두리가 지금 이야기하는 곳이고, 캡션이 무슨 일이 일어났는지 말해 준다. 굵게 밑줄 친 말은 누르면 뜻이 나온다. 빨간 딱지는 그때 벌어지는 일의 이름이고, 캡션 바로 위 회색 글자는 지금 스텝이 속한 묶음의 이름이다.

카드 아래 회색 줄은 그 일이 실제로 적혀 있는 소스의 파일 이름 : 줄 번호와 함수 이름이다. 글 안에 (:669)처럼 줄 번호만 적힌 것은 바로 앞에 적은 파일의 그 줄이라는 뜻이다. 캡션 한가운데 박힌 긴 영어 이름과 괄호 속 파일 · 줄 번호도 같다 — 건너뛰고 한글만 읽어도 뜻이 통하게 써 두었다.

장면 · 읽기의 핵심은 파일 시스템이 아니라 메모리 관리에 있다 0 / 10

▶ 를 누르면 cat big.log 한 번이 커널 안에서 어디까지 내려가는지 따라간다. 먼저 알아 둘 것이 하나 있다 — 디스크는 메모리보다 수백에서 수만 배 느리다. 그래서 「빠르다」는 말은 거의 언제나 「디스크에 안 갔다」는 뜻이다. 읽기를 실제로 맡은 코드가 파일 시스템(fs/)이 아니라 메모리 관리(mm/)에 있는 이유가 그것이고, 그래서 두 번째 읽기는 디스크까지 가지 않는다. 이 장면은 우분투가 기본으로 쓰는 저장 방식(ext4)을 기준으로 한다. 다른 방식을 쓰면 아래쪽 자세한 길은 조금 달라지지만, 「캐시에 있으면 디스크에 안 간다」는 줄기는 같다.

키보드 · ← → 한 스텝 · Space 재생/멈춤 · Home 처음

읽기의 핵심은 파일 시스템이 아니라 메모리 관리에 있다. 캐시에 있으면 블록 장치까지 가지 않고 거기서 끝난다.

어떻게 확인했나

소스 어디를 보고 적었는지와 무엇을 돌려서 쟀는지를 남긴 자리다. 처음 읽을 때는 건너뛰어도 된다.

  • 읽는 코드는 fs/ 에 없다. read(fs/read_write.c:723) → ksys_read(:705) → vfs_read(:554)까지는 넘기기만 하고, vfs_read 는 file->f_op->read_iter(:573)로 빠진다. ext4 의 ext4_file_read_iter(fs/ext4/file.c:132)도 마지막 줄에서 generic_file_read_iter(:149)를 부를 뿐이고, 그것이 filemap_read(mm/filemap.c:2787)다 — 여기서 파일 시스템이 끝나고 메모리 관리가 시작된다. 소스에서 확인했다.
  • 캐시를 먼저 보고 없을 때만 내려간다 — filemap_get_pages(mm/filemap.c:2686)가 filemap_get_read_batch(:2704 → 함수는 :2469)로 페이지 캐시를 뒤지고, if (!folio_batch_count(fbatch))(:2705) 안에서만 page_cache_sync_ra(:2714)를 부른다. 그 캐시의 실체는 파일마다 하나씩 달린 struct address_space 의 i_pages 라는 xarray 다(include/linux/fs.h:473).
  • 블록 장치 요청이 나가는 자리는 read_pages(mm/readahead.c:160)의 aops->readahead(rac)(:174)이고, ext4 쪽은 ext4_readahead(fs/ext4/readpage.c:342)다. 그 전에 page_cache_ra_unbounded(mm/readahead.c:222)가 filemap_add_folio(:293)로 캐시에 빈 자리를 먼저 걸어 두고, 그 자리를 채우게 한다(:315).
  • 사용자 버퍼로는 언제나 캐시에서 복사한다 — filemap_read 의 copy_folio_to_iter(mm/filemap.c:2875). 두 번째 읽기가 짧아지는 이유는 filemap_get_read_batch 가 folio 의 folio_test_uptodate(:2494)를 보고 그대로 돌려주기 때문이다. page_cache_sync_ra 와 read_pages 를 아예 지나지 않는다.
  • 256MB 파일을 캐시를 비운 뒤 두 번 읽었다. 첫 번째가 0.81초 · 주요폴트 2, 두 번째가 0.02초 · 주요폴트 0 으로 40배 차이였다. /proc/meminfo 의 Cached 는 451,988 kB 에서 743,296 kB 로 늘었고, 두 번째 읽기 뒤에는 더 늘지 않았다 — 디스크에 안 갔다는 뜻이다.
‹ 이전 다음 항목 ›