최근 회사 엔지니어들이 주고받는 메일링 리스트를 보고 다시 이쪽저쪽 들쑤시려고 하는 중...
메일링 리스트에 고객이 많은 메모리를 쓰고 있고, 어떤 이슈가 생겨서, kdump 설정을 통해 덤프를 떠
분석하려고 하는데 이게 자꾸 설정이 안된다는 것이다..
답변으로 오는 것들은 대부분 이것저것 crashkernel 값을 조절해 보라 정도...
그래서 살펴보았다 어떤 공식을 통해 해당 영역이 Allocation 되는지를 말이다.
일단, 커널에서 메모리 맵 ( Memory Layout ) 이 어디에 기록되는지 확인해 볼 필요가 있다.
- 어떻게 구성되는지따윈 여기서 다룰 필요 없다, 책에 다 있다.
/proc/iomem 을 보면 아래와 같이 구성되어 있다 :
00010000-0009fbff : System RAM
0009fc00-0009ffff : reserved
000a0000-000bffff : Video RAM area
000c0000-000c7fff : Video ROM
000f0000-000fffff : System ROM 00100000-403fffff : System RAM 01000000-015ad548 : Kernel code 015ad549-019ca8ff : Kernel data 01ba7000-01e41fff : Kernel bss
e4000000-e4003fff : Matrox Graphics, Inc. MGA G200 AGP
e5000000-e57fffff : Matrox Graphics, Inc. MGA G200 AGP
e8000000-e8ffffff : PCI Bus #01
e8000000-e8ffffff : Matrox Graphics, Inc. MGA G200 AGP
ea000000-ea00007f : Digital Equipment Corporation DECchip 21140 [FasterNet]
ea000000-ea00007f : tulip ffff0000-ffffffff : reserved
여기서 주목할 것은 "System RAM" 과 Kernel code,data,bss 영역!
이 파일을 통해 우리는 어떤 계층으로 메모리 영역 ( Memory Area ) 이 구성되어 있는지 잘 볼 수 있다.
자 그럼 crashkernel 이 어디 자리 잡는지 확인해 보도록 하자.
crashkernel=256M 로 설정한 시스템의 iomem 내용이다 ( Crash 랑 system 영역만 보자 ):
# grep -E "(System RAM|Crash|Kernel)" /proc/iomem 00010000-0009dfff : System RAM 00100000-dff00000
: System RAM 01000000-015ad548 : Kernel code 015ad549-019ca8ff : Kernel data 01ba7000-01e41fff : Kernel bss 04000000-13ffffff : Crash kernel
자, Kernel BSS 뒤에 위치하고 있다.. 그래서 이게 뭐 어쩌라고 냐고?
저 iomem 에서 표시하는 16 진수는 메모리 주소를 나타내고 있음을 눈치 챘을 것이다 ( 아님 나가!)
즉, 0x100000 ~ dff00000 까지 System 에서의 RAM 영역으로 잡혀있다는것으로,
대략 1M ~ 3583M 까지 물리적 메모리를 사용할 수 있다는 것이다. ( 10진수로 변환해라 )
짜잔, 우리는 crashkernel 파라메터의 형식이 X@Y (sizeM@offsetM) 로 되어 있는 것을 알고 있다.
자, 여기서 crashkernel 의 offset 영역이 하는 일을 명확히 알 수 있다.
이것이 바로 crashkernel 이 시작할 위치를 지정해 주는 것이였다!
그냥 다음것 보자 ( crashkernel=256M@32M ) :
# grep -E "(System RAM|Crash|Kernel)" /proc/iomem 00010000-0009dfff : System RAM 00100000-dff00000
: System RAM 01000000-015ad548 : Kernel code 015ad549-019ca8ff : Kernel data 01ba7000-01e41fff : Kernel bss 02000000-11ffffff : Crash kernel<<<<
보았는가? 보기 쉽게 해당 메모리영역을 Megabytes 로 변환해주는 스크립트를 사용해 보자 :
#
grep -E "(System RAM|Crash|Kernel)" /proc/iomem | sed -e 's/ : / /g' -e
's/-/ /g' | awk '{ printf("%d M to %d M - %s %s\n",
strtonum("0x"$1)/1024/1024, strtonum("0x"$2)/1024/1024, $3, $4)}'
0 M to 0 M - System RAM 1 M to 1031 M - System RAM 16 M to 21 M - Kernel code 21 M to 25 M - Kernel data 27 M to 30 M - Kernel bss
32 M to 286 M - Crash kernel
자, 이제 조금 더 쉽게 보이는가 ? 커
널 메모리 시작으로 부터 32M 가 되는 지점부터 256M 만큼 Crash kernel 이 할당되었다.
Offset 의 역할을 이제 잘 알았을 것이다.
그러면 한번 16M 로 Offset 을 줄여보도록 하자 ( crashkernel=256M@16M ) :
#
grep -E "(System RAM|Crash|Kernel)" /proc/iomem | sed -e 's/ : / /g' -e
's/-/ /g' | awk '{ printf("%d M to %d M - %s %s\n",
strtonum("0x"$1)/1024/1024, strtonum("0x"$2)/1024/1024, $3, $4)}'
0 M to 0 M - System RAM 1 M to 1031 M - System RAM 16 M to 21 M - Kernel code 21 M to 25 M - Kernel data 27 M to 30 M - Kernel bss
없다.... 왜일까? 당연히 16M 영역에는 Kernel code,data,bss 가 이미 할당되어 있기 때문이다.
즉, Crash kernel 은 다른 영역과 중복되어 할당 될 수 없다는 점!
이번에 보여주는 예제는 offset 을 지정하지 않았을 경우의 예제들을 보여주겠다
Sample 1 - crashkernel=128M :
#
grep -E "(System RAM|Crash|Kernel)" /proc/iomem | sed -e 's/ : / /g' -e
's/-/ /g' | awk '{ printf("%d M to %d M - %s %s\n",
strtonum("0x"$1)/1024/1024, strtonum("0x"$2)/1024/1024, $3, $4)}' 0 M to 0 M - System RAM 1 M to 1051 M - System RAM 16 M to 21 M - Kernel code 21 M to 25 M - Kernel data 27 M to 30 M - Kernel bss 736 M to 863 M - Crash kernel
Sample 2 - crashkernel=256M :
#
grep -E "(System RAM|Crash|Kernel)" /proc/iomem | sed -e 's/ : / /g' -e
's/-/ /g' | awk '{ printf("%d M to %d M - %s %s\n",
strtonum("0x"$1)/1024/1024, strtonum("0x"$2)/1024/1024, $3, $4)}' 0 M to 0 M - System RAM 1 M to 1039 M - System RAM 16 M to 21 M - Kernel code 21 M to 25 M - Kernel data 27 M to 30 M - Kernel bss 608 M to 863 M - Crash kernel
Sample 3 - crashkernel=512M :
grep -E "(System RAM|Crash|Kernel)" /proc/iomem | sed -e 's/ : / /g' -e
's/-/ /g' | awk '{ printf("%d M to %d M - %s %s\n",
strtonum("0x"$1)/1024/1024, strtonum("0x"$2)/1024/1024, $3, $4)}' 0 M to 0 M - System RAM 1 M to 1047 M - System RAM 16 M to 21 M - Kernel code 21 M to 25 M - Kernel data 27 M to 30 M - Kernel bss 96 M to 863 M - Crash kernel
잘 보았는가??
그렇다! Offset 을 지정하지 않아도 최신 커널에서는 자동으로 Crashkernel 을 설정해주는데,
그 원리는 System Map 에서 커널이 차지하는 메모리 주소의 가장 윗부분에서부터 밑으로
Crashkernel 의 Size 만큼 할당하는 것이다!
이로써 사실 요즘은 좀체로 Crashkernel 이 올라오지 않는 경우는 없어졌다..
자, 그럼 메일링리스트에서 문제가 무었이였나 확인해보자.,
일단 고객은 크래쉬설정은 어찌 잘 했는데, Dump 도중OOM ( Out Of Memory ) 메시지가 뜨면서
정상적으로 덤프가 생성되지 않는다는 것이였다.
유저의 시스템은 대략 몇백테라의 메모리를 사용중이였기 때문에 crashkernel 값은
넉넉하게 800M 로 설정되어 있었다..
여기서 이슈는 무엇일까? 바로 적절한 Crashkernel Sizing 을 이야기 하고자 하는 것..
얼마나 적절한 사이즈를 할당해야 될까? 이다...
답은, "그런거 없음" 이란다...
기본적으로, Crashkernel 은 "System RAM" 이 허용하는 범위 내에만 있으면,
얼마든지 설정 할 수 있도록 되어 있다.
따라서 고객에게 우리는 "그냥 늘려보셈" 이랬을 뿐만 아니라,
우리 커널개발팀의 초고수 Herbert 가 간단하게 그냥
"아 그거 사이즈? 그냥 간단하게 doubling 으로 테스트 해서 찾아! 많다싶으면 줄이고 적절하게 찾음됭~"
이라고 메일링에 답변했는데, 내가 암만 생각해도 이건 아닌거지... 왜냐면,,,,
Kexec 가 crashkernel 을 올리는 부분은 중요한 점이 Userspace 가 아닌 Kernel space 라는 것이고,
커널 스페이스를 넘어서는 범위를 할당할 수 없다는 것이지..
64Bit 시스템에서 커널의 범위제한이 없어진 것이 아니라,
커널 영역을 미리 Top address 에서 정의하고 나머지를 User Space 로 쓰는 것일 뿐,
커널 영역은 역시 1G 미만, 사실은 890M 이하라는 점!
따라서 사실상 crashkernel 은 869M ( 내 테스트에서는 860M ) 를 넘길수없다는것!
엄청나지? 그럼, OOM 이 발생했는데 어쩔?? 못해??? 라는 난관에 봉착하는데,
Kdump 가 어떻게 crash file 을 덤프할까 살펴보는게 필요하다.
커널 스페이스에서 User space 로의 복제를 위해서 사용되는 함수는 아주 유명하다.
Copy_to_user 라는 녀석인데, 원래 커널영역과 User space 는 완벽하게 분리되어
서로 정상적으로 교류 할 수 없도록 되어 있는데,
꼭 커널스페이스와 유저스페이스를 넘나들어야 하는 상황에서 사용되고 있다.
crash_dump 라는 커널 코드에서는 이 copy_to_user 를 이용해서
kmap_atomic 이라는 함수가 알려주는 kernel memory 주소와
지정된 범위만큼을 User buffer(Userspace) 로 memcpy 를 통해 복제하는 것이다.
그러면 이 memcpy 에서는 다른 buffer 를 전혀 사용 안해도 되느냐?
내가 알기론 memcpy 에선 loop unrolling 을 위해 CPU register 를 이용하여 bit 의 배수로 복사하고
쪼개서 또 복사하는 형식을 사용하는데, 800M crashkernel 이 할당된 상태에서는
남는 버퍼가 없어 문제가 발생 하지 않을 까 하는 의심을 하는것이다..
따라서, 이런 copy_to_user 의 memcpy 방식 복제로 인해서 오히려 버퍼가 너무 없어
OOM 이 발생할 수도 있을 것 같다는 것이지..
이는 kexec 의 debug level 을 이용해서 확인이 가능할 것 같은데,
아직 명확한 답을 주고 있는 사람은 없다..
내 생각으로는 오히려 crashkernel 영역을 줄여야 한다고 생각되는데,
다들 OOM 나오면 size 늘리라고만 답변을 하고 있다는 것...
여기서 내 결론은 일반적으로 crash kernel 은 전체 메모리를 완벽하게 다 뜬다고 볼 수 없으며,
Kernel 에 대한 영역을 위주로 커널이 갖고 있는 PTE 등의 Linked List 정보를 덤프한다고 볼 수 있으므로,.
물리적 메모리 양이 많이 있다고 해서 crashkernel 을 무턱대고 올리는 것이 아니라,
2G - 256M 를 기준으로 64M 씩 올려가면서 테스트 해야 한다는 것이다...
참고로 레드햇 기준으로는 2G-256M, 6G - 512M, 8G - 768M, 그 이상이어도 869M 넘지 않도록