본문 바로가기
Kernel

[Linux Kernel] 리눅스 커널 모듈 작성

by hyeyoo 2021. 4. 13.
※ 이 블로그의 글은 글쓴이가 공부하면서 정리하여 쓴 글입니다.
※ 최대한 내용을 검토하면서 글을 쓰지만 틀린 내용이 있을 수 있습니다.
※ 만약 틀린 부분이 있다면 댓글로 알려주세요.

리눅스 커널 모듈이란

리눅스 커널 모듈은, 시스템에 설치된 커널 바이너리와는 다르게 동적으로 기능을 추가할 수 있는 오브젝트 파일이다. 예를 들어 커널에 어떤 기능을 추가하기 위해선 코드를 수정하고, 커널을 컴파일하고 설치한 후에 재부팅을 해야한다. 하지만 모듈로 작성하면 시스템이 실행중인 도중에도 동적으로 기능을 추가하거나 제거할 수 있다. 물론 모듈을 추가하거나 제거하려면 root 권한이 필요하다. 시스템 호출을 구현하거나, 문자/블록 디바이스 드라이버, 파일시스템 구현 등 다양하게 사용될 수 있다.

 

시스템 상에서 모듈의 위치

커널 모듈의 로딩과 언로딩

lsmod로 설치된 모듈을 확인하고, insmod로 모듈을 로드하고, rmmod로 모듈을 언로드할 수 있다. modprobe, depmod 등의 명령도 존재한다. 간단한 차이점을 서술하면 modprobe는 /lib/modules/$(uname -r)/modules.dep에서 모듈과 관련된 의존성을 확인해서 관련된 모듈을 자동으로 로드해주는 반면 insmod는 모듈의 직접적인 경로를 넘겨주어 순서대로 사용자가 로드해야 한다.

lsmod
sudo insmod moduleName.ko
sudo rmmod moduleName

Hello, World!

간단한 Hello, World! 커널 모듈을 작성해보자. 설명은 주석에 적어두었다. (helloworld.c)

#include <linux/module.h> /* necessary for every kernel module */
#include <linux/kernel.h> /* for printk */

/* 모듈을 로드할 때 초기화하는 함수 */
int __init helloworld_init(void)
{
    /* "Hello, World!"를 출력한다. 커널에선 printf가 아니라 printk를 사용한다.
     * KERN_DEBUG는 로그 레벨을 지정하는 매크로로
     * KERN_INFO, KERN_ALERT, KERN_DEBUG 등이 있다.
     * printk로 출력한 내용은 dmesg 명령어로 확인할 수 있다.
     * 자세한 사항은: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html
     */
	printk(KERN_DEBUG "Hello, World!\n");
	return 0;
}

/* 모듈을 언로드할 때 실행되는 함수 */
void __exit helloworld_exit(void)
{
	printk(KERN_DEBUG "HelloWorld - made by hyeyoo\n");
}
/* 모듈의 init 함수를 등록한다 */
module_init(helloworld_init);
/* 모듈의 exit 함수를 등록한다 */
module_exit(helloworld_exit);

/* 모듈에 대한 정보(라이센스, 저자, 설명)를 나타내는 매크로이다. */
MODULE_AUTHOR("Hyeonggon Yoo <42.hyeyoo@gmail.com>");
MODULE_DESCRIPTION("simple Hello, World! module");
MODULE_LICENSE("GPL");

Makefile

NAME = helloworld

obj-m += ${NAME}.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
	rm -f Module.symvers modules.order
	rm -f ${NAME}.o ${NAME}.mod ${NAME}.mod.c ${NAME}.mod.o

fclean: clean
	rm -f ${NAME}.ko

re: fclean all

빌드 및 모듈 로딩

make
sudo insmod helloworld.ko
dmesg

make와 insmod가 성공적으로 실행되었다면 dmesg 명령어를 입력하면 다음 출력문을 볼 수 있다. 서명된 모듈만 로드하는 옵션이 (CONFIG_MODULE_SIG) 활성화된 경우, 다음과 같은 오류가 나타날 수 있다. "module verification failed: signature and/or required key missing - tainting kernel" - 필자의 컴퓨터에서는 언로드하고 다시 로드하면 잘 불러와지는데, 서명이 필요한 경우 다음 글을 참고하자. "How to sign a kernel module"

 

dmesg 결과

EXPORT_SYMBOL과 외부 모듈 참조

우리가 위에서 만든 helloworld.ko 모듈은 독립적인 모듈이다. 이와 다르게 모듈은 서로에게 의존할 수 있다. 예를 들어서 복잡한 모듈을 작성할 때, 라이브러리 역할을 하는 모듈과 이를 참조하는 모듈을 분리해서 작성할 수 있다. 간단하게 adder 모듈을 만들어서 helloworld 모듈에서 adder 모듈을 참조하도록 해보자. 방법은 단순하다. adder 모듈에서 정의한 함수를 EXPORT_SYMBOL로 등록한 후에 helloworld 모듈에서 사용하면 된다. EXPORT_SYMBOL로 등록한 심볼은 cat /proc/kallsysms 명령어로 확인할 수 있다.

