reference:
- "리눅스 커널 내부구조" / 백승재, 최종무
- "Operating Systems: Three Easy Pieces" / Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
리눅스에서는 동적 커널 모듈(dynamic kernel module) 또는 간단히 모듈이라고 불리는 매커니즘을 제공한다. 커널 대부분의 기능은 모듈로 구현될 수 있다. 예를 들어 파일시스템, 디바이스 드라이버, 통신 프로토콜 패밀리, 새로운 시스템 호출 등이 모듈로 구현될 수 있다.
모듈은 필요할 때만 동적으로 메모리에 적재되어 사용된다. 따라서 특정 커널 기능을 필요할 때만 메모리에 적재함으로 메모리를 효율적으로 사용할 수 있게 한다. 모듈 기능은 커널을 작게 만들 수 있도록 해주며 이는 매우 중요한 커널의 장점이 된다.
리눅스는 커널 구조상 모노리딕(monolithic) 커널이다. 모노리딕 커널이란 커널이 제공해야 할 모든 기능 즉 태스크 관리, 메모리 관리, 파일 시스템, 디바이스 드라이버, 통신 프로토콜 등의 기능이 단일한 커널 공간에 구현된 구조이다.
참고: 모노리딕 커널과 마이크로커널
https://velog.io/@jinh2352/%EB%AA%A8%EB%85%B8%EB%A6%AC%EB%94%95-%EC%BB%A4%EB%84%90%EA%B3%BC-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%BB%A4%EB%84%90
모노리딕 커널과 구조상 반대되는 개념으로 마이크로 커널이 있다. 마이크로 커널은 모노리딕 커널과 다르게 반드시 필요한 기능만을 구현한다. 일반적으로 문맥교환, 주소 변환, 시스템 콜 처리, 그리고 디바이스 드라이버의 일부 등 주로 하드웨어와 밀접하게 관련된 기능등을 커널 공간에 구현한다. 그 외의 다른 커널 기능들은 사용자 공간에 구현한다.
마이크로 커널의 장점으로 아래와 같은 것들이 있다.
1) 커널의 크기를 작게 할 수 있음.
2) 커널의 크기가 작기에 휴대용 시스템의 운영체제로 사용될 수 있다.
3) 많은 기능이 사용자 공간에 서버 형태로 구현되기에 분산 환경 특히 클라이언트-서버 모델에 잘 적용될 수 있다.
대표 OS) Mach, L4, VxWorks Windows NT 등
모노리딕 구조인 리눅스 커널은 모듈을 지원함으로써 마이크로 커널의 장점을 제공한다. 즉 커널을 작게 만드는 것이 가능하며, 또한 많은 기능들을 필요할 때 적재하여 사용할 수 있도록 해주는 것이다. 물론 사용자 공간에 적재하는 것은 아니고, 커널 공간에 적재된다. 또한 매력적인 것은 모듈을 사용하면 커널에 새로운 기능을 추가할 때 커널 소스를 직접 컴파일 할 필요가 없다.
hello_module.c
#include <linux/kernel.h>
#include <linux/module.h>
int hello_module_init(void){
printk(KERN_EMER "Hello Module~! I'm in Kernel \n");
return 0;
}
void hello_module_cleanup(void){
printk("<0>Bye Module~! \n");
}
module_init(hello_module_init);
module_exit(hello_module_cleanup);
MODULE_LICENSE("GPL");
모듈은 커널 공간에서 동작하며 커널 내부에 정의된 변수나 함수들을 사용한다. 따라서 모듈 프로그램을 작성할 때 커널이 사용하는 몇 가지 헤더 파일을 삽입해야 한다.
ex) "linux/kernel.h", "linux/module.h"
또한 모듈은 커널에 적재될 때 커널에 의해 자동으로 호출될 함수를 작성해주어야 하는데 이 함수의 이름이 리눅스 커널 버전 2.4에선 init_module()과 cleanup_module()이었다. 한편 2.4 후반부터 등장한 module_init()과 module_exit() 매크로는 이러한 함수의 이름을 임의로 지정할 수 있도록 해준다. 위 코드에서 이 메크로를 사용하였으며, 결국 모듈이 시작될 때 hello_module_init()이 수행되고, 종료될 때 hello_module_cleanup() 함수가 수행된다.
"printk() 함수는 디바이스 드라이버를 작성하고, 테스트할 때 가장 편하고 강력한 디버깅 도구이다!"
cf) printk()는 커널 공간에 문자열을 출력하는 함수이다.
인자로 넘어온 문자열과 출력지정자, 그리고 인자를 하나의 문자열로 조합한 뒤 이를 커널 내부에 유지되고 있는 원형 큐에 넣어주는 일을 담당한다.
원형 큐에 삽입된 문자열은 원형 큐를 관리하고 있으며, 원형 큐 내의 데이터를 터미널에 출력해주는 콘솔 디바이스가 활성화 될 때까지 보류되었다가 출력된다. 따라서 printk()를 호출하는 시점과 실제 화면에 출력되는 시점이 정확히 동기화 되지 않는다.
콘솔 디바이스가 활성화되기 이전에 원형 큐의 용량을 다 채우게 되면 printk()로 출력했다고 믿었던 내용이 출력되지 않고 사라지는 경우가 발생하게 된다. 또한 리눅스 커널은 출력하는 수많은 메시지들의 등급을 나누어 관리하는데, 이때 등급을 지정하는 방법은 KERN_EMERG 등의 매크로를 사용하거나 <0>처럼 직접 숫자로 지정한다.
커널 공간에서 동작하기에 libc 라이브러리 함수들 사용X
Make는 유닉스 계열 시스템에서 프로젝트 빌드나 document 생성 등 매우 유용하게 사용되는 유틸리티이다.
그냥 "make" 명령을 내리면 첫번째 만나는 target인 default 레이블에 지정된 대로 리눅스 커널 버전 2.6(책 기준)에서 사용하는 모듈인 .ko 파일을 생성해준다.
또는 "make install"이라고 명령을 내리면 생성한 Makefile 내에서 install 레이블을 찾아서 그 레이블에 지정되어 있는 명령을 수행해준다. 따라서 생성된 .ko 파일을 "MODULE_DIR"로 지정된 위치에 복사해준다.
(참고로 "make clean" 명령을 수행하면 clean 레이블을 찾아 명령 수행)
일단 컴파일이 완료되면 생성된 모듈을 커널에 적재할 수 있다. 이는 insmod 명령어를 통해 이루어진다. 이 명령은 인자로 주어진 파일을 커널에 넣기 위한 공간을 할당받고 모듈을 삽입한다. 또한 이때 모듈에서 사용하는 함수나 extern한 변수 등을 실제 커널 정보를 참조하여 연결시켜준다.
그런 뒤 모듈에 구현된 module_init() 매크로에 정의된 함수가 호출되며, printk() 함수를 통해 메시지가 출력된다.
일반 애플리케이션으로 컴파일하면 실행가능한 바이너리 파일이 만들어지지만, 커널 모듈로 컴파일하면 오브젝트 파일이 생성되고, 이를 insmod 명령으로 커널에 적재시켜 커널에서 동작시킨다.
(cf, insmod: insert a module into the Linux kernel)
적재된 모듈을 해제하는 명령은 rmmod이며 이 명령은 해당 모듈에 module_exit() 매크로를 통해 정의되어 있는 함수를 호출하고 모듈이 차지하고 있던 공간을 회수한다. => "Bye Module~!" 출력 후 모듈 공간 회수
모듈과 관련된 다른 명령으로 lsmod가 있다. 이 명령은 현재 적재된 모듈들의 정보를 보여주는 명령어이다.