모듈구현하기
지난 호에 우리는 리눅스 커널의 관한 기본적인 내용들과 함께 새로운 커널을 컴파일하기 위한 방법을 배웠다. 이번 호에는 지난 호에 이어 우리가 새롭게 컴파일한 커널에 일부가 되 어 동작할 수 있는 모듈 프로그래밍에 관해 알아보기로 한다. 본 강좌는 지난 호에서 새롭게 컴파일한 커널로 부팅하여 동작하고 있다는 가정 하에 진행하기로 한다.
글 _ 김민찬 KLDP 멤버, 전문 프로그래머
연재 순서
① 커널 프로그래밍의 환경 구축
② 모듈 구현하기
③ 커널의 동기화에 관하여
④ 커널의 시간관리 및 지연 함수에 대하여
⑤ 파일시스템과 proc file system 사용하기
⑥ 디버깅 기술에 관하여
1. 커널 모듈이란 ?
모듈이란 동적으로 커널 속으로 로드 또는 언로드 될 수 있는 코드의 묶음을 말한다. 이렇게 해서 얻을 수 있는 장점은 시스템을 재부팅하지 않고도 필요할 때 필요한 기능을 동적으로 커널에 추가할 수 있다는 점이다. 이런 것들의 가장 흔한 예는 장치 드라이버나 파일 시스템들이 될 수 있다. 만일모듈이 없다면 이런 장치들을 위한 코드나 자주 사용하지 않는 파일 시스템을 위한 코드들을 커널을 빌드할 때 이미 포함한 상태로 컴파일을 해야 하므로 커널의 크기가 점점 커지게 되며 메모리 사용량 또한 증가하게 된다. 더욱아찔한 것은 미처 생각치 못한 기능을 넣지 못했다면 필요할 때 마다 커널을 다시 빌드해야 한다는 점이다. 여러분은 앞으로 어떤 장치를 새로 사용하게 될지 미리 예측할 수 있는가?
자, 현재 여러분의 시스템에는 어떤 모듈들이 사용되고 있을까? 이를 알아보는 명령어는 다음과 같다.
lsmod
이 명령어는 /proc/modules을 읽어들어 출력하게 된다. 그렇다면 여러분들은 언제 이 모듈들을 로드 했는지 기억이 나는가? 커널 모듈은 kmod라는 데몬에 의해 커널이 필요로 할 때 자동적으로 로드되게 된다. 이 일을 담당하는 kmod라는 데몬이 커널 내에 존재하게 되며 커널이 자신이 갖고 있지않은 새로운 기능을 필요로 할 때 modprobe라는 어플리케이션을 실행시켜 필요한 모듈을 로드하게 되는 것이다. modprobe는 필요한 모듈을 찾기 위해서 /etc/modprebe.conf 설정 파일을 먼저 찾게 되며 필요한 모듈을 로드하기 전에 어떤 다른 모듈을 먼저 로드해야 하는지를 살피기 위하여 /lib/modules/version/modules.dep 파일을 살피게 된다. 이 파일은 다음 명령어를 통해 만들 수 있다.
depmod -a
modprobe는 모듈간의 의존성을파악한 후 마지막으로 insmod라는 어플리케이션을 실행하여 커널에 모듈을 로드하게 된다. 이때 로드되는 모듈들은 /lib/modules/version/ 디렉토리에서 찾게 된다. 지금까지 본 것처럼 insmod는 마지막에 실행되는 어플리케이션으로써 단지 module을 로드하는 일만 하지만 modprobe는 insmod를 실행시킬 뿐만 아니라 모듈들의 위치, 상호 의존성 체크까지 다 알아서 해준다. 그러므로 모듈을 로드할 때 insmod를 사용하는 것보다는modprobe를 사용하는 것이 편리할 때가 많다. 하지만 명령어를 사용할 때 주의해야 할 것이 modprobe는 아래와같이 확장자를 취하지 않는 다는 점이다. 또한 modprobe를 사용하기위해서는 위에서 설명한 설정 파일들에 적절하게 명시되어 있어야 한다.
insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko
insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko
modprobe msdos
modprobe, insmod, depmod는 module-init-tools라는 패키지로 각 배포판마다 제공된다.
2. 모듈 작성하기
자, 이제 우리는 새로운 모듈을 작성할 준비가 다 되었다. 지금부터 여느 프로그램을 배울 때와 마찬가지로 이번에도 helloworld 모듈을 작성할 것이다. 가장 기본적인 모듈 프로그램을 통해 각 함수들의 역할과 기능에 대해 알아보도록 한다.
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h>/* Needed for KERN_EMERG */
#include <linux/init.h> /* Needed for the macros */
static int __init hello_init(void)
{
printk(KERN_ EMERG"Hello, world ");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG "Goodbye, world ");
}
module_init(hello_init);
module_exit(hello_exit);
커널 모듈들은 적어도 두 가지의 함수는 반드시 가지고 있어야 한다. 그 함수들은 module_init과 module_exit의 매크로로 등록된 함수들이다.
module_init 매크로로 등록된 함수는 insmod 명령을 통해 모듈이 커널에 등록될 때 호출되는 함수이며 반대로,module_exit는 rmmod 명령을 통해 커널에서 언로드될 때 호출되는 함수이다. 각 함수들의 선언에 붙여진 매크로는 다음과 같은 의미를 갖는다.
● __init 매크로는 커널로 하여금 init 함수가 사용된 후 그 함수가 차지했던 메모리 공간을 회수할 수 있도록 하기위한 방법이다. 하지만 이 매크로는 모듈로 만들어진 커널드라이버들에게는 적용되지 않고 built-in 드라이버들에게만 적용된다. 이렇게 하는 이유는 커널의 입장에서 built-in 드라이버들의 init 함수들은 한번 실행되고 나며 더 이상 사용되지 않을것이기 때문에 그 코드들이 차지하는 메모리 공간을 회수하여 최대한 많은 메모리를 확보할 수 있기 때문이다.
● __exit 매크로는 커널의 built-in 드라이버들을 만들어 하나의 커널 이미지를 빌드할 때 그 함수들을 커널의 이미지에서 뺄 수 있도록 하기 위한 방법이다.그렇게 하는 이유는 built-in 드라이버들은 커널이 살아있는 동안 메모리에서 언로드되지 않기 때문에 _exit 함수가 전혀 호출되지 않는다. 그러므로 굳이 실행되지 않는 함수를 커널의 이미지에 포함시켜 커널의 사이즈를 크게 만들필요가 없기 때문이다.
마지막으로 printk 함수는 printf와 같이 단순히 console에 출력을 하기 위한 함수와는 다르다. 이 함수는 커널의 여러정보들을 기록하는 데 사용된다. printk함수는 8개의 우선순위로 이루어진 매크로들을 사용하여 출력할 수 있으며 각 우선 순위마다 아래의 표와 같은 의미를 가지고 있다. 커널의 많은함수들은 상황에 맞는 우선순위를 사용하여 정보들을 로깅하고있다.
여러분이 어떤 매크로도 명시하지 않는다면 default priority인 DEFAULT_MESSAGE_LOGLEVEL을 사용하게 될 것이며 이 정보는 kernel/printk.c에 다음과 같이 정의되어 있다.
/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
우선순위가 console_loglevel 값보다 커야만(즉, 숫자로는 작은 수) 메시지가 터미널을 통해 화면에 출력될 수 있다.console_loglevel 값은 필자의 시스템에는 다음과 같이 정의되어 있다.
#define console_loglevel (console_printk[0])
int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,
/* console_loglevel */
DEFAULT_MESSAGE_LOGLEVEL,
/* default_message_loglevel */
MINIMUM_CONSOLE_LOGLEVEL,
/* minimum_console_loglevel */
DEFAULT_CONSOLE_LOGLEVEL,
/* default_console_loglevel */
};
#define DEFAULT_CONSOLE_LOGLEVEL 7 /
* anything MORE serious than KERN_DEBUG */
우리는 이 예제에서 제일 높은 우선순위를 갖는 KERN_EMERG를 사용하였다. 왜냐하면 default priority의 메시지는여러분이사용하고 있는 커널 버전과 klogd의 버전, 여러분 커널의 설정, 또는 터미널을 사용하느냐, X를 사용하고 있느냐에 따라 화면에 출력되지 않고 /var/log/messages에만 로깅될 수 있기 때문이다. 그럴 경우는 dmesg 명령을 사용하거나 아니면 직접 /var/log/messages 파일을 열어 읽을 수 있다.
3. 모듈 컴파일
이번에는 모듈을 컴파일 하는 방법을 알아보자. 커널 모듈을 컴파일하는 방법은 일반적인 응용 프로그램의 컴파일방식과는 많이 다르다. 2.6이전에는 모듈을 컴파일하기 위한 과정들이 다소 복잡하였지만 2.6으로 오면서 kbuild라는 툴을 통해 상당히 간결해졌다(kbuild와 커널의 Makefile에 관한 보다 자세한 사항은 커널 소스의 Documentation/kbuild 디렉토리를 참고하라). hello world를 컴파일 하기 위한 Makefile은 다음과 같다.
obj-m += hello_world.o
all:
make -C /lib/modules/$(shell uname -r)/buildM=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/buildM=$(PWD) clean
위와 같이 모듈로 빌드할 파일의 이름을 .o 확장자를 이용하여 obj-m에 지정해주면 된다.
또한 여러 파일로 이루어진 소스를 모듈로 빌드하는 경우는 다음과 같이 하면 된다.
obj-m += hello_world.o
hellow_world-objs += hello_world1.o hello_world2.o
hello_world3.o
all:
make -C /lib/modules/$(shell uname -r)/build
M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build
M=$(PWD) clean
나머지 all과 clean의 문법은 uname r을 이용하여 현재 커널버전을 파싱하고 그에 맞는 /lib/modules/your-kernelversion/build 디렉토리를 기준으로 모듈을 빌드하는 것이다.
만일 여러분의 시스템에 동작하고 있는 커널과 모듈을 컴파일할 커널의 버전이 서로 다르다면 다음과 같이 직접컴파일 할커널 패스를 지정해 주면 된다.
obj-m += hello_world.o
KDIR := /usr/src/your-linux-source
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
우리의 Makefile을 통해 빌드하게 되면 다음과 같은 메시지를 볼 수 있을 것이다.
root@barrios-desktop:~/example# make
make -C /lib/modules/2.6.18-barrios/build
M=/root/example modules
make[1]: Entering directory `/usr/src/linux-2.6.18'
CC [M] /root/example/hello_world.o
Building modules, stage 2.
MODPOST
CC /root/example/hello_world.mod.o
LD [M] /root/example/hello_world.ko
make[1]: Leaving directory `/usr/src/linux-2.6.18
모듈을 컴파일 중 특별한 내용은 MODPOST라는 과정이다. 이것은 module을 위해 만들어진 object 파일에.modinfo라는 특별한 ELF section을 추가하는 과정이다. 이 내용은 다음의 명령을 통해 확인해 볼 수 있다.
root@barrios-desktop:~/example# modinfo
hello_world.ko
filename: hello_world.ko
vermagic: 2.6.18 SMP mod_unload 586 REGPARM gcc-4.1
depends:
srcversion: 0F8BB5095DEA8A557DD6317
출력된 정보는 지금까지는 별 내용이 없다. 하지만 다음절에서 파라미터와 저자, 라이선스를 추가하면 더 많은 정보들이 출력되는 것을 볼 수 있을 것이다. 자, 이제 모듈을 커널의 일부가 되도록 로드해보자. 아래의 명령을 통해 만들어진 모듈을 로드, 언로드할 수 있다.
insmod barrios_world.ko
rmmod barrios_world
위의 명령을 실행하게 되면 예상했던 것처럼 hello_init과 hello_exit가 수행되며 화면에 로그가 출력되는 것을 볼 수있을 것이다.
만일 여러분의 printk의 우선순위를 KERN_EMERG 보다 작은 것으로 소스를 수정했다면 화면에 출력되지 않을 수있다. 그것은 여러분들이 X를 사용하고 있기 때문에 그럴 수도 있으며 /proc/sys/kernel/printk의 설정의 console 로그 레벨이 여러분이 설정한 값보다 높기 때문에 그럴 수도 있다. 그럴 경우 dmesg를 실행하면 출력된 메시지를 확인 할 수 있다.
4. 모듈 파라미터 처리
모듈은 command line에서 인자들을 받을 수 있다. 인자를 모듈로 넘기기 위해서는 module_param() 매크로를 사용하여 변 수 를 선 언 하 여 야 한 다 . 예 를 들 어 “ insmod hello_world_parm.ko myarg1=1”같이 int형의 하나의 인자를 넘기기 위해서는 커널의 모듈 코드에 다음과 같이 선언해야 한다.
int myarg1 = 0
module_param(myarg1, int, 0);
module_parm 매크로는 변수의 이름, 타입, sysfs의 관련된 파일의 permission 이렇게 3개의 인자를 갖는다. 인자로배열을 넘기는 방법은 약간 다르다. 먼저 매크로는 module_param_array를 사용하며 4개의 인자를 갖는다. 3번째 인자로 배열의 갯수를 받을 수 있는 변수를 추가되었다.
int myarg2 [4];
int count;
module_parm_array(myarg2, int, &count , 0);
인자의 갯수를 특별히 받고 싶지 않다면 단순히 NULL을 사용하면 된다.
다음, 모듈의 파라미터를 설명하기 위한 MODULE_PARM_DESC() 매크로가 있다. 이것은 모듈을 사용하는 사람을위해서 인자를 설명하기 위한 방법이다. 이것은 변수의 이름과 그 변수를 설명하는 문자열을 받게 된다.
MODULE_PARM_DESC(myarg2, "This is int array forI/O port data");
위의 모든 것을 다 포함해서 hello_world2를 만들어 보자.
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("you");
static int argint = 1;
static char *argstring = "This is a string";
static int argarray[2] = { 0, };
static int size_argarray= 0;
module_param(argint, int, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(argint, "A short integer");
module_param(argstring, charp, 0000);
MODULE_PARM_DESC(argstring, "A character string");
module_param_array(argarray, int, &size_argarray,
0000);
MODULE_PARM_DESC(argarray, "An array of
integers");
static int __init hello_init(void)
{
int i;
printk(KERN_EMERG "argint : %d ", argint);
printk(KERN_EMERG "argstring : %s ", argstring);
for (i = 0; i < (sizeof (argarray) / sizeof (int)); i++)
{
printk(KERN_INFO "argarray [%d] : %d ", i,
argarray [i]);
}
printk(KERN_INFO "argarrary size : %d ",size_argarray);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG "Goodbye, world ");
}
module_init(hello_init);
module_exit(hello_exit);
위의 코드를 컴파일 한 후 실행하기 전에 modinfo를 통해 모듈의 정보를 확인해보자.
root@barrios-desktop:~/example/module2# modinfo
hello_world.ko
filename: hello_world.ko
license: GPL
author: you
vermagic: 2.6.18 SMP mod_unload 586 REGPARM gcc-4.1
depends:
srcversion: 300231DCA83E809D0F54644
parm: argarray:An array of integers (array of int)
parm: argstring:A character string (charp)
parm: argint:A short integer (int)
위와 같이 이전에 비하여 많은 정보들이 출력되는 것을 볼 수 있다. 이 중에는 여러분들이 파라미터를 설명하는 문자열도 출력된다. 이번에는 모듈을 로드해보자. 무엇이 출력되는가?
root@barrios-desktop:~/example/module2# insmod
hello_world.ko argint=100 argstring="hey"
argarray=100,200
여러분들의 예상이 맞는가?
강좌를 마치며
이번 호에서는 리눅스 커널의 모듈에 동작과정을 간략히 살펴보고 기본적인 모듈을 만들어보고 컴파일하여 실행해보았다.그리고 마지막으로 모듈에게 파라미터를 넘기는 방법에 관하여 배워보았다. 지난 호에 배웠던 커널 컴파일만 제대로 되어있었다면 따라하는 것에 별 무리가 없었을 것이라고 생각한다. 다음 호에서는 모듈을 움직여 볼 수 있는 인터페이스를 만들어 볼 것이다
출처 : 공개 SW 리포트 8호 페이지 54 ~ 59 발췌(2007년 8월) - 한국소프트웨어 진흥원 공개SW사업팀 발간
'L I N U X' 카테고리의 다른 글
Step by Step 커널 프로그래밍 강좌⑥ (0) | 2012.02.08 |
---|---|
Step by Step 커널 프로그래밍 강좌⑤ (0) | 2012.02.08 |
Step by Step 커널 프로그래밍 강좌④ (0) | 2012.02.08 |
Step by Step 커널 프로그래밍 강좌③ (0) | 2012.02.08 |
Step By Step 커널 프로그래밍 강좌 ① 커널프로그래밍의환 (0) | 2012.02.08 |