Makefile

makefile에는 adder와 helloworld 모두를 추가해준다.

ADDER = adder
NAME = helloworld

obj-m += ${ADDER}.o
obj-m += ${NAME}.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
	rm -f Module.symvers modules.order
	rm -f ${NAME}.o ${NAME}.mod ${NAME}.mod.c ${NAME}.mod.o

fclean: clean
	rm -f ${NAME}.ko

re: fclean all

adder.c

#include <linux/module.h>
#include <linux/kernel.h>

int __init adder_init(void)
{
	printk(KERN_DEBUG "loading adder\n");
	return 0;
}

void __exit adder_exit(void)
{
	printk(KERN_DEBUG "unloading adder\n");
}

int add(int x, int y)
{
	return x + y;
}
EXPORT_SYMBOL(add);

module_init(adder_init);
module_exit(adder_exit);

MODULE_AUTHOR("Hyeonggon Yoo <42.hyeyoo@gmail.com>");
MODULE_DESCRIPTION("adder module for helloworld module");
MODULE_LICENSE("GPL");

helloworld.c

#include <linux/module.h>
#include <linux/kernel.h>

extern int add(int x, int y);

int __init helloworld_init(void)
{
	printk(KERN_DEBUG "Hello, World! 1 + 1 = %d!\n", add(1, 1));
	return 0;
}

void __exit helloworld_exit(void)
{
	printk(KERN_DEBUG "HelloWorld - made By hyeyoo\n");
}

module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_AUTHOR("Hyeonggon Yoo <42.hyeyoo@gmail.com>");
MODULE_DESCRIPTION("helloworld module using adder module");
MODULE_LICENSE("GPL");

이제 make를 하고 modinfo로 각각의 모듈을 확인해보자. helloworld 모듈은 depends: adder라고 나와있다. 따라서 모듈을 로드하려면 adder를 먼저 로드하고, 그 다음 helloworld를 로드해야한다. (언로드할 때는 반대로!)

 

modinfo 수행 결과
insmod로 adder, helloworld를 로드한 결과

주의사항

커널 모듈은 커널 공간에서 실행된다. 커널 공간은 사용자 공간과 몇가지 차이점이 존재한다. 주된 차이점은 아래와 같다.

메모리 보호 없음

커널 공간에서 실행되므로, 메모리 보호가 되지 않는다. 이는 특히 디바이스 드라이버나 시스템 호출을 작성할 때 문제가 될 수 있다. 예를 들어서 우리가 read 시스템 호출을 등록했고, 사용자가 read(fd, buf, size)를 호출했는데, buf가 다른 프로세스의 메모리 주소를 참조하고 있다고 생각해보자. 이때 커널 모듈은 커널 공간에서 실행되므로 사용자 공간에서 처럼의 권한 체크는 하지 않는다. 따라서 사용자 공간에 접근할 때는 access_ok,copy_to_user, copy_from_user와 권한 체크를 하는 함수를 사용해야 한다.

고정적인 스택

스택의 크기가 고정적이다. 보통 1 ~ 2 페이지가 할당된다. 따라서 커널에서 스택을 무작정 사용하다보면 언젠가 스택이 부족해 문제가 발생할 수 있다.

libc 사용 불가능

libc에서 제공하는 함수를 사용할 수 없다.

모듈 파라미터

글에서 설명한 것 외에도, 모듈을 로드할 때 insmod helloworld key=value 처럼 파라미터를 넘길 수 있다. 이 부분은 관련 링크로 남기고 글을 마무리하겠다. 

참고 문서

 

IT EXPERT, 리눅스 커널 프로그래밍

이 책은 커널을 과감히 뜯어고쳐가면서 다양한 실험을 하는 데 집중하고 있다. 눈으로 보고, 머릿속으로 생각하는 것이 아니라 손가락을 바삐 움직이면서 커널을 이해하는 것을 목표로 하고 있

m.hanbit.co.kr

 

The Linux Kernel Module Programming Guide

/* * chardev.c - Create an input/output character device */ #include /* We're doing kernel work */ #include /* Specifically, a module */ #include #include /* for get_user and put_user */ #include "chardev.h" #define SUCCESS 0 #define DEVICE_NAME "char_dev"

tldp.org

 

Kernel module signing facility — The Linux Kernel documentation

Kernel module signing facility Overview The kernel module signing facility cryptographically signs modules during installation and then checks the signature upon loading the module. This allows increased kernel security by disallowing the loading of unsign

www.kernel.org

 

(개정 3판) 리눅스 커널 심층 분석

리눅스 커널의 핵심을 간결하면서도 심도있게 다룬 책

www.acornpub.co.kr

댓글