리눅스 커널 동적 메모리 할당을 위해 슬럽 캐시를 씁니다.
이번 시간에서는 kmalloc-64 슬럽 캐시 자료 구조를 통해 kmalloc-64 슬럽 오브젝트를 확인하는 방법을 소개합니다.
슬럽 캐시 확인하기
먼저 kmalloc-size 타입 슬럽 캐시를 관리하는 kmalloc_caches 전역 변수를 보겠습니다.
kmalloc_caches 전역 변수의 세부 필드는 다음과 같습니다.
(static struct kmem_cache * [14]) kmalloc_caches = (
[0] = 0x0,
[1] = 0x0,
[2] = 0xF1401E00, // "kmalloc-192"
[3] = 0x0,
[4] = 0x0,
[5] = 0x0,
[6] = 0xF1401F00 -> (
(struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
(long unsigned int) flags = 0,
(long unsigned int) min_partial = 5,
(int) size = 64,
(int) object_size = 64,
(int) offset = 0,
(int) cpu_partial = 30,
(struct kmem_cache_order_objects) oo = ((long unsigned int) x = 64),
(struct kmem_cache_order_objects) max = ((long unsigned int) x = 64),
(struct kmem_cache_order_objects) min = ((long unsigned int) x = 64),
(gfp_t) allocflags = 0,
(int) refcount = 10,
(void (*)()) ctor = 0x0,
(int) inuse = 64,
(int) align = 64,
(int) reserved = 0,
(char *) name = 0xF1402040 -> "kmalloc-64",
(struct list_head) list = ((struct list_head *) next = 0xF1401FC4, (struct lis
(int) red_left_pad = 0,
(struct kobject) kobj = ((char *) name = 0xEC1E1FC0 -> ":t-0000064", (struct l
(struct kmem_cache_node * [1]) node = ([0] = 0xF1400FC0)),
[7] = 0xF1401E80, // "kmalloc-128"
[8] = 0xF1401D80, // "kmalloc-256"
[9] = 0xF1401D00, // "kmalloc-512"
[10] = 0xF1401C80, // "kmalloc-1024"
[11] = 0xF1401C00, // "kmalloc-2048"
[12] = 0xF1401B80, // "kmalloc-4096"
[13] = 0xF1401B00) // "kmalloc-8192"
kmem_cache[6] 배열 인덱스에는 "kmalloc-64" 슬럽 캐시 자료구조가 있습니다.
[6] = 0xF1401F00 -> (
(struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
(long unsigned int) flags = 0,
(long unsigned int) min_partial = 5,
(int) size = 64,
(int) object_size = 64,
(int) offset = 0,
(int) cpu_partial = 30,
(struct kmem_cache_order_objects) oo = ((long unsigned int) x = 64),
(struct kmem_cache_order_objects) max = ((long unsigned int) x = 64),
(struct kmem_cache_order_objects) min = ((long unsigned int) x = 64),
(gfp_t) allocflags = 0,
(int) refcount = 10,
(void (*)()) ctor = 0x0,
(int) inuse = 64,
(int) align = 64,
(int) reserved = 0,
(char *) name = 0xF1402040 -> "kmalloc-64",
필드 중 size와 object_size가 같으니 슬럽 오브젝트에 레드 존이 없다는 사실을 유추할 수 있습니다.
(struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
(long unsigned int) flags = 0,
(long unsigned int) min_partial = 5,
(int) size = 64,
(int) object_size = 64,
위 필드 중에 percpu 슬럽 캐시는 0xC19D82E0 주소이며 percpu 타입입니다.
슬럽 캐시에서 슬럽 오브젝트 할당을 관리하는 자료구조입니다.
percpu 슬럽 캐시 확인하기
따라서 __per_cpu_offset 오프셋을 반영해 percpu1 슬럽 캐시 정보를 봅시다.
v.v %s %t %h (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1])
01 (struct kmem_cache_cpu *) (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1]) = 0xF1CC12E0
02 (void * *) freelist = 0xC44EC100,
03 (long unsigned int) tid = 0x104B4349,
04 (struct page *) page = 0xEC89D880 -> (
05 (long unsigned int) flags = 0x0100,
06 (struct address_space *) mapping = 0x0,
07 (void *) s_mem = 0x0,
08 (long unsigned int) index = 0xC44EC200,
09 (void *) freelist = 0xC44EC200,
10 (bool) pfmemalloc = FALSE,
11 (unsigned int) counters = 0x8040003B,
12 (atomic_t) _mapcount = ((int) counter = 0x8040003B),
13 (unsigned int:16) inuse = 0x3B,
14 (unsigned int:15) objects = 0x40,
15 (unsigned int:1) frozen = 0x1,
16 (int) units = 0x8040003B,
17 (atomic_t) _count = ((int) counter = 0x1),
18 (unsigned int) active = 0x8040003B,
19 (struct list_head) lru = ((struct list_head *) next = 0xECF69D00, (struct list_head *) prev =
20 (struct page *) next = 0xECF69D00,
21 (short int) pages = 0x13,
09 번째 로그를 보면 슬럽 페이지에서 현재 할당 가능한 Free kmalloc-64 슬럽 오브젝트의 주소가 0xC44EC200 이라고 말해줍니다.
04 (struct page *) page = 0xEC89D880 -> (
05 (long unsigned int) flags = 0x0100,
06 (struct address_space *) mapping = 0x0,
07 (void *) s_mem = 0x0,
08 (long unsigned int) index = 0xC44EC200,
09 (void *) freelist = 0xC44EC200,
페이지 디스크립터로 물리주소 변환하기
페이지 디스크립터 주소가 0xEC89D880 이니 다음 공식으로 페이지 프레임 번호를 알 수 있습니다.
0x19D880 = 0xEC89D880 - 0xEC700000
0x19D880 / 0x60 = 0x19D880 / (struct page) 구조체 사이즈
0x44EC = 0x19D880 / 0x60
페이지 디스크립터를 관리하는 mem_map[0..1] 정보는 다음과 같습니다.
v.v %l %t(struct page[100])*mem_map
(static struct page [100]) [D:0xEC700000] (struct page[100])*mem_map = (
((long unsigned int) [D:0xEC700000] flags = 32768, (struct address_space *)
((long unsigned int) [D:0xEC700060] flags = 65536, (struct address_space *)
0xEC700000 시작 주소와 1 번째 인덱스인 0xEC700060를 빼니 0x60입니다. struct page 구조체 사이즈가 0x60입니다.
페이지 프레임 번호는 0x44EC, 10진수로는 17644입니다.
페이지 프레임 번호를 알면 자연히 물리 주소를 알 수 있습니다.
페이지 프레임 번호가 0x44EC이니 커널 이미지를 로딩한 물리주소 기준으로 +0x44EC000입니다.
0x44EC << 12 = 0x44EC000
커널 이미지가 실행 중인 물리 주소 시작 주소가 0x8000_0000이니 다음 계산으로 0x844EC000가 됩니다.
0x844EC000 = 0x8000_0000 +0x44E_C000
정리하면 다음 페이지 디스크립터에 매핑하는 물리 주소는 0x844EC000입니다.
(struct page *) page = 0xEC89D880
왜 물리 주소를 계산했는지는 조금 후에 알게 됩니다.
슬럽 오브젝트를 메모리 덤프에서 확인하기
다시 본론으로 돌아가서 슬럽 페이지 디스크립터의 freelist 필드를 보면 0xC44EC200 주소가 Free된 슬럽 오브젝트 주소입니다.
04 (struct page *) page = 0xEC89D880 -> (
05 (long unsigned int) flags = 0x0100,
06 (struct address_space *) mapping = 0x0,
07 (void *) s_mem = 0x0,
08 (long unsigned int) index = 0xC44EC200,
09 (void *) freelist = 0xC44EC200,
즉, 0xC44EC200 주소를 바로 할당할 수 있습니다. 그럼 0xC44EC200 주소 메모리 덤프를 보겠습니다.
0xC44EC200 주소를 보니 0xC44ECE40 주소가 있습니다. 이는 다음 Free 슬럽 오브젝트 주소입니다.
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44EC200|>C44ECE40 00000200 00000003 00000001 @.N.............
NSD:C44EC210| 00000005 0000000A 00000000 00000015 ................
NSD:C44EC220| 00000001 000002C5 00000000 00000000 ................
NSD:C44EC230| C44ECE40 00000018 00000001 00000000 @.N.............
현재 분석 중인 슬럽 캐시가 kmalloc-64 이니 슬럽 오브젝트 사이즈는 64, 16진수로는 0x40입니다.
그래서 C44EC200~C44EC3FF까지 메모리 덤프를 본 것입니다.
그러면 다음 Free 슬럽 오브젝트를 보겠습니다. 주소는 C44ECE40입니다.
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44ECE40|>C44EC180 C44ECE40 00000000 00020002 ..N.@.N.........
NSD:C44ECE50| DEAD4EAD FFFFFFFF FFFFFFFF E645B388 .N............E.
NSD:C44ECE60| 0000001C 00000000 00000000 00000000 ................
NSD:C44ECE70| 00000000 00000000 00000000 00000000 ................
0xC44ECE40 주소에 가보니 0xC44EC180 주소를 가르키고 있습니다.
이는 다음 Free 슬랩 오브젝트 주소를 의미합니다.
이 방식으로 계속 꼬리 물듯이 주소를 따라가 보겠습니다.
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44EC180|>C44ECA00 00000021 00000000 00000000 ..N.!...........
NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C ..............N.
NSD:C44EC1A0| 00000000 00000000 00000100 00000200 ................
NSD:C44EC1B0| C236E400 00000000 00000000 00000000 ..6.............
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44ECA00|>C44EC400 00000200 00000003 00000001 ..N.............
NSD:C44ECA10| 00000005 0000000A 00000000 00000015 ................
NSD:C44ECA20| 00000001 000002C5 00000000 00000000 ................
NSD:C44ECA30| C44EC400 00000018 00000001 00000000 ..N.............
이제 정리를 해보겠습니다.
1. kmem_cache 구조체에 있는 percpu 슬랩 오브젝트에 접근
[6] = 0xF1401F00 -> (
(struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
2. 0xC19D82E0 주소로 cpu1 percpu 로 캐스팅
v.v %s %t %h (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1])
01 (struct kmem_cache_cpu *) (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1]) = 0xF1CC12E0
02 (void * *) freelist = 0xC44EC100,
03 (long unsigned int) tid = 0x104B4349,
04 (struct page *) page = 0xEC89D880 -> (
05 (long unsigned int) flags = 0x0100,
06 (struct address_space *) mapping = 0x0,
07 (void *) s_mem = 0x0,
08 (long unsigned int) index = 0xC44EC200,
09 (void *) freelist = 0xC44EC200,
(struct kmem_cache_cpu*).page->freelist가 0xC44EC200임
3. page->freelist가 가르키는 0xC44EC200 주소 꼬리 물고 따라다니기
0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44EC200|>C44ECE40 00000200 00000003 00000001 @.N.............
NSD:C44EC210| 00000005 0000000A 00000000 00000015 ................
NSD:C44EC220| 00000001 000002C5 00000000 00000000 ................
NSD:C44EC230| C44ECE40 00000018 00000001 00000000 @.N.............
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44ECE40|>C44EC180 C44ECE40 00000000 00020002 ..N.@.N.........
NSD:C44ECE50| DEAD4EAD FFFFFFFF FFFFFFFF E645B388 .N............E.
NSD:C44ECE60| 0000001C 00000000 00000000 00000000 ................
NSD:C44ECE70| 00000000 00000000 00000000 00000000 ................
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44EC180|>C44ECA00 00000021 00000000 00000000 ..N.!...........
NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C ..............N.
NSD:C44EC1A0| 00000000 00000000 00000100 00000200 ................
NSD:C44EC1B0| C236E400 00000000 00000000 00000000 ..6.............
________address|________0________4________8________C_0123456789ABCDEF
NSD:C44ECA00|>C44EC400 00000200 00000003 00000001 ..N.............
NSD:C44ECA10| 00000005 0000000A 00000000 00000015 ................
NSD:C44ECA20| 00000001 000002C5 00000000 00000000 ................
NSD:C44ECA30| C44EC400 00000018 00000001 00000000 ..N.............
여기서 Free 슬럽 오브젝트 주소는 다음과 같습니다.
0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
슬럽 오브젝트 주소를 물리 주소로 변환하기
흥미로운 패턴을 발견할 수 있습니다. 모든 주소가 다음 주소 범위 내에 있습니다.
0xC44ECxxx
그러면 0xC44EC000 가상 주소를 물리 주소로 변환해 볼까요?
0xC44EC000 주소의 [31:19] 가 0xC44입니다.
swapper_pg_dir 주소를 보니 TTBR 즉 페이지 테이블 베이스 주소가 0xC0004000입니다.
(unsigned int *) (unsigned int*)&swapper_pg_dir = 0xC0004000
0xC44EC000 가상 주소에 대한 페이지 테이블 엔트리 주소는 다음 공식으로 계산할 수 있습니다.
0xC0004000 + (0xC44 << 2)
0xC0004000 + (0x3110)
0xC0007110
0xC0007110 주소가 0xC44EC000 가상 주소에 대한 주소 변환 정보가 있는 페이지 테이블 엔트리 주소입니다.
________address|________0________4________8________C_0123456789ABCDEF
NSD:C0007100| 8401141E 8411141E 8421141E 8431141E ..........!...1.
NSD:C0007110|>8441141E 8451141E 8461141E 8471141E ..A...Q...a...q.
NSD:C0007120| 8481141E 8491141E 84A1141E 84B1141E ................
0xC0007110 주소를 보니 페이지 테이블 엔트리 레코드가 0x8441141E 입니다.
0x8441141E 값을 2진수로 바꿔 보겠습니다.
10000100010000010001010000011110
하위 비트 [1:0]이 10으로 끝나니 섹션 엔트리입니다. 바로 물리 주소로 변환할 수 있습니다.
0x8441141E 페이지 테이블 엔트리 레코드에서 [31:19] 비트 주소만 남기고 나머지는 0으로 밀겠습니다.
0x8441141E
0x84400000
0x84400000 주소가 물리 주소 베이스 주소가 됩니다. 여기에 가상 주소 [18:0] 비트를 더하면 물리 주소가 됩니다.
0x84400000
0x000EC000( where: 0xC44EC000)
------------------------------
0x844EC000
0xC44EC000 가상 주소를 물리 주소로 변환한 결과가 0x844EC000입니다.
그런데 0x844EC000 주소는 어디서 본 것 같습니다.
위에서 다음과 같이 (struct page *) page = 0xEC89D880 페이지 디스크립터로 물리 주소를 변환했습니다.
-----------------------------------------------------------------------------------------------------------
페이지 프레임 번호는 0x44EC, 10진수로는 17644입니다.
페이지 프레임 번호를 알면 자연히 물리 주소를 알 수 있습니다.
페이지 프레임 번호가 0x44EC이니 커널 이미지를 로딩한 물리주소 기준으로 +0x44EC000입니다.
0x44EC << 12 = 0x44EC000
커널 이미지가 실행 중인 물리 주소 시작 주소가 0x8000_0000이니 다음 계산으로 0x844EC000가 됩니다.
0x844EC000 = 0x8000_0000 +0x44E_C000
-----------------------------------------------------------------------------------------------------------
메모리 디버깅 정보 요약하기
분석한 디버깅 정보를 요약해 보겠습니다.
- 페이지 디스크립터 주소
(struct page *) page = 0xEC89D880
- 해당 가상 주소: 0xC44EC000
- 해당 물리 주소: 0x844EC000
- Free 슬럽 오브젝트 주소는 다음과 같습니다.
0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
위에서 분석한 결과를 최종 정리하면 다음과 같은 결론을 이끌어 낼 수 있습니다.
1. (struct page *) page = 0xEC89D880는 슬럽(kmalloc-64 슬럽 캐시) 페이지이고 해당 가상 주소는 0xC44EC000입니다.
2. kmalloc-64 슬럽 오브젝트 시작 주소는 0xC44EC000 이며 64 바이트 단위로 슬럽 오브젝트가 있습니다.
다음 메모리 덤프는 0xC44EC000 주소에 64 바이트 씩 위치한 kmalloc-64 슬랩 오브젝트입니다.
0xC44EC200 주소가 Free된 슬럽 오브젝트 주소이고 나머지는 모두 이미 할당해 쓰고 있습니다.
________address|________0________4________8________C
NSD:C44EC000|>02D37147 00000002 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC000)
NSD:C44EC010| C44ECDD1 00000000 00000000 C42F1D1C
NSD:C44EC020| 00000000 00000000 D1F31128 EA38EBA8
NSD:C44EC030| E6190000 DA79D700 00000000 00000000
NSD:C44EC040| 031AAE0C 0000001E 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC040)
NSD:C44EC050| E9EFA3D1 E222A910 E9B72310 C535E59D
NSD:C44EC060| C44ECF5C C535E25C 00000000 E8C617B0
NSD:C44EC070| DD08C800 E8C61780 00000000 00000000
NSD:C44EC080| 02D37148 00000003 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC080)
NSD:C44EC090| C44EC391 C44EC350 C44ECDD0 D36D0D9D
NSD:C44EC0A0| C325BE5C D109EA1C D1F31228 EA38EEA8
NSD:C44EC0B0| E6190000 DA7F5B00 00000000 00000000
NSD:C44EC0C0| 02D37149 00000004 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC0C0)
NSD:C44EC0D0| C44EC351 00000000 00000000 C40D6CDD
NSD:C44EC0E0| C4A7C79C D0562E5C D1F31868 EA38E7A8
NSD:C44EC0F0| E6190000 CEA7F100 00000000 00000000
NSD:C44EC100| C44EC480 C44EC100 00000002 00000000 //<<--kmalloc-64 (시작주소: C44EC100)
NSD:C44EC110| 00000000 00000000 00000000 00000000
NSD:C44EC120| 00000000 00000000 00000000 00000000
NSD:C44EC130| 00000000 00000000 00000000 00000000
NSD:C44EC140| 031AAE7F 000001AE 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC140)
NSD:C44EC150| E1826991 C44ECB50 E2391990 DC14639D
NSD:C44EC160| DD26F1DC C402DC5C 00000000 E8C61F30
NSD:C44EC170| E9B92E00 E8C61F00 00000000 00000000
NSD:C44EC180| C44ECA00 00000021 00000000 00000000 //<<--kmalloc-64 (시작주소: C44EC180)
NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C
NSD:C44EC1A0| 00000000 00000000 00000100 00000200
NSD:C44EC1B0| C236E400 00000000 00000000 00000000
NSD:C44EC1C0| 03169726 0000008E 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC1C0)
NSD:C44EC1D0| C44EBF51 00000000 00000000 DD535ADC
NSD:C44EC1E0| 00000000 00000000 00000000 C2998CB0
NSD:C44EC1F0| D8D7D200 C2998C80 00000000 00000000
NSD:C44EC200| C44ECE40 00000200 00000003 00000001 //<<--kmalloc-64(Free) (시작주소: C44EC200)
NSD:C44EC210| 00000005 0000000A 00000000 00000015
NSD:C44EC220| 00000001 000002C5 00000000 00000000
흥미로운 사실은 슬럽 오브젝트 마지막 바이트를 보면 0x0000_0000이 보입니다.
이는 kmalloc(50, GFP_KERNEL); 로 메모리를 할당했을 것이라 생각됩니다.
NSD:C44EC170| E9B92E00 E8C61F00 00000000 00000000
NSD:C44EC180| C44ECA00 00000021 00000000 00000000 //<<--kmalloc-64
NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C
NSD:C44EC1A0| 00000000 00000000 00000100 00000200
NSD:C44EC1B0| C236E400 00000000 00000000 00000000
NSD:C44EC1C0| 03169726 0000008E 00000001 00000001 //<<--kmalloc-64
NSD:C44EC1D0| C44EBF51 00000000 00000000 DD535ADC
다음과 같이 kmalloc() 함수 첫 번째 인자로 메모리 할당을 요청하면 "kmalloc-64" 슬럽 캐시가 메모리를 할당해 줍니다.
kmalloc(50, GFP_KERNEL);
kmalloc(48, GFP_KERNEL);
kmalloc(46, GFP_KERNEL);
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널] 비트 마스크를 어셈블리 코드로 빨리 읽는 방법 - HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK (0) | 2023.05.06 |
---|---|
[리눅스커널] smp thread에 대해서 (0) | 2023.05.06 |
[리눅스커널][디버깅] 유저공간 High Memory Zone 페이지 할당 과정 (0) | 2023.05.06 |
[Linux][Kernel] preempt_disable()/preempt_enable() 주의 사항 (0) | 2023.05.06 |
[라즈베리파이] 비트 처리 __test_and_set_bit() __test_and_clear_bit() 함수 동작 원리 (0) | 2023.05.06 |