리눅스 커널 번역 프로젝트 Feb 27, 2008

리눅스 커널 번역 프로젝트 페이지를 만들었습니다.

작년 7월 일본과 중국의 번역문서들이 mainline에서 토론되어지기 시작하는 것을 보고 바로 시작하였다. 하지만 일이나 부족한 번역실력으로 한 문서를 submit하기까지는 많은 시간이 걸리고 있는 실정이다. 뿐만 아니라 보다 중요한 것은 이미 일본이나 중국은 굉장히 활발하게 활동하는 리눅스 커널 로컬 커뮤니티를 가지고 있다는 것이다.
우리나라에도 많은 커널개발자가 있다고 생각한다. 하지만 mainline을 보면 정말 몇 안되는 분들만이 활동을 하고 있는 것이 오픈소스에 대한 우리나라의 리눅스 커널 개발에 대한 실정이다. 반면, 일본은 근래들어 점차 많은 사람들이 mainline에서 활동을 하고 있으며 중국은 이미 꽤 많이 눈에 띄었었다. 이렇게 할 수 있는 가장 큰 힘은 각국의 로컬 커뮤니티라고 생각한다. 우리나라에도 많은 커널 개발자들이 있고 지역적인 커뮤니티 또는 학생들의 스터디 그룹이 있는 것으로 알고 있지만 국내를 대표할만한 리눅스 커널 커뮤니티가 없는 실정이다.
나는 리눅스 커널 번역 프로젝트가 활성화되어 많은 국내 리눅스 커널 개발자들이 의견을 교환하고 새로운 지식을 공유하며 나아가서 오픈 소스 리눅스 커널 mainline에서의 많은 국내 리눅스 커널 개발자들의 활동을 통하여 한국의 위상을 높였으면 한다.

stack back trace for arm Feb 22, 2008

예전에 작성한 arm backtrace에 관한 간단한 문서이다.

다음과 같은 테스트 프로그램을 만들어보자.


#include
#include
#include

void trace (void)
{
void *array[2]; int size;
size = backtrace (array, 2);
}

void dummy_function (void)
{
trace ();
}

int main (void)
{
dummy_function ();
return 0;
}


위의 프로그램을 컴파일 한후 역어셈 해보자. (동적 라이브러리 함수 호출과정의 설명을 생략하기 위하여 static으로 컴파일하였다.)

arm_xxx-gcc -o trace main.c -static
arm_xxx-objdump -D trace > trace.dis

trace.dis 파일을 보면 다음과 같이 trace 함수의 역어셈 결과를 볼 수 있다.

