Memory Model -1- (Basic)

<kernel v5.15>

물리 메모리 모델

물리 메모리의 0번 주소부터 시작하여 마지막 끝 주소까지 연속된 주소를 사용하는 시스템의 경우 물리 메모리를 관리하는 방법이 가장 간단하다. 그런데 그러한 물리 메모리의 중간에 약간의 홀(hole)이 있어 cpu로 부터 접근할 수 없는 경우가 있다. 어떠한 아키텍처들은 물리 메모리를 배치할 때 중간에 홀(hole)이 생성되는 경우가 있는 경우가 있다. 또한 NUMA 시스템을 보면 메모리 뱅크 사이에 커다란 홀이 발생할 수도 있다.

 

리눅스 커널은 물리 메모리를 페이지 프레임 단위(디폴트 4K)로 1:1로 대응하여 관리를 하는 page 구조체를 사용한다. 예를 들어 1GB 메모리를 가진 ARM64 시스템의 경우 에서 의 경우 1G 물리 메모리는 페이지 프레임 단위로 1M(0x100000)개를 사용하고, 이에 대응하는 page 구조체는 1M * 64바이트(디폴트) = 64MB 의 용량을 차지한다. 그러니깐 전체 물리 메모리의 약 1.5%를 page 구조체들이 사용을 하며, 이들을 mem_map이라고 부른다.

 

물리 메모리는 물리 메모리 주소 공간 0부터 시작하는 PFN(Page Frame Number) 숫자 개념을 사용하며, 이 PFN과 page 구조체는 1:1로 대응하므로 서로 변환을 많이하며 변환이 쉽고 빨라야 한다. 이 때 사용하는 API는 pfn_to_page()와 page_to_pfn() 매크로 함수를 사용한다. 전체 물리 메모리가 hole 없이 구성된 경우 page 구조체들로 구성된 mem_map은 일괄적으로 생성하고 이에 대해 pfn을 통한 접근이 단순 산술 연산으로 쉽게 해당 page 구조체를 가리킬 수 있게되는데, 전체 물리 메모리에 홀(hole)이 있는 경우 mem_map 구성에 대한 방법이 달라져아함을 알 수 있고, 이에 대해 리눅스 커널은 3가지의 물리 메모리 모델을 제공하고 있다.

 

3가지 물리 메모리 모델

리눅스는 물리 메모리를 관리하기 위해 FLATMEM, DISCONTIGMEM 및 SPARSEMEM의 3가지 모델을 제공하다, 최근에 DISCONTIGMEM을 제외하였고, 아키텍처들은 이 중 하나 이상을 지원하며 커널 빌드 시 지원되는 메모리 모델 중 하나를 선택하여 빌드한다. (32비트 시스템의 대부분은 FLATMEM을 사용하고, 64비트 시스템의 경우 SPARSEMEM을 사용한다)

mm-1b

 

물리 메모리 모델별 특징

각 물리 메모리 모델은 물리 메모리 사이에 hole이 존재하는 경우 이를 처리하는 방법이 다르다. 물리 메모리의 각 프레임 관리를 위해 page 구조체가 필요하고, 이들의 집합형태가 mem_map이다. 이들을 관리하는 방법에 따라 아래 3가지의 물리 메모리 모델을 알아본다.

 

FLATMEM

  • 설치된 메모리 뱅크들의 주소가 연속된다. 이러한 경우 가장 심플하게 이 모델을 선택할 수 있다.
  • 설치된 메모리 뱅크들의 주소 사이에 작은 hole이 발생하는 경우에도 이 모델을 사용할 수 있다. 대신 해당 hole에 대응하는 mem_map 만큼의 메모리를 낭비한다.

 

DISCONTIGMEM

  • 2009년 NUMA 시스템을 위해 소개되었다.
  • 설치된 메모리 뱅크들의 주소 사이에 큰 hole이 발생하는 경우에 이 모델을 사용했었다.
  • ARM은 2010년 핫 플러그에 대응을 못하는 DISCONTIGMEM을 대체하는 SPARSEMEM이 도입되면서 이 모델의 지원을 종료하였다.
  • 커널 메인라인에서 조차 이 모델을 완전히 제거하였다.

 

