본문 바로가기

2012
임베디드월드

글: 라영호 | ratharn@naver.com / 2012-04-02


[연재 차례]

1. 안드로이드 시스템의 역사 및 동향
2. 안드로이드 시스템과 리눅스
3. 안드로이드 플랫폼의 이해
4. 안드로이드 바인더(Binder)의 이해
5. 안드로이드 서비스
6. 안드로이드 SurfaceFlinger와 프레임버퍼 드라이버
7. 안드로이드 User Interface와 ADK2012
8. Linux Sound Device와 안드로이드 사운드 시스템
9. 안드로이드 카메라 시스템
10. 안드로이드 카메라와 멀티미디어 프레임워크
11. 안드로이드 카메라와 멀티미디어 프레임워크 ②
12. 안드로이드 시스템 디버깅 및 기타





안드로이드 시스템 개발자를 위한 안드로이드 시스템의 분석 및 이해 ③
안드로이드 플랫폼의 이해

안드로이드
지난 호에는 안드로이드 부팅에 관한 내용과 안드로이드의 전체적인 구조를 통해 안드로이드 운영체제와 임베디드 리눅스와의 관계를 간략히 살펴 봤다. 4월호에서는 안드로이드 운영체제를 구성하고 있는 리눅스에서부터 안드로이드 운영체제가 동작하기 시작하기까지의 관계에 대해 살펴 보도록 하겠다.

