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을 만들어 내기 시작했다.
것은 다음 기회에.

회상이 지나간 오후 Jul 18, 2009

http://tvpot.daum.net/clip/ClipView.do?clipid=12845336

Pop보다는 한국 대중 가요를 더 좋아하는 이유는 영어를
잘 못해서이기도 하지만, 그 가사에서 그 상황에서 느껴지는 그 멜로디와 그 의미를
내 의지대로 해석하지못하기 때문이다.

20살 때의 일이다.

완벽하게 이상적인 생각들로 가득찼던 시절이 있었다.

내 눈으로 바라본 세상을 내 의지대로 해석하는 지금의 모습과는 분명 같지 않으며,
지금의 모습을 상상할 수 없을 정도로 순수하게 이상을 쫓았던 시절이 내겐 분명 있었다.

힘들지만 보람된 시간들이었으며, 너무 다행스러웠던 것은
내 주위 나와 같은 사람들도함께 하였지만, 그렇진 않지만
나를 이해해줄 수 있는 사람들도 있었다는 것이다.

어느덧 시간이 흘러, 군을 나오고, 졸업을 하고, 취업을 하고, 정신 없이 시간을 흘려보내고 나니
그들은 여전히 이렇게 작은 기쁨을 선사한다.

모두들 잘들 살고는 있는 것인지. 나와 같이 이미 그때의 모습을 잃어버린 것은 아닌지..

내밀면 닿을 곳에 있는 그들을 한번 만나봐야 할 것 같다.
누가 먼저 손을 내미느냐는중요하진 않다. 우리들에겐 의미 없는 허세일 뿐이다.

술 많이 먹고 쓰는 건 아니다.그저 적당한 술이 나를 부지런하게 만들어 줄 뿐.

꼬랑지) 적당한 제목을 만들어 주는 툴이 내겐 늘 필요하다.

come back zero_page Jul 14, 2009

http://lwn.net/Articles/340370/

ZERO_PAGE. Linux kernel의 VM에 관심있는 사람들은 한번쯤 들어봤을 법한
설비이다. Linux kernel은 2.6.24시절까지는 zero page를 사용하여 vma에
최초 발생한 read fault는 zero page에 매핑하였다.

이는 다음과 같은 장점을 가져온다.

1. memory save
2. reduced cache pressure
3. eliminating the need to clear the new page

하지만 24때 Nick에 의해 MuliProcessor system에서 cache line bouncing문제가
심각하다고 밝혀져 제거되었다.(물론 revert되기까지는 많은 discussion이 있었다.
avoiding zero page reference counting, per-cpu zero page등)
cache line bouncing이 발생하는 이유는 많은 CPU들이 하나의 zero page를 공유하게
되면서, zero page의 reference counting 때문에 발생하게 되었다.

그 당시에 Nick의 이런 노력에 사실 Linus는 심기가 불편했지만 일단 Nick의 이 패치가
어떤 문제들을 발생시킬지 보기로 하고는 우선 merge했었다. Linus가 zero page의 제거를
싫어했던 이유는 zero page는 리눅스와 함께 태동했기 때문이다. Linux가 세상에 알려지기
시작할 무렵부터 zero page는 거기에 있었으며, 2.6.24까지도 큰(?) 문제 없이 잘 사용되고
있어왔던 것이다. 그러므로 이미 몇몇 application들은 Linux kernel의 이런 특성(zero page)
을 이미 활용하고 있어왔다. 그러므로 kernel에서 zero page를 제거하게 된다면, 그런 특성을
활용하던 application들의 regression은 피할 수 없게 될 것이다.

정확히 18개월이 지나고, 문제가 터지기 시작했다. 마지막으로 카운터를 날린 것은 Kame였다
Kame는 다음과 같이 주장했다. "Kernel이 바뀌어서 regression이 생겼다고, app 개발자들한테
너희들이 다시 app를 개발해야해!" 라고 말할 수는 없는 것 아니냐고.

Linus의 전폭적인 지지와 함께 zero page의 대한 구현이 다시 시작되었다.(물론 reference counting을
피하기 위해 아소 깨끗하지 못한 구현이 되어가고 있을 무렵..). 다시 한번 Nick은 zero page의 도래를
마땅해하지 못하고 있음을 여러모로 피력하였다. 하지만 Linus는 이미 마음을 굳힌 것 같다. 이에 대해
Nick과 Linus가 다소 격양된 어조의 토론이 있었지만...
언제나 그렇듯이, Linus의 승이다.

결국 우리는 머지 않아 zero page를 다시 보게 될 것이며, 그 구현은 그렇게 평이한 수준이 되지는
않을 것이다.

꼬랑지)
barrios의 기억으로 zero page의 제거가 문제가 된 부분 중의 하나는, core파일을 만들어 낼때이다.
core dump는 기본적으로 프로세스의 모든 vma에 page들을 읽어 file로 덤프를 뜬다.
이때 zero page의 제거는 실제 page를 할당받게 됨으로써, vma의 크기에 따라 순식간에 시스템의
memory를 모두 소비할 수 있게 된다. 그러므로 embedded system에서는 oops로 swap을 가지고 있는
일반 시스템 또한 순식간에 swap out이 발생하게 되어 working set들을 버릴 수도 있게 되며,
그 순간 시스템의 응답성을 떨어뜨리게 된다.