SPARSEMEM

  • 설치된 메모리 뱅크들의 주소 사이에 큰 hole이 발생하는 경우(NUMA 시스템 포함) 또는 hotplug memory 기능이 필요한 경우 이모델을 선택할 수 있다.
  • 섹션 단위로 online/offline(hotplug memory) 관리가 되며 섹션 사이즈는 수십MB~수G로 커널 옵션 또는 아키텍처별로 다르다.
    • arm32의 경우 설정된 커널 옵션마다 다르다.
    • arm64의 경우 디폴트 값으로 1G를 사용다가 최근 128M로 변경하였다.

 

ARM with NUMA

mm-2a

메모리 모델 주요 커널 옵션

mm-4
  • ARM64의 경우 다음과 같은 옵션이 사용되어 SPARSEMEM 메모리 모델 1개만을 사용하고 있다.
    • CONFIG_ARCH_SELECT_MEMORY_MODEL=y
    • CONFIG_SELECT_MEMORY_MODEL=y
    • CONFIG_ARCH_SPARSEMEM_ENABLE=y
    • CONFIG_ARCH_SPARSEMEM_DEFAULT=y

 

메모리 모델 부가 커널 옵션

mm-5
  • NEED_MULTIPLE_NODES
    • NUMA 시스템 또는 DISCONTIGMEM 메모리 모델에서 사용할 수 있다.
    • NODE_DATA() 매크로를 사용하여 각 노드의 mem_map을 가리킬 수 있게 한다.
    • 전역 node_data[] 배열을 사용하여 노드별로 mem_map에 접근한다.
  •  FLAT_NODE_MEM_MAP
    • FLATMEM 또는 DISCONTIGMEM 메모리 모델에서 사용할 수 있다.
    • 노드 정보를 기술해 놓은 pglist_data 구조체의 멤버 변수 node_mem_map을 통해 mem_map에 접근한다.
  • HAVE_MEMORY_PRESENT
    • SPARSEMEM 메모리 모델을 위해 요청한 범위에 hole이 발생할 수 있으므로 각 섹션 별로 메모리 존재 여부를 관리할 수 있도록 한다.
  • SPARSEMEM_EXTREME
    • sparse 메모리 모델을 사용할 때 사용되는 두 가지 선택 중 하나이다.
    • mem_section[] 배열을 dynamic 하게 할당 받아 사용하려고 할 때 사용
    • 주로 생성해야 할 섹션 수가 많은 64비트 시스템에서 사용한다.
  • SPARSEMEM_STATIC
    • sparse 메모리 모델을 사용할 때 사용되는 두 가지 선택 중 하나이다.
    • 컴파일 시 static하게 할당해둔 mem_section[]을 사용한다.
    • 주로 생성해야 할 섹션 수가 적은 32비트 시스템에서 사용한다.
  • SPARSEMEM_VMEMMAP
    • vmemmap을 사용하여 빠르게 pfn과 page descriptor 주소를 변환할 수 있다.
    • vmalloc 주소 범위가 큰 64비트 시스템에서 사용한다.
  • MEMORY_HOTPLUG
    • 메모리를 시스템 동작 중에 추가할 수 있다.
  • MEMORY_HOTREMOVE
    • 메모리를 시스템 동작 중에 제거할 수 있다.
    • MEMORY_HOTPLUG, ARCH_ENABLE_MEMORY_HOTREMOVE, MIGRATION 옵션이 있을 때 선택할 수 있다.
  • HIGHMEM
    • 32bit에서 커널에 미리 1:1 매핑되지 않아 커널이 직접 access를 해야 할 때마다 매핑하여 사용해야 하는 메모리 범위이다.
    • 32bit 시스템에서 시스템 메모리가 커널 공간보다 큰 경우 HIGHMEM 옵션을 사용하여 더 많은 메모리를 활용할 수 있게 한다.
  • HIGHPTE
    • 32bit 시스템에서 2차 PTE를 highmem에 할당한다.
    • 32bit 시스템에 수 기가 이상의 메모리를 사용하는 경우 2차 PTE를 highmem에 로드하게 하여 lowmem 메모리를 소모하지 않게 유도한다.
  • NODE_NOT_IN_PAGE_FLAGS
    • page 구조체의 flags 멤버변수에 노드 번호를 기록하지 못하는 경우 노드 번호를 별도로 섹션에 대해 1:1로 매핑하여 저장한다
    • 32비트 시스템에서 비트 자리가 부족하여 노드 번호를 기록하지 못하는 경우 사용

 