119 00008218 :
120 8218: e1a0c00d mov ip, sp
121 821c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
122 8220: e24cb004 sub fp, ip, #4 ; 0x4
123 8224: e24dd00c sub sp, sp, #12 ; 0xc
124 8228: e24b3018 sub r3, fp, #24 ; 0x18
125 822c: e1a00003 mov r0, r3
126 8230: e3a01002 mov r1, #2 ; 0x2
127 8234: eb001d28 bl f6dc <__backtrace>
128 8238: e1a03000 mov r3, r0
129 823c: e50b3010 str r3, [fp, #-16]
130 8240: e24bd00c sub sp, fp, #12 ; 0xc
131 8244: e89da800 ldmia sp, {fp, sp, pc}

여느 함수와 마찬가지로 스택 프레임을 만드는 코드는 같다. - 120 ~ 122
지역 변수를 위해 12바이트 공간을 할당하였다. - 123
r0에 array 배열을 위한 첫 주소를 지정한다 - 124 ~ 125
r1에 2를 지정하여 __backtrace 함수를 호출하기 전 인수를 모두 지정한다. - 126
브랜치


7899 0000f6dc <__backtrace>:
7900 f6dc: e1a0c00d mov ip, sp
7901 f6e0: e92dd810 stmdb sp!, {r4, fp, ip, lr, pc}
7902 f6e4: e24cb004 sub fp, ip, #4 ; 0x4
7903 f6e8: e24dd004 sub sp, sp, #4 ; 0x4
7904 f6ec: e1a0c000 mov ip, r0
7905 f6f0: e3a00000 mov r0, #0 ; 0x0
7906 f6f4: e24b4011 sub r4, fp, #17 ; 0x11
7907 f6f8: e24b200c sub r2, fp, #12 ; 0xc
7908 f6fc: e1500001 cmp r0, r1
7909 f700: a91ba810 ldmgedb fp, {r4, fp, sp, pc}
7910 f704: e59fe030 ldr lr, [pc, #48] ; f73c <.text+0x766c>
7911 f708: e1520004 cmp r2, r4
7912 f70c: 391ba810 ldmccdb fp, {r4, fp, sp, pc}
7913 f710: e59e3000 ldr r3, [lr]
7914 f714: e1520003 cmp r2, r3
7915 f718: 291ba810 ldmcsdb fp, {r4, fp, sp, pc}
7916 f71c: e5923008 ldr r3, [r2, #8]
7917 f720: e78c3100 str r3, [ip, r0, lsl #2]
7918 f724: e2800001 add r0, r0, #1 ; 0x1
7919 f728: e5923000 ldr r3, [r2]
7920 f72c: e243200c sub r2, r3, #12 ; 0xc
7921 f730: e1500001 cmp r0, r1
7922 f734: a91ba810 ldmgedb fp, {r4, fp, sp, pc}
7923 f738: eafffff2 b f708 <__backtrace+0x2c>
7924 f73c: 00069828 andeq r9, r6, r8, lsr #16


  1. backtrace 함수는 일반 함수와는 약간 달리 r4 레지스터를 stack에 추가로 보관한다. - 7901
  2. 지역변수를 위한 공간은 4byte 할당한다. - 7903
  3. trace함수가 인수로 넘겨준 array의 주소를 ip 레지스터에 저장한다. - 7904
  4. r0레지스터에는 0을 지정한다. 루프의 counter로 사용될 것이다. - 7905
  5. r4레지스터에 fp - 17의 값을 저장한다. 이것은 현재 스택 프레임보다 1 바이트 더 내려간 값으로 비교를 위해 사용된다. - 7906
  6. r2레지스터에 saved fp의 주소를 지정한다. - 7907
  7. r0과 r1을 비교하여, 즉 counter 값이 trace에서 넘겨준 counter값보다 같거나 크다면 정리하고 함수를 빠져 pop한다. - 7908 ~ 7909
  8. lr레지스터에 69828 값을 저장한다. 현재 pc는 0xf70c이다.여기에 48을 더하면 0xf73c이며 그곳에는 69828값이 들어 있다. - 7910
  9. r2와 r4를 비교한 후 r2가 r4보다 작다면 pop한다. - 7911~7912
  10. lr 을 저장되어 있는 주소에 있는 값을 가져와 r3에 저장한다. 이 주소는 69828이다. 69828의 주소는 __libc_stack_end의 라벨이 되어 있으며 image에는 0x0 값이 들어가 있지만 실행시에는 어떤 값으로 채워질 것이며(누가 채워줄까??) 이 값이 __libc_stack_end 값이 될 것이다. - 7913
  11. r2의 saved fp의 주소가 __libc_stack_end의 값보다 같거나 더 크다면 pop한다. - 7914 ~ 7915
  12. saved lr의 값을 r3레지스터에 저장한다. - 7916
  13. 인수로 넘어온 arrary의 주소 * count * 4의 주소에 r3 값을 저장한다. 즉 return address를 인수의 array에 저장하는 것이다. - 7917
  14. counter를 1 증가시킨다. - 7918
  15. saved fp에 있는 값을 r3에 저장한다. 즉 이전 스택 프레임의 주소를 r3에 저장하는 것이다. - 7919
  16. 마찬가지로 r3에서 12를 빼서 이전 스택 프레임의 saved fp의 주소의 값을 r2에 저장한다. - 7920
  17. counter의 값이 인수로 넘어온 counter값보다 크지 않다면 9번으로 돌아간다. 그렇지 않으면 pop한다.

nopfn

http://lwn.net/Articles/242625/


populate, nopage, nopfn이 fault 메소드로 통합된지 오래다.
사실 예전부터 nopfn을 언제 써야하는지 궁금했었다. 좋은 기사에 정확한 설명이 있어 덧붙인다.

http://lwn.net/Articles/200213/


Meanwhile, one of the longstanding limitations of nopage() is that it can only handle situations where the relevant physical memory has a corresponding struct page. Those structures exist for main memory, but they do not exist when the memory is, for example, on a peripheral device and mapped into a PCI I/O memory region. Some architectures also do very strange things with special memory and multiple views of the same memory. In such cases, drivers must explicitly map the memory into user space with remap_pfn_range() instead of using nopage().

Memory Controller background reclaim Feb 18, 2008

https://lists.linux-foundation.org/pipermail/containers/2007-October/008122.html

valinux의 YAMAMOTO Takashi의 패치이다.

background reclaim 정책을 두어 굉장한 성능 향상을 한 것을 알 수 있다. 물론 위의 테스트는 SMP였을 것이라 생각된다. 하지만 UP에서도 어느 정도의 향상이 있을 것으로 보인다.

Memory Controller에 대한 얘기는 다음을 참조하라.
http://lwn.net/Articles/243795/

How much memory are applications really using Feb 17, 2008

http://lwn.net/Articles/230975/

이번 2.6.25에 추가된 기능중의 하나이다.

임베디드 시스템에서 어플리케이션이 사용하는 정확한 물리 메모리의 양을 측정하는 것은 중요하다. 왜냐하면 메모리의 양은 돈과 직결되는 문제이기도 하고, 개인적으로는 그것보다 RTOS시절의 app들이 그대로(?) 포팅되어져서 VMA와 PAGE에 대한 개념이 없기 때문이기도 하다.

Anyway,
현재 리눅스 커널이 제공하는 rss는 별로 유용하지 못하다. 왜냐하면 shared page에 대한 account 정보가 올바르지 못하기 때문이다. 또한 /proc/pid/smaps를 이용해서 보다 많은 정보를 알 수 있기는 하지만 아직은 부족하다.

Matt Mackal은 이 문제를 해결하기 위해 pagemap과 kpagemap을 구현하였다.
이는 각 프로세스가 사용중인 실제 물리 메모리에 대한 정보와 함께 PSS와 USS를 구현하여 보다 정확한 정보를 제공한다.

PSS(proportional set size)는 공유되는 페이지를 공유 프로세스의 수로 나누어서 정확하게 할당된 메모리를 파악할 수 있게 해준다. 반면 USS(unique set size)는 공유되지 않는 페이지들의 합이다. 또한 clear_refs를 각 프로세스마다 만들어 페이지 테이블에 reference bit를 초기화 할 수 있게 해준다. 그렇게 함으로써 프로세스가 실행중에 어떤 페이지들을 access하는지 알수 있게 해준다. 하지만 이 기능에 대해서는 의문이다. 굳이 필요한 기능일까?

referenced bit은 kswapd와 같은 회수 처리에 의해 다시 reset될 수도 있기 때문에 특정 시점에 가서는 참조된 페이지라 할지라도 reference bit은 다시 reset되어 있을 수 있다.

현재 PSS는
Fengguang Wu에 의해 smaps에 의해서도 볼 수 있다.
http://lkml.org/lkml/2007/8/13/1224


임베디드 시스템에서 페이지 회수 이슈

아직까지는 임베디드 리눅스의 제품들은 몇 안되는 프로세스가 실행된다. 이는 이전까지 RTOS를 사용하여 task 기반의 프로그램들이 비정상(?)적으로 포팅되었기 때문이다. 기존의 RTOS에서 address space를 공유하며 수행되던 task들을 리눅스에 포팅하기 위해 thread 모델을 사용하기 때문이다. 응용의 성격에 따라 틀리겠지만 PVR과 같은 어플리케이션이 아니라면 일반적으로 page cache의 페이지 보다는 anonymous page들이 시스템에 더 많이 존재하게 될 것이다.

하지만 현재까지의 Linux kernel의 페이지 회수 정책은 !SMP && !SWAP 장치에 대해서는 그리 효과적이지 못한 것 같다. 이를 해결하기 위해서는 페이지의 type별로 분리된 lru list를 만들어야 한다. 제일 우선순위가 높은 list는 물론 page cache에 있고 mapped되어 있지 않은 페이지들 일 것이다.

먼저 scan_control의 may_swap이 바뀔 필요가 있다. 또한 !SMP에서 수행될 때 pagevec은 과연 효과가 있을까?

임베디드와 같이 order 0이상의 페이지를 많이 요구하지 않는 시스템에서 주로 메모리 요구의 대상은 application일 것이고 이는 order 0의 페이지들에 대한 많은 요구가 있을 것이다. 특히나 많은 쓰레드들이 도는 환경이라면 더욱 그럴 확률이 높을 것이다.

그러므로 지금의 zone의 order 0의 페이지 캐시는 상황에 따라 dynamic 하게 더욱 많은 bulk page들을 만들도록 수정되면 성능에 향상이 있을 것이다.

마지막으로 시스템이 어떤 페이지들을 어느 순간에 얼마나 많이 사용하는지 프로파일링하고 하눈에 그래프로 볼 수 있는 프로파일링 도구가 필요하다.

PAGE_OWNER Feb 13, 2008

이 패치에 원본에 대해서는 다음을 참고하라.

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.24-rc5/2.6.24-rc5-mm1/broken-out/page-owner-tracking-leak-detector.patch

이 패치가 하는 역할은 시스템에 커널 메모리 leak을 알 수 있는 간접적인 방법을 제공하는 데 있다.
이 패치를 이번에 arm으로 포팅하였다.

동작메커니즘은 간단하다.

리눅스 커널의 물리 메모리 할당을 위해서는 반드시 alloc_pages함수를 통하게 되어 있다. 그러므로 alloc_pages함수를 통해서 buddy system으로부터 물리 메모리를 할당받는 시점에서 backtrace를 통하여 alloc_pages함수의 호출 경로를 페이지 디스크립터에 기록하여 놓는 것이다.
그런 후 할당된 물리 메모리가 해지될 때 backtrace정보를 삭제하는 것이다. 정확하게는 페이지에 더이상 tracking되지 않고 있다는 표시만 한다.

사용자는 특정 시점에

cat /proc/page_owner > page_owner_full.txt

를 통해서 시스템에 존재하는 할당된 페이지들에 대한 call path를 모두 덤프받게 되며 같이 제공된 application을 통해서 제일 많이 할당된 call path순으로 정렬하여 어떤 함수에서 제일 많이 호출했는지를 알 수 있다.

예를 들어보면 다음과 같다.

> [sorted_page_owner.txt text/plain (100.2KB)]
> 51957 times:
> Page allocated via order 0, mask 0x80d0
> [0xc015b9aa] __alloc_pages+706
> [0xc015b9f0] __get_free_pages+60
> [0xc011b7c9] pgd_alloc+60
> [0xc0122b9e] mm_init+196
> [0xc0122e06] dup_mm+101
> [0xc0122eda] copy_mm+104
> [0xc0123b8c] copy_process+1149
> [0xc0124229] do_fork+141
>
> 12335 times:
> Page allocated via order 0, mask 0x84d0
> [0xc015b9aa] __alloc_pages+706
> [0xc011b6ca] pte_alloc_one+21
> [0xc01632ac] __pte_alloc+21
> [0xc01634bb] copy_pte_range+67
> [0xc0163827] copy_page_range+284
> [0xc0122a79] dup_mmap+427
> [0xc0122e22] dup_mm+129
> [0xc0122eda] copy_mm+104

위의같이 출력된 결과를 검토하면 현재 시스템에 있는 물리 메모리 중 order 0짜리의 페이지가 pgd_alloc을 통하여 51957번 됐음을 알 수 있다. pgd_alloc은 do_fork를 통해서 호출되었다.

그러므로 우리는 현재 시스템에 pgd_alloc과 pair로써 동작하는 메모리 해지 함수가 정상적으로 호출되 되고 있지 않음을 간접적으로 알 수 있게 된다.

arm의 backtrace과정은 간단하다.



(위의 그림에서 sb는 현재 사용되지 않는다.)

위의 그림과 같이 현재의 fp 레지스터는 다음 함수의 frame pointer를 가리키고 있기 때문에 stack이 깨지지 않는 한 그냥 backtrace하여 가면 된다.

하지만 위의 기능이 제대로 동작하기 위해서는 리눅스 커널에 CONFIG_FRAME_POINTER기능이 enable되어 있어야 한다.

symbol_get/symbol_put

inter_module_xxx 관련 함수들이 2.6.10이후로 symbol_get/symbol_put 함수로 바뀌면서 제거되었다. 참조할 만한 문서는 다음과 같다.(함수에 문제가 있다는 얘기)

http://lwn.net/Articles/119013/


위의 함수들이 필요한 이유는 다음과 같다.
예를 들어 모듈 A가 특정 상황에 모듈 B의 함수 BFunction()을 사용한다고 하자.
BFunction을 사용하기 위해서는 모듈 B는 EXPORT_SYMBOL(BFunction);을 선언해야 하며 모듈 A는 BFunction을 extern으로 선언하고 사용하여야 한다.

이때 문제가 되는 것은 모듈 A가 모듈 B가 커널에 load되지 않은 상황에서 먼저 load된다면 커널은 모듈 A의 심볼테이블에 있는 Bfunction을 resolve하려다가 그만 linking error를 발생시키고 모듈 A 자체가 load 되지 못하는 현상이 발생하게 되는 것이다. 모듈 A는 그 함수를 정말 특정한 상황에서 단지 한번 호출할 때 뿐인데도 불구하고..

그래서 나온 것이 symbol_get과 symbol_put을 사용하여 runtime에 심볼을 찾아내서 호출하는 방식이다. glibc의 dl_open, dl_sym과 같은 이치라고 보면 될 것이다.