Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널] 메모리관리/디버깅: kmalloc() 함수로 메모리 할당 후 슬랩 캐시 종류 확인하기 14. Memory Management

이번 소절에서는 가상주소를 물리주소로 변환하는 과정을 실습으로 알아봅시다.

3장 3.6 절에 소개한 rpi_debugfs.c 소스를 활용해 실습을 진행합니다. 먼저 rpi_debugfs.c 소스를 입력하고 커널 빌드를 하시기 바랍니다. 

패치 코드 입력 방법 알아보기

먼저 입력할 패치 코드부터 소개합니다.
[drivers/soc/bcm/rpi_debugfs.c]  
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03     int ret;
04
05    raspbian_debug_state = (uint32_t)val;
06    
07    if ( raspbian_debug_state == 1402 ) {
08 unsigned long phys_address, page_frame_num;
09 struct page *page_ptr;
10
11 void * func_ptr = schedule;
12 unsigned long vir_address = (unsigned long)func_ptr;
13
14 phys_address = __pa(vir_address);
15 page_frame_num = virt_to_pfn(vir_address);
16
17 page_ptr = virt_to_page(vir_address);
18
19 trace_printk("[+]sym: %pS vir_address: 0x%lx phys_address: 0x%lx pfn: 0x%lx page_ptr:%p\n", 
20 func_ptr, vir_address, phys_address, page_frame_num, page_ptr);
21   }

원래 위에서 본 패치 코드를 입력하기 전 rpi_kernel_debug_stat_set() 함수 구현부는 다음과 같았습니다. 패치 코드의 07~21번째 줄 코드를 다음 오리지널 코드 04번째 줄 부분에 입력하면 됩니다.
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03    raspbian_debug_state = (uint32_t)val;
04
05    printk("[rpi] [%s][L:%d], raspbian_debug_state[%lu],value[%lu]===\n", 
__func__, __LINE__, (long unsigned int)raspbian_debug_state, (long unsigned int)val);
06
07    return 0;
08 }

패치 코드 입력 방법을 알아봤으니 이제 패치 코드 내용을 소개합니다.

11번째 줄 코드를 보겠습니다.
11 void * func_ptr = schedule;

schedule() 함수 주소를 func_ptr 포인터에 저장합니다. 라즈비안 커널은 가상주소 기반으로 구동하므로 schedule() 함수 주소는 가상 주소입니다.

다음 12번째 줄 코드입니다.
12 unsigned long vir_address = (unsigned long)func_ptr;
 
func_ptr 포인터를 unsigned long 타입으로 캐스팅해 vir_address 지역 변수에 저장합니다. schedule() 함수 주소를 vir_address 지역 변수로 저장하려는 목적입니다.

다음 14번째 줄입니다.
14 phys_address = __pa(vir_address);

커널에서 제공하는 __pa() 함수를 사용해 가상 주소를 담고 있는 vir_address를 물리주소로 변환합니다. 결과 phys_address 변수는 물리 주소를 저장하게 됩니다.

이번에는 15번째 줄 코드를 보겠습니다.
15 page_frame_num = virt_to_pfn(vir_address);

virt_to_pfn() 함수를 사용해 가상 주소인 vir_address에 대한 페이지 프레임 번호를 page_frame_num지역 변수에 저장합니다.

    가상 주소에 대한 페이지 프레임 번호를 계산하는 코드입니다.

마지막 19~20 번째 줄 로그는 이 정보를 ftrace로 출력하는 동작입니다.
19 trace_printk("[+]sym: %pS vir_address: 0x%lx phys_address: 0x%lx pfn: 0x%lx page_ptr:%p\n", 
20 func_ptr, vir_address, phys_address, page_frame_num, page_ptr);

trace_printk() 함수로 출력하는 세부 정보는 다음과 같습니다.
sym: %pS = func_ptr // func_ptr 주소에 대한 심볼 정보 
vir_address: 0x%lx = vir_address // vir_address 주소 
phys_address: 0x%lx = phys_address // phys_address 주소 
pfn: 0x%lx = page_frame_num // 페이지 프레임 번호
page_ptr:%p = page_ptr  // 페이지 디스크립터 주소

출력 정보는 주석문 내용을 참고하세요.
실습 코드를 입력한 후 컴파일 후 라즈비안에 설치를 합니다. 다음 라즈베리파이를 재부팅시킵니다.

