2014년 10월 6일 월요일

리눅스 커널 심층 분석 - 12장, 메모리 관리

요약: 메모리를 얻는 방법에 대한 내용임 
전체적인 이야기... 
  • memory 영역 크기는 TASK_SIZE 설정한다. /arch/arm/include/asm/memory.h 있음. 
  • user 영역과 kernel 영역은 서로 접근 불가능 . 
  • 그래서 복사하려면 copy_to_user / copy_from_user() 등의 함수를 사용한다. 
    • copy_from_user() 작은 단위… buddy 조금 할당하는 것은 동작하지만, 미디어.. 동영상 같은 G 단위의 복사는 어렵다. 
    • 이를 해결하기 위한 것이 mmap. 복사할 필요 없이 kernel/user 영역이 동시에 접근하도록 해준다. 것이 해주는 역할은 특정 주소를 kernel 영역일 때는 kernel 주소로, user 영역일 때는 user 주소로 변경해 주는 역할을 하는 것이다. 
    • copy_to_user(to, from, n) 
    • copy_from_user(to, from, n) 
    • kernel 영역이 잘못된 memory pointer 참조하면 page fault, abort 발생함. 
  • kernel 영역에서는 __user라고 되어있는 것을 접근하면 안됨. 
  • $ cat /proc/<pid>/maps //MMU table 나옴 ㅋㅋ 
    • $ pmap <pid>: 보기 편하게 메모리 맵을 보여줌. 
    • 웃긴건 동일한 lib이나 thread 만들어서 자료를 공유하면 app에서 동일한 메모리에 매핑됨. 똘똘하네 ㅋㅋ 
  • kernel MMU 부팅 타임에 kernel 용으로 만들어 준다. --> page_address_init() 
  • MMU(memory manage unit) 
    • VA:PA = :1 [O] 
    • VA:PA = 1: [X] 
  1. Page  - <linux/mm_types.h> struct page 
    1. PFN(page frame number) = PA / 4KB = PA >> 12 = PA >> PAGE_SHIFT 
      1. #define PAGE_SHIFT 12 
      2. page 커지면 할당이 빨라지지만 낭비가 심하다. 1B 요청하는데 4048 x 2 하면 너무 낭비임. 
    2. MMU 관리하는 최소 단위, 가상 메모리 관점에서의 최소 단위임 
    3. 32bit 아키텍쳐는 4KB, 64bit 아키텍쳐는 8KB --> 세부적으로 slab object 할당됨 
    4. <linux/mm_types.h> struct page 모든 물리적 페이지를 표현함 
    5. page 40B, 4G memory라고 가정했을 , memory 모든 page struct 524,288 * 40B = 20MB 
    6. 이미지
    7. Flag buddy 관련 비트 
    8. 이미지
    9. Demand paging 기법: 메모리가 필요한 시점에 물리 페이지를 할당하는 기법 
    10. 이미지
  2. Zone - <linux/mmzone.h> struct zone 
    1. 메모리를 여러 zone으로 나누는 이유는 
      1. 하드웨어적인 제약으로 커널은 모든 페이지를 동일하게 취급할 없음 
      2. 메모리의 실제 물리적인 주소로 인해 특정 task에서는 일부 페이지를 사용할 없음. 
        1. 예를들어 메모리는 1G인데 IO 32byte 까지만 접근할 있는 경우 같은 . 
    2. <linux/mmzone.h> struct zone_type, 메모리 구역 
      • 구분은 주로 x86 관계된 것이다 arm 모든 영역이 DMA/NORMAL 사용 가능함. 
      1. ZONE_DMA - DMA 수행 가능한 페이지의 zone  
        1. ZONE_DMA is used when there are devices that are not able to do DMA to all of addressable memory (ZONE_NORMAL). Then we carve out the portion of memory that is needed for these devices. 
        2. 모든 메모리가 접근 가능하지 않을 ZONE_DMA 따로 배치함. 
        3. DMA 필요한 메모리가 곳에 쓰인다. 
          1. 오래된 ISA 기반의 장치는 DMA 사용하기 위해 메모리의 처음 16MB 사용할 있는 제약이 있음 
        4. 64bit architecture에는 모든 메모리에 접근 가능하니깐 ZONE_HIGHMEM 필요 없고 ZONE_DMA ZONE_NORMAL 있으면 . 

      2. ZONE_DMA32 
      3. ZONE_NORMAL - 일반적으로 할당되는 페이지 zone 
        1. Normal addressable memory is in ZONE_NORMAL. DMA operations can be performed on pages in ZONE_NORMAL if the DMA devices support transfers to all addressable memory. 
        2. 일반적으로 접근 가능한 메모리 영역을 ZONE_NORMAL 할당한다. 모든 메모리 영역에 DMA 있으면 DMA 그냥 ZONE_NORMAL에서 한다. 
      4. ZONE_HIGHMEM - kernel 주소 공간 외의 high mem 
        1. A memory area that is only addressable by the kernel through mapping portions into its own address space. This is for example used by i386 to allow the kernel to address the memory beyond 900MB. The kernel will set up special mappings (page table entries on i386) for each page that the kernel needs to access. 
        2. 커널이 접근 가능한 메모리는 커널의 메모리 공간에 매핑된다. ZONE_HIGHMEM 900M 이상의 메모리를 kernel 접근하도록 해주는 메모리이다. 메모리를 이용해서 Kernel 필요한 page 접근 하도록 특별하게 매핑을 한다. 
        3. 커널 / 사용자 영역 구분은 1G / 3G 나뉘는데 1GB DMA 있고 이상은 직접 접근이 안된다. 따라서 128MB 공간을 1GB 이상의 영역을 매핑하는데 쓰고 이를 896MB ~ 1024MB 사이에 두어 결국 896MB ~ 4G 해당하는 영역을 high memory라고 부름. 
      5. ZONE_MOVABLE 
    3. 기껏해야 가지 zone밖에 없기 때문에 시작 시에 mm/page_alloc.c에서 page->name 초기화하고 값은 DMA, Normal, HighMem. 
    4. Struct free_area 필드는 버디 시스템의 free page 관리를  
    5. 이미지

  3. Page 가져오기 / 반환하기 
    1. 저수준으로 page 요청할 사용하는 api이며, BYTE 단위로 할당하려면 kmalloc() 사용한다. 
    2. <linux/gfp.h> - get free page 줄인 말인 gfp 파일에 api 형태로 있음. 
    3. alloc_pages(gfp_t gfp_mask, unsigned int order) 
      1. Order 2^n개의 page 뜻함. 왜냐하면 buddy allocation으로 할당하기 때문임. 
      2. 3개를 요청하려면 2 + 1 요청해야함. 
      3. 'gfp' 원하는 형태의 page 고르는 mask. 이는 동작 지정자와 구역 지정자로 구성되어 있음 
      4. 동작 지정자와 구역 지정자가 어려우니깐 그냥 형식 플래그를 이용할 . 
      5. 자세한건 <linux/gfp.h> 참고할 . 
      6. struct page return 
    4. void *page_address(const struct page *page) - linux/mm/highmem.c 
      1. Page 주소값 return 
    5. unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) 
      1. 8 alloc_pages 동일하지만 struct page 반환하지 않고 page 주소값을 반환함 
    6. unsigned long get_zeroed_page(gfp_t gfp_mask) 
      1. 0으로 채워진 페이지 가져오기 
      2. User 영역에 쓰레기 값이 없는 page 할당할 쓰임 
    7. 위의 __get_free_pages() 동일하고 내용이 0으로 채워져 있음 
    8. 페이지 반환 
      1. void __free_pages(struct page *page, unsigned int order); 
      2. void free_pages(unsigned long addr, unsigned int order); 
      3. 예시 
      Page = __get_free_pages(GFP_KERNEL, 3); if (!page) {  return -ENOMEM; } 
      Free_pages(page, 3); 
  4. Buddy allocator 
    1. 물리 page 할당하는 매커니즘임. 
    2. 페이지를 관리하는 기법임. external fragment 없애준다. slab internal fragment 
    3. 효율을 위해서 연결된 page 할당하기 위해 2 order 관리함. 그래서 요청을 하면 연속된 page 할당해줌. 
    4. buddy 페이지를 2 거듭 제곱 단위로 관리함. --> 그래서 alloc_pages() 인자가 2 order 받음. 
    5. 최대 10 page, 1024 page 가능함. 
    6. Zone struct free_area free_area[] 배열의 인덱스를 지수로 하는 크기만큼의 page 관리 
    7. 이미지
    8. 예를 들어 사용자가 특정 zone 지수 3짜리 page(8 * 4k = 32k) 요청하고 마침 해당 free_area[] free_list[3] 비어있다고 하면 준다. 하지만 만약 order 2 요청했는데 order 2에는 공간이 없으면 order 3 order 2 내려서 할당함. free 되면 다시 order 3으로 반납됨. 
  5. 함수 kmalloc() 
    1. kmalloc() 
    2. 커널 메모리를 바이트 단위로 할당할 사용함. 이미 할당된 page에서 slab allocator 나눠주는 것임. 
    3. 물리적 / 가상적으로 연속된 공간 할당. 
    4. 하드웨어 장치는 메모리 관리 장치가 없기 때문에 반드시 연속적이어야 한다. 따라서 반드시 kmalloc() 사용해야  
    5. kmalloc() sleep 빠질 있다는 말은 무슨말이냐 하면, 만약 아주 메모리를 요청하면 slab process 물리 메모리를 요청하러 가야 하니깐 current sleep 빠지게 된다. ㅋㅋㅋ 아주 논리적인 설명임!! 
    6. Kernel 성능 때문에 kmalloc() 이용함. Vmalloc() 이용하면 MMU 수정 작업이나 TLB(translation lookaside buffer, 주소 변환 버퍼) 많이 사용하므로 효율적임. 
    7. void *kmalloc(size_t size, gfp_t flags) 
      1. size크기 이상 메모리가 있는 곳의 pointer 반환함. 
      2. Return 대한 예외 처리를 하는 것이 좋다. 
      Mem = kmalloc(sizeof(int), GFP_KERNEL); if (!mem) {  //exception 처리 } 
    8. GFP flag 동작 지정자와 구역 지정자(zone 선택) 나뉘는데 이를 조합해 놓은 형식 플래그를 사용하면 헷깔리지 않고 적절한 목적을 수행할 있다. 
    9. void kfree(const void *) - 메모리를 사용하면 호출하여 free 해줌 
  6. 함수 vmalloc() 
    1. void *vmalloc(unsigned long size) 
    2. void vfree(const void *addr) - vmalloc()으로 생성된 포인터를 free. 
    3. 가상적으로 연속된 공간이지만 물리적으로는 연속적이지 않음. 
    4. 영역 메모리를 할당하거나 동적 메모리 할당할 사용함. 
  7. Slap 할당자(slab allocator) 
    1. internal fragment 최소화 하는 역할을 . buddy external fragment 담당 ㅋㅋ 
    2. kmalloc 등으로 page보다 작은 단위를 요청할 나눠주는 역할임. 
    3. 단순히 kmalloc만을 할당하는 것이 아니라, kernel 자료구조를 효율적으로 이용하기 위한 구조임. task_struct, file_struct 등을 위한 자료구조가 따로따로 구성되어 있음. 예를 들어 task_struct kmalloc 쓴다면 sleepable이므로 task_struct update 하다가 sleep 빠질 있다. 그래서 task_struct 만의 kmem_cache 따로 있다. inode 다른 것들도 마찬가지임. cache 만들 gfp 옵션을 주면 된다 ㅋㅋㅋ 
    4. 범용 자료구조 캐시 계층.. 이게 뭔말이냐 하면, 자주 사용하는 object 캐시에 미리 할당해 놓고 사용자 요구가 있을 마다 바로 반환/회수/재사용 한다는 것임. 
    5. 자세히 설명하자면, 
      사실 slab 할당자는 일반적인 할당자가 아니라 특정한 객체(자료 구조)에 대해서만 할당을 수행하기 때문에 slab을 이용하기 위해서는 먼저 해당 slab이 어떤 객체를 다룰지 지정해 주어야 한다. 다르게 표현하면, slab이 해당 객체에 대한 캐시를 관리해주는 역할을 한다고 볼 수 있으므로 먼저 객체에 대한 캐시를 만들어 두고 이 후에 필요할 때 캐시에서 객체를 할당받을 수 있는 것이다. 
      따라서 slab 할당자는 주어진 객체의 크기에 따라 커널의 buddy system으로부터 적당한 수의 페이지를 할당받고 이를 하나로 묶어 여러 객체를 저장할 수 있는 단위로 관리하는데 이것이 바로 slab이다. 아래는 2개의 페이지로 구성된 하나의 slab을 나타낸 그림이다. 
      이미지

    6. 구조 
      1. Slab mm/slab.c struct slab 되어 있음. 
      2. Struct kmem_cache_s(하나의 캐쉬 단위) struct kmem_list3 이용해서 가지의 slab 관리함 
      3. 종류: slab_full, slab_partial, slab_empty 
      4. Slab 내에 사용자에게 할당할 object 저장되어 있는 구조임 
      5. Slab 하나 이상의 물리 페이지로 구성됨(일반적으로는 page 하나임) 
      6. Cache slab 여러 개로 구성되어 있음 
      7. kmem_cache_init byte cache 생성함 
      8. 요청 용량 제한이 있음. 
    7. 이미지
    8. 방식 
      1. kernel object 요청 --> slab_partial에서 할당 
      2. object 요청 --> slab_partial 없는 경우 slab_empty 할당 
      3. object 요청 --> 모두 있는 경우 새로운 slab 생성 
    9. Slab 생성/삭제 
      1. 생성: static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid) 
      2. 삭제: static void kmem_freepages(struct kmem_cache *cachep, void *addr) 
    10. Cache 생성/삭제 
      1. 생성: kmem_cache_create() 
      2. 삭제: kmem_cache_destroy() 
    11. Cache 할당 
      1. 내부의 객체: kmem_cache_alloc() // mem 할당함. 인자로 GFP_XXX flag 있음으로 확인해 있음 ㅋㅋ 더불어 sleepable임도 추측해볼 있다. 
      2. 해제: kmem_cache_free() 

  8. Stack 정적으로 할당 
    1. 기존 Kernel page 크기의 stack 사용 
    2. 2.6 stack 페이지 개로 있는 옵션이 있고, 경우 interrupt stack 따로 사용함. Interrupt handler 낑기기 힘들었기 때문임. Interrupt stack page . 
    3. Stack 최소한으로 사용하고 특정 함수의 변수를 stack 선언하려면 최대 바이트 이하로 유지하는 것이 좋음. 
    4. 만약 stack overflow 되면 가장 먼저 struct thread_info 먹힘! 
  9. 상위 메모리 연결(HIGHMEM) 
    1. 고정연결 -  
      1. 커널 주소 공간에 연결: kmap() 
      2. Free: kunmap() 
    2. 임시연결 
      1. 연결: kmap_atomic() 
      2. Free: kunmap_atomic() 
  10. CPU 할당(percpu 인터페이스) 
    1. app 관계 없음 
    2. kernel cpu별로 같은 전역 변수 접근하는 경우에 동기화 처리를 해줘야 ! 
    3. 그렇지 않으면 처리를 하고 있는 cpu 명시해서 데이터에 접근하면 원초적으로 위의 문제를 봉쇄할 있다. 
    4. 방법: 
      cpu = get_cpu(); //현재 cpu 확인, 그리고 커널 선점을 비활성화 해줌 
      my_percpu[cpu]++; 
      put_cpu(); //get_cpu() 짝으로 해서 커널 선점을 활성화 해줌 
    5. 매크로 함수는 참고하시기 바람 낄낄~ 
  11. swap partition 
    1. 가상 memory 4G인데 physical memory 부족하면 swap 저장하고 상황이 좋아지면 다시 불러오는 역할을 . 

댓글 없음: