이 코드는 어떤 문제를 푸나요?
페이지 캐시는 파일 내용을 메모리에 보관해 반복 읽기의 저장장치 접근을 줄입니다. folio를 할당한 뒤에는 어느 파일의 어느 위치인지 연결하고 회수 대상 목록에도 넣어야 합니다. v6.6의 filemap_add_folio는 folio 잠금을 표시한 뒤 하위 삽입 함수에 등록을 맡기고, 성공하면 최근 재접근 이력과 LRU를 갱신합니다. 하위 함수가 수행하는 메모리 비용 청구와 현재 함수의 책임을 구분합니다.
읽을 범위: v6.6 · mm/filemap.c · filemap_add_folio 932–957행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
address_space
한 파일의 캐시된 데이터를 인덱스로 관리하는 객체입니다. 이름에 address가 있어도 여기서는 프로세스의 전체 가상 주소 공간을 뜻하지 않습니다.
folio
한 개 이상의 물리 페이지를 함께 관리하는 메모리 단위입니다. 항상 정확히 한 페이지라고 가정하면 큰 folio를 잘못 이해하게 됩니다.
인덱스
파일의 어느 위치에 대응하는지 나타내는 페이지 단위 값입니다. 사용자 가상주소나 디스크의 LBA와 같지 않습니다.
shadow와 workingset
최근 캐시에서 쫓겨난 자리의 흔적을 남겨 다시 들어온 데이터가 자주 쓰이는지 판단합니다. 실제 파일 데이터의 백업 사본은 아닙니다.
처음 읽을 때
예를 들어 기본 페이지가 4KiB인 환경에서 인덱스 2는 파일의 8KiB 위치를 가리킵니다. 이는 설명용 환경이며 페이지 크기와 folio 크기는 구성에 따라 다릅니다. 캐시에 등록한 뒤 데이터 유효성은 별도 상태로 확인합니다.
더 깊이 살펴볼 때
이 버전의 memcg 청구·해제는 __filemap_add_folio 내부에 있고 현재 함수는 삽입 실패 때 folio 잠금 비트를 되돌립니다. 최근 회수 이력이 있어도 __GFP_WRITE인 덮어쓰기 목적이면 workingset_refault를 생략하는 이유를 살펴보십시오. 더 뒤 버전의 kernel_file 전용 분기는 여기 없습니다.
그림으로 보는 변화

1. folio를 잠긴 상태로 둡니다
파일 위치에 연결하는 동안 접근 규칙을 유지하도록 folio 잠금 비트를 설정합니다.
화살표는 folio 상태 변화이며 파일의 실제 데이터 읽기가 아닙니다.
2. 파일 위치에 연결합니다
__filemap_add_folio가 메모리 비용 처리와 인덱스 삽입을 수행합니다. 실패하면 현재 함수는 잠금 표시를 되돌립니다.
화살표는 파일 인덱스에서 folio로 연결되는 참조입니다.
3. 회수 관리에 등록합니다
최근 재접근 여부를 working set 판단에 반영하고 folio를 LRU에 넣습니다.
화살표는 캐시 객체가 메모리 회수 정책의 관리 대상이 되는 순서입니다.
filemap_add_folio를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
int filemap_add_folio(struct address_space *mapping, struct folio *folio,
pgoff_t index, gfp_t gfp)
{
void *shadow = NULL;
int ret;
__folio_set_locked(folio);
ret = __filemap_add_folio(mapping, folio, index, gfp, &shadow);
if (unlikely(ret))
__folio_clear_locked(folio);
else {
/*
* The folio might have been evicted from cache only
* recently, in which case it should be activated like
* any other repeatedly accessed folio.
* The exception is folios getting rewritten; evicting other
* data from the working set, only to cache data that will
* get overwritten with something else, is a waste of memory.
*/
WARN_ON_ONCE(folio_test_active(folio));
if (!(gfp & __GFP_WRITE) && shadow)
workingset_refault(folio, shadow);
folio_add_lru(folio);
}
return ret;
}int filemap_add_folio(struct address_space *mapping, struct folio *folio,파일의 mapping과 등록할 folio를 받습니다. 이 함수는 데이터를 읽는 것보다 캐시 등록 절차에 초점을 둡니다.
pgoff_t index, gfp_t gfp)파일 내 페이지 인덱스와 메모리 할당·회수 정책을 나타내는 gfp 플래그를 추가로 받습니다.
void *shadow = NULL;해당 위치의 과거 회수 이력이 없다는 초기값으로 shadow를 준비합니다.
int ret;단계별 실패 코드를 담을 변수입니다.
__folio_set_locked(folio);새 folio를 잠긴 상태로 표시하여 등록과 데이터 준비 동안의 접근 규칙을 세웁니다.
ret = __filemap_add_folio(mapping, folio, index, gfp, &shadow);하위 __filemap_add_folio에 삽입을 맡깁니다. v6.6에서는 일반 folio의 memcg 청구와 삽입 실패 정리가 이 하위 함수 안에 있습니다.
if (unlikely(ret))하위 삽입 함수가 오류를 반환했는지 검사합니다. unlikely는 빈도 힌트이며 실패의 의미를 바꾸지 않습니다.
__folio_clear_locked(folio);삽입에 실패했으므로 이 함수가 설정한 folio 잠금 비트를 되돌립니다. 하위 함수가 맡은 청구 정리와 역할을 나눕니다.
else {삽입에 성공한 경우에만 최근 회수 이력과 LRU 등록을 처리합니다.
WARN_ON_ONCE(folio_test_active(folio));아직 활성 상태가 아니어야 하는 새 캐시 folio에 잘못된 active 표시가 있는지 경고합니다.
if (!(gfp & __GFP_WRITE) && shadow)덮어쓰기를 위한 할당이 아니면서 과거 회수 흔적이 있는 경우인지 검사합니다.
workingset_refault(folio, shadow);최근에 쫓겨났다가 다시 필요해진 데이터를 working set 판단에 반영합니다.
folio_add_lru(folio);folio를 LRU 관리에 추가하여 이후 메모리 회수 정책이 추적할 수 있게 합니다.
return ret;등록 성공 0 또는 삽입 오류를 반환합니다.
함께 생각해 볼 질문
캐시에 넣었으면 파일 데이터도 읽힌 건가요?
이 함수만으로 내용 읽기 완료를 보장하지 않습니다. 데이터 유효성과 I/O 완료는 후속 경로에서 확인합니다.
folio는 항상 4KiB인가요?
아닙니다. 기본 페이지 크기와 folio 차수에 따라 여러 크기가 가능합니다.
shadow는 파일 내용을 복사해 둔 버퍼인가요?
아닙니다. 과거 회수 이력을 이용해 working set을 판단하기 위한 정보입니다.
출처와 읽은 범위
Linux stable v6.6 · mm/filemap.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
