[patch] mm: fix lockless pagecache reordering bug (was Re: BUG: soft lockup - is this XFS problem? Jan 6, 2009

http://lkml.org/lkml/2009/1/5/282

오늘 report된 버그이다. 이 문제는 Linus가 쓰레드에 참여할 만큼 쉽지 않은 문제이며, 심각한 문제이기도 하다.
Nick이 오랫동안 작업해서 mainline에 merge시킨 lockess pagecache의 버그가 발생하였다. 하지만 문제를 더 깊게 파고 내려가면 lockess pagecache의 bug라기 보다는 근본적인 원인은 lockless radix tree의 버그일 수도 있다.

현재 Linux와 Nick의 주장은 합의점을 찾지 못하고 있으며, 좀더 추이를 지켜봐야 할 듯 하다. 쓰레드에 Paul과 Peter까지 참여하였다. 당대 최고의 커널 expert들이 토론을 하고 있으니, 이 문제의 난위도가 어떨지는 쉽게 생각해 볼 수 있을 것이다. 문제는 굉장히 간단하다.

find_get_pages의 page_cache_get_speculative가 실패한 이후, 다시 radix_tree_deref_slot을 하는 과정에서 page의 _count값을 읽어오기 위해 memory로부터 값을 읽어오는 것이 아니라, 이미 register에 저장되어 있는 값을 계속 사용하기 때문에, page가 radix tree에서 제거되고 _count가 0이 되었음에도, page_cache_get_speculative가 보는 page의 _count는 0이 아니어서 또 실패하고, 다시 radix_tree_defef_slot을 실행하고.. . 해당 cache가 invalid될 때까지는 lockup이 되는 것이다. 아래는 find_get_pages의 컴파일 결과이다.

역어셈 코드 참조


.L220:
movq (%rbx), %rax #* ivtmp.1162, tmp82
movq (%rax), %rdi #, prephitmp.1149
.L218:
testb $1, %dil #, prephitmp.1149
jne .L217 #,
testq %rdi, %rdi # prephitmp.1149
je .L203 #,
cmpq $-1, %rdi #, prephitmp.1149
je .L217 #,
movl 8(%rdi), %esi # ._count.counter, c
testl %esi, %esi # c
je .L218 #,



그래서 Nick은 간단히, repeat의 다음 라인에 barrier를 추가하여 해결하려고 했으나, Linus는 Nick의 그런 제안에 문제점을 지적하였다. Linus의 주장은 그 문제는 lockess pagecache의 버그라기 보다는 RCU를 사용하여 lockless radix tree를 사용할 때 이미 근본적인 문제점을 가지고 있는 것이 아니냐는 것이다. Linus는 RCU를 사용하여 데이터를 보호할 때 RCU 사용하에서 변경될 수 있는 데이터의 필드들은 rcu_dereference를 사용하여 fetch해야 한다는 것이다. 하지만 이 의견에 대해 Nick과의 의견이 상충하고 있고, Paul은 이 문제에 대해 명확한 답을 주지 못하고, 지금부터 주의깊게 살펴볼 듯 하다.

결국 위의 코드는 패치로 인하여 다음과 같이 바뀌었다.
코드를 보면 rdi의 값을 rbx와 rax 레지스터를 이용하여 memory에서 직접 읽어오는 것을 볼 수 있다.


.L212:
movq (%rbx), %rax #* ivtmp.1109, tmp81
movq (%rax), %rdi #, ret
testb $1, %dil #, ret
jne .L211 #,
testq %rdi, %rdi # ret
je .L197 #,
cmpq $-1, %rdi #, ret
je .L211 #,
movl 8(%rdi), %esi # ._count.counter, c
testl %esi, %esi # c
je .L212 #,

1 개의 덧글:

Minchan Kim said...

결국 Linus의 승으로 끝났다. radix_tree_deref_slot에서 rcu_dereference를 사용하는 것으로 모두 ack하였다. Linus가 ACK하였음으로 2.6.29-rc series들에 반영될 것이다.