ftrace를 설정하고 로그를 추출해보기

이어서 ftrace 로그를 설정하는 방법을 소개합니다.
#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
sleep 1
echo "tracing_off" 

echo 0 > /sys/kernel/debug/tracing/events/enable
sleep 1
echo "events disabled"

echo  secondary_start_kernel  > /sys/kernel/debug/tracing/set_ftrace_filter
sleep 1
echo "set_ftrace_filter init"

echo function > /sys/kernel/debug/tracing/current_tracer
sleep 1
echo "function tracer enabled"

echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
sleep 1
echo "event enabled"
echo  rpi_kernel_debug_stat_set > /sys/kernel/debug/tracing/set_ftrace_filter

sleep 1
echo "set_ftrace_filter enabled"

echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo 1 > /sys/kernel/debug/tracing/options/sym-offset
echo "function stack trace enabled"

echo 1 > /sys/kernel/debug/tracing/tracing_on
echo "tracing_on"

위 명령어를 입력한 후 vir_mem_test.sh 이름으로 저장합니다. 

ftrace 설정 셸 스크립트는 이전 소절에서 다룬 내용과 다르지 않습니다. 대신 다음 명령어가 추가됐습니다.
echo rpi_kernel_debug_stat_set > /sys/kernel/debug/tracing/set_ftrace_filter

rpi_kernel_debug_stat_set() 함수 콜스택을 ftrace로 출력하기 위한 명령어입니다.

vir_mem_test.sh 셸 스크립트를 실행해 ftrace를 설정합니다.
root@raspberrypi:/home/pi# ./vir_mem_test.sh

이어서 다음 명령어를 입력합니다.
root@raspberrypi:/home/pi# echo 1402 > /sys/kernel/debug/rpi_debug/val

위 명령어를 입력하면 raspbian_debug_state 전역 변수가 1402로 바뀝니다.
결과 rpi_kernel_debug_stat_set() 함수 08~20번째 줄 코드를 실행합니다. 
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03     int ret;
04
05    raspbian_debug_state = (uint32_t)val;
06    
07    if ( raspbian_debug_state == 1402 ) {
08 unsigned long phys_address, page_frame_num;
09 struct page *page_ptr;
...
19 trace_printk("[+]sym: %pS vir_address: 0x%lx phys_address: 0x%lx pfn: 0x%lx page_ptr:%p\n", 
20 func_ptr, vir_address, phys_address, page_frame_num, page_ptr);
21   }

이어서 get_ftrace.sh 셸 스크립트를 실행해 ftrace 로그를 받습니다. 
root@raspberrypi:/home/pi# ./get_ftrace.sh

get_ftrace.sh 셸 스크립트는 이전 챕터에서 소개했으니 코드 내용은 생략합니다.

여기까지 실습 과정을 정리해볼까요?
1. rpi_kernel_debug_stat_set() 함수에 실습 코드를 작성한다.
2. 커널을 빌드하고 라즈비안에 커널 이미지를 설치한다.
3. ftrace를 설정하고 다음 명령어를 실행해 실습 코드를 실행한다.
root@raspberrypi:/home/pi# echo 1402 > /sys/kernel/debug/rpi_debug/val
4. get_ftrace.sh 셸 스크립트를 실행해 ftrace를 추출한다.

ftrace 로그 분석하기

분석할 ftrace 로그를 소개합니다.
01 bash-863 [000] .... 742.800103: rpi_kernel_debug_stat_set+0x14/0x428 <-simple_attr_write+0xbc/0x104
02 bash-863 [000] .... 742.800155: <stack trace>
03 => rpi_kernel_debug_stat_set+0x18/0x428
04 => simple_attr_write+0xbc/0x104
05 => full_proxy_write+0x64/0x80
06 => __vfs_write+0x4c/0x170
07 => vfs_write+0xb4/0x1c0
08 => ksys_write+0x5c/0xbc
09 => sys_write+0x18/0x1c
10 => ret_fast_syscall+0x0/0x28
11 => 0x7ed602d4
12 bash-863 [000] .... 742.800168: rpi_kernel_debug_stat_set+0xa0/0x428: [+]sym: schedule+0x0/0xa8 vir_address: 0x807a0a8c phys_address: 0x7a0a8c pfn: 0x7a0 page_ptr:ba3b0280
 
