2011년 11월 13일 일요일

디자인 패턴

Chromium 소스 분석을 시작한지 이제 한달이 넘어가고 있습니다...
아직 포스팅 할만큼은 분석이 안되서 못 올리고 있지만, 앞으로 조금씩 포스팅할 생각입니다.

한달을 돌이켜 생각해보면, 패턴을 알고있었더라면 조금은 더 쉽게 구조를 잡지않았을까 하는 아쉬움이 있습니다.
이제 시작이고 6개월이 더 걸릴지 1년이 더 걸릴지는 모르겠지만 언젠간 실시간으로 올라오는 개발자들의 commit log를 이해할 날이 오겠지요.

다른 대부분의 오픈소스 프로젝트들도 마찬가지겠지만, Chromium 도 역시 패턴으로 구현이 되어있는 것을 알 수 있습니다. 그중에서도 singleton, observer, delegate, proxy 등등은 거의 모든 소스 파일에서 볼수 있는 단어라고 생각해도 과언이 아니니까요.

Chromium 분석을 하기 전까지 디자인 패턴이란 말을 들어보기는 했으나, 공부를 해본적은 없었습니다. 그래서 소스코드에서 delegate, observer 등의 단어가 섞여있는 클래스 들을 보고도 전혀 감을 잡지 못했었죠. 클래스들이 뭐 이리 복잡하게 엮여있어!! 하는 불평만 했습니다.

만약 제가 패턴을 공부했었더라면, 아니 적어도 무엇인지 정도만 알고 있었더라도 분석을 하는데 많은 도움이 되었을 것입니다.

Chromium 은 방대합니다. 그만큼 수많은 라이브러리와 패턴들로 구현이 되어있구요.

Chromium 소스 분석은 공부하는데 많은 도움이 되는것 같습니다.

느낍니다. 저는 하수 중에 하수라는 것을...

많이 노력해야겠습니다

TCMalloc

Chromium의 소스 분석을 시작하면서 다음의 코드를 만나게 되었습니다.

//src/content/app/content_main.cc
tc_set_new_mode(1);

이 함수는 tcmalloc 에서 제공하는 API 입니다.

흐음... tcmalloc은 뭘까요?
tcmalloc 은 Thread-Caching Malloc의 약자로 구글성능도구(google-perftools) 패키지에 포함되어 있습니다.

이름에서 알수있는 것처럼 thread 별로 malloc 관리를 하는 라이브러리인데요,
doc page 를 읽어보면 각각의 쓰레드에 thread-local cache를 배정한다고 나와있습니다. (TCMalloc assigns each thread a thread-local cache.)

쓰레드에서 malloc 이나 new 를 통해 메모리 할당을 요청하면, thread-local cache에서 메모리할당이 수행되는 것인데요, 왜 이런 과정이 필요하게 된걸까요?
쓰레드를 많이 사용하는 프로그램에서는 기존 메모리 할당 방법(glibc malloc)으로는 어떤 문제가 있길래 쓰레드단위로 메모리 할당이 이루어지도록 했는지 궁금하네요.

