[LKM] Module Programming Basic
Kernel/Development

[LKM] Module Programming Basic

반응형
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");

이렇게 바꿔주고 인자를 넘겨줘서 결과를 확인해본다.

 

반응형