1. 메모리 관리 기법과 가상 메모리
가상메모리
- 물리 메모리의 한계를 극복하기 위해 개발된 기법들 중 하나
- 실제 시스템에 존재하는 물리 메모리의 크기와 관계 없이 가상적인 주소 공간을 사용자 태스크에게 제공
- 사용자에게 개념적으로 큰 공간을 제공함과 동시에 물리 메모리는 필요한 만큼의 메모리만 사용되므로 가능한 많은 태스크가 동시에 수행될 수 있는 장점을 제공함
- 메모리 배치 정책이 불필요하며 태스크 간 메모리 공유/보호가 쉽고, 태스크의 빠른 생성이 가능하다는 장점도 있음
2. 물리 메모리 관리 자료 구조
SMP(Symmetric Multiprocessing)
- 복수 개의 CPU를 가지고 있는 컴퓨터 시스템 중 모든 CPU가 메모리와 입출력 버스 등을 공유하는 구조
- 성능상 병목 현상이 발생할 수 있음 → CPU들을 몇 개의 그룹으로 나누고 각각의 그룹에게 별도의 지역 메모리를 주는 구조 탄생 ⇒ NUMA(Non-Uniform Memory Access) ↔ 기존 시스템을 UMA라고 함
NUMA
- CPU에서 어떤 메모리에 접근 하느냐에 따라 성능의 차이가 생길 수 있음
- 각 구조에 적합한 메모리 정책을 사용해야 함
** 리눅스개발자는 UMA 구조에서도 NUMA 구조에서도 모두 효율적으로 수행될 수 있도록 설계함
Node
- bank : 접근 속도가 같은 메모리의 집합
- UMA 구조에서는 한 개의 뱅크가, NUMA 구조에서는 복수개의 뱅크 존재
- 노드는 뱅크를 표현하는 구조
- UMA 구조에서 리눅스가 수행된다면 한 개의 노드가, NUMA 구조에서 리눅스가 수행된다면 복수 개의 노드가 존재
** 리눅스는 하드웨어 시스템에 관계 없이 노드라는 일관된 자료구조를 통해서 전체 물리메모리를 접근할 수 있게 됨
- pg_data_t 구조체를 통해 표현됨
- 해당 노드에 속해있는 물리 메모리의 실제 양이나, 해당 물리메모리가 메모리 맵의 몇 번지에 위치하고 있는지 나타내기 위한 변수 등이 정의되어 있음
- zone 구조체를 담기 위한 배열과, zone의 개수를 담는 변수 등이 선언되어 있음
💡 리눅스가 물리 메모리의 할당 요청을 받게 되면, 되도록 할당을 요청한 태스크가 수행되고 있는 CPU와 가까운 노드에서 메모리를 할당하려고 함 → 보다 높은 성능을 얻을 수 있음
Zone
- 일부 ISA 버스 기반 디바이스의 경우 정상적인 동작을 하기 위해서 반드시 물리메모리 중 16MB 이하 부분을 할당해 줘야 함 ⇒ 노드에 존재하는 물리메모리 중 16MB 이하 부분을 특별하게 관리할 수 있도록 만든 자료구조
- 동일한 속성을 가지며, 다른 zone의 메모리와는 별도로 관리되어야 하는 메모리의 집합
- 16MB 이하 부분 : ZONE_DMA / 16MB 이상 부분 : ZONE_NORMAL
- 가상 주소공간과 물리메모리 공간을 1:1로 연결하면 비효율적 → 물리메모리가 1GB 이상이라면 896MB까지를 커널의 가상주소공간과 1:1로 연결해주고, 나머지 부분은 필요할 때 동적으로 연결하여 사용 ⇒ ZONE_HIGHMEM 이라고 함
- 시스템에서 항상 DMA, NORMAL, HIGHMEM의 zone이 존재하는 것은 아님 → 한 개의 zone만 존재하는 것도 가능
- 자신에게 속해있는 물리 메모리를 관리하기 위해 zone 구조체 사용
Page frame
- 물리 메모리의 최소 단위
- 페이지 프레임 당 하나씩 page 구조체 존재
- 시스템이 부팅되는 순간 구축되어 물리 메모리 특정 위치에 저장됨
💡 복수개의 페이지 프레임이 zone 구성 → 하나 이상의 zone이 node 구성 → 하나 이상의 node가 존재하는 것이 전체 물리 메모리 관리 구조
3. Buddy와 Slab
- 리눅스는 물리 메모리의 최소 관리 단위인 페이지 프레임 단위로 메모리를 할당
- 페이지 프레임 단위보다 작은 크기 요청 → 내부 단편화 문제 발생 ⇒ 해결을 위해 슬랩 할당자 도입
- 페이지 프레임 단위보다 큰 크기 요청 → 외부 단편화 발생 ⇒ 해결을 위해 버디 할당자 도입
버디 할당자 (Buddy Allocator)
- 메모리 관리의 부하가 적으며 외부 단편화를 줄일 수 있다는 장점 제공
- zone 당 하나씩 존재
- 요청된 크기를 만족하는 최소의 order에서 페이지 프레임을 할당 → 만일 order에 가용한 페이지 프레임이 존재하지 않으면 상위 order에서 페이지 프레임을 할당받아 두 부분으로 나누어, 한 부분은 할당해주고 나머지 부분은 하위 order에서 가용 페이지 프레임으로 관리 ⇒ 이때 나누어진 두 부분을 buddy라고 부르기 때문에 이 할당자를 버디 할당자라고 함
Lazy Buddy
- 커널 버전 2.6.19부터 free_area의 구조와 버디 할당자의 구현이 바뀜
- 기존 버디 할당자의 문제점
-
페이지 프레임을 할당할 때 큰 페이지를 쪼개서 할당하고, 이를 해제하기 위해 다시 큰 페이지로 합쳐서 관리
→ 할당/해제를 위해 많은 오버헤드 동반
⇒ 할당되었던 페이지 프레임을 합치치 않고 합치는 작업을 뒤로 미루는 방식으로 변경
- zone에 가용 메모리가 충분한 경우 해제된 페이지의 병합 작업을 최대한 뒤로 미루다 가용 메모리가 부족해지는 경우 병합 작업 수행
슬랩 할당자 (Slab Allocator)
- 페이지 프레임 크기가 클수록 내부 단편화로 인한 낭비되는 공간 또한 증가
- 미리 페이지 프레임을 한 개 할당받은 뒤 일정한 크기로 분할한 후 사용자가 요청한 크기만큼 할당 → 할당받은 공간을 해제하면 버디로 반납하는 것이 아닌 미리 할당받아 관리하던 공간에서 다시 관리 ⇒ 일종의 캐시로 사용
- 자주 할당되고 해제되는 크기의 cache를 가지고 있어야 내부 단편화를 최소화 시킬 수 있음 → 커널 내부에서 자주 할당/해제되는 자료구조의 크기를 위한 cache 유지 ++ 일반적인 메모리 할당 요청에 대비하기 위해 32Byte ~ 4MB크기까지 유지
슬랩
- cache는 슬랩들로 구성되고 슬랩들은 다시 객체(Object)들로 구성됨
- 구성하고 있는 객체들의 상태에 따라 구분됨
- Full : 모든 객체가 이미 사용 중인 상태
- Free : 모든 객체가 사용 가능한 상태
- Partial : 일부는 사용중이고 일부는 비사용 중인 상태
💡 슬랩 할당자에게 메모리 공간의 할당 요청이 들어오면 가장 적합한 크기의 캐시를 찾아가서, partial 슬랩으로부터 객체를 할당해줌
4. 가상 메모리 관리 기법
- 가상 메모리와 관련된 정보는 task_struct의 mm 필드에 담겨 있음
- mm_struct 자료구조가 관리하는 정보
-
태스크를 구성하고 있는 vm_area_struct 구조체
- 가상 메모리 공간 중 같은 속성을 가지며 연속인 영역인 region(일반적으로 segment) 관리
- 효율적인 관리를 위해 레드-블랙 트리로 연결되어 있음
-
주소 변환을 위한 페이지 디렉터리의 시작점 주소를 pgd 변수에 유지
-
가상 메모리 구조에 대한 변수
ex) start_code, start_data, start_stack 등의 변수
- 물리 메모리 관리 기법과 같이 고정된 크기의 할당 단위로 관리됨
- 할당 단위를 페이지라고 부름 → 공통 속성을 갖는 페이지들이 모여 vm_area를 구성하고 이를 vm_area_struct 자료구조로 관리 ++ 같은 태스크에 속한 vm_area_struct들이 모여 하나의 mm_struct 내에서 관리됨
가상 메모리 할당/해제 기법
- vm_area_struct 할당/해제 문제
- 새로운 페이지가 할당될 때 인접한 vm_area_struct와 속성이 같다면, 하나로 통합되어 관리될 수 있음 → 동적 분할 메모리 관리 시스템에서 메모리 병합 기법과 유사
- page의 할당/해제 문제 → 주소변환과 연관된 문제
5. 가상 메모리와 물리 메모리의 연결 및 변환
- 페이지 테이블을 사용하여 가상 주소를 물리 주소로 변환
페이지 테이블
- 가상 주소를 물리 주소로 변환하는 주소 변환 정보를 기록한 테이블
- 페이지들을 페이지 프레임에 적재할 때 주소변환을 위한 페이지 테이블도 함께 생성 → 태스크를 실행할 때 이 테이블을 이용해 주소변환 수행
- 페이지 테이블에 NP(Not Present)라고 표시됨 → 접근하려는 페이지가 물리 메모리에 적재되어 있지 않음을 표시 → 페이지 폴트 발생 ⇒ free한 페이지 프레임을 할당받아 실행 파일에서 필요한 페이지를 이 페이지 프레임에 읽어다 놓게 됨 → 적재하면서 페이지 테이블에 적재된 페이지 프레임 번호 기록 → 주소변환과정 다시 수행
- 실행파일을 처음 메모리에 적재할 때 모든 페이지를 적재하는 것은 아님 → 수행에 필요한 일부 페이지만 적재하고 다른 페이지들은 실제로 참조될 때 비로소 페이지 폴트 처리 과정을 통해 적재되도록 함 ⇒ 이를 요구 페이징(demand paging)이라고 함
가상 메모리
- 장점
- 물리 메모리의 크기와 관계 없이 상당히 큰 주소 공간을 프로그래머에게 제공할 수 있음 → 물리메모리가 굳이 클 필요 없음
- 프로그램의 모든 페이지들을 물리 메모리에 적재할 필요 없이 페이지 폴트를 이용해 필요할 때마다 페이지를 물리 메모리에 적재할 수 있어 메모리를 보다 효율적으로 사용 가능
- 여러 태스크가 특정 영역을 공유하고 싶을 때 단지 페이지 테이블에서 같은 페이지 프레임을 가리키게 하는 것 만으로 공유 메모리 지원 가능
- 단점
-
물리 메모리를 접근하기 위해 주소 변환 과정이 필요함
- 주소변환을 위해 페이지 디렉터리와 페이지 테이블을 탐색해야 하는데 이것이 프로그램 수행 시간을 지연시킬 가능성이 있음
-
메모리 접근 시간에 대한 예측성을 떨어뜨림
→ 대부분의 시스템은 주소변환과정의 많은 부분을 하드웨어 적으로 처리
++ TLB 같은 페이지 테이블 엔트리 캐시를 사용하여 빠른 주소 변환 지원
6. 커널 주소 공간
- 커널도 페이징 단계를 거쳐야 가상 주소를 물리 주소로 변환할 수 있음
7. Slub, Slob
슬랩 할당자 단점
- 시스템 규모가 커질수록 슬랩 할당자 자체를 위한 메타 데이터가 커짐
- 캐시 내의 각 스랩은 자신에게 할당된 공간 내에 별도의 메타 데이터를 유지하는데 이것이 성능 저하의 원인이 됨
⇒ 해결을 위해 slub 할당자 등장
Slub 할당자
- 각 슬랩에 별도의 메타데이터를 유지하지 않음
- 대신 페이지 별로 할당되는 page 구조체에 freelist, inuse, offset 필드를 이용하여 이들을 매우 ’단순‘하게 관리
- 할당시에 단지 해당 슬랩만의 락만을 획득하면 되기 때문에 성능 향상 도모 중
Slob (Simple List Of Blocks)
- 메모리가 아주 작은 상황을 위해 고안
- 보통 4Byte부터 시작하는 작은 크기의 메모리 블록을 단순 링크드 리스트로 유지하는 일종의 힙 형태의 할당자
- 단편화 방지와 블록의 효율적 분류를 위해 256Byte 미만, 1024Byte 미만, 그 외의 크기의 메모리 블록을 위한 세 개의 리스트 유지 → 할당 요청이 들어오면 요청된 크기에 맞는 블록을 찾기 위해 리스트를 탐색하여 최초 적합에 기반한 할당을 함
이 글은 아래의 책을 공부 및 정리한 내용입니다.
리눅스 커널 내부구조 - 예스24