많은 쓰레드가 동작하는 상황에서 기존 malloc 사용시 다음 문제가 생기는 것 같습니다.
아래 글은 다음 블로그에서 참고하였습니다 (http://lethean.pe.kr/2009/04/29/tcmalloc-google-perftools/)

장기간 실행되면서 빈번하게 메모리를 할당 / 해제하는 것은 물론 수십 개의 쓰레드가 동작하는 프로그램에서는 어쩔 수 없이 메모리 단편화(Memory Fragmentation)가 발생합니다. 메모리 단편화가 많을 경우 어플리케이션 로직에 메모리 누수(memork leak)가 없어도 C 라이브러리 메모리 관리자가 메모리를 커널에 반환하지 않기 때문에 프로세스의 메모리 사용량은 계속 늘어납니다.(참고로 이러한 경우인지 여부는 주기적으로mallinfo() 정보를 확인하면 됩니다) 물론 이를 회피하기 위한 기법이나 아키텍쳐는 많이 있지만, 그리 쉽게 원하는 성능과 효율을 얻기는 힘들더군요. "


여러 쓰레드에서 메모리 할당/해제를 하게되면 glibc 내 malloc이 관리하는 메모리 풀의 단편화가 심해져서 커널에 메모리 반환이 잘 안되는 모양입니다. 그래서 쓰레드가 종료가 되더라도 메모리 단편화로 인해 glibc는 종료된 쓰레드가 사용했던 메모리 반환을 못하게되어 메모리 사용량이 줄지않고 계속 늘어나는 현상이 생길 수 있겠습니다.

tcmalloc 을 사용하면 malloc에 비해 빠르고 효율이 좋다고 합니다. 성능은 할당/해제 시간일 테고, 효율은 오래 프로그램을 돌리더라도 메모리 사용량이 잘 관리가 되는 것이겠네요.

사용해보지는 않았지만, 이제 앞으로는 tcmalloc을 이용하여 개발을 해야겠습니다. ^^

마지막으로, tcmalloc을 알게 해준 tc_set_new_mode() 함수에 대해 간단히 정리를 하고 넘어가야겠습니다.
이 함수의 주석은 다음과 같습니다.

" This function behaves similarly to MVSC's _set_new_mode.
   If flag is 0 (default), calls to malloc will behave normally.
   If flag is 1, calls to malloc will behave like calls to new,
   and the std_new_handler will be invoked on failure. "

아~ tc_set_new_mode(1)은 메모리 할당 실패 시, std_new_hander에 정의된 핸들러를 호출하라고 지시하는 함수네요.

근데,,, glibc, tcmalloc 모두 링크되는데 프로그램은 실행시 어떻게 tcmalloc의 malloc을 사용할까요???

2011년 11월 5일 토요일

LD_PRELOAD

API 후킹이라는 말을 많이 들어보셨을겁니다.

어떤 프로그램이 사용하는 API를 실행중에 가로채는 것을 말하는데요,
가로채서 그 API가 동작 못하게 할 수도 있고, 다른 동작을 수행하게 바꿀수도 있죠. 런타임에!

예를 들어, hostname 명령은 gethostname() 함수를 사용해서 현재 시스템의 호스트네임을 가져오고 있습니다.


제 노트북의 호스트네임이 출력이 되네요.

이제 API 후킹을 해볼까요?
런타임에 gethostname() 함수를 가로채서 다른 호스트네임을 출력하도록 바꿔보겠습니다.

gethostname() 함수는 libc에 정의되어 있습니다.


그렇다면 hostname 명령이 libc의 gethostname() 을 호출할 때, 새로 정의한 gethostname()이 호출되도록 해줘야 원하는 동작이 수행이 되겠네요.

hostname 명령은 libnsl.so 와 libc.so 에 의존하는 프로그램입니다.


gethostname()은 libc 에 정의되어 있습니다.
hostname  명령이 로더에 의해 로딩 될때 의존성을 가지는 libnsl, libc 라이브러리가 동적로딩이 될것인데,
그렇다면 어떻게 libc의 gethostname()이 아닌 다른 gethostname() 함수가 호출되게 할 수 있을까요?

우선 간단한 gethostname()을 작성해 봅시다.


이 소스를 이용해 gethostname.so 라이브러리를 생성합니다.

그리고 LD_PRELOAD 환경변수에 gethostname.so를 설정하고 hostname 명령을 실행 합니다.

와우~!! 어떻게 이게 가능한 걸까요?
strace 를 통해 hostname을 검사해 봅시다.


hostname 명령이 의존하는 라이브러리도 오픈하는게 보이네요.

open("/lib/i386-linux-gnu/libnsl.so.1", O_RDONLY) = 3
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3

앗 자세히 보니 preload 가 눈에 들어옵니다!!
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
그런데 파일이 존재하지 않는군요.

여기서 잠깐, LD_PRELOAD 와 /etc/ld.so.preload 는 무슨 차이가 있을까요.
loader의 source code를 보면 두가지에 대해 알 수 있습니다.
 
elf/rtld.c
로더는 LD_PRELOAD에 설정된 라이브러리를 먼저읽고, 그다음  /etc/ld.so.preload 파일에 기록된 라이브러리를 읽는 것을 알 수 있습니다.

다시 돌아와서, 다른 라이브러리가 open 되지 않는걸 보니,
hostname은 libc 내 정의된 gethostname() 을 이용하고 있는 것 같습니다.

그렇다면! LD_PRELOAD 환경변수를 설정한뒤 실행한 hostname은 어떻게 다를지 궁금하네요.

아하~ 다른 라이브러리보다 gethostname.so 라이브러리를 제일 먼저 읽네요.
그래서(?)  libc.so 가 아닌 gethostname.so 내 gethostname()이 호출될꺼라고 얼렁뚱땅 넘어가게되면 안되겠죠? ㅎㅎ

라이브러리 오픈한 뒤, read, fstat, mmap2 를 통해 라이브러리를 프로세스의 Address space에 매핑을 하는것 같습니다.
gethostname.so, libc.so 모두 로더에 의해 로딩이되는데, 어떻게 gethostname.so의 gethostname() 함수가 실행이 되는 걸까요?


어렵네요... 우선 LD_PRELOAD 에 대해 알아보았으니 다음 시간에 이 문제에 대해 정리를 해보겠습니다.

2011년 11월 4일 금요일

프로그램 국제화 도구 - Locale

소스코드의 시작 부분에서  setlocale() 을 종종 보게 됩니다.

예전엔 무심코 지나쳤었는데, 이젠 꼼꼼하고 디테일하게 하나씩 알아나가야 할 것 같네요.
(점점 위대하신 분의 영향을 받는것 같습니다 ㅎㅎ)

프로그램의 국제화 (Internationalization, i18n)은 프로그램이 어떤 환경(?)에서 수행할 지 선택할 수 있도록 하는 것인데요,

설명이 좀 애매합니다

예를들어 언어의 경우, 출력 시 어떤 언어를 출력할 것인가도 위에서 언급한 환경 중 하나의 종류가 되겠습니다.

이러한 종류를 Category라고 부르는데요, Category 는 다음과 같습니다.
















위 그림에서 ko_KR.UTF-8 은 로케일 값이구요, "locale -a"  명령을 통해 사용가능한 로케일의 이름을 알 수 있습니다.

각각의 Category에 로케일 값이 설정되어 있는데, LC_ALL에는 설정이 되어 있지 않네요.
LC_ALL 의 값을 설정하게 되면 LC_ALL의 값이 모든 다른 Category의 값으로 사용됩니다.
그리고 LC_ALL의 값을 없애면 다른 Category의 값들은 이전 값을 유지 합니다.

소스코드에서 setlocale(LC_ALL, "") 과 같은 코드를 사용하면, 시스템에 설정된 Category의 값을 그대로 이용하겠다는 의미가 되겠네요.

setlocale()을 호출하지 않으면 시스템에 설정된 Category의 값을 이용하지 않으므로  반드시 setlocale() 호출을 통해 프로그램에 locale 설정을 해야하겠습니다.

이 포스트는 아래의 링크를 참조하여 정리하였습니다.
자세한 설명이나 테스트 코드는 아래 링크를 참고하시면 됩니다.
로케일(Locale)에 관하여...

그리고 포스트에 틀린 내용이 있다면 코멘트 부탁드립니다!!

2011년 9월 21일 수요일

Chromium 분석 - 소스트리 (1)

어제 컴파일 걸어놓고 일어나서 보니 빌드가 끝나있네요 ㅎㅎ
Debug 모드로 빌드했더니 chrome 바이너리 크기가 1GB 가까이 됩니다...
Release 모드로 빌드하면 100MB 정도 되려나요~ 아무튼 거대한 프로그램 입니다.

오늘은 Chromium 소스코드의 디렉토리가 어떻게 구성이 되어 있는지 알아보겠습니다.

압축을 풀고 디렉토리를 들여다보니 디렉토리며 소스들이 엄청납니다.
커널소스를 처음 봤을때의 막연함이 생각나네요.
브라우저가 OS 와 같은 플랫폼처럼 되가고 있으니 점점 더 크고 복잡해져갈 것 같습니다.

아래와 같이 루트 디렉토리 내 많은 디렉토리들이 있습니다.

Chromium 소스 트리







흠... 디렉토리 이름 중에 알만한 건 out 밖에 없네요...
이 많은 디렉토리 트리를 어떻게 알아가야할지 대략난감입니다.

courgette, jingle, ppapi, sdch 등등 디렉토리 이름만 봐서는 전혀 감을 잡을 수가 없네요.

다행히 chromium.org 내 sitemap -> For Developers -> Design Document 에서 정보를 찾을 수가 있습니다.

얼핏 보니 다음 두 페이지가 눈에 들어오네요.

Getting Around the Chromium Source Code Directories

How Chromium Displays Web Pages

소스트리가 방대해서 여러 포스트에 걸쳐서 정리를 해야할 것 같습니다.

우선 위 두 페이지를 보고 이해한 다음에야 대략 소스트리 구조를 알 수 있을것 같네요.

2011년 9월 20일 화요일

Chromium 분석 - Build

이번 포스트에서는 다운 받은 Chromium 소스코드를 빌드하는 방법을 알아보겠습니다.

빌드하기 전에 어제 다운받은 소스코드를 최신버전으로 업데이트하기 위해 "gclient sync"를 이용하여 최신버전으로 업데이트 해보겠습니다.

이런... 리비전 101697에서 101737로 업데이트가 되었습니다...

이런 개발속도를 따라가려면 하루 24시간 따라가도 모자라겠네요 ;;;

Chromium은 많은 다른 오픈소스 라이브러리를 필요로합니다.
따라서 빌드를 위해서는 시스템에 의존성이 있는 라이브러리들을 밀 설치해야하는데, Chromium은 이를 위해 ./build/install-build-deps.sh 를 제공하고 있습니다.
이 스크립트를 실행하면 Chromium이 의존성을 가지고 있는 라이브러리들을 설치할 수 있습니다. (참 편하죠? ^^ - 나중에 ARM 환경에서 Cross-compile도 시도해 보아야겠습니다.)

이제 의존성을 다 해결했으니 Makefile을 생성해야겠죠?

이전 포스트에서 depot tools 에 대해 잠깐 살펴보았는데요, 빌드는 gyp라는 빌드 시스템을 사용하고 있습니다. gyp는 configure 스크립트 처럼 Make를 위해 Makefile을 생성해주는 시스템 입니다.

gclient sync를 하면 자동으로 gyp 을 실행하면서 Makefile을 생성해줍니다.
새로 Makefile을 업데이트 하고 싶다면 "./build/gyp_chromium -Dflag1=value1 -Dflag2=value2" 명령을 이용해서 원하는 플래그도 설정합니다.
(기본적으로 Chromium은 compiler warning이 발생하면 빌드를 멈춥니다. warning 을 만나도 빌드를 계속하게 하려면 -Dwerror= 을 추가해 줍니다)

이제 make 명령을 사용하여 빌드를 시작할 수 있습니다.

저는 make -j4 BUILDTYPE=Debug chrome 으로 빌드를 하였습니다.
BUILDTYPE은 Release 와 Debug를 선택할 수 있습니다. Debug 빌드는 디버그 심볼등이 포함되어 있어서 Release 모드 빌드 보다 chrome 실행 파일 크기가 큽니다.

빌드가 오래 걸리네요... 낼 출근을 위해 빌드를 걸어놓고 잠을 자야겠습니다.

낼 아침 빌드가 잘 되어 있으면 다음 포스트는 Chromium 의 소스트리에 대해 알아보도록 하겠습니다.

2011년 9월 18일 일요일

Chromium 분석 - 다운로드

이번 포스트에서는 Chromium 소스 다운로드 방법 & 소스 내비게이션에 대해 알아보겠습니다

소스 다운로드 방법은 두가지가 있습니다
    - tarball 로 다운받기
    - svn 에서 checkout 하기

svn으로 checkout하면 시간이 상당히 걸리므로 tarball로 다운을 받아서 update 하는 방법을 이용하겠습니다.

아래 링크를 클릭하여 최신의 tarball을 다운로드 받을 수 있습니다.(약 1.1GB의 tgz 파일)
  http://chromium-browser-source.commondatastorage.googleapis.com/chromium_tarball.html

다운받은 tarball을 풀면 생기는 home/chrome-svn/tarball/chromium/src 에 Chromium의 소스코드가 있습니다.
src 의 상위 디렉토리(home/chrome-svn/tarball/chromium) 에서 gclient update 를 해보니 새로운 업데이트가 있습니다.
정말 빠르게 업데이트가 되는군요!! :)