메모리 모델과 페이지 관리맵(mem_map)

전체 물리 메모리의 페이지 프레임 만큼 page 구조체 배열이 mem_map이라하는데 다음과 같이 각 메모리 모델별로 mem_map을 관리한다.
  • FLATMEM
    • *mem_map 포인터가 page[] 구조체 배열을 가리킨다.
    • 노드가 하나 이므로 전역 구조체 contig_page_data를 사용하는데 이 구조체의 멤버 변수 node_mem_map 역시 page[] 구조체 배열을 가리킨다.
  • DISCONTIGMEM
    • 노드가 두 개 이상이므로 node_data[] 배열을 사용하는데 이 구조체의 멤버 변수 node_mem_map은 각 노드와 관련된 page[] 구조체 배열을 가리킨다.
  • SPARSEMEM
    • 두 가지 방법을 사용한다.
      • SPARSEMEM_STATIC
        • 섹션 수 만큼 mem_section[] 배열을 컴파일 타임에 정적 메모리에 두고 각 섹션 엔트리의 멤버 변수 section_mem_map이 해당 섹션 크기 만큼의 페이지를 관리하는 page[] 구조체 배열을 가리킨다.
      • SPARSEMEM_EXTREAM
        • 섹션 수 만큼 *mem_section[] 포인터 배열을 정적 메모리에 두고 실제 각 mem_section[] 배열은 커널 초기화를 진행할 때 mem_section[] 배열을 dynamic하게 할당받아 메모리를 사용하고 mem_section[]의 사용은 SPARSEMEM_STATIC과 같다.
mm-3b

참고

 

 

