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

SLUB: 빈 객체 안에 다음 빈 객체를 연결하기

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

커널은 수십 바이트의 객체를 만들 때마다 페이지 한 장씩 소비하지 않습니다. SLUB는 페이지를 같은 종류의 객체로 나누고 빈 객체들을 연결해 둡니다. 여기서는 한 객체에서 다음 빈 객체의 위치를 읽는 함수를 통해, 데이터의 주소와 그 주소에 저장된 포인터를 구분합니다.

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

먼저 알아둘 개념

객체 캐시

같은 크기와 속성을 가진 객체를 반복해서 공급하는 단위입니다. kmem_cache는 객체 크기뿐 아니라 정렬, freelist 위치와 각종 검사 설정을 포함합니다.

freelist

아직 사용하지 않는 객체들을 연결한 목록입니다. 빈 객체 내부의 정해진 위치에 다음 객체를 가리키는 값이 저장될 수 있습니다.

포인터 인코딩

강화 설정에서는 다음 객체 주소를 그대로 저장하지 않고 캐시의 난수와 저장 위치 등을 사용해 변환합니다. 저장된 비트열과 실제 주소는 같지 않을 수 있습니다.

처음 읽을 때

object는 현재 빈 객체의 시작, object+s->offset은 연결값이 놓인 곳, 그곳에서 읽어 해독한 결과는 다음 객체의 시작입니다. 이 세 값을 서로 다른 이름으로 적어 보세요.

더 깊이 살펴볼 때

freelist_ptr_decode와 encode의 대칭 관계를 확인하고 저장 위치를 섞는 이유를 살펴보세요. 이 함수만으로 동시 할당의 원자성이 보장되는 것은 아니므로 호출자의 동기화도 확인하셔야 합니다.

그림으로 보는 변화

SLUB: 빈 객체 안에 다음 빈 객체를 연결하기의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 빈 객체 선택

현재 객체의 시작 주소 object

화살표는 포인터를 따라가는 순서입니다. 객체 할당 전체 경로를 세 단계로 압축한 것은 아닙니다.

2. 연결값 위치 계산

ptr_addr = object + s->offset

이 화살표는 같은 객체 안에서 저장 위치를 찾는 주소 계산입니다. 다음 객체로 아직 이동하지 않았습니다.

3. 읽고 해독

메모리의 freeptr_t → 다음 객체 주소

저장값을 decode하여 다음 빈 객체 주소를 얻습니다. 값이 NULL이면 목록 끝입니다.

get_freepointer를 한 줄씩 읽기

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

static inline void *get_freepointer(struct kmem_cache *s, void *object)
{
	unsigned long ptr_addr;
	freeptr_t p;

	object = kasan_reset_tag(object);
	ptr_addr = (unsigned long)object + s->offset;
	p = *(freeptr_t *)(ptr_addr);
	return freelist_ptr_decode(s, p, ptr_addr);
}
static inline void *get_freepointer(struct kmem_cache *s, void *object)

캐시 s에 속한 빈 객체 object에서 다음 빈 객체 주소를 읽습니다. static inline은 파일 내부의 짧은 보조 함수임을 나타냅니다.

	unsigned long ptr_addr;

연결값이 저장된 메모리 위치를 담을 정수 변수를 마련합니다. 아직 다음 객체 주소가 들어 있는 것은 아닙니다.

	freeptr_t p;

freeptr_t는 메모리에 저장된 연결값의 표현입니다. 디코딩된 void 포인터와 타입부터 구분합니다.

	object = kasan_reset_tag(object);

KASAN 주소 태그를 정리해 실제 객체 위치 계산에 사용할 주소를 얻습니다. 객체의 내용을 지우는 함수가 아닙니다.

	ptr_addr = (unsigned long)object + s->offset;

객체 시작에서 캐시가 지정한 offset만큼 이동해 연결값의 저장 위치를 계산합니다.

	p = *(freeptr_t *)(ptr_addr);

계산한 주소를 freeptr_t 포인터로 해석하고 그 위치의 값을 읽습니다. 주소 계산만 하던 앞줄과 달리 실제 메모리를 읽는 줄입니다.

	return freelist_ptr_decode(s, p, ptr_addr);

캐시 정보, 저장값, 저장 위치를 이용해 실제 다음 객체 주소를 복원하여 반환합니다. 강화 설정이 없으면 더 단순한 변환이 됩니다.

함께 생각해 볼 질문

object와 ptr_addr가 항상 같은가요?

아닙니다. 연결값의 저장 위치인 s->offset이 0이 아니라면 객체 내부의 다른 주소입니다.

메모리에서 읽은 p가 곧 다음 객체 주소인가요?

강화 기능이 켜졌다면 인코딩된 값일 수 있습니다. freelist_ptr_decode의 결과를 주소로 사용합니다.

이 함수가 반환하면 객체 할당까지 완료되나요?

아닙니다. 다음 연결을 읽는 보조 함수입니다. 실제 목록 갱신과 동시성 처리는 상위 할당 경로에서 수행합니다.

출처와 읽은 범위

Linux stable v6.18.37 · mm/slub.c

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

맨 위로 ↑