# 페이지 캐시: 메모리 한 묶음이 파일의 일부가 되려면

v6.18.37 / mm/filemap.c

페이지 캐시는 파일의 내용을 메모리에 보관해 다시 읽을 때 저장장치 접근을 줄입니다. 하지만 folio를 할당한 것만으로 어느 파일의 어느 위치인지 정해지지는 않습니다. filemap_add_folio는 메모리 사용량을 계산하고 파일의 인덱스에 folio를 연결하며 회수 관리에 등록합니다. 실제 내용을 읽어 채우는 단계와 캐시에 자리를 잡는 단계는 구분해야 합니다.

## filemap_add_folio

```c

int filemap_add_folio(struct address_space *mapping, struct folio *folio,
				pgoff_t index, gfp_t gfp)
{
	void *shadow = NULL;
	int ret;
	struct mem_cgroup *tmp;
	bool kernel_file = test_bit(AS_KERNEL_FILE, &mapping->flags);

	if (kernel_file)
		tmp = set_active_memcg(root_mem_cgroup);
	ret = mem_cgroup_charge(folio, NULL, gfp);
	if (kernel_file)
		set_active_memcg(tmp);
	if (ret)
		return ret;

	__folio_set_locked(folio);
	ret = __filemap_add_folio(mapping, folio, index, gfp, &shadow);
	if (unlikely(ret)) {
		mem_cgroup_uncharge(folio);
		__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);
		if (kernel_file)
			mod_node_page_state(folio_pgdat(folio),
					    NR_KERNEL_FILE_PAGES,
					    folio_nr_pages(folio));
	}
	return ret;
}

```

### 962행

```c

int filemap_add_folio(struct address_space *mapping, struct folio *folio,

```

파일의 mapping과 등록할 folio를 받습니다. 이 함수는 데이터를 읽는 것보다 캐시 등록 절차에 초점을 둡니다.

### 963행

```c

				pgoff_t index, gfp_t gfp)

```

파일 내 페이지 인덱스와 메모리 할당·회수 정책을 나타내는 gfp 플래그를 추가로 받습니다.

### 965행

```c

	void *shadow = NULL;

```

해당 위치의 과거 회수 이력이 없다는 초기값으로 shadow를 준비합니다.

### 966행

```c

	int ret;

```

단계별 실패 코드를 담을 변수입니다.

### 967행

```c

	struct mem_cgroup *tmp;

```

임시로 바꿀 활성 메모리 cgroup을 복구하기 위한 포인터입니다.

### 968행

```c

	bool kernel_file = test_bit(AS_KERNEL_FILE, &mapping->flags);

```

커널 파일로 표시된 mapping인지 비트 플래그를 확인합니다.

### 970행

```c

	if (kernel_file)

```

커널 파일에 해당하면 일반 태스크의 메모리 귀속과 다른 처리를 적용합니다.

### 971행

```c

		tmp = set_active_memcg(root_mem_cgroup);

```

root 메모리 cgroup을 잠시 활성 대상으로 설정하고 이전 값을 저장합니다.

### 972행

```c

	ret = mem_cgroup_charge(folio, NULL, gfp);

```

folio의 메모리 사용량을 해당 cgroup에 청구합니다. 한도를 초과하면 실패할 수 있습니다.

### 973행

```c

	if (kernel_file)

```

앞서 활성 cgroup을 바꾼 경로인지 확인합니다.

### 974행

```c

		set_active_memcg(tmp);

```

임시 설정을 원래대로 돌려 이후의 무관한 할당에 영향을 주지 않습니다.

### 975행

```c

	if (ret)

```

메모리 비용 청구가 실패했는지 검사합니다.

### 976행

```c

		return ret;

```

등록을 시작하지 않고 오류를 반환합니다.

### 978행

```c

	__folio_set_locked(folio);

```

새 folio를 잠긴 상태로 표시하여 등록과 데이터 준비 동안의 접근 규칙을 세웁니다.

### 979행

```c

	ret = __filemap_add_folio(mapping, folio, index, gfp, &shadow);

```

mapping의 index 위치에 실제로 folio를 삽입하고 이전 shadow 정보도 받습니다.

### 980행

```c

	if (unlikely(ret)) {

```

인덱스 충돌이나 할당 문제 등으로 삽입에 실패한 경로입니다.

### 981행

```c

		mem_cgroup_uncharge(folio);

```

등록하지 못한 folio에 대해 앞서 청구한 cgroup 사용량을 취소합니다.

### 982행

```c

		__folio_clear_locked(folio);

```

설정해 둔 folio 잠금 비트도 해제하여 호출자가 실패 상태를 정리할 수 있게 합니다.

### 983행

```c

	} else {

```

캐시 삽입에 성공했을 때의 회수 정책 등록 경로입니다.

### 992행

```c

		WARN_ON_ONCE(folio_test_active(folio));

```

아직 활성 상태가 아니어야 하는 새 캐시 folio에 잘못된 active 표시가 있는지 경고합니다.

### 993행

```c

		if (!(gfp & __GFP_WRITE) && shadow)

```

덮어쓰기를 위한 할당이 아니면서 과거 회수 흔적이 있는 경우인지 검사합니다.

### 994행

```c

			workingset_refault(folio, shadow);

```

최근에 쫓겨났다가 다시 필요해진 데이터를 working set 판단에 반영합니다.

### 995행

```c

		folio_add_lru(folio);

```

folio를 LRU 관리에 추가하여 이후 메모리 회수 정책이 추적할 수 있게 합니다.

### 996행

```c

		if (kernel_file)

```

커널 파일의 캐시였다면 전용 계측도 갱신합니다.

### 997행

```c

			mod_node_page_state(folio_pgdat(folio),

```

folio가 속한 메모리 노드의 페이지 계수를 갱신합니다.

### 998행

```c

					    NR_KERNEL_FILE_PAGES,

```

NR_KERNEL_FILE_PAGES라는 커널 파일 페이지 통계를 선택합니다.

### 999행

```c

					    folio_nr_pages(folio));

```

folio가 포함하는 실제 기본 페이지 수만큼 더합니다. folio 하나를 무조건 1페이지로 세지 않습니다.

### 1001행

```c

	return ret;

```

등록 성공 0 또는 삽입 오류를 반환합니다.

