커널에서 사용하는 메모리는 크게 연속적인 메모리 공간과 비연속적인 메모리 할당으로 나누어진다. 연속메모리는 kmalloc()이나 __get_free_pages() 계열의 함수를 통해서 획득할 수 있으며, virt_to_page()나 __pa() 매크로를 통해 쉽게 물리적인 메모리에 대한 정보를 얻을 수 있는 형태이다. 물리적 메모리의 주소를 얻을 수 있는 __pa() 매크로를 보면 다음과 같이 구성되어 있다.

125 #define __pa(x)                 ((unsigned long)(x)-PAGE_OFFSET)

여기서 PAGE_OFFSET은 x86의 경우 3G로서 커널의 시작 주소를 나타낸다. 즉, 커널의 시작 주소 부분만이 더해져서 가상 주소가 만들어진 것이 연속적인 메모리의 특징이다.

반면, 비연속적인 메모리는 메모리 요청에 대해서 여기저기 산재해 있는 page들을 묶어서 하나의 가상 주소로 보여주는 형태이기 때문에 가상 주소로부터 물리적인 주소를 바로 알아낼 수 없다.(물론, page table 항목을 조사하면 알수는 있다.)

비연속적인 메모리를 사용하면 외부 단편화에 의해서 사용할 수 없었던 메모리를 사용할 수 있게 된다는 장점이 있다. 즉, 물리적으로 흩어져 있어서 __get_free_pages() 계열의 함수로는 얻을 수 없었던 메모리이지만, 비연속적인 메모리 할당 방식을 사용하면 얻을 수 있다는 것이다. 또한가지 장점은 HighMemory를 접근하기 위한 방법으로도 사용할 수 있다는 점이다.

단점으로는, DMA 연산등에 사용할 수 없다는 점과 page table에 대한 잦은 변화에 따른 속도 저하가 발생한다는 점이다. DMA연산에서는 CPU를 거치지 않기 때문에 물리적인 주소를 제공해야 하는데, page들이 여기저기 흩어져 있기 때문에 주소 변환 작업을 하지 않는 DMA에서는 처리가 불가능하다. 또한, 메모리를 할당하고 해제할 때마다 여기 저기 흩어져 있는 페이지들을 모아서 사용하기 때문에 페이지 테이블에 대한 업데이트가 자주 발생하며, TLB의 캐쉬 정보를 사용하는 효율성이 떨어질 수 도 있다.

 
사용자 삽입 이미지

PAGE_OFFSET :  커널 가상 메모리의 시작 주소

high_memory : 가상 메모리중 연속적 메모리 공간으로 사용할 수 있는 부분으로 PAGE_OFFSET + 896MB임

VMALLOC_START : vmalloc() 함수에서 사용하는 가상 메모리 주소 공간의 시작 주소로서 high_memory + VMALLOC_OFFSET 위치임

82 #define VMALLOC_OFFSET  (8*1024*1024)
83 #define VMALLOC_START   (((unsigned long) high_memory + vmalloc_earlyreserve + \
84                         2*VMALLOC_OFFSET-1) & ~(VMALLOC_OFFSET-1))
85 #ifdef CONFIG_HIGHMEM
86 # define VMALLOC_END    (PKMAP_BASE-2*PAGE_SIZE)
87 #else
88 # define VMALLOC_END    (FIXADDR_START-2*PAGE_SIZE)
89 #endif

PKMAP_BASE : High Memory를 매핑해서 사용하기 위한 용도의 공간

vm_struct : 비연속 메모리 공간에 대한 Descriptor

25 struct vm_struct {
26         /* keep next,addr,size together to speedup lookups */
27         struct vm_struct        *ne
xt
;
28         void                    *addr;
29         unsigned long           size;
30         unsigned long           flags;
31         struct page             **pages;
32         unsigned int            nr_pages;
33         unsigned long           phys_addr;
34 };

addr : 할당된 가상 주소에 대한 정보를 가지고 있음
size : 할당된 메모리의 크기
pages : 실제로 할당된 페이지들에 대한 디스크립터인 page 구조체들의 배열(page들은 물리적으로 연속적이지 않음)
nr_pages : 할당된 페이지의 갯수
phys_addr : 하드웨어 장치에 대한 I/O 공유 메모리를 매핑하기 위한 경우가 아니면 0

비연속 메모리 할당 : vmalloc()

34 static inline void * vmalloc (unsigned long size)
35 {
36         return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
37 }

235 void * __vmalloc (unsigned long size, int gfp_mask, pgprot_t prot)
236 {
237         void * addr;
238         struct vm_struct *area;
239
240         size = PAGE_ALIGN(size);
241         if (!size || (size >> PAGE_SHIFT) > num_physpages) {
242 BUG();
243                 return NULL;
244         }
245         area = get_vm_area(size, VM_ALLOC);
246         if (!area)
247                 return NULL;
248         addr = area->addr;
249         if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask, prot)) {
250                 vfree(addr);
251                 return NULL;
252         }
253         return addr;
254 }
255

