https://www.iue.tuwien.ac.at/phd/weinbub/dissertationsu16.html
위 NUMA(Non-Uniform Memory Access)에서 동일한 Memory에 접근하는 CPU들을 Node라고 한다. UMA(Uniform Memory Access)에서는 1개의 node로 구성된다.
linux kernel에서는 각 node의 물리 메모리를 Zone이라는 영역으로 나누어 관리한다.
page frame에 대한 정보는 struct page에 있으며, 이를 Page Frame Descriptor이라 한다. page frame 각각에 대해 존재하며, 기본 kernel config 기준 최소 56 bytes를 차지한다.
https://elixir.bootlin.com/linux/v5.15.113/source/include/linux/mm_types.h#L70
unsigned long flags; // page frame 속성 정보. 각 플래그 정보는 /include/linux/page-flags.h에서 찾을 수 있다.
struct list_head lru; // LRU linked list node
struct address_space *mapping; // 해당 page frame이 어디에 속해 있는지를 기술하는 구조체 address_space의 포인터
pgoff_t index; // mmap mapping 내에서의 offset
unsigned long private; // 아무 용도로 사용 (pointer size). 원하는 구조체의 포인터로 casting해서 사용하기도 한다.
atomic_t _mapcount; // page table에 매핑된 카운트. page table에서 해당 page frame을 참조 시 1씩 증가한다. (초기 값은 -1). page_mapcount() inline function으로 해당 필드를 읽는다.
atomic_t _refcount; // kernel code에서 참조 시 1씩 증가(LRU list 등...), 참조 해제되면 1씩 감소. page 사용 여부를 판단하고, count를 감소시켜 회수하는 방법도 있다.
Zone은 각 영역에 따라 사용 용도가 다르다.
Lowmem = ZONE_DMA + ZONE_DMA32 + ZONE_NORMAL
Pseudo Zone 항상 존재하지 않고 필요할 때만 있는 zone
만약 DMA 버퍼를 할당해야 할 경우, 가능한 경우 ZONE_DMA, ZONE_DMA32에서 할당하고, 해당 zone의 메모리 공간이 부족하면 ZONE_NORMAL에서 할당한다.
각 node의 정보는 struct pglist_data로 관리되며, 이들의 포인터 배열 struct pglist_data *node_data[MAX_NUMNODES]를 통해 접근할 수 있다.
page frame 회수는 각 node에서 동작하며, 각 node에는 kswapd kernel thread가 backgroud 상에서 동작한다.
https://elixir.bootlin.com/linux/v5.15.113/source/include/linux/mmzone.h#L800
struct zone node_zones[]; // 해당 node에 있는 zone
struct zonelist node_zonelists[]; // page frame 할당을 위한 zone들의 fall-back list
struct page *node_mem_map; // 해당 node에 있는 page frame의 array
struct lruvec __lruvec; // LRU list들의 head를 저장 (ACTIVE-ANON, INACTIVE-ANON, ACTIVE-FILE, INACTIVE-FILE, UNEVICTABLE)
Fall Back: 원래 동작이 실패할 경우를 위한 대안. node_zonelists는 기존에 할당 받으려던 zone의 메모리가 부족할 경우 다른 zone에서 메모리를 받기 위해 존재한다.
https://unix.stackexchange.com/questions/42095/high-memory-user-space-and-highmem-kernel-space
32bit architecture에서는 가상 메모리 주소가 0B~4GB까지이다. 보통 이 가상 메모리 영역을 1:3으로 나누어 0B~3GB를 user spcae, 3GB~4GB를 kernel space로 사용하는데, kernel virtual memory space는 물리 메모리와 1대1 mapping(direct mapping)된다.
kernel space와 user space를 나누는 경계를 나타내는 매크로는 PAGE_OFFSET이다.
예를 들어, kernel space의 가장 가운데 영역을 접근하면, 물리 메모리의 가장 가운데 부분에 접근되는 것이다.
그러면 kernel space와 direct mapping되는 물리 메모리의 크기는 1GB여야 한다. 하지만 물리 메모리가 1GB보다 커지게 되면 kernel space와 direct mapping되지 않는 일부 물리 메모리 영역이 생길 것이고, 그렇게 되면 kernel에서 직접 접근할 수 없게 된다.
이를 해결하기 위해 kernel virtual space의 상위 128MB를 Reserved 영역으로 두고, 나머지 kernel space의 896MB는 직접 물리 메모리에 매핑된다. 그리고 할당된 128MB 영역은 direct mapping되지 않는 물리 메모리 영역을 필요할 때 따로 mapping하여 사용한다. 또한 사용이 끝나면 mapping을 해제한다.
위 그림에서 kernel space에 direct mapping되는 영역을 ZONE_NORMAL, 위에 direct mapping되지 않은 영역을 ZONE_HIGHMEM이라 한다. 64bit architecture에서는 virtual address 영역이 매우 넓으므로 ZONE_HIGHMEM이 필요없다.
https://elinux.org/images/b/b0/Introduction_to_Memory_Management_in_Linux.pdf