2022. 5. 7. 16:18ㆍLinux
- 목차
Memory management
word ? page ? 커널은 물리적 페이지를 메모리 관리의 기본 단위로 사용 processor는 memory에 접근 시 byte나 word 단위로 접근, MMU는 page 단위로 처리함 * virtual memory 관점에서는 page가 유의미한 최소 단위임 |
page 크기
대부분의 32 bit architecture는 4KB
64bitarchitecture의 경우 8KB
page 구조체
struct page 구조체를 사용해모든 물리적 page를 표현
<linux/mm_types.h>에정의
struct page { unsigned long flags; atomic_t _count; atomic_t _mapcount; ... struct list_head lru; void *virtual; }; |
_count항목: page의 사용 횟수, 참조 횟수
virtual 항목: page의 가상 주소를 나타냄
가상 주소 상의 page 주소
high mem처럼 커널 주소공간에 고정되어 할당되지 않은 경우 NULL 값임
. 커널 address space에 고정되어 있어야 값을 지님
(이 경우 필요 시 동적 page설정)
page 구조체는 가상 page가 아닌 물리적 page를 나타낸다.
Page 구조체를 사용해서physical page-frame의 사용 여부를 판단할 수 있음
Process는 (만약 해당 page가 refcount가 -1이면) 사용 가능함 (multiple map 가능)
-> 즉, 일시적인 내용임 (swap 등이 발생 가능)
-> 늘, data가 같은 page 구조체에 들어있을 보장이 없음
page 소유자는
1) user space process
2) 동적 할당된 kernel data
3) 정적 kernel code
4) page cache 등이 가능
page 구조체의 크기가 40Byte의 경우
4GB의물리적 메모리에 대해 약 이 구조체가 20MB 를 사용
zone (구역)
page가관리할 memory device (node)의 HW적 차이로인해 서로 다르게 취급하기 위함
zone으로구분 - 비슷한 속성 별 page들의 모음
HW적 단점 극복 위함
1) 일부 HW 장치는 특정 memory 주소로만 DMA 사용 가능 (예전 x86 - 하위 16 MB)
2) 일부 architecture에서는 가상적으로접근 가능한 것보다 많은 물리적 memory로 접근 가능 (이경우 일부는 kernel 주소 공간에 상주 할 수 없음)
4가지 zone (Linux는 필요할때 할당할 수 있도록 system page를 논리적으로 구역별로 관리)
. ZONE_DMA : DMA 를 수행할 수 있는page가 존재 (memory mapped 가 아님)
. ZONE_DMA_32 : ZONE_DMA와 동일하나 차이는 32 비트 장치들만 접근 가능
. ZONE_NORMAL : 정상적인, 통상적으로할당되는 page들 존재 영역
. ZONE_HIGHMEM : 커널 주소 공간에 상주하지 않는 page (상위 메모리) 가 존재
<linux/mmzone.h> 참고
일부 architecture (ARM의 경우)는모든 memory 영역에서 DMA를 수행하는데 문제가 없음
-> memory mapped I/O 경우
x86의 경우 ISA 장치는 물리적memory의 앞부분 16MB만 접근할 수 있음
(전체 32비트 주소 공간에 DMA를수행할 수 없음)
x86 system의 경우 ZONE_HIGHMEM구역은 물리적으로 896MB를 넘어간 모든 memory가해당
다른 architecture 경우 모든memory에 직접 접근이 가능하므로 ZONE_HIGHMEM은 비어있음
cf.
ZONE_HIGHMEM 구역에 속한 memory를 high memory라고 함
나머지는 low memory
일반적인 page는 ZONE_DMA, 혹은 ZONE_NORMAL 구역 어느 쪽에서도 할당할 수 있음
되도록이면 일반적인 할당 작업은 일반 구역에서 처리 함
x86-64에는ZONE_HIGHMEM 구역이 없으며 ZONE_DMA와ZONE_NORMAL 구역에 모든 물리적 메모리가 들어감
zone 구조체 (논리적 memory 구역을 구분하고 관리하는 구조체)
struct zone { unsigned long watermark[NR_WMARK]; unsigned long lowmem_reserve[MAX_NR_ZONES]; struct per_cpu_pageset PAGESET[NR_CPUS]; spinlopck_t lock; struct free_area free_area[MAX_ORDER]; spinlock_t lru_lock; struct zone_lru { struct list_head list; ... }; ... const char *name; <- "DMA" or "NORMAL" or "HighMem" }; |
system에는 세 가지 구역밖에 없으므로 세 개의 구조체만 존재 (2.6 기준)
watermark 배열에는 해당 구역의 메모리 사용량에 대한 최소, 하위, 상위 수치 정보가 존재
mm/page_alloc.c file에서 name 항목을 초기화
page 가져오기
Kernel 내부에서memory 할당을 위해 구현된 kernel interface
<linux/gfp.h>
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) |
2order(1<<order) 개수만큼 연속된 물리적 page를할당하고
첫번째 페이지의 page 구조체 pointer를 반환
void * page_address(struct page *page) |
물리적 page에 현재 담겨있는 논리적 주소 pointer를 반환
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) |
struct page 구조체가 필요하지 않은 경우 사용
요청한 첫 번째 page의 논리적주소를 반환 (나머지는 alloc_pages와 동일)
struct page * alloc_page(gfp_t gfp_mask) unsigned long __get_free_page(gfp_t gfp_mask) |
한 개의 page를 할당/해제
0으로 채워진 page 가져오기
unsigned long get_zeroed_page(unsigned int gfp_mask)
1) __get_free_page와 동일하나 page 내용을 0으로 채움
2) system 보안을 유지하려고 사용자 공간으로반환하는 page의 모든 data를 0으로 채우거나 그에 준하는 정리작업을 해야 함
저수준 page 할당 함수들
alloc_page(gfp_mask) | Allocates a single page and returns a pointer to its |
alloc_pages(gfp_mask, order) | Allocates 2order pages and returns a pointer to the first page’s page structure |
__get_free_page(gfp_mask) | Allocates a single page and returns a pointer to its logical address |
__get_free_pages(gfp_mask) | Allocates 2order pages and returns a pointer to the first page’s logical address |
get_zeroed_page(gfp_mask) | Allocates a single page, zero its contents and returns a pointer to its logical address |
페이지 반환
할당된 page를 해제하는 함수들
void __free_pages(struct page *page, unsigned int order) void free_pages(unsigned long addr, unsigned int order) void free_page(unsigned long addr) |
Ex.
// alloc. 8 pages unsigned long page; page = __get_free_pages(GFP_KERNEL, 3); if (!page) { /* insufficient memory: you must handle this error! */ return ?ENOMEM; } |
// free the 8 pages free_pages(page, 3); |
GFP_KERNEL 인자
gfp_mask flag 중 하나임
저수준 page 함수들은 물리적으로 연속된 page 단위의 memory가 필요한 경우,
특히정확히 한두 개의 page가 필요한 경우에 유용함
더일반적인 byte 단위 memory 할당을 위해 kernel은 kmalloc 함수를 제공
kmalloc()
flags 인자가추가됐다는 점을 제외하고는 malloc과 유사
page가통째로 필요한 경우에는 앞서 살펴본 interface를 사용하는 편이 좋음
(e.g., alloc_pages())
<linux/slab.h> void * kmalloc(size_t size, gfp_t flags) |
kmalloc으로 할당된 메모리 영역은 물리적으로 연속된 영역임
p = kmalloc(sizeof(struct dog), GFP_KERNEL); if (!p) /* handle error ... */ |
gfp_mask Flags
gfp는앞서 살펴본 할당 함수 중의 하나인 __get_free_pages()를 의미함
flag는동작 지정자, 구역 지정자, 형식 세 가지로분류
예를들면,
1. 동작 지정자: 인터럽트 핸들러는 (re-scheduling이 불가능
커널에게 memory 할당 작업 중 휴면 상태로전환하지 말라고 알려줘야 함)
2. 구역 지정자: 어느 곳의 memory를 할당할지 지정
3. 형식 지정자: 동작 지정자와구역 지정자가 조합된 것
Action Modifiers (동작 지정자)
<linux/gfp.h> <-- GFP (Get Free Pages)
<linux/slab.h> <-- 에서위 동작 지정자가 들어있는 gfp.h를 포함
현실에서는 나중에 살펴볼 형식 flag만 사용하는 경우가 보통임
__GFP_WAIT | The allocator can sleep. |
__GFP_HIGH | The allocator can access emergency pools |
__GFP_IO | The allocator can start disk I/O |
__GFP_FS | The allocator can start filesystem I/O |
__GFP_NOWARN | The allocator does not print failure warnings |
__GFP_REPEAT | The allocator repeats the allocation if it fails, but the allocation can potentially fail |
__GFP_NOFAIL | The allocator indefinitely repeats the allocation. The allocation cannot fail |
__GFP_NORETRY | The allocator never retries if the allocation fails |
__GFP_NOMEMALLOC | The allocator does not fall back on reserves |
__GFP_HARDWALL | The allocator enforces “hardwall” cpuset boundaries |
__GFP_RECLAIMABLE | The allocator marks the pages reclaimable. |
__GFP_COMP | The allocator adds compound page metadata (used internally by the hugetlb code) |
__GFP_COLD | The allocator should use cache cold pages |
다음과같이 조합해서 사용 가능
ptr = kmalloc(size, __GFP_WAIT | __GFP_IO | __GFP_FS); |
할당 작업이 중단 가능하며, 필요할 경우 입출력이나 file system을 사용할 수 있음
Zone Modifiers
__GFP_DMA | Allocates only from ZONE_DMA |
__GFP_DMA32 | Allocates only from ZONE_DMA32 |
__GFP_HIGHMEM | Allocates from ZONE_HIGHMEM or ZONE_NORMAL |
보통 ZONE_NORMAL을 우선 사용함
__get_free_pages()나kmalloc() 함수에서는 __GFP_HIGHMEM을 지정할 수 없음
1) 이 두 함수는 page 구조체가 아니라 논리적주소를 반환
왜 page struct가 아니라주소를 반환하나?
a. 현재 kernel의 가상 memory 공간에 올라와 있지 않아
b. 논리적 주소가 없는 memory를 할당하는 상황이벌어질 수 있기 때문
è Virtual address space로 mapping의 보장을 위함
alloc_pages() 함수만 상위 memory를할당할 수 있음 (kmalloc은 불가능)
그러나대부분의 경우 ZONE_NORMAL이면 충분하기에 구역 지정자를 사용하지 않음
Type Flags
미리정의된 flag set
동작지정자와 구역 지정자를 정해둔 것임
GFP_ATOMIC | 우선순위가 높고, 휴면 상태로 전환하면 안 되는 할당 작업. ISR, 후반부 처리 작업 spinlock 사용 중인 경우와 같이 휴면 상태로 전환할 수 없는 경우에 이 flag를 사용 |
GFP_NOWAIT | 비상용 memory 영역을 사용할 수 없다는 점만 제외하면 GFP_ATOMIC과 유사 (상대적으로 fail 가능성 높음) |
GFP_NOIO | This allocation can block, but must not initiate disk I/O. This is the flag to use in block I/O code when you cannot cause more disk I/O, which might lead to some unpleasant recursion |
GFP_NOFS | This allocation can block and can initiate disk I/O, if it must, but it will not initiate a filesystem operation. This is the flag to use in filesystem code when you cannot start another filesystem operation |
GFP_KERNEL | 중단 가능한 일반적인 할당 작업. 안전하게 휴면 상태로 전환할 수 있는 process context에서 사용 커널은 요청한 memory를 할당하는 데 필요한 모든 일을 시도. 기본값으로 사용하는 flag |
GFP_USER | This is a normal allocation and might block. This flag is used to allocate memory for user-space processes |
GFP_HIGHUSER | This is an allocation from ZONE_HIGHMEM and might block. This flag is used to allocate memory for user-space processes |
GFP_DMA | ZONE_DMA 구역의 메모리를 할당함. 보통 DMA 가능한 memory가 필요한 device driver에서 이 flag를 다른 flag와 조합해서 사용 |
/* This equals 0, but use constants in case they ever change */ #define GFP_NOWAIT (GFP_ATOMIC & ~__GFP_HIGH) /* GFP_ATOMIC means both !wait (__GFP_WAIT not set) and use emergency pool */ #define GFP_ATOMIC (__GFP_HIGH) #define GFP_NOIO (__GFP_WAIT) #define GFP_NOFS (__GFP_WAIT | __GFP_IO) #define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS) #define GFP_TEMPORARY (__GFP_WAIT | __GFP_IO | __GFP_FS | \ __GFP_RECLAIMABLE) #define GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL) #define GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM) ... #ifdef CONFIG_NUMA #define GFP_THISNODE (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY) #else #define GFP_THISNODE ((__force gfp_t)0) #endif ... #define GFP_DMA __GFP_DMA /* 4GB DMA on some platforms */ #define GFP_DMA32 __GFP_DMA32 |
GFP_KERNEL
1) 커널의 대다수 할당 작업은 GFP_KERNEL flag를사용한다.
2) 휴면할 수 있는 할당 방식 -> 메모리할당 성공 확률이 상당히 높다.
3) 휴면 불능 시 원하는 크기의 연속된 memory가없다면 커널은 memory를 확보하기 힘듦
4) 하지만 GFP_KERNEL 플래그를 사용하는할당 작업의 경우에는 메모리를 요청한 process를 휴면시키고 비활성화된 page를 디스크 swap 공간으로 옮기고, 페이지의 변경된 내용을 디스크에 저장하는 등의 작업을 실행
GFP_ATOMIC
GFP_ATOMIC의 경우 이런 작업을 할 수 없어서 (memory가충분하지 않은 상황에서는) GFP_KERNEL에 비해서 실패할 확률이 높음
그렇지만 ISR, tasklet 같이 휴면 상태로 전환할 수 없는 코드에서는GFP_ATOMIC flag만 가능
GFP_NOIO
요청을 처리하는 동안 disk 입출력 작업을 할 수 없음
GFP_NOFS
disk 입출력은 할 수 있으나, filesystem 입출력 작업은 할 수없음
GFP_NOIO, GFP_NOFS은 각각 특정 저수준 block 입출력 system과 file system code에서 사용
할당자를 호출하는코드에서 또 다른 할당자를 실행하지 않는 것을 확실히 보장하지 않으면, .할당 과정에서 데드락이 발생할수 있음
그래서 NOFS와 같은 flag를 사용해서 할당해야 하는 상황이 존재함
GFP_DMA
ZONE_DMA 구역에서 할당 작업을 처리해야 할 경우 사용
device driver에서 사용
대부분의 코드에서는 GFP_KERNEL 혹은 GFP_ATOMIC 둘 중 하나를 사용
적절한 flag의사용
Process context, can sleep Use | GFP_KERNEL |
Process context, cannot sleep Use | GFP_ATOMIC, or perform your allocations with GFP_KERNEL at an earlier or later point when you can sleep |
Interrupt handler Use | GFP_ATOMIC |
Softirq Use | GFP_ATOMIC |
Tasklet Use | GFP_ATOMIC |
Need DMA-able memory, can sleep | (GFP_DMA | GFP_KERNEL) |
Need DMA-able memory, cannot sleep | (GFP_DMA | GFP_ATOMIC), or perform your allocation at an earlier point when you can sleep |
kfree()
<linux/slab.h> void kfree(const void *ptr) |
vmalloc()
1) 물리적으로 연속될 필요 없이 가상적으로 연속된memory 영역을 할당한다는 점을 제외하면 kmalloc()과 유사한 방식으로 동작
2) vmalloc()함수가 반환하는 page는 process의 가상 주소 공간에서 연속된 공간이지만, kmalloc 함수는 할당된 page가 물리적으로도 (그리고 가상적으로도) 연속된 공간이라는 것을 보장
많은 architecture에서hardware 장치는 memory 관리 장치가 없는 곳에 살고 있어서, 가상 주소를 처리할 수 없다. (no IOMMU)
특정한 경우에만 물리적으로 연속된 memory가 필요함에도 불구하고
kernel에서는 메모리를 할당할 때 대부분 vmalloc이 아닌 kmalloc을 사용 이는 주로 성능 때문
1) vmalloc은 상당량의 page table 항목 조정 작업을 수행
2) 또한 계속 물리적 page가 변경될 수있기에 TLB 를 휠씬 많이 사용
3) 이런 문제로 vmalloc 함수는 절대적으로필요한 경우에만 사용
보통 큰 영역의 memory를 할당하는 경우가 이에 해당
mm/vmalloc.c void * vmalloc(unsigned long size) |
휴면 상태로 전환이 가능
ISR등 interrupt context에서 사용 불가
void vfree(const void *addr) |
'Linux' 카테고리의 다른 글
로그를 콘솔에 표시하면서 파일에 저장하기 (3) | 2023.02.23 |
---|---|
Linux kernel memory management - 2 (0) | 2022.05.07 |
dlopen 시 undefined symbol 발생 이슈 해결 방법 (0) | 2022.05.06 |
Kernel device driver 추가 (0) | 2021.12.31 |
Linux kernel: kthread & wait_event_interruptible 사용법 (0) | 2021.12.31 |