vmalloc() 함수 코드 부분은 불필요한 코드 부분을 생략하기 위해서 2.4.20 버전의 코드를 가지고 설명하겠다.
비연속 메모리 공간을 확보하는 것은 크게 두 단계로 이루어진다.

첫번째는 비연속 메모리로 사용할 수 있는 가상 메모리 공간이 존재하는지 확인(get_vm_area 함수)
다음으로는 할당된 공간에 대해서 실제 물리 메모리 할당 및 페이지 테이블 업데이트

171 struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
172 {
173         unsigned long addr;
174         struct vm_struct **p, *tmp, *area;
175
176 area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
177         if (!area)
178                 return NULL;
179
180         size += PAGE_SIZE;
181         if (!size) {
182                 kfree (area);
183                 return NULL;
184         }
185
186         addr = VMALLOC_START;
187         write_lock(&vmlist_lock);
188         for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
189                 if ((size + addr) < addr)
190                         goto out;
191                 if (size + addr <= (unsigned long) tmp->addr)
192                         break;
193                 addr = tmp->size + (unsigned long) tmp->addr;
194                 if (addr > VMALLOC_END-size)
195                         goto out;
196         }
197         area->flags = flags;
198         area->addr = (void *)addr;
199         area->size = size;
200         area->next = *p;
201         *p = area;
202         write_unlock(&vmlist_lock);
203         return area;
204
205 out:
206         write_unlock(&vmlist_lock);
207         kfree(area);
208         return NULL;
209 }

get_vm_area() 함수에서는 VMALLOC_START 부터 시작해서 VMALLOC_END 까지의 사이 중에서 사용 가능한 가상 메모리가 남아 있는지 확인한다. 현재 할당된 메모리들은 vmlist 로부터 단일 링크드 리스트 형태로 연결되어 있으므로 그것을 따라 가면서 확인한다.

vmalloc()에서 사용하는 구조체인 vm_struct 를 만들어야 하는데, 이때 kmalloc() 함수를 이용한다(176번째 줄). 이때, 두번째 인자로 GFP_KERNEL을 사용하는데, 이 때문에 vmalloc() 함수는 메모리 할당 중 블럭킹이 되면 안되는 인터럽트 컨텍스트등에서는 사용하면 안된다.

140 inline int vmalloc_area_pages (unsigned long address, unsigned long size,
141                                int gfp_mask, pgprot_t prot)
142 {
143         pgd_t * dir;
144         unsigned long end = address + size;
145         int ret;
146
147         dir = pgd_offset_k(address);
148         spin_lock(&init_mm.page_table_lock);
149         do {
150                 pmd_t *pmd;
151                
152                 pmd = pmd_alloc(&init_mm, dir, address);
153                 ret = -ENOMEM;
154                 if (!pmd)
155                         break;
156
157                 ret = -ENOMEM;
158                 if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot))
159                         break;
160
161                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
162                 dir++;
163
164                 ret = 0;
165         } while (address && (address < end));
166         spin_unlock(&init_mm.page_table_lock);
167         flush_cache_all();
168         return ret;
169 }

get_vm_area()에서 리턴된 정보는 단지 사용 가능한 메모리 영역에 대한 부분이고, 실제로 이를 기반으로 페이지를 확보해야 한다. get_vm_area()에서 정상적으로 메모리 주소를 반환했다 하더라도 물리적인 메모리가 존재하지 않는다면 실제 메모리 확보는 이루어지지 않게 된다. vmalloc_area_pages()는 실제 물리적인 메모리를 확보하는 함수이다.

먼저 커널과 연관된 Page Global Directory 테이블중 address에 대응하는 위치를 얻어온다. 다음에는 해당 address에 대응한 pmd를 얻어오며, 없을 경우 새로 확보한다 (2.6의 경우에는 pmd에 앞서 pud에 대한 부분을 같은 방식으로 확보한다).

120 static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, int gfp_mask, pgprot_t prot)
121 {
122 unsigned long end;
123
124         address &= ~PGDIR_MASK;
125         end = address + size;
126         if (end > PGDIR_SIZE)
127                 end = PGDIR_SIZE;
128         do {
129                 pte_t * pte = pte_alloc(&init_mm, pmd, address);
130                 if (!pte)
131                         return -ENOMEM;
132                 if (alloc_area_pte(pte, address, end - address, gfp_mask, prot))
133                         return -ENOMEM;
134                 address = (address + PMD_SIZE) & PMD_MASK;
135                 pmd++;
136         } while (address < end);
137         return 0;
138 }

95 static inline int alloc_area_pte (pte_t * pte, unsigned long address,
96                         unsigned long size, int gfp_mask, pgprot_t prot)
97 {
98         unsigned long end;
99
100         address &= ~PMD_MASK;
101         end = address + size;
102         if (end > PMD_SIZE)
103                 end = PMD_SIZE;
104         do {
105                 struct page * page;
106                 spin_unlock(&init_mm.page_table_lock);
107                 page = alloc_page(gfp_mask);
108                 spin_lock(&init_mm.page_table_lock);
109                 if (!pte_none(*pte))
110                         printk(KERN_ERR "alloc_area_pte: page already exists\n");
111                 if (!page)
112                         return -ENOMEM;
113                 set_pte(pte, mk_pte(page, prot));
114                 address += PAGE_SIZE;
115                 pte++;
116         } while (address < end);
117         return 0;
118 }