01~11 번째 줄 로그는 특별히 유용한 정보를 담지 않습니다. 단지 rpi_kernel_debug_stat_set() 함수가 실행했다는 정보를 알려줍니다. 

우리가 분석할 메모리 디버깅 정보는 모두 12번째 줄에 모여 있습니다.
12 bash-863 [000] .... 742.800168: rpi_kernel_debug_stat_set+0xa0/0x428: [+]sym: schedule+0x0/0xa8 vir_address: 0x807a0a8c phys_address: 0x7a0a8c pfn: 0x7a0 page_ptr:ba3b0280

12번째 줄에 담긴 메시지는 다음과 같습니다.
[+]sym: schedule+0x0/0xa8 // 0x807a0a8c 주소 심볼 정보
vir_address: 0x807a0a8c     // schedule() 함수 주소(가상주소)
phys_address: 0x7a0a8c     // 0x807a0a8c 가상 주소에 대한 물리주소
pfn: 0x7a0           // 페이지 프레임 번호
page_ptr:ba3b0280           // 페이지 디스크립터 주소

세부 설명은 주석문 내용을 참고하세요.

이번 소절에서는 가상주소를 물리주소로 변환하는 원리를 파악하는 것이 목적이니 각각 메시지의 의미를 알아보겠습니다. 먼저 가상 주소에서 물리주소로 어떻게 변환하는지 알아볼까요?

물리주소는 다음 계산식으로 변환됩니다.

14.4 절에서 kmalloc 슬랩 캐시를 다뤘습니다. 그런데 커널 소스를 분석하거나 이론을 배우고 다음과 같은 생각이 들 때가 많습니다.

    실제 리눅스 시스템에서 내가 배운 내용이 맞는지 확인보고 싶다.

이번 소절에서는 실습을 통해 kmalloc 슬랩 캐시에서 배운 내용을 검증하는 시간을 갖겠습니다.
이번에도 3장 3.6 절에 소개한 rpi_debugfs.c 소스를 활용해 실습을 진행합니다. 먼저 rpi_debugfs.c 소스를 입력하고 커널 빌드를 하시기 바랍니다. 

패치 코드 입력 방법 알아보기

먼저 입력할 패치 코드부터 소개합니다.
[drivers/soc/bcm/rpi_debugfs.c]  
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03 raspbian_debug_state = (uint32_t)val;
04
05  if ( raspbian_debug_state == 1403 ) {
06 u32 *mem_ptr1, *mem_ptr2;
07 struct page *mem1_page, *mem2_page;
08 struct kmem_cache * k_cache_ptr;
09
10 mem_ptr1 = kmalloc(124, GFP_KERNEL);
11 mem_ptr2 = kmalloc(48, GFP_KERNEL);
12
13 memset(mem_ptr1, 0x78, 124);
14 memset(mem_ptr2, 0x78, 48);
15
16 trace_printk("[+]mem_ptr1: %p mem_ptr2: %p \n", 
17 mem_ptr1, mem_ptr2);
18
19 mem1_page = virt_to_head_page(mem_ptr1);
20 trace_printk ("[+] page: %p \n", mem1_page);
21 if (PageSlab (mem1_page)) {
22 k_cache_ptr = mem1_page->slab_cache; 
23 trace_printk ("[+] [kmem_cache] name: %s, size: %d \n", 
24 k_cache_ptr->name, k_cache_ptr->size);
25
26
27 mem2_page = virt_to_head_page(mem_ptr2);
28 trace_printk ("[+] page:%p \n", mem2_page);
29 if (PageSlab (mem2_page)) {
30 k_cache_ptr = mem2_page->slab_cache; 
31 trace_printk ("[+] [kmem_cache] name: %s, size: %d \n", 
32 k_cache_ptr->name, k_cache_ptr->size);
33 }
34
35 kfree(mem_ptr1);
36 kfree(mem_ptr2);
37 }

원래 위에서 본 패치 코드를 입력하기 전 rpi_kernel_debug_stat_set() 함수 구현부는 다음과 같았습니다. 패치 코드의 05~37번째 줄 코드를 다음 오리지널 코드 04번째 줄 부분에 입력하면 됩니다.
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03    raspbian_debug_state = (uint32_t)val;
04
05    printk("[rpi] [%s][L:%d], raspbian_debug_state[%lu],value[%lu]===\n", 
__func__, __LINE__, (long unsigned int)raspbian_debug_state, (long unsigned int)val);
06
07    return 0;
08 }