core dump에서 이 문제를 어떻게 해결했는지 barrios는 follow up하지 못하였으며, 이 부분에 대한
언급이 없는 것으로 봐서는 해결이 되었을 것으로 기대한다. (그렇지 않다면 역시 나의 몫!)

Perfcounters Jul 6, 2009

http://lwn.net/Articles/339361/

2.6.31의 새로운 기능 중 가장 주목할 만한 것은 모니모니 해도 Ingo의 perfcounters가 아닐까 싶다. 오랫동안 tip-tree에서 성숙된 perfcounters가 드디어 2.6.31의 merge window에 merge되었다.

perfcounters는 Ingo, Peter 등에 의해 최근 굉장히 활발히 개발된 performance analyser이다. 물론 여러분들이 아는 툴 중에 가장 유명한 oprofile과 같은 툴도 있겠지만, perfcounters의 취지는 정말 사용하기 쉬운, 그리고 정말 필요한 기능을 넣겠자는 의도에서 시작되었다.

perfcounters를 개발하면서 perfmon의 개발자 Stéphane Eranianr과 Ingo는 많은 부분에서 의견충돌이 있었다. Ingo는 Stéphane Eranian가 PMU 기능의 많은 부분들을 export하자는 제안에 단호히 저지했다. 요는 다음과 같다.


"A tool might want to do this" is not a good enough answer. We now have a working OSS tool-space with 'perf' where such arguments for more PMU features can be made in very specific terms: patches, numbers and comparisons. Actual hands-on utility, happy developers and faster apps is what matters in the end - not just the list of PMU features we expose.


다시한번 Ingo의 kernel에 대한 철학을 엿볼수 있다. Ingo는 언제난 이런 투의 말을 싫어한다. - "Tool이란 이러한 것을 하길 원할지도 모른다". 막연한 추측 보다는 실제 필요한 것들을 먼저 해보고 데이터를 수집해보고 어떤 데이터가 어떻게 어떤 사용자들한테 유용할지 말하는 것이 결국 happy developer, faster apps들을 만들어 내는 것이다라고 생각한다.

여기서 말하지 않고 넘어간 것이 하나 있다. perf라는 툴이다. perf는 perfcounters를 이용하는 user space tool이다. 이것이 중요한 이유는 이 tool이 커널의 tool 디렉토리에 merge되었다는 것이다. 지금까지 많은 커널 개발자들은 user space 툴들이 kernel에 merge되는 것을 좋아하지 않아왔다. 그럼에도 불구하고 이번 perf는 Linus의 지지를 받으며 입봉하게 된 것이다. Linus는 Oprofile의 예를 들며, 이번 perf의 입봉을 전격 추진하였다. Oprofile userspace tool은 커널의 밖에서 maintain되어왔다. 그러다 보니, 커널의 최신 패치를 항상 한발 늦게, 또는 3~4발 늦게 쫓아왔다. Linus는 항상 이게 불만이었다. Anyway 이게 적절한 예는 아닐지 몰라도, 다른 개발자들의 만류에도 불구하고 Linus는 이번에 한번 그렇게 해보는 게 어떻겠냐고 반문하며 perf의 입봉을 대환영하였다.

"Let's give a _new_ approach a chance, and see if we can avoid the mistakes of yesteryear this time."



어떻게 됐든, 지금 perf와 perfcounter는 merge되었고 이변이 없는 한 revert되지는 않을 것이다. 또한 현재의 perfcounter는 개발 초기와는 달리 굉장히 많은 기능이 추가되고 있으며 barrios가 생각하건데, 1년 안에 Oprofile을 능가하는 멋진 툴이 될 것이라고 생각한다.

현재 barrios 또한 perfcounter에 allocation과 reclaim의 분석을 위한 도구들을 add할 생각을 가지고 있다.

기분 좋은 하루 Jul 4, 2009

간만에 즐거운 술 한잔.

고등교육을 마치고 나서부터, 언제나 즐거웠던.(단 즐거운 사람들과 함께 있는 경우만. 굉장히 까탈스러운 성격으로 인해 어렵거나 유쾌하지 못한 술자리는 되도록 피하는 편이다.)

여러가지 일로 미뤄왔었던 기분좋은 마무리다.

창문을 열어 놓으니 바람이 솔솔 들어온다.
아내는 TV를 보고 있고 아들은 조용히 잠을 자고 있는 이 순간이 삶의 낙이다.

TV에 한 가수가 얼마전 종용했던 드라마의 주제가를 부르고 있다.

나를 믿고 의지하는 가족이 있어서 행복하다.

당장 패치를 하나 만들어야 하는데.. 코드를 볼 정신이 없다.
(Andrew한테 상당히 미안하다.)

끝으로 기분 좋게 울 아들 왕눈이 사진 한장.