다음으로 alloc_area_pmd()에서는 Page TAble Entry에 대한 정보를 검색하며 없으면 새로 할당한다 (pte_alloc 함수). 마지막으로 alloc_area_pte()에서는 실제 사용할 메모리에 대한 부분을 확보한다.  이를 위해서 107번째 줄과 같이 alloc_page() 함수를 호출해서 한개의 페이지를 확보하고, 이에 대한 정보를 pte 부분에 업데이트 시킨다(113번째 줄).

이것을 전체 메모리에 대해서 반복하기 때문에, 실제로 확보되는 물리적인 메모리는 페이지 단위로 떨어져 있을 수 있다.

비연속 메모리 공간의 반납 : vfree()

vfree() 함수는 vmalloc()이나 vmalloc32(), vunmap() 함수등에 의해서 확보된 메모리를 반납할 때 사용된다.

211 void vfree(void * addr)
212 {
213         struct vm_struct **p, *tmp;
214
215         if (!addr)
216                 return;
217         if ((PAGE_SIZE-1) & (unsigned long) addr) {
218                 printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr);
219                 return;
220         }
221         write_lock(&vmlist_lock);
222         for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
223                 if (tmp->addr == addr) {
224                         *p = tmp->next;
225                         vmfree_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
226                         write_unlock(&vmlist_lock);
227                         kfree(tmp);
228                         return;
229                 }
230         }
231         write_unlock(&vmlist_lock);
232         printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n", addr);
233 }

vmalloc()에 비해서 훨씬 간단한 구조를 가지고 있다. 주어진 addr에 대해서 기본적인 검사를 수행한 후 vmlist에서 시작되는 전체 비연속 메모리 공간에 대한 구조체들을 조사해서 addr로 시작하는 것을 찾는다.

발견된 vm_struct에 대해서 vmfree_area_pages()를 호출해서 할당되었던 물리 메모리를 해제하고 나서 vm_struct 자체에 대한 공간(kmalloc()으로 할당되었음)도 반납한다.

80 void vmfree_area_pages(unsigned long address, unsigned long size)
81 {
82         pgd_t * dir;
83         unsigned long end = address + size;
84
85         dir = pgd_offset_k(address);
86         flush_cache_all();
87         do {
88                 free_area_pmd(dir, address, end - address);
89                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
90                 dir++;
91         } while (address && (address < end));
92         flush_tlb_all();
93 }

56 static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size)
57 {
58         pmd_t * pmd;
59         unsigned long end;
60
61         if (pgd_none(*dir))
62                 return;
63         if (pgd_bad(*dir)) {
64                 pgd_ERROR(*dir);
65                 pgd_clear(dir);
66                 return;
67         }
68         pmd = pmd_offset(dir, address);
69         address &= ~PGDIR_MASK;
70         end = address + size;
71         if (end > PGDIR_SIZE)
72                 end = PGDIR_SIZE;
73         do {
74                 free_area_pte(pmd, address, end - address);
75                 address = (address + PMD_SIZE) & PMD_MASK;
76                 pmd++;
77         } while (address < end);
78 }

22 static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
23 {
24         pte_t * pte;
25 unsigned long end;
26
27         if (pmd_none(*pmd))
28                 return;
29         if (pmd_bad(*pmd)) {
30                 pmd_ERROR(*pmd);
31                 pmd_clear(pmd);
32                 return;
33         }
34         pte = pte_offset(pmd, address);
35         address &= ~PMD_MASK;
36         end = address + size;
37         if (end > PMD_SIZE)
38                 end = PMD_SIZE;
39         do {
40                 pte_t page;
41                 page = ptep_get_and_clear(pte);
42                 address += PAGE_SIZE;
43                 pte++;
44                 if (pte_none(page))
45                         continue;
46                 if (pte_present(page)) {
47                         struct page *ptpage = pte_page(page);
48                         if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
49                                 __free_page(ptpage);
50                         continue;
51                 }
52                 printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");
53         } while (address < end);
54 }



vmfree_area_pages()에서는 커널에 대한 Page Global Directory에 관련된 Page Middle Directory를 찾으며(2.6은 이것 저것 Page Upper Directory를 먼저 처리한다), 다시 Page Table Entry에 대한 처리를 위해 free_area_pte() 함수를 호출한다. pte를 이용해 연관된 물리 메모리를 찾은 후 (pte_page 함수 사용), 해당 페이지가 사용 가능하며, 제거될 수 있는 형태인지(!PageReserved)를 확인 한 후 __free_Page 함수를 불러 메모리를 반납한다. 이 작업을 반납해야 할 전체 메모리에 대해서 반복 수행한다.

크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)

'Linux > Memory' 카테고리의 다른 글

사용자 메모리 공간  (0) 2007/10/08
비연속 메모리 영역 관리  (0) 2007/10/08
Posted by Daniel Kwon