패치 코드 입력 방법을 알아봤으니 이제 패치 코드 내용을 소개합니다.

10번째 줄 코드를 보겠습니다.
10 mem_ptr1 = kmalloc(124, GFP_KERNEL);

kmalloc() 함수를 호출해 124바이트 크기로 동적 메모리 할당을 받습니다.

다음 11번째 줄 코드입니다.
11 mem_ptr2 = kmalloc(48, GFP_KERNEL);

이번에도 kmalloc() 함수를 호출해 124바이트 크기로 동적 메모리 할당을 받습니다.

13~14번째 줄은 할당 받은 메모리 공간을 memset() 함수를 써서 초기화하는 코드입니다.
13 memset(mem_ptr1, 0x78, 124);
14 memset(mem_ptr2, 0x78, 48);

이어서 16~17번째 줄 코드를 보겠습니다.
16 trace_printk("[+]mem_ptr1: %p mem_ptr2: %p \n", 
17 mem_ptr1, mem_ptr2);

할당 받은 메모리 주소를 ftrace로그로 출력합니다.
mem_ptr1: 124 바이트 메모리 할당
mem_ptr2: 48 바이트 메모리 할당

다음 19번째 줄 코드를 보겠습니다.
19 mem1_page = virt_to_head_page(mem_ptr1);
 
virt_to_head_page() 함수에 할당 받은 동적 메모리 주소가 담긴 mem_ptr1를 입력으로 페이지 디스크립터 주소를 읽어 mem1_page 에 저장합니다.

    virt_to_head_page() 함수는 가상 주소를 입력 받아 페이지 디스크립터로 변환하는 
     역할입니다.

여기서 struct page 타입인 mem1_page 페이지 디스크립터는 '슬랩 페이지'입니다. kmalloc() 함수로 할당 받은 가상 주소이기 때문입니다.

다음 20번째 줄 코드를 보겠습니다.
20 trace_printk ("[+] page: %p \n", mem1_page);

페이지 디스크립터의 주소를 출력합니다.