소스 코드 내비게이션 기능 또한 제공하고 있습니다.
빌드가 아닌 소스 분석만을 하고 싶으면 굳이 시간이 많이 걸리고 디스크 공간만 차지하는 소스 코드 다운로드를 할 필요가 없습니다.
아래의 링크에서 소스 내비게이션이 가능합니다.

    - http://src.chromium.org/viewvc/chrome/ 
    - http://codesearch.google.com/#&exact_package=chromium

소스만 최소 약 1.6GB 정도이고, 빌드까지 한다면 약 10GB 정도가 필요하다고 합니다. 엄청나네요...

다음 포스트에서는 Chromium의 빌드 방법에 대해서 알아보겠습니다.

2011년 9월 17일 토요일

Chromium 분석 - depot_tools

Chromium 분석 작업을 시작하려고 합니다.
어려운일이겠지만 처음부터 하나씩 시작하다보면 언젠가 패치도 올릴 수 있겠지요? ㅎㅎ

첫번째로, Chromium 분석에 앞서 개발과정에서 사용되는 depot_tool 툴에 대해 알아보겠습니다

1. depot tool?
    - Chromium 에서 사용하는 스크립트 패키지
    - Chromium은 depot_tools 를 이용하여 소스 체크아웃 및 코드리뷰 작업을 수행
    - 포함된 유틸리티들 (사용하면서 사용법 업데이트)
      * gclient - svn, git repository checkout tool
      * gcl - code review tool for subversion 
      * git-cl
      * drover
      * etc

2.  depot tools 설치 
    - 다운로드
       - svn co http://src.chromium.org/svn/trunk/tools/depot_tools
    - 설치
       - $export PATH="$PATH":`pwd`/depot_tools
       - 또는 .bash_rc (ubuntu) 에 추가

다음 포스트에서는 Chromium 소스 다운로드 방법에 대해서 알아보겠습니다

2011년 5월 15일 일요일

NPAPI

NPAPI 는 Plugin 을 구성하는 API 모음이다.
Browser 에서 기본으로 제공하지 않는 기능을 추가하기 위해 Plugin 을 사용하게 되는데, 예를 들면, Browser에서 PDF 파일을 Browser 안에서 보여주기 위해 사용되는 것이 pdf plugin  인 것이다.