Environment: Linux Kernel v2.6.14.6
LKM (Loadable Kernel Module).
Module programming을 하기 위해 필요한 기본 지식과 만드는 법.
1. callee, caller 모듈 작성
callee.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
// module init
int __init init_callee(void)
{
return 0;
}
// module exit
void __exit exit_callee(void)
{
}
int add(int a, int b)
{
printk(KERN_ALERT "[callee Module] add called...\n");
return a + b;
}
int sub(int a, int b)
{
printk(KERN_ALERT "[callee Module] sub called...\n");
return a - b;
}
EXPORT_SYMBOL(add);
EXPORT_SYMBOL(sub);
module_init(init_callee);
module_exit(exit_callee);
MODULE_LICENSE("GPL");
caller.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int add(int, int);
int sub(int, int);
int __init init_caller(void)
{
printk(KERN_ALERT "[caller Module] I'll call add(), sub() from caller.\n");
printk(KERN_ALERT "[caller Module] add: %d\n", add(3,2));
printk(KERN_ALERT "[caller Module] sub: %d\n", sub(3,2));
return 0;
}
void __exit exit_caller(void)
{
}
module_init(init_caller);
module_exit(exit_caller);
MODULE_LICENSE("GPL");
- linux/init.h
- __init : 초기화 때 사용할 코드 섹션을 지정
- __exit : 정리할 때 사용할 코드 섹션을 지정 - linux/kernel.h
- printk() - linux/module.h
- module_init() : 모듈 시작 함수를 지정하는 함수
- module_exit() : 모듈 종료 함수를 지정하는 함수
- EXPORT_SYMBOL() : 2.6부터 static을 제외하고 나머지 함수들이 커널 심볼로 등록되어 노출되도록 만듦. 등록하지 않으면 보이지 않음. 즉, 참조할 때 에러 발생함.
- MODULE_LICENSE() : 모듈의 라이선스를 지정. 2.6 커널부터는 꼭 지정하게 만듦.
2. 모듈의 상호 참조
모듈 빌드용 Makefile 작성 및 모듈 로드, 제거
Official Documentation Link: https://www.kernel.org/doc/html/latest/kbuild/index.html
Other Reference: https://injunech.tistory.com/98
obj-m += callee.o caller.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
다음과 같이 작성한 후에 "make" 명령어를 통해서 빌드해준다.
- -C : change directory. 주어진 경로로 디렉터리를 변경.
- M= : Kbuild에게 외부 모듈이 빌드되고 있다는 것을 알려줌. SUBDIRS= 의 backward-compatibility이기도 함.
- modules : make modules 할 때 그 modules.
$ insmod callee.ko
$ insmod caller.ko
$ dmesg | tail
insmod 명령어로 각 모듈을 순서대로 삽입해주고 dmesg 명령어로 printk로 나온 로그를 확인해준다.
정상적으로 잘 나온 것을 확인할 수 있고 심볼 리스트를 확인해보자.
이런 식으로 등록된 모듈의 심볼 정보를 알 수 있다.
모듈을 제거할 때는 등록한 반대로 해야 한다. 그렇지 않으면 다음과 같은 오류를 볼 수 있다.
그렇기 때문에 의존성을 이용해서 설치를 하면 편할 것 같다는 생각이 든다. 이를 위한 명령어도 당연히 존재한다. 바로 "modprobe" 명령어다. 먼저 /lib/modules/`uname -r`/kernel 밑에다가 만든 모듈 위치시키고 "depmod" 명령어로 모듈 간 의존성을 정의하는 파일인 /lib/modules/`uname -r`/modules.dep을 만든다. 이후 "modprobe caller" 식으로 로드해주면 의존성 관계에 따라 자동으로 로드시켜준다.
의존성에 따라 제거해줄 때는 "modprobe -r <modules>"로 해주면 참조에 안 쓰이는 것까지 다 제거해준다.
3. 모듈과 매개변수
모듈에 값을 입력해서 테스트 해보고 싶은 경우 일일이 수정해서 컴파일하면 귀찮을 것이다. 그래서 모듈에 매개변수를 전달하여 실행할 수 있도록 한다.
param.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h> // MODULE_PARM macro function is here
int a;
char *str;
MODULE_PARM(a, "i");
MODULE_PARM(str, "charp");
// init routine
int __init init_param(void)
{
printk(KERN_ALERT "[Module Message] parameter a = %d\n", a);
printk(KERN_ALERT "[Moduel Message] parameter str = %s\n", str);
return 0;
}
// exit routine
void __exit exit_param(void)
{
}
module_init(init_param);
module_exit(exit_param);
MODULE_LICENSE("GPL");
MODULE_PARM(a, "i) 부분이 핵심인데 전역 변수로 설정한 정수형 변수 a와 그 타입(i; integer)을 알려주고 있다. 매개변수 전달할 때는 insmod 할 때 지정해준 변수명으로 다음과 같이 전달해주면 된다.
$ insmod param.ko a=100
$ insmod param.ko a=333 str=Karatus
전달 방식의 변화
2.6.17 버전부터는 MODULE_PARM 방식으로 매개변수를 정의하는 것이 폐지되었다. 대신에 moduleparam.h 헤더 파일 안에 있는 매크로 함수들로 바뀌었다. 이번에는 바뀐 방식으로 이전에 소개한 코드를 바꾸고 배열을 전달하는 매개변수도 추가해 볼 것이다.
param.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
int a;
int b[] = { 1, 2, 3, 4, 5 };
char *str;
module_param(a, int, 0);
module_param(str, charp, 0);
module_param_array(b, int, NULL, 0);
// init routine
int __init init_param(void)
{
printk(KERN_ALERT "[Module Message] parameter a = %d\n", a);
printk(KERN_ALERT "[Module Message] parameter str = %s\n", str);
printk(KERN_ALERT "[Module Message] parameter array b = %d %d %d %d %d\n", \
b[0], b[1], b[2], b[3], b[4]);
return 0;
}
// exit routine
void __exit exit_param(void)
{
}
module_init(init_param);
module_exit(exit_param);
MODULE_LICENSE("GPL");
이렇게 바꿔주고 인자를 넘겨줘서 결과를 확인해본다.