3 thoughts to “Memory Model -1- (Basic)”

  1. 안녕하세요. 좋은 정보 게시해주셔서 정말 감사합니다.
    아직 다 읽어보진 않았지만 궁금한 점이 있어서 질문 남깁니다.

    먼저 이 글에서 지칭하시는 메모리 뱅크라는 것이
    DRAM Device 내에서 여러 개의 Memory array로 구성된 Bank를 의미하는 것인지,
    아니면 단순히 메모리 주소 영역의 한 묶음을 나타내는 뱅크(ex. 캐시 뱅크)를 의미하는 것인지 궁금합니다.

    또한 SPARSEMEM으로 설정할 경우 Section단위로 Memory Hotplug가 가능하다고 되어있는데,
    여기서 Section이 지칭하는 것은 하드웨어적으로 어떤 부분으로 봐야하는지 궁금합니다.
    말하자면 Section이 DRAM Device의 내부 구조 상 어떤 단위로 이어지는지 알고 싶습니다.

    1. 안녕하세요?
      메모리 뱅크는 논리적으로 언급하였습니다.
      다음 2개의 노드로 구성된 NUMA 시스템 예를 설명해보겠습니다.
      각 노드에 사용한 DRAM 컨트롤러는 최대 물리 메모리를 64G까지 사용할 수 있다고 가정합니다. 또한 물리메모리 시작 주소를 0x0부터 시작하였다고 가정합니다.
      1st 노드: DRAM Controller – 0x0000 0000_0000 ~ 0x0010 0000 0000 까지 관리
      2nd 노드: DRAM Controller – 0x0010 0000 0000 ~ 0x0020 0000 0000 까지 관리

      참고로 cpu 아키텍처 또는 SoC 디자인 시 두 번째 노드를 첫 번째 노드가 사용하는 64G 공간 다음에 붙일 수도 있고 적당히 떨어뜨려 배치할 수도 있습니다.

      사용자는 첫 번째 노드에 4GB 메모리를 4개 꽂고, 두 번째 노드에도 4GB 메모리를 4개 꽂았습니다. 이러한 경우 총 32GB를 구성한 경우죠.

      이런식으로 시스템을 구성하면 제가 표현한 논리적 메모리 뱅크는 2개입니다.
      (하드웨어에서 사용하는 메모리 뱅크, 메모리 슬롯 등등과 연결하여 생각하면 안됩니다)

      또 한가지 예를 들어봅니다.
      하나의 컨트롤러를 사용하더라도 아키텍처 설계 시 메모리 배치가 일정 용량을 초과하는 경우 일정 공간을 건너띄웁니다.
      DDR 메모리 컨트롤러가 64G까지 관리하지만 물리메모리에 배치할 때 다음과 같이 사용하지만 arm64의 경우는 다음과 같이 일정 공간을 벌려놓습니다.

      0x00 0000 0000 – 0x00 FFFF FFFF …
      0x00 8000 0000 – 0x00 FFFF FFFF (1st 뱅크: 첫 번째 물리 공간에 2G를 배치)
      0x01 0000 0000 – 0x08 7FFF FFFF …
      0x08 8000 0000 – 0x0F FFFF FFFF (2nd 뱅크: 메모리가 2G를 초과하는 경우 여기서 추가로 30G까지 배치)
      0x10 0000 0000 – 0x88 FFFF FFFF …
      0x88 0000 0000 – 0x8F FFFF FFFF (3rd 뱅크: 메모리가 32G를 초과하는 경우 여기서 나머지 32G까지 배치)

      이러한 경우 최대 3개의 뱅크가 존재하는 것일까요?

      만일 4G 메모리를 모두 꽂아 64G를 채웠습니다. 이 때에는 3개의 뱅크가 맞죠.
      그런데 만일 두 번째 뱅크 중간의 위치에 있는 4GB DRAM을 제거하였습니다.
      그럼 몇 개의 뱅크일까요? 따라서 이러한 것을 하드웨어와 연결하여 생각하지 마시고,
      커널 입장에서 물리 메모리가 한꺼번에 연달아 배치가 불가능하면 각각을 뱅크라고 생각하시면됩니다.

      – – –

      이번에는 섹션에 대해 말씀드립니다.
      일단 arm 아키텍처에서 언급하는 섹션 매핑(1M)를 언급하는 것은 아닙니다.
      동일한 용어를 사용하지만 리눅스 커널의 sparsemem에서 사용하는 섹션은 다른 것임을 먼저 말씀드립니다.

      섹션은 hotplug를 고려하여 메모리를 추가 및 삭제하거나,
      SOC 디자인 시 메모리의 배치가 어느 일정 규칙에 의해 배치되는 것을 고려하여 커널 컴파일 시 사이즈를 결정할 수 있습니다.
      물론 커널 파라메터나 CONFIG로 시작하는 커널 옵션으로 설정할 수 없고, 보통 SoC 개발자가 결정합니다.
      arm64의 경우 SECTION_SIZE_BITS=30을 사용하여 1GB를 디폴트로 사용합니다.
      이 사이즈는 작으면 수십M에서 큰 경우 수G 단위로 지정하여 사용하는데 하드웨어적으로 물리메모리가 추가/제거될 수 있는 용량을 선택합니다.
      예를 들어 내 서버가 최소 메모리의 증설이 512MB 단위이면 섹션 크기를 512MB로 설정합니다.
      아직 ARM 시스템에서 하드웨어적으로 메모리를 핫플러그 하거나 핫리무브하는 경우는 없지만 다른 아키텍처들은 가능한 서버들이 있습니다.
      또한 소프트웨어적으로는 하이퍼 바이저를 사용하여 리눅스 커널을 운영하는 경우 핫플러그 메모리 증설 단위를 128MB 단위로 운영하는 것이 효과적이다라고 판단하면 섹션 크기를 128M로 할 수 있습니다. (자주 사용되는 단위는 128MB, 256MB, 512MB, 1G, 4G 단위를 사용합니다)

댓글 남기기