Bad page state Aug 26, 2009

이러니 Linus에게 반하지 않을 수 밖에.

문제는 8월 초로 거슬러 올라간다.

Title : Bad page state (was Re: Linux 2.6.31-rc7)

8월 1일

Greetings;

2.6.31-rc5 rebooted to before amanda ran last night. The amanda run looks ok,
but I had also plugged in an empty 8GB usb key so I did a quick dmesg to see
where it was, and found this at the end of the dmesg report:

[ 7423.221754] BUG: Bad page state in process tar pfn:a1293
[ 7423.221760] page:c28fc260 flags:80004000 count:0 mapcount:0 mapping:(null)
index:0
[ 7423.221764] Pid: 19211, comm: tar Not tainted 2.6.31-rc5 #1
[ 7423.221766] Call Trace:
[ 7423.221774] [] ? printk+0x23/0x40
[ 7423.221780] [] bad_page+0xcf/0x150
[ 7423.221784] [] get_page_from_freelist+0x37d/0x480
[ 7423.221788] [] ? add_to_page_cache_lru+0x84/0x90
[ 7423.221791] [] __alloc_pages_nodemask+0xdf/0x520
[ 7423.221795] [] __do_page_cache_readahead+0x104/0x220
[ 7423.221798] [] ra_submit+0x34/0x50
[ 7423.221801] [] ondemand_readahead+0x120/0x240
[ 7423.221804] [] page_cache_async_readahead+0x9c/0xb0
[ 7423.221807] [] generic_file_aio_read+0x33c/0x6a0
[ 7423.221830] [] do_sync_read+0xe9/0x140
[ 7423.221835] [] ? autoremove_wake_function+0x0/0x60
[ 7423.221839] [] ? security_file_permission+0x1e/0x40
[ 7423.221842] [] ? rw_verify_area+0x60/0xe0
[ 7423.221845] [] vfs_read+0xb7/0x180
[ 7423.221848] [] ? do_sync_read+0x0/0x140
[ 7423.221850] [] sys_read+0x58/0xa0
[ 7423.221854] [] sysenter_do_call+0x12/0x22
[ 7423.221856] Disabling lock debugging due to kernel taint

The machine seems 100% so far. This kernel was built without that patch that
would make an oom more verbose. htop looks ok, as does slabtop.

Where should I take this?


위와 같은 BUG reporting이 올라왔다.
또한 8월 21일, 다시 한번 비슷한 문제가 같은 사람(Gene)에 의해 보고 되었다.

Aug 21 22:37:47 coyote kernel: [ 1030.152737] BUG: Bad page state in process lzma pfn:a1093
Aug 21 22:37:47 coyote kernel: [ 1030.152743] page:c28fc260 flags:80004000 count:0 mapcount:0 mapping:(null) index:0
Aug 21 22:37:47 coyote kernel: [ 1030.152747] Pid: 17927, comm: lzma Not tainted 2.6.31-rc7 #1
Aug 21 22:37:47 coyote kernel: [ 1030.152750] Call Trace:
Aug 21 22:37:47 coyote kernel: [ 1030.152758] [] ? printk+0x23/0x40
Aug 21 22:37:47 coyote kernel: [ 1030.152763] [] bad_page+0xcf/0x150
Aug 21 22:37:47 coyote kernel: [ 1030.152767] [] get_page_from_freelist+0x37d/0x480
Aug 21 22:37:47 coyote kernel: [ 1030.152771] [] __alloc_pages_nodemask+0xdf/0x520
Aug 21 22:37:47 coyote kernel: [ 1030.152775] [] handle_mm_fault+0x4a9/0x9f0
Aug 21 22:37:47 coyote kernel: [ 1030.152780] [] do_page_fault+0x141/0x290
Aug 21 22:37:47 coyote kernel: [ 1030.152784] [] ? do_page_fault+0x0/0x290
Aug 21 22:37:47 coyote kernel: [ 1030.152787] [] error_code+0x73/0x78
Aug 21 22:37:47 coyote kernel: [ 1030.152789] Disabling lock debugging due to kernel taint


