본문 바로가기

L I N U X

Step by Step 커널 프로그래밍 강좌②


모듈구현하기

 

 

지난 호에 우리는 리눅스 커널의 관한 기본적인 내용들과 함께 새로운 커널을 컴파일하기 위한 방법을 배웠다이번 호에는 지난 호에 이어 우리가 새롭게 컴파일한 커널에 일부가 되  동작할  있는 모듈 프로그래밍에 관해 알아보기로 한다 강좌는 지난 호에서 새롭게 컴파일한 커널로 부팅하여 동작하고 있다는 가정 하에 진행하기로 한다.

 

 _ 김민찬 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사업팀 발간