다음 21~25번째 줄 코드를 보겠습니다.
21 if (PageSlab (mem1_page)) {
22 k_cache_ptr = mem1_page->slab_cache; 
23 trace_printk ("[+] [kmem_cache] name: %s, size: %x \n", 
24 k_cache_ptr->name, k_cache_ptr->object_size);
25

이번 실습 코드에서 가장 중요한 부분이 눈여겨봅시다.

먼저 21번째 줄을 보겠습니다. 조건문인데 PageSlab(mem1_page) 함수가 true를 반환하면 22~24번째 줄 코드가 실행됩니다. kmalloc 슬랩 캐시는 슬랩 캐시 종류 중 하나이므로 mem1_page는 슬랩 페이지입니다. 

    따라서 PageSlab(mem1_page) 함수는 TRUE를 반환할 것입니다.

다음 22번째 줄 코드를 보겠습니다.
struct page 구조체 slab_cache 필드 주소를 k_cache_ptr 변수에 저장합니다.

    슬럽 할당자는 struct page 구조체 필드 정보를 활용해 슬럽 오브젝트를 관리합니다.

만약 페이지가 슬랩이면 struct page 구조체 slab_cache 필드는 슬랩 캐시 주소를 저장합니다. 만약 페이지가 슬랩 캐시가 아닌 경우 slab_cache 필드는 NULL을 저장합니다. 

다음 23~24번째 줄 로그를 보겠습니다.
23 trace_printk ("[+] [kmem_cache] name: %s, size: %x \n", 
24 k_cache_ptr->name, k_cache_ptr->object_size);

ftrace로 다음 정보를 출력하는 코드입니다.
k_cache_ptr->name: 슬랩 캐시 이름
k_cache_ptr->object_size: 슬럽 오브젝트 사이즈

27~33번째 줄도 같은 패턴의 코드입니다. 대신 mem_ptr2 가상 주소에 대한 슬랩 캐시 정보를 출력하는 동작입니다.

마지막으로 35~36번째 줄 코드를 보겠습니다.
35 kfree(mem_ptr1);
36 kfree(mem_ptr2);

할당한 메모리를 해제하는 코드입니다. 이 코드를 조금 진지하게 슬랩 캐시 관점으로 다음과 같이 해석할 수 있습니다.

    kmalloc() 슬랩 캐시에게 슬럽 오브젝트를 반환한다.
 
실습 코드를 입력한 후 컴파일 후 라즈비안에 설치를 합니다. 다음 라즈베리파이를 재부팅시킵니다.

ftrace를 설정하고 로그를 추출해보기

이어서 ftrace 로그를 설정하는 방법을 소개합니다.
#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
sleep 1
echo "tracing_off" 

echo 0 > /sys/kernel/debug/tracing/events/enable
sleep 1
echo "events disabled"

echo  secondary_start_kernel  > /sys/kernel/debug/tracing/set_ftrace_filter
sleep 1
echo "set_ftrace_filter init"

echo function > /sys/kernel/debug/tracing/current_tracer
sleep 1
echo "function tracer enabled"

echo 1 > /sys/kernel/debug/tracing/events/kmem/kmalloc/enable 
echo 1 > /sys/kernel/debug/tracing/events/kmem/kfree/enable 
sleep 1
echo "event enabled"
echo  kmem_cache_alloc_trace > /sys/kernel/debug/tracing/set_ftrace_filter

sleep 1
echo "set_ftrace_filter enabled"

echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo 1 > /sys/kernel/debug/tracing/options/sym-offset
echo "function stack trace enabled"

echo 1 > /sys/kernel/debug/tracing/tracing_on
echo "tracing_on"

위 명령어를 입력한 후 vir_kmem_test.sh 이름으로 저장합니다. 

ftrace 설정 셸 스크립트는 이전 소절에서 다룬 내용과 다르지 않습니다. 대신 다음 명령어가 추가됐습니다.
echo kmem_cache_alloc_trace > /sys/kernel/debug/tracing/set_ftrace_filter

kmem_cache_alloc_trace() 함수 콜스택을 ftrace로 출력하기 위한 명령어입니다. kmem_cache_alloc_trace() 함수를 필터로 지정한 이유는 다음과 같습니다.

    kmalloc() 함수를 호출하면 커널 내부에서 kmem_cache_alloc_trace() 함수가 
     호출된다.

vir_kmem_test.sh  셸 스크립트를 실행해 ftrace를 설정합니다.
root@raspberrypi:/home/pi# ./vir_kmem_test.sh 

이어서 다음 명령어를 입력합니다.
root@raspberrypi:/home/pi# echo 1403 > /sys/kernel/debug/rpi_debug/val

위 명령어를 입력하면 raspbian_debug_state 전역 변수가 1403로 바뀝니다.
결과 rpi_kernel_debug_stat_set() 함수 05~37번째 줄 코드를 실행합니다. 
01 static int rpi_kernel_debug_stat_set(void *data, u64 val)
02 {
03 raspbian_debug_state = (uint32_t)val;
04
05  if ( raspbian_debug_state == 1403 ) {
06 u32 *mem_ptr1, *mem_ptr2;
07 struct page *mem1_page, *mem2_page;
...
34
35 kfree(mem_ptr1);
36 kfree(mem_ptr2);
37 }

이어서 get_ftrace.sh 셸 스크립트를 실행해 ftrace 로그를 받습니다. 
root@raspberrypi:/home/pi# ./get_ftrace.sh

get_ftrace.sh 셸 스크립트는 이전 챕터에서 소개했으니 코드 내용은 생략합니다.

여기까지 실습 과정을 정리해볼까요?
1. rpi_kernel_debug_stat_set() 함수에 실습 코드를 작성한다.
2. 커널을 빌드하고 라즈비안에 커널 이미지를 설치한다.
3. ftrace를 설정하고 다음 명령어를 실행해 실습 코드를 실행한다.
root@raspberrypi:/home/pi# echo 1403 > /sys/kernel/debug/rpi_debug/val
4. get_ftrace.sh 셸 스크립트를 실행해 ftrace를 추출한다.

ftrace 로그 분석하기

분석할 ftrace 로그를 소개합니다.
01 bash-863 [001] .... 301.320081: kmem_cache_alloc_trace+0x14/0x25c <-rpi_kernel_debug_stat_set+0x23c/0x428
02 bash-863 [001] .... 301.320105: <stack trace>
03 => kmem_cache_alloc_trace+0x18/0x25c
04 => rpi_kernel_debug_stat_set+0x23c/0x428
05 => simple_attr_write+0xbc/0x104
06 => full_proxy_write+0x64/0x80
07 => __vfs_write+0x4c/0x170
08 => vfs_write+0xb4/0x1c0
09 => ksys_write+0x5c/0xbc
10 => sys_write+0x18/0x1c
11 => ret_fast_syscall+0x0/0x28
12 => 0x7ed602d4
13 bash-863 [001] .... 301.320107: kmalloc: call_site=80554994 ptr=7576a566 bytes_req=124 bytes_alloc=128 gfp_flags=GFP_KERNEL
14 bash-863 [001] .... 301.320131: kmalloc: call_site=805549bc ptr=811a4f70 bytes_req=44 bytes_alloc=64 gfp_flags=GFP_KERNEL
15 bash-863 [001] .... 301.320141: rpi_kernel_debug_stat_set+0x220/0x428: [+]mem_ptr1: 7576a566 mem_ptr2: 811a4f70 
16 bash-863 [001] .... 301.320144: rpi_kernel_debug_stat_set+0x220/0x428: [+]page: 45b1cfc2
17 bash-863 [001] .... 301.320149: rpi_kernel_debug_stat_set+0x2ec/0x428: [+][kmem_cache]name : kmalloc-128, size : 0x80
18 bash-863 [001] .... 301.320151: rpi_kernel_debug_stat_set+0x304/0x428: [+]page: fb6b2253
19 bash-863 [001] .... 301.320155: rpi_kernel_debug_stat_set+0x350/0x428: [+][kmem_cache]name : kmalloc-64, size : 0x40
20 bash-863 [001] .... 301.320156: kfree: call_site=80554ac8 ptr=7576a566
21 bash-863 [001] .... 301.320158: kfree: call_site=80554ad0 ptr=811a4f70

먼저 1번째 줄 로그를 보겠습니다.
01 bash-863 [001] .... 301.320081: kmem_cache_alloc_trace+0x14/0x25c <-rpi_kernel_debug_stat_set+0x23c/0x428
 
rpi_kernel_debug_stat_set() 함수에서 kmem_cache_alloc_trace() 함수를 호출하는 정보입니다. kmalloc() 함수를 호출하면 실제 커널에서 실행하는 함수는 kmem_cache_alloc_trace()입니다.
 
02~12 번째 줄 로그는 특별히 유용한 정보를 담지 않습니다. 단지 rpi_kernel_debug_stat_set() 함수가 실행했다는 정보를 알려줍니다. 

다음 13번째 줄 로그를 보겠습니다.
13 bash-863 [001] .... 301.320107: kmalloc: call_site=80554994 ptr=7576a566 bytes_req=124 bytes_alloc=128 gfp_flags=GFP_KERNEL

위 로그에서 출력되는 메시지는 다음과 같습니다.
ptr=7576a566 : 할당 받는 동적 메모리 주소
bytes_req=124 : 요청한 동적 메모리 사이즈
bytes_alloc=128 : 커널 kmalloc 슬랩 캐시가 실제 할당해준 메모리 사이즈
gfp_flags=GFP_KERNEL : gfp 플래그

여기서 한 가지 의문이 생깁니다. 

    위에서 본 13번째 줄 ftrace 메시지는 어느 실습 코드를 실행할 때 출력할까?

다음 10번째 줄 코드를 실행할 때 출력합니다.
10 mem_ptr1 = kmalloc(124, GFP_KERNEL);

10번째 줄 코드에서 kmalloc() 함수를 호출해 124바이트 만큼 동적 메모리를 할당했습니다. 그러데 커널에서는 124바이트가 아니라 128바이트 크기 동적 메모리를 할당했습니다. 

    그렇다면 'ptr=7576a566' 메시지는 할당 받는 동적 메모리 주소라고 언급했습니다.
      이 주소는 슬럽 오브젝트인가요?

맞습니다. 124바이트만큼 동적 메모리를 할당 요청을 하면 'kmalloc-128' 캐시가 이미 할당해 놓은 128바이트 크기 슬럽 오브젝트를 할당해줍니다.

    kmalloc() 함수로 할당 받는 주소는 슬럽 오브젝트 주소이겠군요?

제대로 이해를 하셨습니다. kmalloc() 함수로 동적 메모리를 할당 받는다고 말할 수 있습니다.
슬랩 캐시 관점으로 보면 이는 슬럽 오브젝트 주소인 것입니다.

이렇게 ftrace 로그 한 줄에 많은 정보가 담겨 있습니다. 이어서 14번째 줄 로그를 볼까요?
14 bash-863 [001] .... 301.320131: kmalloc: call_site=805549bc ptr=811a4f70 bytes_req=44 bytes_alloc=64 gfp_flags=GFP_KERNEL

위 로그에서 출력되는 메시지는 다음과 같습니다.
ptr=811a4f70 : 할당 받는 동적 메모리 주소
bytes_req=44 : 요청한 동적 메모리 사이즈
bytes_alloc=64 : 커널 kmalloc 슬랩 캐시가 실제 할당해준 메모리 사이즈
gfp_flags=GFP_KERNEL : gfp 플래그

이어서 15번째 줄 로그를 보겠습니다.
15 bash-863 [001] .... 301.320141: rpi_kernel_debug_stat_set+0x220/0x428: [+]mem_ptr1: 7576a566 mem_ptr2: 811a4f70

위 ftrace 로그는 16~17번째 줄  실습 코드가 실행할 때 출력합니다.
16 trace_printk("[+]mem_ptr1: %p mem_ptr2: %p \n", 
17 mem_ptr1, mem_ptr2);

kmalloc() 함수로 할당 받은 메모리 주소를 출력합니다. 이 정보는 13~14번째 줄 'ptr=' 메시지에 있는 주소와 같습니다.
13 bash-863 [001] .... 301.320107: kmalloc: call_site=80554994 ptr=7576a566 bytes_req=124 bytes_alloc=128 gfp_flags=GFP_KERNEL
14 bash-863 [001] .... 301.320131: kmalloc: call_site=805549bc ptr=811a4f70 bytes_req=44 bytes_alloc=64 gfp_flags=GFP_KERNEL

이어서 17번째 줄 로그를 보겠습니다.
17 bash-863 [001] .... 301.320149: rpi_kernel_debug_stat_set+0x2ec/0x428: [+][kmem_cache]name : kmalloc-128, size : 128

위 메시지는 다음 코드로 동적 메모리 할당 요청을 할 때 '슬랩 캐시' 정보를 출력합니다.
10 mem_ptr1 = kmalloc(124, GFP_KERNEL);

ftrace 세부 정보는 다음과 같습니다.
[kmem_cache]name : kmalloc-128  // 슬랩 캐시 이름
size : 128    // 슬럽 오브젝트 사이즈 

이번에는 19번째 줄 로그를 보겠습니다.
19 bash-863 [001] .... 301.320155: rpi_kernel_debug_stat_set+0x350/0x428: [+][kmem_cache]name : kmalloc-64, size : 64

위 메시지는 다음 코드로 동적 메모리 할당 요청을 할 때 '슬랩 캐시' 정보를 출력합니다.
11 mem_ptr2 = kmalloc(48, GFP_KERNEL);

ftrace 세부 정보는 다음과 같습니다.
[kmem_cache]name : kmalloc-64  // 슬랩 캐시 이름
size : 64 // 슬럽 오브젝트 사이즈 

다음 20번째 줄 로그를 보겠습니다.
20 bash-863 [001] .... 301.320156: kfree: call_site=80554ac8 ptr=7576a566
21 bash-863 [001] .... 301.320158: kfree: call_site=80554ad0 ptr=811a4f70

kfree() 함수를 호출해 할당 받은 메모리를 해제하는 로그입니다. 이 로그는 커널 메모리 관점으로 다음과 같이 해석할 수 있습니다.

    'kmalloc-128'과 'kmalloc-64'의 슬럽 오브젝트를 반환한다.

지금까지 kmalloc()/kfree() 함수를 호출했을 때 출력하는 ftrace의 의미를 자세히 살펴봤습니다. 이번 실습으로 kmalloc 슬랩 캐시에 대한 개념이 오랫동안 머리에 남길 희망합니다.

 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

Reference(커널 메모리 소개) 
가상 주소를 물리 주소로 어떻게 변환할까?   
메모리 존(Zone)에 대해서   
커널 메모리 할당은 어떻게 할까   
슬랩 메모리 할당자와 kmalloc 슬랩 캐시 분석   
커널 메모리 디버깅




핑백

덧글

댓글 입력 영역