bad_page는 사실 커널에서 거의 볼 수 없는 문제이다.
왜냐하면 mm guy들이 패치를 할 때 굉장히 꼼꼼히 챙기는 부분이기 때문이다.
그럼에도 불구하고 발생했다.

먼저 Linus가 의심한 것은 Wu의 20a0307c0396c2edb651401d2f2db193dda2f3c9 이 패치이다.
이 패치는 /proc/pagemap을 Huge Page까지 확장하기 위한 단순한 변경이었다.
그러므로 그리 가능성이 그리 크지 않아 보였다.

barrios를 비롯한 다른 mm guy들도 그 문제에 대해서
별 다른 언급을 하지 않았다. 그랬던 이유는 특별히 의심가는
부분을 생각할 수 없었기 때문이다.

하지만 Linus는 그 문제에 대해서 꾸준히 관심을 가지고
개인적으로 Gene에게 메일을 더 보내 여러 테스트를 요구한 것으로 보인다.

Gene가 보내온 또 다른 Oops.

Aug 22 22:29:07 coyote kernel: [ 2449.053652] BUG: Bad page state in process python pfn:a0e93
Aug 22 22:29:07 coyote kernel: [ 2449.053658] page:c28fc260 flags:80004000 count:0 mapcount:0 mapping:(null) index:0
Aug 22 22:29:07 coyote kernel: [ 2449.053662] Pid: 4818, comm: python Not tainted 2.6.31-rc7 #3
Aug 22 22:29:07 coyote kernel: [ 2449.053664] Call Trace:
Aug 22 22:29:07 coyote kernel: [ 2449.053672] [] ? printk+0x23/0x40
Aug 22 22:29:07 coyote kernel: [ 2449.053678] [] bad_page+0xcf/0x150
Aug 22 22:29:07 coyote kernel: [ 2449.053682] [] get_page_from_freelist+0x37d/0x480
Aug 22 22:29:07 coyote kernel: [ 2449.053686] [] __alloc_pages_nodemask+0xdf/0x520
Aug 22 22:29:07 coyote kernel: [ 2449.053691] [] handle_mm_fault+0x4a9/0x9f0
Aug 22 22:29:07 coyote kernel: [ 2449.053695] [] ? tick_dev_program_event+0x43/0xf0
Aug 22 22:29:07 coyote kernel: [ 2449.053699] [] ? tick_program_event+0x36/0x60
Aug 22 22:29:07 coyote kernel: [ 2449.053703] [] do_page_fault+0x141/0x290
Aug 22 22:29:07 coyote kernel: [ 2449.053707] [] ? do_page_fault+0x0/0x290
Aug 22 22:29:07 coyote kernel: [ 2449.053710] [] error_code+0x73/0x78
Aug 22 22:29:07 coyote kernel: [ 2449.053712] Disabling lock debugging due to kernel taint


결국, 8월 24일 Linus는 문제를 찾아냈다.

"It's not a kernel BUG!"

그것을 어떻게 알 수 있었을까?
위의 3 Oops를 보면 모두 bad page에서 detection된 것을 알 수 있다.
아주 운이 좋은 경우이다. bad page의 detection은 flag값을
통해 detect 되었음을 알 수 있다.

flag값의 0x4000의 bit이 set되어져 있었기 때문에 bad_page가
호출된 경우이다. 또한 3경우 모든 page의 가상 주소는 c28fc260이다.
즉, x86의 경우 28fc260의 물리 주소에서 문제가 발생하였다는 것이다.
왜 그렇게 생각할 수 있을까?

그럼 생각해보자. 모두 다른 경로를 통해 Oops가 발생하였지만,
희안하게도 Oops를 발생시켰던 곳은 28fc260 번지이고 모든
경우 flag 값이 같다. 즉, 0x4000이 set되어져 있다. PFN은
달라질 수 있다. 왜냐하면 pfn을 계산하기 위한
mem_map을 할당하는 위치가 booting 중 timing에따라 또는
kernel configuration에 따라 달라질 수 있기 때문이다.

