전체 흐름
memblock은 보통의 페이지 할당기가 준비되기 전, 물리 주소 구간을 관리한다. Linux v6.6의 memory 목록은 커널이 인식하는 RAM을, reserved 목록은 예약된 구간을 나타낸다. 예약된 RAM도 memory에 남을 수 있다. 따라서 RAM으로 등록됐다는 사실과 새 할당에 쓸 수 있다는 사실은 다르다.
두 목록은 같은 배열의 서로 다른 상태가 아니다. 예를 들어 RAM [0x1000, 0x9000)에 [0x3000, 0x5000)을 예약하면 RAM 기록은 그대로이며, 차집합은 [0x1000, 0x3000)과 [0x5000, 0x9000)이다. 숫자는 설명용이다.
소스 코드 위치
아래 코드는 Linux v6.6의 mm/memblock.c 기준이다. memblock_add()와 memblock_reserve()는 대상 목록만 달리하여 memblock_add_range()를 부른다. include/linux/memblock.h에는 자료구조와 반복자, arch/arm64/mm/init.c에는 ARM64 초기화 호출부가 있다.
다른 버전의 주석이나 도식을 그대로 적용하지 않는다. 특히 v6.6의 구간 추가는 배열 여유가 충분하면 카운트 전용 순회를 건너뛸 수 있고, 배열 확장 실패 시 오류를 돌려준다.
대표 코드
memblock_add(): memory 목록에 추가
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}
end는 디버그 출력용 마지막 주소다. 실제 범위 처리는 base와 size를 받은 memblock_add_range()가 맡는다. 반환값은 그대로 호출자에게 전달하므로 성공 로그처럼 보이는 호출 기록만으로 추가 성공을 판정하지 않는다.
memblock_reserve(): reserved 목록에 추가
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}
대상이 &memblock.reserved인 점을 비교한다. 이 함수는 memory 배열에서 해당 RAM을 삭제하지 않는다. 구간을 경계에서 분리하는 memblock_isolate_range()나 제거하는 memblock_remove_range()와 혼동하지 않는다.
함수별 분석
memblock_add_range()는 요청 구간 중 기존 목록에 없는 부분만 추가하고, 조건이 맞는 이웃 구간을 병합한다. 빈 배열에는 첫 구간을 바로 기록한다. 이미 완전히 덮인 요청은 새로운 구간이 없어 성공으로 끝날 수 있다.
모든 경로가 두 번 순회하는 것은 아니다. 카운트 경로에서 필요한 배열 확장이 실패하면 -ENOMEM을 반환한다. 인접하다는 이유만으로 node와 flags가 다른 구간까지 병합하지 않는다.
추가·예약·제거를 구분하는 예
같은 node와 flags의 목록에 [0x1000, 0x4000)이 있을 때 [0x3000, 0x6000)을 추가하면, 겹침을 중복 계산하지 않고 [0x1000, 0x6000)으로 합칠 수 있다. 이 설명은 한 목록 안의 추가·병합이다. RAM 목록과 예약 목록 사이를 병합한다는 뜻이 아니다.
반면 memblock_remove()는 memory에서, memblock_phys_free()는 reserved에서 구간을 제거한다. memblock_phys_free()가 예약을 해제했다고 그 자리에서 buddy 할당기에 페이지를 넘기는 것은 아니다.
구조체와 필드
memory.total_size와 reserved.total_size는 각 목록의 합계다. 두 목록이 겹칠 수 있으므로 두 값을 더해서 전체 RAM 용량으로 해석하지 않는다.
current_limit은 접근 가능한 범위로 할당하는 경로에서 사용하는 상한이다. 모든 API의 명시적 주소 상한을 무조건 덮어쓰는 값은 아니다. bottom_up이 참이면 낮은 주소 쪽부터, 거짓이면 높은 주소 쪽부터 후보를 찾는다.
구간의 flags와 NUMA 조건도 검색에 영향을 준다. 단순히 두 목록의 차집합이 존재한다는 이유만으로 특정 크기·정렬의 할당이 성공한다고 판단하지 않는다.
v6.18.37 글과 비교할 때 달라진 점
같은 개념을 다루지만 v6.18.37 분석의 초기 계수와 예약 flag를 v6.6 코드에 그대로 대입하면 안 된다. 아래는 두 고정 버전의 mm/memblock.c에서 직접 확인한 차이다.
| 지점 | v6.6 | v6.18.37 |
|---|---|---|
빈 목록의 cnt | 초기 dummy entry를 세어 1; 첫 추가가 그 entry를 채운다. | 초기 0; 첫 추가 때 1로 바꾼다. |
| 일반 초기 할당의 예약 | memblock_alloc_range_nid()가 memblock_reserve()를 호출한다. | 같은 경로에서 MEMBLOCK_RSRV_KERN flag로 예약한다. |
| 겹침 앞쪽에 새 조각을 삽입할 때 flags 경고 | rbase > base 분기에서 새 flags와 기존 flags가 다르면 경고한다. | 새 flags가 MEMBLOCK_NONE이면 기존 flags와 달라도 해당 경고를 내지 않는다. |
실행 단계와 상태 변화
개념적인 단계다. 모든 아키텍처가 동일한 호출 순서를 갖는다는 뜻은 아니다. 예약되지 않은 범위를 페이지 경계에 맞추고, 해당 경로의 주소 상한도 적용한 뒤 buddy에 넘긴다.
memblock_free_all()은 남은 free 영역의 페이지를 buddy에 넘기는 초기화 경로다. 반대로 memblock_phys_free()는 reserved 기록을 제거하는 API다. 이름에 free가 있어도 같은 작업이 아니다.
CONFIG_ARCH_KEEP_MEMBLOCK을 쓰지 않는 구성에서는 초기화 뒤 memblock 자료구조를 폐기할 수 있다. 부팅 후의 일반 할당·해제를 이 초기 구간 목록만으로 설명하지 않는다.
불변 조건과 실패 사례
- 한 목록의 겹치는 주소를 중복 합산하지 않는다. 병합은 주소 연속성뿐 아니라 node와 flags 호환 조건도 확인한다.
memblock_reserve()는 RAM 등록을 제거하는 연산이 아니다. 예약 제외 효과는 free-range 검색에서 확인한다.- region 배열 확장에는 별도의 저장 공간이 필요하다.
memblock_allow_resize()사용 시 아직 예약하지 않은 initrd 등과 겹치지 않도록 초기화 순서를 검토한다. - 추가 함수의 0 반환은 성공을 뜻하지만, 새 entry가 생겼다는 보장은 아니다.
-ENOMEM같은 실패는 호출자가 처리해야 한다. memblock_phys_alloc*()의 물리 주소와memblock_alloc*()의 가상 주소 반환을 혼용하지 않는다.
계측과 검증
memblock=debug 부팅 인자로 구간 변경과 할당 관련 로그를 살핀다. 출력은 호출 시점의 인자일 수 있으므로 반환값과 최종 목록도 대조한다. 이 글은 실제 보드를 부팅하여 검증한 결과가 아니라 고정 v6.6 소스에 근거한 설명이다.
earlycon은 해당 보드의 UART와 설정이 맞아야 초기 로그를 전달할 수 있다. 초기 RAM·예약 목록, 요청 크기와 정렬, 반환 주소 또는 오류를 함께 기록한다. 부팅이 끝난 뒤 켠 function tracer나 page_owner로 이미 끝난 초기 memblock 호출을 소급 관찰할 수 있다고 가정하지 않는다.
아키텍처별 호출 순서, CONFIG_ARCH_KEEP_MEMBLOCK, NUMA 및 메모리 맵 조건을 먼저 기록한다. 일반 tracefs 모듈 필터 명령은 이 초기 부팅 구간의 재현 절차를 대신하지 못한다.
추가 확인 사항
- 예약 전후에 memory 목록이 그대로인데 free 범위만 달라지는 것을 설명할 수 있는가?
- 추가 요청이 기존 구간에 완전히 포함될 때 cnt와 total_size는 어떻게 되는가?
- 현재 배열 용량은 직접 삽입 조건을 만족하는가, 아니면 카운트·확장 경로가 필요한가?
- 현재 보드의 mem_init 경로에서 어떤 free 범위가 buddy에 전달되고 어떤 예약이 남는가?