안드로이드에서 사용하고 있는 임베디드 리눅스의 특징을 잠시 살펴보면 모놀리틱 커널 구조(Monolithic)로써 다양한 컴포넌트로 구성된 거대하고 복잡한 프로그램이다. 마이크로 커널 구조와 설계 철학이 상이함으로 볼 수 있다. 또한 모듈 지원을 하고, 동적 로딩 및 제거가 가능한 커널 코드를 제공한다는 장점이 있다. 이 외에도 커널 스레드 제공, 주기적으로 수행되는 커널 함수를 지원, 다중 프로세서 (SMP:Symmetric Multiprocessing) 지원한다는 장점이 있다. 특히 다중 프로세서(한 프로세서 내에 여러 개의 동일한 프로세서 코어를 내장하고 있는 시스템) 시스템에서 중요한 이슈라고 하겠다. 목적에 맞게 컴포넌트 커스터마이징 가능하다는 점이 임베디드 시스템으로 사용되기 위한 중요한 요소다. GPL에 따라서 소스코드의 수정 및 배포가 가능하고 저가의 하드웨어 플랫폼에서 수행 가능하다는 장점이 있다. 최근 $25 컴퓨터로 유명세를 타고 있는 Raspberry Pi (http://www.raspberrypi.org/ ) 모듈의 경우 역시 리눅스를 탑재한 임베디드 시스템이다.

장점
다른 운영체제와 호환성이 뛰어나고 에뮬레이터를 통한 윈도우 애플리케이션 수행, POSIX 준수에 따른 유닉스 애플리케이션의 포팅이 용이하다는 장점이 있다. 또 오픈 소스 시스템이기 때문에 디버깅 및 문제 해결에 있어 시스템의 소스를 이용하여 직접 해결할 수 있다는 장점이 있다.

단점
표준화 되어 있지 않다는 단점이 있다. 하지만 근래는 각 회사에서 참여하고 있어서, 단일화 되는 경향이 있다. 현재도 개발되고 있는 운영 체제이며, 따라서 운영 교육이나 업그레이드 등의 사후 관리가 어렵고 복잡해지고 있다. 리눅스 운영체제는 컴퓨터 및 시스템에 대한 많은 지식을 요구하고 있다. 따라서 실제 개발에 있어 이러한 기본적인 지식을 가지고 있다면 개발에 어려움을 겪을 수 있다.

임베디드 리눅스 시스템의 구성
일반적인 임베디드 리눅스 시스템 구축 방법의 이해를 통해 안드로이드 시스템과 임베디드 리눅스 시스템의 관계를 확인해 보도록 한다.
선택한 프로세서 및 만든 개발 보드용 임베디드 리눅스 시스템 만들기 위해서는 다음과 같은 부분에 대해서 결정을 하고 진행하여야 한다.

● 툴 체인(Toolchain) 결정 - 어떠한 컴파일러 및 툴을 이용하여 시스템을 구성할 것인지 결정한다.
● 부트로더(Bootloader) - 통상 리눅스에서는 uboot가 사용되고 경우에 따라서는 고유의 부트로더를 구성하는 경우도 있다.
● 시스템 주변 장치(System Peripheral)에 따른 소프트웨어 지원문제 - 카메라, 멀티미디어, 사운드 장치에 따른 여러 기술들을 어떻게 지원할지 결정
● 커널 버전(Kernel version)결정 - 현재 리눅스 커널의 버전은 3.3 (www.kernel.org 참조)이지만, 대부분의 안드로이드 시스템에서는 3.0이나 그 이하의 버전이 사용된다. 커널 이미지를 만들어 임베디드 시스템에 탑재할 수 있도록 구성한다.
● 루트 파일시스템(Root File System)을 빌드 - 임베디드 리눅스에서 필요한 정보들을 가지고 있는 루트 파일 시스템 이미지를 만들어 임베디드 리눅스가 정상적으로 동작할 수 있도록 구성한다.

안드로이드 개발환경 구축
기존의 임베디드 시스템과는 차별화된 개발방법을 사용한다. 통상 이더넷(Ethernet)을 이용한 개발방법을 권장 혹은 필수로 사용한다. 실제 개발에 있어 하드웨어 플랫폼에 이더넷을 장착할 수 없기 때문에 개발 초기에 사용을 하게 되고, 개발이 어느 정도 진행이 되면, 이더넷보다는 JTAG을 이용하는 하드웨어 디버거나 시리얼 포트를 사용하여 커널의 로그를 통해 시스템 문제를 분석하게 된다.

리눅스와 안드로이드 개발환경의 특징
모든 디버깅은 로그를 기반으로 하게 되어 있다. 일반 리눅스의 경우는 GDB를 이용한 개발이 가능하지만, 안드로이드의 경우에는 적용이 조금 번거롭다(.so 형태가 많아서, 즉 동적 로딩 형태가 많기 때문이다.) 이더넷을 사용하지 않고도 개발이 가능하지만, 이더넷을 사용해서 개발할 경우 2배 이상의 효율을 보여준다.

[그림3]은 일반적인 안드로이드 개발 환경 구축의 예제이다. 통상 서버를 사용하여 안드로이드 운영체제를 빌드할 수 있는 개발 환경을 구축하는 것이 추세다. JTAG는 디버깅이나 이미지를 램이나 플래시 메모리에 다운로드 하기 위해서 사용되며, 이더넷의 경우 네트워크 기능을 이용하여 리눅스 커널의 이미지나 안드로이드 시스템을 다운로드하기 위해 사용한다. 통상 안드로이드 시스템에서 사용하는 커널 이미지는 TFTP를 사용하여 다운로드 하고, 안드로이드의 루트 파일 시스템은 NFS 파일 시스템을 이용하여 마운트 하게 된다. 시리얼은 일반적인 커널의 로그를 확인 하기 위해 사용된다.

일반적인 리눅스 부팅 과정
일반적인 리눅스의 부팅 과정은 다음과 같이 정리할 수 있다.

다음 코드는 리눅스의 초기화 코드를 init 프로세스에 관련된 내용이 있는 소스다. 초기화 과정에는 커널을 위한 각종 내부 변수와 구조체를 초기화 하고 init 프로세스들을 실행하여 초기화 작업을 진행 하는 것을 확인 할 수 있다.

다음 소스는 리눅스 커널에서 초기화를 담당하는 init 프로세스의 주요 코드다. 리눅스 커널이 동작하기 위해 프로세서 관련 초기화 작업, 디버그 로그를 위한 콘솔창 열기, init 과정을 진행하게 된다.

루트 파일 시스템(Root File System)
루트 파티션에 사용될 파일 시스템으로, 리눅스/UNIX 부팅 시 필요한 파일들을 모아놓은 저장장치 혹은 디렉토리의 구성 말한다. 커널과 루트 파일 시스템은 반드시 필요한 구성 요소이다. 루트 파일 시스템은 부팅 시에 항상 마운트 되도록 되어 있다.
루트 파일 시스템이 없거나 구성이 올바르지 않으면 리눅스가 구동되지 않고 커널 패닉이 일어나게 된다.

루트 파일 시스템의 구성
/ - Root Directory라고 불리며, 모든 디렉토리는 / 를 기준으로 생성된다. /는 모든 디렉토리의 출발점이자 다른 파티션의 연결점이다.
/bin, /sbin - 시스템을 사용하기 위한 기본적인 명령어들이 시스템 관리 명령어와 시스템을 복구할 때 사용하는 필수 명령어 등이 존재.
/boot - PC 전용으로 일반적으로 사용되는 디렉토리며, 부팅에 필요한 커널등이 존재.
/dev - Device driver관련된 모든 장치 파일이 위치한다. (2.6버전 부터는 udev, sysfs와 연동)
/etc - 시스템 설정파일들이 위치(가장 중요한 디렉토리다)
/home - 사용자의 홈 디렉토리가 생성되는 곳이다. (ftp, mysql, tomcat 등 별도의 공간을 요구하는 서비스를 위한 디렉토리로 사용할 수도 있다.)
/lib - 시스템 운영 및 프로그램 구동할 때 필요한 공유 라이브러리와 부팅할 때 사용되는 커널 모듈이 위치한다.(가장 필수적인 것만 위치한다)
/mnt - 저장 장치를 일시적으로 마운트하기 위한 마운트 포인트를 제공한다. 일반적으로 해당 디렉토리는 비어 있음
/opt -애드온 패키지가 설치되는 디렉토리, 임베디드 시스템에서는 주로 툴 체인 등이 위치하는 경우가 많다.
/root -루트 사용자의 홈 디렉토리
/proc - 프로세스와 시스템 정보를 제공하기 위한 목적으로 설계된 가상 파일 시스템을 사용하는 디렉토리이다. /proc 디렉토리의 파일은 cat 혹은 more 명령어로 읽을 수 있으며 특정 파일의 경우 echo 같은 명령어로 내용을 변경할 수도 있다. 주로 디버깅 용도로 많이 사용된다.
/tmp - 임시 파일을 저장하는 디렉토리. 수시로 파일이 생성되고 삭제된다. NAND나 NOR를 이용한 root filesystem구성일 경우는 조심해야하는 디렉토리
/usr - 전통적으로 시스템 부팅에 반드시 필요하지 않은 유틸리티 성격의 파일들이 존재함. 하지만, 근래는 반드시 필요한 패키지들도 존재하게 됨으로써 반드시 구성해 주어야 하는 디렉토리
/sys - 2.6 버전 커널에서 새로 생겨난 kernel device model지원을 위해 사용되는 디렉토리
/var - 내용이 자주 변경되는 가변 자료가 저장됨
시스템 운영할 때 발생되는 로그(log), 메일 송/수신할 때 임시로 저장되는 스풀(spool), 프린터로 전송하기 위해 임시로 저장하는 프린트 스풀 데이터 등
/lost+found - fsck가 파일시스템을 점검할 때 손상된 데이터를 복구하지 못했다면 lost+found 디렉토리에 복구하지 못한 데이터 파일을 위치시킨다. 일반적으로 특정 파일시스템으로 포맷되고, 마운트되면서 생기는 경우가 많다.

안드로이드 init 프로세스
리눅스의 init 과정에 의해 리눅스 커널이 초기화되면 안드로이드의 init 과정이 진행하게 된다. 이 과정에서 안드로이드 운영에 필요한 파일 시스템 및 디렉토리를 구성하는 초기 작업이 이루어지게 된다.
위 소스에서 생성되는 디렉토리들은 앞에서 설명한 것처럼 안드로이드 운영에 필요한 시스템 정보들을 관리할 가상 파일 시스템을 생성하는데 목적이 있다.

지난 호에도 설명했던 것처럼 안드로이드 초기화 과정 중에 필요한 두 가지 init 파일이 다음 소스에서와 같이 처리되게 된다. Init.rc 파일에 정의된 순서에 의해 필요한 데몬 프로세서들이 로드가 되면서 안드로이드 시스템이 동작하기에 필요한 환경을 구성하게 된다.
다음 소스는 init.rc의 일부분이다. init.rc는 안드로이드 시스템의 초기화 과정에서 필요한 디렉토리의 생성 및 심볼릭 링크 구성, 안드로이드 내부에서 사용하는 각종 설정 값을 관리하는 프로퍼티 영역 설정, 필요한 데몬 프로세서를 로드 하도록 설정하는데 사용된다. 다음 소스는 프로퍼티 값과 서비스 매니저 데몬을 로드 하기 위한 설정의 일부분이다.

다음은 안드로이드 init 프로세서의 마지막 동작에 관련된 소스이다. init 프로세서의 동작은 init.rc 파일을 처리한 후 아래와 같이 for문과 무한 루프를 통해 대기하게 된다. 안드로이드 시스템 동작에 필수적인 프로세서의 동작을 감시하다 문제가 발생하면 재 시작하는 것으로 init 프로세서의 역할은 마무리 된다.

지금까지 리눅스 및 안드로이드의 초기화 과정 분석을 통해 어떻게 리눅스와 안드로이드 운영체제가 시작되는지 확인했다. 초기화 과정을 이해하는 것은 안드로이드 시스템을 이해하기 위한 가장 기초적인 내용이다.

안드로이드 개발 환경 구축
안드로이드 개발 환경에는 우분투(Ubuntu Linux)나 여러 리눅스 버전을 사용할 수 있다. 안드로이드 소스코드 전체 빌드에는 대략 3~4시간이 소요되기 때문에 멀티 코어를 이용한 시스템이 필수적이다. 4코어 이상의 PC의 경우 SMP를 이용하여 컴파일 시간을 단축 시킬 수 있다.

안드로이드 소스는 repo라는 툴을 사용하여 다운로드 하면 된다. repo는 GIT로 구성된 여러 가지의 프로젝트를 한꺼번에 관리해 주는 툴이다.

필요한 패키지 설치
- http://source.android.com/download 참조
Git(GNI Privacy Guard), JDK, flex, bison, gperf, libsdl-dev, libesd0-dev, libwxgtk2.6-dev,build-essential, zip, curl와 같은 툴을 설치한다.

패키지 설치 방법
$ sudo apt-get install git-core gnupg sun-java5-jdk flex bison gperf libsdl-dev libesd0-dev libwxgtk2.6-dev build-essential zip curl libncurses5-dev zlib1g-dev

안드로이드 소스 디렉토리
안드로이드의 전체 소스를 완료 후 디렉터리의 구조는 다음과 같다.

bionic/ - Android Bionic C 라이브러리
bootable/ - 부트로더 및 Disk Installer 등
build/ - Makefile 관련 세팅파일들, script, map file
development/ - 개발 시 필요한 utility & application들.....
external/ - 안드로이드 프레임워크가 아닌 외부에서 가져온 라이브러리와 바이너리 위치
frameworks/ - android framework, C/C++(JNI포함)/JAVA source들, 일부 HAL source도 포함
/base -Android Framework source
/libs : Android base library(C++서부터)
/audioflinger : Android audio service & HAL source
/surfaceflinger : Android video service & HAL
/ui :Application Framework에서 JNI를 통해서 호출되는 Android framework의 client part, HAL (Input device의 경우) - EventHub.cpp
/utils : wrapping class, 압축관련 유틸리티 등...
/binder : Android Binder & Anonymous shared memory
/cmds : binder관련인 service manager소스와 여러가지 command들
/media : media관련 client & service library
hardware/ - HAL source & include, 일반적으로 android에서 사용하는 hardware관련 소스들을 포함, 반드시 이 디렉토리에만 위치하는 것은 아니다(vendor 디렉토리에 존재하는 경우도 많음)
out/ - 컴파일 된 결과물이 생성되는 디렉토리
packages/ - android 기본 application source(JAVA)
prebuilt/ - compiler & binaries
system/ - android의 기본 binary 소스
/core/init : android init source
/core/vold : external storage 제어 모듈

끝으로
지금까지 리눅스 및 안드로이드 내부의 초기화 과정과 동작 관계를 살펴봤다. 전체 동작 과정이 많은 소스들로 이루어져 있기 때문에 요약을 하는 과정에서 자세히 살펴 보지 못한 점이 있다. 하지만, 소스를 참고하고 동작되면서 출력되는 로그를 비교 확인하면서 좀 더 내부 동작 과정을 살펴볼 수 있을 것이다.





1. 부트로더가 동작하면서 커널을 로드하게 된다. 커널을 로드 하여 재 배치 과정을 진행한 후 커널을 시작하게 된다.
2. 커널의 시작
● 커널은 kernel command line boot argument의 내용에 따라 루트 파일시스템이 저장되어 있는 블록 디바이스를 마운트
● 마운트 하고 나면 “/sbin/init” 실행파일을 찾고 실행시킨다
● 이 때서부터 루트 파일시스템 영역으로 동작하게 된다
루트 파일시스템 /sbin/init 가 /etc/inittab 파일을 찾아서 내용대로 스크립트를 실행
/etc/inittab의 내용대로 /sbin/getty를 실행시켜서 터미널 세팅
/bin/login: /etc/passwd 내용참조
login후 /bin/sh: 이 때부터 리눅스를 사용할 수 있는 상태



static noinline int init_post(void)
__releases(kernel_lock)
{
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();

// 중략
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...n", execute_command);
}
// 초기화 프로세스 코드

run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}