하지만 barrios의 생각은 아무래도 booting 중 타이밍
문제보다는 kernel configuration이 틀려지지 않는 한 잘
바뀌지 않을 것 같다. kernel configuration 중에서도
bootmem allocator에 영향을 주는 경우에 한해서 그 값이
달라질 수 있을 것이다.


또한 flag는 page descriptor의 첫번째 멤버이다. 위에서 본 것 처럼
모든 error는 flag값에 의해 발생하였다. 그렇다는 얘기는 28fc260의
물리 메모리에 문제가 있다는 것을 의미할 수 있다. 그러므로 이 문제는
운이 좋게 mem_map이 할당된 메모리의 일부가 깨졌기 때문에 또한 해당
page가 사용중이 아니며 buddy에 free page로 존재하고 있었기 때문에
쉽게(?) 문제를 찾을 수 있는 경우이다. memory corruption이 다른 메모리
번지 였다면 시스템이 runtime에 random하게 crash되는 현상이 발생하였을
것이다.

현재 Linus는 문제가 28fc260의 bad memory로 인해 발생한 것이
거의 틀림 없다고 보고 있으며 Gene 또한 memory test로 인해
memory에 문제가 있음을 확인하였다.

Flexible Array Aug 25, 2009

8월에 글이 많이 부족했다.
회사일로도 많이 바뻤고, 집안일로도 많이 바뻤고, 도저히
블로깅을 할 시간은 내기 힘들었다.

8월이 가기전에 또 다른 시간을 내기 힘들 것 같아, 아들 밥 먹고
있는 틈을 타서 지난번 봐두었던 기사에 대해 정리하기로 한다.

http://lwn.net/Articles/345273/


커널 프로그래밍을 할때 흔히들 큰 메모리 버퍼를 할당해서 사용해야
핲 필요가 있는 경우가 있다. 이때 큰 버퍼를 할당하기 위해서는
메모리 공간을 어떤 API로 확보해야 할까?
이미 우리는 여러 서적이가 기타 article들을 바탕으로 커널에서 big
order page allocation은 실패할 확률이 높다는 것을 알고 있다.
그러므로 kmalloc은 선택할 수 있는 방법은 아니다.
그래도 어쩔 수 없지 않는가? 우리는 big memory chunk가 필요한데.

Big memory가 물리적으로 연속될 필요가 없다면 가장 쉽게 선택할 수
있는 방법은 단연 vmalloc이다. vmalloc은 여러 문제가 있지만,
이번 기사에서 지적하고 있는 문제는 일반적인 32 bit system에서의
linear address space의 부족함이다. 커널이 사용하는 메모리 1G 중
896M는 direct mapped 되어지고, 나머지 공간 중에서도 일부는
kmap, kmap_atomic, fixed_map등을 위해 띠어주고 나면 대략 120M
정도가 vmalloc을 위해 사용가능하다.

또 다른 이유는 SMP system에서의 overhead가 크다는 것이다.
(TLB flush 및 IPI로 인하여. 이 중 많은 부분이 Nick Piggin에 의해
lazy flush방식으로 optimization되었었지만, 아무리 그렇더라도
vmalloc을 사용하지 않는 경우에 비해 overhead가 큰 것은 사실이다.)

그럼 여지껏 개발자들은 어떻게 그런 요구를 만족시켰는가?
답은 여러가지가 있을 수 있지만 일부 취했던 방식은 kmalloc을 통해
할당된 4K page들을 manage하여 array based로 접근하였다. array 방식으로
사용했던 이유는 간단하다. 여러분이 big memory chunk가 필요하다고 하자.
단일 object을 위해 그 메모리 공간을 사용하려고 그 큰 공간이 필요한건가?
그렇다. 대부분은 object pooling을 위해서이다. 그 얘기는 결국 array based
접근하는 것이 효율적이라는 것을 의미한다. object size가 고정이라면 말이다.

