= 임베디드 리눅스 실전 가이드 = -- [김도집] [[DateTime(2005-08-24T02:41:25)]] [[TableOfContents]] == 개요 == == 저작권 == 본 문서에 대한 저작권은 김도집에 있으며, 자유롭게 배포가 가능하다. 단, 출처는 명시해야 한다. == 개발 도구 == === uclibc를 이용한 toolchain 만들기 === [http://www.uclibc.org]를 참고한다. uClibc와 관련된 사이트는 다음과 같다. * NPTL Support for uClibc [http://www.realitydiluted.com/nptl-uclibc/] === kegel의 toolchains 만들기 === kegel의 스크립트를 이용하여 툴체인을 만드는 방법은 다음과 같다: 1. crosstool을 다운 받는다. wget [http://kegel.com/crosstool/crosstool-0.38.tar.gz] 1. 압축을 푼다. {{{tar zxvf crosstool-0.38.tar.gz}}} 1. {{{demo-arm-softfloat.sh}}}을 수정한다. gcc-3.3.3 줄의 주석처리를 없앤다. 1. {{{arm-softfloat.dat}}}을 수정한다. {{{Set TARGET=arm-linux}}} 1. 빌드 디렉토리를 만든다. {{{sudo mkdir /opt; sudo chown $USER /opt}}} 1. {{{unset LD_LIBRARY_PATH}}}를 실행한다. 1. tool을 실행한다. {{{sh demo-arm-softfloat.sh}}} 1. 빌드 될 동안 커피 한잔 마시면서 기다리면 된다. 만약, 빌드 할 디렉토리를 변경하고자 한다면 {{{demo-arm-softfloat.sh}}}를 수정하고 빌드 디렉토리를 만들 때 수정된 디렉토리로 만들면 된다. 혹, 다음과 같은 에러가 난다면 {{{ atic hello.c -o arm-linux-hello-static hello.c: In function `main': hello.c:4: error: `PATH_MAX' undeclared (first use in this function) hello.c:4: error: (Each undeclared identifier is reported only once hello.c:4: error: for each function it appears in.) }}} testhello.sh에서 ''#include ''의 아래에 ''#include ''를 추가한다. === 라이브러리 === ==== zlib ==== zlib은 널리 사용되는 압축 라이브러리이다. zlib 1.2.2 버전 이하에서는 보안 문제가 있으므로 반드시 최신 버전을 사용하기 권한다. zlib는 다음의 사이트에서 구할 수 있다: [http://www.zlib.net] ===== 빌드 하기 ===== 다음은 zlibc 1.2.3의 빌드 과정이다. 1. ./configure 또는 ./configure --shared 를 실행한다. --shared 옵션은 zlib 라이브러리를 동적으로 만들라는 의미이다. {{{ ./configure --shared --prefix=/usr/local/arm/arm-uclibc-3.3.5 }}} 1. Makefile 을 다음과 같이 수정한다: {{{ CROSS=arm-linux- CC=$(CROSS)gcc LDSHARD=$(CROSS)gcc -shared -Wl,-soname,libz.so.1 CPP=$(CROSS)gcc -E AR=$(CROSS)ar rc RANLIB=$(CROSS)ranlib }}} 1. make install 를 실행하여 빌드된 것을 설치한다. === 사용자 정의 라이브러리 === 사용자가 직접 라이브러리를 만들어 사용할 수 있다. 이럴 때는 라이브러리를 만들어 사용하는 것이 좋다: * 소스 코드를 공개하고 싶지 않을 때 * 동일한 코드를 여러 프로그램에서 반복적으로 사용할 때 * 업데이트를 용이하게 하고자 할 때 라이브러리는 두 가지 타입이 있다. 정적 라이브러리와 동적 라이브러리이다. 정적 라이브러리를 이용하는 경우엔 이 라이브러리를 사용하는 실행 파일에 라이브러리가 포함이 된다. 반면에, 동적 라이브러리를 이용하는 경우엔 실행 파일에 라이브러리가 포함되지 않고 동적으로 라이브러리를 참조하여 사용하게 된다. 즉, 여러 프로그램에서 동일한 라이브러리를 반복적으로 사용하는 경우엔 동적 라이브러리로 생성하여 사용하는 것이 좋다. 그외에도 라이선스 문제도 있는데, 정적 라이브러리의 경우 실행 파일에 라이브러리 자체가 포함되므로 GPL 라이선스의 영향을 받지만, 동적 라이브러리의 경우엔 이러한 문제를 피해갈 수도 있다. ==== 정적 라이브러리 만들기 ==== 정적 라이브러리는 object 파일들을 하나의 파일로 만든 것이라고 보면 된다. ar 이라는 명령을 이용하여 만든다. {{{ ar rcs 정적라이브러리명.a 파일1.o 파일2.o 파일3.o ... }}} 예를 들어 파일1.o, 파일2.o를 libmy.a라는 정적 라이브러리로 만든다면 다음과 같이 만든다: {{{ ar rcs libmy.a 파일1.o 파일2.o }}} ==== 동적 라이브러리 만들기 ==== 동적 라이브러리를 만드는 방법은 다음과 같다: {{{ gcc -shared -Wl,-soname,your_soname \ -o libarary_name file_list library_list }}} 예를 들어 파일1.c, 파일2.c를 libmy.so.1이라는 동적 라이브러리로 만든다면 다음과 같다: {{{ gcc -fPIC -g -c -Wall 파일1.c gcc -fPIC -g -c -Wall 파일2.c gcc -shared -Wl,-soname,libmy.so.1 \ -o libmy.so.1.0.1 파일1.o 파일2.o -lc }}} ==== 라이브러리 사용하기 ==== 사용자가 만든 라이브러리는 사용자 임의의 디렉토리에 위치한다. 따라서 표준 라이브러리 경로에서 해당 라이브러리를 찾지 못해, 라이브러리를 이용하여 컴파일 하고자 할 때 라이브러리를 찾을 수 없다는 에러를 보게 될 것이다. 컴파일러를 이용하여 컴파일 할 때는 -L과 -l를 지정하여 라이브러리를 경로를 지정해 줄 수 있다: ||-L{경로}||라이브러리를 찾을 경로를 추가해 준다|| ||-l{lib를뺀라이브러리이름}||라이브러리 이름|| 예를 들어 /home/foo/bar/라는 디렉토리 아래에 libmy.a가 있다면 다음과 같이 컴파일 옵션을 줄 수 있다. {{{ gcc -Wall -O2 -o hello hello.c -L/home/foo/bar -lmy }}} 동적 라이브러리의 경우엔 mylib.so.1.0.1로 만들었다면 다음과 같이 심볼릭 링크를 만들어주어야 한다. {{{ ln -s mylib.so.1.0.1 mylib.so.1 ln -s mylib.so.1 mylib.so }}} libmy.so를 찾을 수 없다면 경로를 명시해도 라이브러리를 찾지 못할 것이다. 동적 라이브러리를 이용하여 컴파일 경우엔 실행 파일을 실행 시에 라이브러리를 필요로 한다. LD_LIBRARY_PATH라는 환경 변수에 라이브러리 경로를 추가하거나 다음과 같이 임시로 지정할 수도 있다: {{{ LD_LIBRARY_PATH={라이브러리경로}:${LD_LIBRARY_PATH} 실행파일 }}}=== Make/Makefile === 빌드시에 일괄 처리를 위한 목적으로 make 를 사용한다. 반복적인 작업을 Makefile에 명시된 절차에 따라 처리한다. make를 대체하는 도구로는 SCons라는 것도 있다. 개인적인 소견으로는 make보다는 SCons가 더 사용하기 편리하다. === SCons === Software construction tool이다. 자세한 것은 [http://www.scons.org]를 참고하라. [wiki:/scons 좀 더 자세히 둘러보기] === vim === === ctags === vim에서는 tags를 지원한다. ctags를 통해 만들어진 tag 파일을 참조하여 소스 내에서 쉽게 탐색을 할 수 있다. tags 파일의 생성은 다음가 같이 한다. {{{ find ./ -name "*.[chS]" | ctags -L- find ./ -name "*.cc" | ctags -a -L- }}} 그외 vim 관련 명령으로는 :ts, :ta, :set tags=tags,../tags 등이 있다. === gdb === === Doxygen === [http://www.pie.pe.kr/cgi-bin/moin.cgi/Doxygen] === 테스트 === * Linux Test Project [http://ltp.sourceforge.net/] * Open POSIX Test Suite [http://posixtest.sourceforge.net/] == 부트로더 == == 커널 == === 모듈 프로그래밍 === ==== Makefile 만들기 ==== 커널 2.6에서는 외부 모듈의 빌드를 커널 소스 내의 빌드 시스템을 이용한다. 예를 들어 foobar.ko 라는 커널 모듈을 만든다고 하면 다음과 같이 Makefile 파일을 만든다. {{{#!vim make ifneq ($(KERNELRELEASE),) #call from kernel build system foobar-objs := foo_1.o foo_2.o obj-m := foobar.o else KERNELDIR ?= /YOUR/KERNEL/SOURCE/PATH PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf *.o *~ core .depend .*.cmd .*.o.d *.ko *.mod.c .tmp_versions }}} /!\ Makefile에서 실행 명령 행 첫 공백은 '''TAB'''으로 띄워야 한다. 공백 문자가 아니다 만약, 컴파일 하는 시스템에서 사용하는 커널 버전으로 컴파일을 하고자 한다면 다음과 같이 Makefile을 작성해도 된다. {{{#!vim make obj-m := foobar.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko *.mod.* .*.cmd *.o }}} ==== 간한단 모듈 예 ==== 다음은 hello module을 출력하는 간단한 모듈 예이다. {{{#!vim c #include #include #include static int __init hello_init(void) { printk("hello module\n"); return 0; } static void __exit hello_exit(void) { } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); }}} === 프로세스 === 관련 함수는 에 정의되어 있다. pid를 갖는 프로세스(태스크)의 디스크립터를 가져오고자 할 때는 '''find_task_by_pid()'''함수를 사용한다. 사용 예는 다음과 같다: {{{#!vim c struct task_struct *p; read_lock(&tasklist_lock); p = find_task_by_pid(pid); read_unlock(&tasklist_lock); }}} === 비동기 === ==== 시그널 ==== 커널에서 호출한 프로세스로 시그널을 전달할 수 있다. 시그널 호출 시 전달되는 데이터는 siginfo 구조체로 정의되어 있다. 다음은 include/asm-generic/siginfo.h에 정의되어 있는 내용 중 일부이다. {{{#!vim c typedef struct siginfo { int si_signo; int si_errno; int si_code; union { ... } _sifields; } siginfo_t; #define si_pid _sifields._kill._pid ... #define si_int _sifields._rt._sigval.sival_int ... }}} 사용할 수 있는 함수들은 다음과 같다: ||recalc_sigpendign|| || ||dequeue_signal|| || ||flush_signals|| || ||force_sig|| || ||kill_pg|| || ||kill_proc||호출한 프로세스(pid)에 signal을 보낸다|| ||ptrace_notify|| || ||send_sig|| || ||send_sig_info||siginfo의 내용을 보내려면 이 함수를 이용한다|| ||sigprocmask|| || ||block_all_signals|| || ||unblock_all_signals|| || 시그널 함수 중 kill_proc()함수는 다음과 같이 이용할 수 있다. {{{#!vim c kill_proc(cpid, SIGINT, 1); }}} 프로세스(cpid)에 SIGUSR2 시그널을 보낸다. 보내는 것에 실패한다면 SIGINT를 보내게 된다. '''send_sig_info()'''를 이용하는 경우는 다음과 같다: {{{#!vim c struct siginfo info; info.si_signo = SIGUSR2; info.si_errno = 0; info.si_code = SI_QUEUE; info.si_int = flags; if (send_sig_info(SIGUSR2, &info, p)) send_sig_info(SIGINT, (struct siginfo *)1, p); }}} 여기서 p는 struct task_struct형의 포인터이다. === 메모리 === ==== IO 메모리 ==== IO 메모리에 관련된 인터페이스는 에 정의되어 있다. IO를 위한 메모리를 할당은 다음과 같이 한다. {{{#!vim c char *name = "MyIO"; if (!request_mem_region(phys, size, name)) return -EBUSY; virt = ioremap(phys, size); }}} * phys : 물리 메모리 시작 번지 * size : IO 메모리로 할당할 메모리의 크기 할당 받은 IO 메모리를 해제하는 경우는 다음과 같다. {{{#!vim c release_mem_region(phys, size); iounmap((void *)virt); }}} * virt : 해제할 가상 메모리 시작 번지 == 응용 프로그램 == === Makefile (skelecton) === 다음은 Makefile을 작성할 때 참조할 만한 간단한 예제이다: {{{ #CROSS := arm-linux- CC := $(CROSS)gcc LIBS := -lpthread INCDIR := -I./ CFLAGS = -Wall -O2 $(INCDIR) BIN := dj_run2 OBJS := dj_event2.o dj_run2.o SRCS := $(OBJS:.o=.c) all: $(BIN) $(BIN): $(OBJS) $(CC) $(CFLAGS) $(LIBS) -o $@ $^ %.o:%.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o $(BIN) tags: find ./ -name "*.[ch]" | ctags -L- dep: gccmakedep -- $(CFLAGS) -- $(SRCS) }}} 다음은 여러 디렉토리의 Makefile이 있을 때 상위 디렉토리에서 일괄적으로 make할 때 사용하는 경우이다: {{{ DIRS= drv src lib test all: for i in $(DIRS) ; do make -C $$i || exit $? ; done clean: for i in $(DIRS) ; do make -C $$i clean ; done dep: for i in $(DIRS) ; do make -C $$i dep ; done }}} === 프로세스 === === 시그널 === 간단하게 다음의 함수를 이용하여 시그널을 다룰 수 있다. {{{#!vim c #include void (*signal(int sig, void (*func)(int)))(int); }}} /!\ signal 함수는 시그널 핸들러가 수행된 후 기본 상태로 복구되어, 등록한 시그널 핸들러를 반복적으로 처리하고자 할 때는 적합하지 않다. 안정적인 시그널 인터페이스는 다음을 사용한다: {{{#!vim c #include int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); }}} sigaction 구조체는 최소한 다음의 멤버를 갖는다: {{{#!vim c void (*) (int) sa_handler sigset_t sa_mask int sa_flags }}} 여기서 sa_handler는 함수가 올 수도 있고 SIG_DFL 또는 SIG_IGN이 올 수도 있다. == 참고 == * [wiki:임베디드리눅스레퍼런스 임베디드 리눅스 레퍼런스]