static int __init kernel_init(void * unused)
{
// 중략

smp_prepare_cpus(setup_max_cpus);

do_pre_smp_initcalls();
start_boot_trace();

smp_init();
sched_init_smp();

do_basic_setup();

/* 콘솔 장치를 여는 부분 */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.n");

// 중략
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";

if (sys_access((const char __user *) ramdisk_execute _command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
init_post(); // 위의 소스와 같이 초기화 작업을 진행
return 0;
}





int main(int argc, char **argv)
{
//중략

if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);

// 중략
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);

mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);








open_devnull_stdio();
log_init();

INFO("reading config filen");
init_parse_config_file("/init.rc");

/* pull the kernel commandline and ramdisk properties file in */
import_kernel_cmdline(0);

get_hardware_name(hardware, &revision);
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);






//중략
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.PERCEPTIBLE_APP_ADJ 2
setprop ro.HEAVY_WEIGHT_APP_ADJ 3

//중략
service servicemanager /system/bin/servicemanager
user system
critical
onrestart restart zygote
onrestart restart media





for(;;) {
int nr, i, timeout = -1;

execute_one_command();
restart_processes();

if (!property_set_fd_init && get_property_set_fd() > 0) {
// 중략

if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

if (!action_queue_empty() || cur_action)
timeout = 0;

// 중략
}




[그림 1] Raspberry Pi 모듈 그림


[그림 2] 리눅스 커널의 일반적인 아키텍처 그림



[그림 3] 안드로이드 개발 환경 그림



[그림 4] 리눅스 부팅 절차



[그림 5] NFS(Network File System)을 이용하여 개발 시 환경 구성


/필/자/소/개/

필자

라영호

국내 스마트폰의 초창기인 Cellvic에서부터 스마트폰을 개발하였고 윈도 모바일 및 다양한 임베디드 시스템을 개발하고 있다. 현재는 안드로이드 시스템 포팅 및 임베디드 시스템 개발, 컨설팅, 교육 등을 진행하는 회사를 운영하고 있다. 개인 블로그(www.embeddedce.com)를 통해 임베디드 시스템개발에 대한 다양한 생각과 방법론을 함께 생각해 보고자 노력 중이다.

※ 본 내용은 (주)테크월드(http://www.embeddedworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
    Copyright ⓒ Techworld, Inc. 무단전재 및 재배포 금지

맨 위로
맨 위로