이러한 공통적인 need들이 증가하는 것은 결국 새로운 feature가
필요하다는 것을 암시하며, 그래서 나온 것이 vmalloc을 사용하지 않으면서도
large array를 할당하기 위한 general한 framework으로써, Andrew Morton에
의한 idea로 IBM의 Dave Hansen에 의해 구현된 "flexible array"이다.

Flexible array는 임의의 수의 고정된 크기의 array를 할당할 수 있으며,
배열과 같이 index를 통해 접근된다. 내부적으로는 single page allocation을
통해 page들 연결시켜 사용하기 때문에 fragmentation 문제를 발생시키지 않으며,
kmalloc을 통해 할당된 4K page들이기 때문에 vmalloc과 같은 overhead도 존재하지
않는다. 하지만 단점은 array는 직접 addressing할 수 없으며, object의 크기는
시스템의 page size 이내여야 한며, array에 data를 저장하기 위해서는
copy operation이 필요하다는 것이다.

현재 Flexible Array는 mmtom에서 계속 패치되고 있으며, 아직까지 특정 사용자가
존재하지 않음으로 API는 계속해서 바뀔 수 있다. 그러므로 사용법을 굳이 언급하지는
않는다.

OOM livelock problem Aug 3, 2009

2ff05b2b4eac2e63d345fc731ea151a060247f53

이 패치는 6월 16일 Google의 David에 의해 적용된 패치이다.
이 패치의 요는 다음과 같다.

1. oom_adj를 task_struct에서 mm_struct으로 옮긴다.
2. OOM의 livelock 상황을 막는다.

1.의 rationale은 쉽게 생각할 수 있다.
oom_adj의 값은 per-task의 member로는 의미가 없기 때문이다.
생각을 해보자. 특정 프로세스 A의 임의의 한 쓰레드의 oom_adj값을 -17(OOM_DISABLE)로 했다고 가정하자.
OOM killer의 입장에서 한 task를 kill해야 하는 경우,
특정 프로세스 A를 kill하는 것이 make sense한가? 당연히 No!.

왜냐하면 프로세스의 A가 포함하고 있는 쓰레드 가운데 한 녀석이라도
OOM_DISABLE이 설정되어 있다면, mm_struct 즉 해당 쓰레드가 사용하고 있는 주소 공간은
보존되어야 하기 때문이다. 주소 공간이 보존된다는 것은 OOM killer는 메모리를 회수하기위해
프로세스 A를 kill할 수 없다는 것이다.

이는 아래와 같이 oom_kill_task의 함수에서도 볼 수 있다.


366 /*
367 * Don't kill the process if any threads are set to OOM_DISABLE
368 */
369 do_each_thread(g, q) {
370 if (q->mm == mm && q->oomkilladj == OOM_DISABLE)
371 return 1;
372 } while_each_thread(g, q);
373


그러므로 oom_adj란 member는 task_struct의 member보다는 mm_struct의 member가 되는 것이
더 자연스러운 것이다.

2.에서 말한 livelock 상황은 위에서 언급한 문제로 인해 발생한다.
select_bad_process가 프로세스 A를 선택했다고 가정하자.
이전의 예와 같이 A는 OOM immutable한 쓰레드를 가지고 있다고 가정하면
oom_kill_process는 결국 retry하게 될 것이다.

530 retry:
531 /*
532 * Rambo mode: Shoot down a process and hope it solves whatever
533 * issues we may have.
534 */
535 p = select_bad_process(&points, NULL);
536
537 if (PTR_ERR(p) == -1UL)
538 return;
539
540 /* Found nothing?!?! Either we hang forever, or we panic. */
541 if (!p) {
542 read_unlock(&tasklist_lock);
543 panic("Out of memory and no killable processes...\n");
544 }
545
546 if (oom_kill_process(p, gfp_mask, order, points, NULL,
547 "Out of memory"))
548 goto retry;
549 }


이 상황은 OOM immuable 쓰레드가 종료될 때까지 계속 될 수 있으며, 해당 쓰레드가 언제 종료될지는
아무도 모른다. 그러므로 livelock 상황을 유발할 수 있었다.

하지만 이 패치가 또 다른 side effect을 만들어 내기 시작했다.
것은 다음 기회에.