The Linux Kernel Module Programming Guide by Peter Jay Salzman, Micheal Burian, Ori Pomerantz Copyright @ 2001 Peter Jay Salzman Translated by YoonMin Nam in PuKyung National University. Lab of Information Security and Internet Application & CERT-IS :: zkdnsxjaos@gmail.com Table of Contents Foreword (Skipping) 1. Authorship 2. Versioning and Notes 3. Acknowledgements Chapter1. Introduction 1.1 What is A Kernel Module? 1.2 How Do Modules Get Into The Kernel? Chapter2. Hello World! 2.1 Hello World Part1 - The Simplest Module 2.2 Compiling Kernel Module 2.3 Hello World Part2 2.4 Hello World Part3 - the _init and _exit Macros 2.5 Hello World Part4 - Licensing and Module Documentation 2.6 Passing Command Line Arguments to a Module 2.7 Modules Spanning Multiple Files 2.8 Building modules for a precompiled kernel Chapter6. Using /proc For Input 6.1. TODO: Write a chapter about sysfs Chapter7. Talking To Device Files 7.1. Talking to Device Files (writes and IOCTLs) Chapter8. System Calls 8.1. System Calls Chapter9. Blocking Processes 9.1. Blocking Processes Chapter 10. Replacing Printks 10.1. Replacing printk 10.2. Flashing keyboard LEDs Chapter3. Preliminary 3.1 Module vs Program Chapter11. Scheduling Tasks 11.1. Scheduling Tasks Chapter4. Character Device Files 4.1 Character Device Drivers Chapter5. The /proc File System 5.1. The /proc File System 5.2. Read and Write a /proc File 5.3. Manage /proc file with standard filesystem 5.4. Manage /proc file with seq_file Chapter12. Interrupt Handlers 12.1. Interrupt Handlers Chapter13. Symmetric Multi Processing 13.1. Symmetrical Multi-Processing Chapter14. Common Pitfalls 14.1. Common Pitfalls Linux Kernel Module Programming Guide 1
Chapter1. Introduction 1.1 What is A Kernel Module? 여러분들은커널모듈을작성하고싶어한다. 이미당신은프로세스로프로그램을실행하기위한몇개의 프로그램들을 c 를사용해서작성을했었을것이고, 이제당신은하나의 single pointer 가 core dump 와 file system 을 돌아다니는실제활동영역을알고싶다.. 정확하게커널모듈이뭘까? 모듈은사용자의혹은커널의요구로읽혀지거나아니면없어지는코드들로이루어진프로그램의어떤한조각을의미한다. ( 역주 : 커널은하나의 component와같은개념으로, 커널은하나의매우큰모듈들의집합체이고, 모듈은커널에탑재혹은제거가가능한하나의기능을수행하는코드의모음이고 flexible 하다. ) 커널은모듈은시스템의 reboot 없이커널의기능을확장할수있다. 예를들어, 하나의모듈이장치드리이버이고, 이모듈은시스템에연결된하드웨어를커널과연결시켜주는것을허용해준다고하자. 이모듈이없다면, 우리는이역할을위한하나의단일구조로된다른커널을작성해야하고현커널이미지에이러한새로운기능을직접적으로추가해야한다. 게다가커널이커진다는것은, 즉이러한모듈의개념이없다고생각해본다면 ( 모듈의역할을하는것이없으니계속적으로단일구조로이루어진작은커널을만들어현커널에추가시켜야하는작업을지속적으로추가해야한다면 ), 우리가어떤추가적인기능을커널에사용하고싶다면계속커널을새로만들고, 전체시스템을 reboot 해야하는번거롭고귀찮은일들이생기게된다. 1.2 How Do Modules Get Into The Kernel? 여러분은이미커널에서실행되고있는 Ismod 를통해어떤모듈들이이미커널속으로로딩되어있나확인해볼수 있다. ismod 는 /proc/modules 파일을읽어현커널의모듈들에대해알려준다. 어떻게모듈은커널에서의자신이있어야할곳을찾는걸까? 커널은자신에게없는새로운기능을필요로할때, 커널모듈데몬인 kmod가 mobprobe 명령어를실행하여필요한모듈을커널로불러들인다. Instruction - mobprobe? 커널모듈추가, 제거하는명령어일반적으로시작스크립트에서자동으로호출되는모듈로더 insmod 와 modprobe 의차이점 insmod 는단일모듈지정된모듈만추가하지만 modprobe 를사용하면단일모듈, 의존성이있는모듈까지모두자동으로추가한다. 사용 :: modprobe [option] module 옵션 -l : 사용가능한모든모듈을보여줌 -r : rmmod 와같은기능으로지정모듈을제거. 한꺼번에여러모듈지정가능하고의존성이있는모듈또한자동으로제거 -c : 기본값과 /etc/modules.conf 에정의된지시자를포함한완전한모듈설정을보여줌 옵션없이사용시에모듈추가 예문 :: e1000 이란모듈을추가? -> modprobe e1000 mobprobe 는다음두가지형태로된모듈명들중하나를커널에전달할수있다. Linux Kernel Module Programming Guide 2
softdog / ppp와같은형식의모듈 char-major-10-30과같은좀더포괄적인구분자로된모듈만약 mobprobe가후자와같은경우를처리한다면, mobprobe는제일먼저 /etc/mobprobe.conf[2] 를열어서기록들을찾아본다. 만일 mobprobe가 mobprobe.conf에서다음과같은라인을찾았다고한다면? : alias char-major-10-30 softdog mobprobe 는이미 char-major-10-30 이 softdog 모듈을말한다는것을알고있다는뜻이다. 다음으로, mobprobe는 /lib/modules/version/modules.dep 파일을확인해다른모듈이현제요청된모듈을커널로로딩하기전에실행되어야하는다른모듈이있는가를확인한다. ( 마치 firefox가실행되기위해서 XWindows가실행되어야하는것처럼..) 이러한파일들은 depmod -a 명령어로부터만들어졌고, 또한모듈의존성을가지고있다. 예를들어, msdos.ko 모듈이실행되기위해서 fat.ko 모듈이먼저커널로로딩되어있어야한다. 요청된모듈이만일다른모듈이정의한심볼들 ( 변수혹은함수 ) 을사용해야한다면, 요청된모듈은모듈의존성을지니게된다. 마지막으로, mobprobe는우선적으로로딩되어야하는모듈들을처리하기위해 insmod를사용한다. 그리고나서요청된모듈을처리한다. mobprobe는 insmod를모듈에대한기본적인사전적정보가있는 /lib/modules/version/ 폴더로이끈다. insmod는모듈의위치에대해잘알지못하지만, 반면에 mobprobe는기본적인모듈의위치를알고있다. 그렇기때문에 mobprobe는모듈을올바른순서대로로딩하기위한의존성과같은정보에대해추정해낼수있다. 예를들어, 여러분이 msmod 모듈을로딩하기위해선다음두가지명렁어중하나를입력해야한다. insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko & insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko mobprobe msdos or 우리가여기서확인할수있는점은 insmod는모듈의정확한위치와입력순서를요구한다는점과, 이와는다르게 mobprobe는우리가불러들이고자하는모듈의이름만입력하면된다는점이다. 단지이름만입력한다면, mobprobe는 msdos 모듈의로딩을위해알아야하는모든것들을 /lib/modules/version/modules.dep 파일을분석하여실행하여준다. Linux distros는 mobprobe와 module-init-tools 패키지를통해 insmod와 depmod를제공한다. 이전버전의 module-init-tools는 modutils 라는패키지명으로불렸다. 다른어떤 distros는 2.4와 2.6버전의커널들을올바르게작동하기위해어떤 wrapper를설정하여두가지의패키지를모두설치하게한다. 사용자들은이것들에대해걱정할부분이없다. 이제여러분은어떻게모듈이커널에로딩되는가에대해알게되었다. 여러분이다른모듈에의존성을가지는새로운 모듈을작성하고싶다면, 본가이드에이부분에대한내용이더있다. ( 우리는위와같은모듈을 sticky module 이라 부른다.) 그러나, 다른부분을먼저살펴본다음에이부분에대한조금더세부적인내용을다루도록하자. 1.2.1 Before We Begin 세부적인커널코드분석전에우리는여러가지부분에대해살펴볼것이다. 모든사용자의시스템은다르고각자자신만의형태를가지고있다. 여러분이 "Hello World" 프로그램을올바르게컴파일해여실행하기위해선가끔은속임수를쓸수있어야한다. 여러분이첫번째관문을통과한다면, 그이후로는쉽게그러한속임수들을사용할수있을것이다. Linux Kernel Module Programming Guide 3
1.2.1.1 Modversioning 어떤특정한커널을위해컴파일된모듈은여러분이다른커널로부팅을할경우에 CONFIG_MODVERSIONING을허용하기전까지로딩되지않는다. 우리는이가이드의후반부전까진이러한 module versioning을다루지않을것이다. 우리가 mod versioning을다루기전에, 만일여러분이 mod versioning이활성화되어있는커널을사용한다면이가이드의어떠한예들은정상적으로작동되지않을수있다. 그러나, 대부분의 linux distro 커널은 mod versioning을활성화하고있다. 만일여러분이모듈로딩에서 versioning error 문제가발생한다면, 커널을 modversioning을비활성화한상태로커널을컴파일해보자. 1.2.1.2 Using X 우리는여러분이우리가다룬모든예를타이핑하고, 컴파일하여로딩해보길권장한다. 또한이러한모든작업을 콘솔모드를통해수행하길추천한다. 이말은, 여러분이 XWindow 를통한작업을하지않았으면한다는거다. 모듈은 printf( ) 와같은출력을하지못한다. 그러나모듈은오직콘솔에서만여러분들의스크린에출력이끝나기전에그에관련된정보와경고들에대해기록을남길수있다. 만일여러분이 xterm을통해어떤모듈을 insmod 한다면 (insmod를통해로딩한다면 ) 그에관련된정보와경고들은오직로그파일을통해기록될것인데, 여러분은그로그파일을열지않는이상그러한정보와경고를확인하지못한다. 이러한정보에빠르게접근하기위해, 모든작업은콘솔을통해서하자. 1.2.1.3 Compiling Issues and Kernel Version 리눅스배포판 ( 역주 : Radhat Linux, Fedora, Ubuntu 와같은..) 은문제의여지가있는것들에대해패치한새로운 커널버전을자주분배한다. 종종리눅스배포판은불완전한커널헤더를분배하는경우가있는데, 여러분은리눅스커널의다양한헤더파일을 통해여러분의코드를컴파일해야할필요가있다. Murphy's Law 는 문제가발생한헤더는커널헤더에서정확하게 당신의모듈을동작시키기위한부분이다. 라고말한다. 이러한두가지문제를피하기위해, 여러분들만의순정의리눅스커널을다운로드하여설치하고그것을통해 작업하길권장한다. 역설적이게도, 이것역시문제를발생시킬수있다. 기본적으로, 여러분의시스템에있는 gcc 는여러분이새롭게 설치한커널보다는 (/usr/src/) 기본적인위치의커널헤더를우선적으로찾기때문인데, 이문제는 gcc's -i switch 를 통해해결할수있다. Linux Kernel Module Programming Guide 4
Chapter2. Hello World 2.1 Hello, World (part 1) : The Simplest Module 아직숙달되지못한초보프로그래머들이프로그래밍을할때제일처음으로제작하는프로그램은다들알고있는거와 같이 "Hello World" 프로그램이다. 로마의프로그래밍교재는 Salut, Mimdi" 프로그램으로시작하는데, 이전통을누가 깨뜨렸는지저자도알지못한다.. 다만나의생각엔누가그전통을깻는지찾지않는것이좋을듯하다.. : ) 우리는몇가지 "Hello World" 프로그램들로커널모듈작성의여러가지다른측면들을살펴볼것이다. 아래에가장간단한모듈의예제가있다. 다음장에서커널컴파일에대하여살펴볼것이기때문에아직컴파일은 안해도되겠다. hello-1.c - The simplest kernel module. #include <linux/module.h> Needed by all modules #include <linux/kernel.h> Needed for KERN_INFO int init_module(void) printk(kern_info "Hello world 1.\n"); A non 0 return means init_module failed; module can't be loaded. return 0; void cleanup_module(void) printk(kern_info "Goodbye world 1.\n"); 커널모듈은최소한두가지함수를갖추어야한다. :: 하나는 start" 함수 ( 초기화 ) 로이것을 init_module( ) 이라부르는데, 이함수는작성된모듈이 insmod 명령어를통해커널에로딩될때호출된다. 그리고나머지하나는 end" 함수로 cleanup_module( ) 이라불리고이것은 rmmod 명령어를통해모듈이종료되기전에 ( 커널에서제거될때 ) 호출된다. 사실커널 2.3.13 버전이후로이러한함수들은변하였다. 여러분은모듈이시작되고종료될때, 함수이름에제한없이이러한시작과종료함수를작성할수있는데, 우리는이것을 2.3에서살펴볼것이다. 사실, 이러한새로운방식은최근에선호되는방식이다. 그러나많은사람들이아직 init_module( ) 과 cleanup_module( ) 을사용한다. 일반적으로 init_module( ) 은커널에 handler 를등록하거나, 혹은커널함수의한부분을자신의코드로 대체한다.( 보통자신의코드를실행한후원본함수를다시불러온다 ) cleanup_module( ) 은 init_module( ) 이수행한 기능을되돌리는역할을하는데이를통해모듈이안전하게커널의한부분으로부터원본으로대체된다. 마지막으로, 모든커널모듈은 linux/module.h 헤더파일을필요로한다. 우리는 printk( ) log level marco expansion 과 KERN_ALERT 을위해서만 linux/kernel.h 헤더파일을로드해야하는데, 우리는 2.1.1 에서이것에대해살펴볼것이다. 2.1.1 Introducing printk( ) 여러분이어떤생각을하던지간에, printk( ) 함수는유저를위한통신수단이아니다. 심지어우리가 hello-1 에서와 Linux Kernel Module Programming Guide 5
같이정확히출력의목적으로사용을했지만말이다. 이함수를실행했을때, 커널의 logging mechanism 이수행되고, 이 logging mechanism은수행한함수의정보를기록하거나혹은경고를알린다. 그리므로, 각각의 printk( ) 선언은우선순위를통해제공되는데, 아까살펴봤던 <1> 과 KERN_ALERT이다. printk( ) 함수에는 8가지의속성과커널이매크로를가지고있어, 여러분은이상한숫자를사용하지않아도되고, 또한매크로에대한정보를 linux/kernel.h에서확인할수있다. 만일여러분이우선순위수준을명시하지않았을경우, 기본우선순위인 DEFAULT_MESSAGE_LOGEVEL이사용될것이다. 우선순위매크로를천천히읽어보자. 위에서언급한헤더파일은역시각각의우선순위가의미하는바를기술해놓았다. 실제모듈작성시에, <4> 와같은숫자를사용하지말고항상 KERN_WARNING 과같은매크로를사용하도록하자. 만일우선순위가 int_console_loglevel보다낮은경우, 메시지가여러분의터미널에출력될것이다. 만일 syslogd와 klogd이동시에실행중이라면, 출력된메시지는콜솔출력여부와관계없이 /var/log/messages에기록될것이다. 우리는단순히메시지들이파일로기록되는것을원치않기때문에, KERN_ALERT와같이높은우선순위를가진매크로를사용하여 printk( ) 함수가여러분의콘솔에확실히출력되게할것이다. 여러분이실제모듈을작성할때, 여러분은현상황에의미가있는우선순위를사용하길원할것이다. 2.2 Compiling Kernel Modules 커널모듈은일반적인사용자프로그램과는약간다르게컴파일되어야한다. 이전의커널버전은이러한차이점에대해좀더상세하게다루도록요구하였다. ( 이러한상세한옵션들은보통 MakeFile에저장되어있다.) 비록계층적으로조직되었다하더라도, 많은불필요한설정들은 MakeFiles의 sublevel에집중되어있고이때문에커널모듈을커지게하고유지하기어렵게한다. 운좋게도, 이러한불행한경우를없애줄새로운방식이등장하였는데, 우리는이것을 kbuild라고부르고, 외부에서읽어올수있는모듈을위한 build process는이제일반적인 kernel build mechanism에포함되어있다. 공식적인커널의일부분이아닌모듈을컴파일하는방법에대해선 ( 우리가작성하고연습하는모든이가이드의예제들을말함 ) linux/documentation/kbuild/modules.txt를참조하자. 이제 MakeFile 을이용해간단한 hello-1.c 의모듈을컴파일해보자. Example 2-2. Makefile for a basic kernel module obj-m += hello-1.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 명령어는단지순수한편의를위해서추가되었다고볼수있다. 이제여러분은 make 명령어를통해모듈을컴파일할수있게되었다. make 명령어로컴파일을했기때문에 여러분은다음과같은출력결과를얻어야한다. hostname:~/lkmpg-examples/02-helloworld# make make -C /lib/modules/2.6.11/build M=/root/lkmpg-examples/02-HelloWorld modules make[1]: Entering directory `/usr/src/linux-2.6.11' CC [M] /root/lkmpg-examples/02-helloworld/hello-1.o Building modules, stage 2. MODPOST CC /root/lkmpg-examples/02-helloworld/hello-1.mod.o LD [M] /root/lkmpg-examples/02-helloworld/hello-1.ko make[1]: Leaving directory `/usr/src/linux-2.6.11' hostname:~/lkmpg-examples/02-helloworld# 커널 2.6버전은새로운파일이름에관한내용을담고있다는것을기억하자 : 커널모듈은이제.ko 확장자를 Linux Kernel Module Programming Guide 6
가지는데 ( 이전의.o 확장자를대채함 ) 이를통해서이전 object file 들로부터쉽게구분할수있다. 이러한확장자 변경의이유는.modinfo 에관한추가적인부분을담고있기때문인데, 모듈이어디에보관되에있는지에대한 정보이다. 우리는이정보가왜좋은지에대해살펴볼것이다. modinfo hello.ko 를사용하여보자. hostname:~/lkmpg-examples/02-helloworld# modinfo hello-1.ko filename: hello-1.ko vermagic: 2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3 depends: 딱히특별한것이없는데, 우리의마지막예제인 hello-5.ko 를살펴볼때달라진다. hostname:~/lkmpg-examples/02-helloworld# modinfo hello-5.ko filename: hello-5.ko license: GPL author: Peter Jay Salzman vermagic: 2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3 depends: parm: myintarray:an array of integers (array of int) parm: mystring:a character string (charp) parm: mylong:a long integer (long) parm: myint:an integer (int) parm: myshort:a short integer (short) hostname:~/lkmpg-examples/02-helloworld# 이출력물엔많은유용한정보들이제공되어있다. 커널모듈을위한 MakeFiles 에관한추가적인정보는 linux/documentation/kbuild/makefiles.txt 에기술되어있다. MakeFiles 를파보기전에이문서를확실히읽어두면많은도움이될것이다. 이제 insmod./hello.1-lo 명령어를통해컴파일한여러분의커널모듈을커널에심어볼시간이다. 커널로불러들여진모든모듈들은 /proc/modules 에기술되어있다. 이파일을살펴본다면실제로커널을구성하고있는모듈들이어떤것인가에대한정보를얻을수있다. 축하한다! 여러분은이제리눅스커널코드의저자가되었다. 또한 rmmod hello-1 명령어를통해여러분이작성한커널모듈을커널로부터제거할수있다. /var/log/messages를통해여러분의시스템로그파일을살펴보아라. 여기다른예제가제공되어있다. init_module( ) 의리턴문위의내용이보이는가? 리턴값을바꾸어새로컴파일을 하고커널로넣어보자. 어떠한일이일어날까? 2.3 Hello, World (part 2) 2.4 리눅스에서는 init과 cleanup 함수를재명명할수있다. 즉, init_module( ) 혹은 cleanup_module( ) 같은이름으로불려지지않아도된다는것이다. 위의함수들은 module_init( ) 과 module_exit( ) 매크로를통해수행된다. 이러한매크로들은 linux/init.h 헤더파일에정의되어있다. 이매크로들을사용할때여러분이주의해야할유일한부분은 init과 cleanup이매크로호출전에정의가되어있어야한다는점이다. 그렇지않다면컴파일에러가발생할것이다. 여기에이러한방법에대한예제가제공되어있다. Linux Kernel Module Programming Guide 7
Example 2-3. hello-2.c hello-2.c - Demonstrating the module_init() and module_exit() macros. This is preferred over using init_module() and cleanup_module(). #include <linux/module.h> Needed by all modules #include <linux/kernel.h> Needed for KERN_INFO #include <linux/init.h> Needed for the macros static int init hello_2_init(void) printk(kern_info "Hello, world 2\n"); return 0; static void exit hello_2_exit(void) printk(kern_info "Goodbye, world 2\n"); module_init(hello_2_init); module_exit(hello_2_exit); 이제여러분은두개의실제커널모듈을다룰수있게되었다. 다른모듈을커널로불러들이는것도아래와같이쉽다 : Example 2-4. Makefile for both our modules obj-m += hello-1.o obj-m += hello-2.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 linux/drivers/char/makefile를살펴보자. 여러분이본것과같이뭔가가커널에내장되어있지만이것들의 obj-m 들은어디로사라진걸까? 그러한쉘스크립트와유사한파일은찾아내기가쉽다. 그렇지않다면, obj-$(config-foo) 파일들은 obj-y 혹은 obj-m 으로확장되어있을것인데, 이것은 CONFIG-FOO 변수가어떻게설정되어있느냐에달려있다. ( y 혹은 m으로 ) 우리가언급하고있지만, 이러한것들은여러분들이 make menuconfig 혹은그러한명령어를통해설정하는 linux/config 파일의변수들의한종류이다. 2.4 Hello World (part 3) : The _init and _exit Macros 이부분은커널 2.2 버전이후에관해실제예를들어설명한다. init 과 cleanup 함수정의의변화를알아보자. _init 매크로는사용가능한모듈이아닌내장된드라이버에대해 init 함수를불러온후후메모리로부터제거된다. init 함수가불려졌다고생각한다면이해가될것이다. 또한함수보단 init 변수를위한 _initdata 가존재한다. _exit 매크로는커널에모듈이로딩되었을때함수의누락을야기하는데, _init 과마찬가지로사용가능한모듈에는 효과가없다. 다시말해여러분이 cleanup 함수가동작할때를생각해본다면, 실행가능한모듈과는반대로내장된 드라이버 (built-in driver) 는 cleanup 함수가필요하지않다는점이완벽하게이해가될것이다. 이러한매크로는 linux/init.h 에정의되어있고커널메모리의공간을확보하여준다. 여러분이커널을부팅하고 "Freeing unused kernel memory : 236k frred" 와같은메시지를본다면, 커널메모리가최적화됬음을명확하게알수있을것이다. Linux Kernel Module Programming Guide 8
Example 2-5. hello-3.c hello-3.c - Illustrating the init, initdata and exit macros. #include <linux/module.h> Needed by all modules #include <linux/kernel.h> Needed for KERN_INFO #include <linux/init.h> Needed for the macros static int hello3_data initdata = 3; static int init hello_3_init(void) printk(kern_info "Hello, world %d\n", hello3_data); return 0; static void exit hello_3_exit(void) printk(kern_info "Goodbye, world 3\n"); module_init(hello_3_init); module_exit(hello_3_exit); 2.5 Hello World (part 4) : Licensing and Module Documentation 만약여러분이커널 2.4 버전이후의커널을사용하고있는경우에, 여러분이제작자가명시된모듈을로딩했을 경우에다음과같은메시지를봤을것이다. # insmod xxxxxx.o Warning: loading xxxxxx.ko will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modules Module xxxxxx loaded, with warnings 커널 2.4 버전이후에, GPL(General Public License) 에영향을받아코드의라이센스를명시하는메커니즘이 고안되었다. 그래서사용자들은위와같은모듈을사용할때그모듈이오픈소스가아니라는것에대한 경고메시지를보게된다. 라이센스에대해 GPL 을적용하게되면위와같은경고메시지는출력되지않는다. 위와같은라이센스메커니즘은 linux/module.h 헤더파일에상세히정의되어있으니참고하길바란다. The following license idents are currently accepted as indicating free software modules "GPL" [GNU Public License v2 or later] "GPL v2" [GNU Public License v2] "GPL and additional rights" [GNU Public License v2 rights and more] "Dual BSD/GPL" [GNU Public License v2 or BSD license choice] "Dual MIT/GPL" [GNU Public License v2 or MIT license choice] "Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice] The following other idents are available "Proprietary" [Non free products] There are dual licensed components, but when running with Linux it is the GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL is a GPL combined work. This exists for several reasons 1. So modinfo can show license info for users wanting to vet their setup is free 2. So the community can ignore bug reports including proprietary modules 3. So vendors can do likewise based on their own policies Linux Kernel Module Programming Guide 9
비슷하게, MODULE_DESCRIPTION( ) 은모듈이어떤역할을수행하는지에대해기술할수있는명령어이고, MODULE_AUTHOR( ) 명령어는모듈의저자를, MODULE_SUPPORTED_DEVICE( ) 는모듈이지원하는장치에대해 기술하는명령어이다. 이러한매크로들은 linux/module.h 헤더파일에정의되어있으며커널스스로이러한매크로들을사용하지않는다. ( 즉, 사용자가코드상에적용해야하는부분이다.) objdump 와같은툴을사용하면위와같은매크로들을손쉽게확인할수있다. 여러분스스로 linux/driver에서위와같은툴을사용하여모듈의저자가어떻게자신들의모듈에위와같은정보들을기술하였는지쉽게확인해볼수있을것이다. grep -inr MODULE_AUTHOR 과같은명령어도추천할만한구문이다. 이런구문에익숙하지않은사용자라면아마 LXR 과같은웹기반의솔루션을좋아할것이다. vi나 emacs와같은유닉스의전통적인문서편집기를주로사용하는유저라면, 그러한편집기들이 tag files를찾는데유용하게사용될것이다. 그러한파일들은 make tags 나 make TAGS 와같은명령어를통해 usr/src/linux-2.6.x/ 에생성되는데, 일단여러분들의커널트리에서그러한태그파일들을찾았다면, 커서를이용하여어떤함수를호출하거나특정키조합을통해함수의정의부로바로이동할수있을것이다. Example 2-6. hello-4.c hello-4.c - Demonstrates module documentation. #include <linux/module.h> Needed by all modules #include <linux/kernel.h> Needed for KERN_INFO #include <linux/init.h> Needed for the macros #define DRIVER_AUTHOR "Peter Jay Salzman <p@dirac.org>" #define DRIVER_DESC "A sample driver" static int init init_hello_4(void) printk(kern_info "Hello, world 4\n"); return 0; static void exit cleanup_hello_4(void) printk(kern_info "Goodbye, world 4\n"); module_init(init_hello_4); module_exit(cleanup_hello_4); You can use strings, like this: Get rid of taint message by declaring code as GPL. MODULE_LICENSE("GPL"); Or with defines, like this: MODULE_AUTHOR(DRIVER_AUTHOR); Who wrote this module? MODULE_DESCRIPTION(DRIVER_DESC); What does this module do This module uses /dev/testdevice. The MODULE_SUPPORTED_DEVICE macro might be used in the future to help automatic configuration of modules, but is currently unused other than for documentation purposes. MODULE_SUPPORTED_DEVICE("testdevice"); Linux Kernel Module Programming Guide 10
2.6 Passing Command Line Arguments to a Module 모듈은커맨드라인의인자들을취할수있는데, 여러분들이이미사용할지모르는 argc/argv 는사용할수없다. 여러분들의모듈에인자들이사용될수있게하기위해, 커맨드라인인자들을전역변수로선언하고, module_parm( ) 매크로를사용하여 ( linux/moduleparm.h 헤더파일에정의되어있다.) 위의인자들의메커니즘을설정할수있다. 실행시에 insmod 는./insmod mymodule.ko myvariable=5 와같은명령어를통해주어진커맨드라인의인자들을이용해변수들을채울것이다. 변수정의와매크로는가독성을위해모듈의앞부분에위치하는것이좋다. 아래에있는예제가위에서설명한조건들을잘반영하고있다. module_parm( ) 매크로는 3 개의인자를취한다 : 변수의이름과타입, 권한. (sysfs 에대응하는파일을위해 ) Integer 타입의변수는 unsigned 나일반적인보통의 signed 로정의될수있다. 여러분들이배열형 Integer 변수를 사용하고싶다면, module_param_array( ) 과 module_param_string( ) 매크로를사용하면된다. int myint = 3; module_param(myint, int, 0); 위에서언급했지만, 배열은지원가능하나 2.4 버전과는약간차이점이있다. 인자들의개수를파악하기위해, 여러분들은세번쩨인자로 count variable 에대한포인터를넘겨주어야한다. 여러분들은 count 를무시하고 NULL 을 대신넘겨줄수도있다. 아래에두가지모두에대해예시를제공하였다. int myintarray[2]; module_param_array(myintarray, int, NULL, 0); not interested in count int myshortarray[4]; int count; module_parm_array(myshortarray, short,, 0); put count into "count" variable 좋은사용예로는, 모듈변수를포트번호혹은 IO 주소와같은기본값으로설정하는것이다. 만약변수들이기본값을가지고있다면 autodetection 을수행하라. 아니면, 현값을유지해라. 이부분에대해추후에명확하게설명할것이다. 마지막으로 MODULE_PARM_DESC( ) 매크로함수가있는데, 모듈이취하는변수의기술에사용된다. 위매크로함수는 2 가지의인자를취하는데, 변수이름과 string 으로이루어진변수에대한설명문이다. Example 2-7. hello-5.c int myint = 3; module_param(myint, int, 0); int myintarray[2]; module_param_array(myintarray, int, NULL, 0); not interested in count int myshortarray[4]; int count; module_parm_array(myshortarray, short,, 0); put count into "count" variable hello-5.c - Demonstrates command line argument passing to a module. #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("Peter Jay Salzman"); static short int myshort = 1; static int myint = 420; static long int mylong = 9999; static char mystring = "blah"; Linux Kernel Module Programming Guide 11
static int myintarray[2] = -1, -1 ; static int arr_argc = 0; module_param(foo, int, 0000) The first param is the parameters name The second param is it's data type The final argument is the permissions bits, for exposing parameters in sysfs (if non-zero) at a later stage. module_param(myshort, short, S_IRUSR S_IWUSR S_IRGRP S_IWGRP); MODULE_PARM_DESC(myshort, "A short integer"); module_param(myint, int, S_IRUSR S_IWUSR S_IRGRP S_IROTH); MODULE_PARM_DESC(myint, "An integer"); module_param(mylong, long, S_IRUSR); MODULE_PARM_DESC(mylong, "A long integer"); module_param(mystring, charp, 0000); MODULE_PARM_DESC(mystring, "A character string"); module_param_array(name, type, num, perm); The first param is the parameter's (in this case the array's) name The second param is the data type of the elements of the array The third argument is a pointer to the variable that will store the number of elements of the array initialized by the user at module loading time The fourth argument is the permission bits module_param_array(myintarray, int, &arr_argc, 0000); MODULE_PARM_DESC(myintArray, "An array of integers"); static int init hello_5_init(void) int i; printk(kern_info "Hello, world 5\n=============\n"); printk(kern_info "myshort is a short integer: %hd\n", myshort); printk(kern_info "myint is an integer: %d\n", myint); printk(kern_info "mylong is a long integer: %ld\n", mylong); printk(kern_info "mystring is a string: %s\n", mystring); for (i = 0; i < (sizeof myintarray / sizeof (int)); i++) printk(kern_info "myintarray[%d] = %d\n", i, myintarray[i]); printk(kern_info "got %d arguments for myintarray.\n", arr_argc); return 0; static void exit hello_5_exit(void) printk(kern_info "Goodbye, world 5\n"); module_init(hello_5_init); module_exit(hello_5_exit); 저자는여러분이아래와같은코드와유사하게사용하길권장한다. satan# insmod hello-5.ko mystring="bebop" mybyte=255 myintarray=-1 mybyte is an 8 bit integer: 255 myshort is a short integer: 1 myint is an integer: 20 mylong is a long integer: 9999 mystring is a string: bebop myintarray is -1 and 420 satan# rmmod hello-5 Goodbye, world 5 satan# insmod hello-5.ko mystring="supercalifragilisticexpialidocious" \ > mybyte=256 myintarray=-1,-1 mybyte is an 8 bit integer: 0 myshort is a short integer: 1 myint is an integer: 20 mylong is a long integer: 9999 mystring is a string: supercalifragilisticexpialidocious myintarray is -1 and -1 satan# rmmod hello-5 Goodbye, world 5 satan# insmod hello-5.ko mylong=hello hello-5.o: invalid argument syntax for mylong: 'h' Linux Kernel Module Programming Guide 12
2.7 Module Spanning Multiple Files 때로는커널모듈을여러개의파일로분리해야할경우가생긴다. 아래에위에대한예를실었다. Example 2-8. start.c The next file: Example 2-9. stop.c And finally, the makefile: Example 2-10. Makefile myint is an integer: 20 mylong is a long integer: 9999 mystring is a string: supercalifragilisticexpialidocious myintarray is -1 and -1 satan# rmmod hello-5 Goodbye, world 5 satan# insmod hello-5.ko mylong=hello hello-5.o: invalid argument syntax for mylong: 'h' start.c - Illustration of multi filed modules #include <linux/kernel.h> We're doing kernel work #include <linux/module.h> Specifically, a module int init_module(void) printk(kern_info "Hello, world - this is the kernel speaking\n"); return 0; stop.c - Illustration of multi filed modules #include <linux/kernel.h> We're doing kernel work #include <linux/module.h> Specifically, a module void cleanup_module() printk(kern_info "Short is the life of a kernel module\n"); obj-m += hello-1.o obj-m += hello-2.o obj-m += hello-3.o obj-m += hello-4.o obj-m += hello-5.o obj-m += startstop.o startstop-objs := start.o stop.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 첫번째다섯개의라인까진별다른특별한점이없지만, 마지막예제에서우리는두개의명령문을필요로한다. 첫번째는여러개의파일들로결합된모듈을위해 object file 을만드는것이고, 두번째는 make 명령어를통해 각각의 object 파일들이모듈에서어떤부분인가를명시해주는것이다. Linux Kernel Module Programming Guide 13
2.8 Building modules for a precompiled kernel 명백하게저자는 forced module unloading (MODULE_FORCCE_UNLOAD) : " 위의옵션이활성화되었다면 rmmod -f module의명령어를통해로딩한모듈이안전하지않다고생각했을때강제로종료할수있다." 와같은유용한것들을여러분들의커널에서사용할수있게끔하기위해여러분의커널을재컴파일할것을추천했다. 위와같은옵션의설정은여러분들의수고를덜어주고, 모듈의개발과정에서의커널의재부팅횟수를획기적으로줄여줄것이다. 반면에이전에컴파일한커널이나, 공통적인 Linux 배포판들을통해딸려온것들과같은이미컴파일된커널들에여러분들의모듈을집어넣고싶을경우가생길것이다. 위와같은상황에서여러분들은재컴파일이허용되지않은커널혹은재부팅하고싶지않은머신에서실행중인커널에여러분들의모듈을컴파일하고탑재할수있다. 만약여러분이위와같은경우의필요성을느끼지못한다면, 이부분은그냥넘기고필요할때다시공부해도좋다. 이제, 만약여러분이커널 source tree 를설치해서여러분들의커널모듈컴파일에사용하고커널에탑재하길 원한다면대부분의경우에아래와같은에러를만나게될것이다. insmod: error inserting 'poet_atkm.ko': -1 Invalid module format 좀더명확한정보는 /var/log/messages 에기록된다. Jun 4 22:07:54 localhost kernel: poet_atkm: version magic '2.6.5-1.358custom 686 REGPARM 4KSTACKS gcc-3.3' should be '2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3' 위를해석해보면, 여러분들이탑재하고싶은모듈의 version string ( 정확히말해 version magic) 이여러분의커널과일치하지않아탑재가허용되지않았다 라는에러메세지이다. 명백히 version magic은 vermagic으로시작하는 static string의형태로 module obeject에저장되어있다. 주어진모듈의 version magic과다른문자들을조사하기위해 modinfo module.ko 명령어를사용하자. [root@pcsenonsrv 02-HelloWorld]# modinfo hello-4.ko license: GPL author: Peter Jay Salzman <p@dirac.org> description: A sample driver vermagic: 2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3 depends: 위와같은경우를극복하기위해우리는 --force-vermagic과같은명령어를사용해야하지만이와같은해결책은잠재적으로안전하지못하고일반적으로모듈제작에서허용되지않는다. 결과적으로볼때, 컴파일된커널과동일한환경에서우리의모듈을컴파일하는것이해결책이된다는말이다. 이부분에대해선이챕터의나머지부분의주제로두겠다. 무엇보다도, 현제커널과동일한버전의커널 source tree 가사용가능하도록하자. 그리고나서우리들의커널의 설정에사용되는파일을찾자. (/boot 폴더에 config-2.6.x 와같은형태로있을것이다.) 그리고위의설정파일을 여러분의커널 source tree 에복사하자. ( cp /boot/config-'uname-r' /usr/src/linux-'uname-r'/config. ) 다시이전의에러메세지에초점을맞추어보자. : 주어진설정파일의 version magic 부분을자세히살펴보자. 심지어두가지의설정파일이정확하게일치할경우도있을것이다. 그러나아주조그마한 version magic의차이도있을것인데, 이러한작은차이는모듈이커널로탑재되는것을막기에충분한차이이다. 이러한자그마한차이를 custom 이라하는데모듈의 version magic 엔존재하지만커널엔존재하지않는부분이다. ( custom은 makefile이만들어내는경우인데, 원본커널을바탕으로수정을가할경우에발생한다.) 그리고우리의 /usr/src/linux/makefile을 Linux Kernel Module Programming Guide 14
살펴서명시된버전정보가우리가사용하는커널의버전정보와일치하는지를확인하자. 예를들어여러분의 makefile 은다음과같이시작될것이다. VERSION = 2 PATCHLEVEL = 6 SUBLEVEL = 5 EXTRAVERSION = -1.358custom... 이러한경우에, 여러분은 EXTRAVERSION의값을 -1.358로변경해야한다. 우리는여러분들의커널컴파일에사용된 makefile을백업할것을권장한다. (/lib/modules/2.6.5-1.358에위치해있고, 간단히 cp /lib/modules/'uname-r'/build/makefile /usr/src/linux-'uname-r' 명령어를통해백업을수행할수있다.) 추가로, 만약여러분이이전의 makefile 로커널을시작하였다면, make 명령어를다시실행하거나혹은직접적으로 /usr/src/linux-2.6.x/include/linux/version.h 헤더파일의 UTS_RELEASE를수정하거나정상적으로설정된파일을덮어씌우는방법도있다. 이제 make 명령어를통해 object, version header 와설정을업데이트하자 : [root@pcsenonsrv linux-2.6.x]# make CHK include/linux/version.h UPD include/linux/version.h SYMLINK include/asm -> include/asm-i386 SPLIT include/linux/autoconf.h -> include/config HOSTCC scripts/basic/fixdep HOSTCC scripts/basic/split-include HOSTCC scripts/basic/docproc HOSTCC scripts/conmakehash HOSTCC scripts/kallsyms CC scripts/empty.o... 만약여러분이커널을컴파일해야하는부분에대해서망설여진다면, CRTL+C 를통해 build process 를중단할수 있다. (SPLIT 라인이나타나기전에 -> 이미여러분이필요한파일이준비되어있다. (version.h. 헤더파일 ) ) 이제여러분들의모듈을다시컴파일해보자. 다른에러없이정상적으로컴파일되어커널에탑재될것이다. Linux Kernel Module Programming Guide 15
Chapter3. Preliminaries 3.1 Modules vs Programs 3.1.1 How modules begin and end 프로그램은보통메인함수로부터시작되고, 여러가지의명령어들을실행하고종료한다. 하지만커널모듈은이와는조금다른방식으로동작한다. 커널모듈은항상 _init_module 혹은 module_init 와같은함수의호출로시작된다. ( 우리는위와같은함수를 entry function 이라부른다. ) 위의함수는모듈의시작을의미하는데, 기능적으로모듈이어떤역할을하는지, 그리고우리가필요로할때모듈이작동될수있도록커널을설정하는역할을한다. 위의것들이수행된후, 모듈이제공하는코드들이커널로부터사용되기전까지모듈은아무것도하지않고, 위의 entry function은종료된다. 모듈은 cleanup_module 혹은 module_exit 의이름으로사용자가구체화한함수의호출을통해종료된다. 이와같은 종료함수는 entry function 이수행한모든기능들을수행하기전의상태로되돌린다. (Undo) 즉, entry function 이 register 한모든기능들을 unregister 하는것이다. 모든모듈들은위의두가지, entry 와 exit 기능을하는함수를가지고있어야한다. 위의두가지기능을하는함수를 구체화하는다양한방법이존재하지만, 우리는 "entry function" 과 exit function" 라는용어를사용하도록하자. ( 각각의용어는 init_module 과 cleanup_module 함수를의미한다.) 3.1.2 Functions available to modules 프로그래머들은항상자신들이정의하지않은함수들을사용한다. printf( ) 함수가좋은간단한예가될수있겠다. 우리는이러한라이브러리함수들을사용하여프로그램을작성한다. 사실위와같은함수들은 linking 과정을거치기전까지우리들의프로그램에실제로호출되지않는다. (linking 과정은 printf( ) 와같은라이브러리함수들을실제로우리가작성한프로그램에연결시켜서호출하는과정을말한다.) 커널모듈은이런부분에서또일반적인프로그램과다르다. 우리가작성한 "hello world" 모듈을예를들어보자면, printk( ) 라는라이브러리함수를사용했지만실제로 I/O Library를 include 하지는않았다. 그이유는모듈은 insmod가실행되는중에 printk( ) 와같은함수들의 symbol이결정되는 object file 이기때문이다. symbol 들의함수적정의는커널스스로가제공한다. 즉, 우리가사용한외부함수 ( 라이브러리함수들을의미 ) 는커널자체에서제공되는함수인것이다. 만약여러분이커널로부터추출되는이러한 symbol 들에대하여궁굼하다면, /proc/kallsyms 에서확인할수있다. 일반적인라이브러리함수와 system call은서로다르다는것을기억하자. 라이브러리함수들은조금더높은수준의함수, 즉 system call 보다조금더편하게프로그래머들이사용할수있고완전한사용자의환경에서작동되기때문에 system call 보다높은수준의함수라말할수있다. 반면에 system call은커널자체에서제공되며커널과유저환경의중간단계에서실행된다. printf( ) 함수는것보기엔매우일반적인수준의출력함수로보이지만, 실제로는낮은수준의 system call인 write( ) 를통해데이터를 string으로 format화하고출력한다. 실제로 printf( ) 함수를사용한매우간단한소스코드를아래에구현해보았다. 이를통해실제로어떠한 system call 이사용되는지확인해보자. Linux Kernel Module Programming Guide 16
#include <stdio.h> int main(void) printf("hello"); return 0; gcc -Wall -o hello hello.c 명령어를사용해컴파일하자. 그후 strace./hello 명령어로실행하여보자. 놀랍지 않은가? 출력되는모른라인은 printf( ) 함수를사용할때의호출되는 system call 을의미한다. strace 명령어는 프로그램이만들어낸 system call 과, 리턴되는인자들이무엇인가에관해상세히출력하여준다. 그래서실제로 프로그램이어떠한파일에접근하는지추측하는것을가능케하는멋진툴이다! 끝으로여러분은 write(1, 'hello', 5hello) 라는라인을볼수있는데, 이것은 printf( hello"); 라인의감춰진또다른라인이다. 여러분이 fopen, fputs, fclose 와같은라이브러리함수를사용하면서부터위와같은 write 호출에대해익숙치않을수있을것이다. 만일그렇다면, man 2 write 로 write 의매뉴얼을살펴보자. write 의 2 번쩨섹션은 system call 에관한 (kill ( ) 과 read( ) ) 내용을담고있다. 3 번쩨섹션은 library call 에관한내요을다루고있는데 cosh( ) 와 random( ) 등의좀더 여러분이친숙한주제들을다르고있다. 여러분은앞으로짧게나마커널의 system call 을여러분이작성한모듈로바꾸는작업을할것이다. 크래커들은종종 트로이얀과같은백도어프로그램을위해위와같은방법을사용하기도한다. 하지만여러분은 Tee hee, that tickles!" 와같이좀더온순한것들을위해여러분의모듈을작성할것이다. 3.1.3 User Space vs Kernel Space 커널은자원에관련되어있다. 그자원이비디오카드, 하드드라이브혹은심지어메모리를의미하기도한다. 프로그램들은종종이러한자원을위해경쟁한다. 우리가어떤문서를저장했다면, updateb가저장한내용을데이터베이스에업데이트했을것이다. 이때여러분의 vim과 updateb는동일한하드드라이브를동시에사용하게된다. 커널은모든것들이올바르고깔끔하게정렬되길원하므로우리와같은유저들에게위와같은상황에서자원에대한접근권한을부여하지않을것이다. 이러한경쟁상황을없애기위해 CPU는각각의상황에서다른모드로사용이가능하다. 각각의모드는각자가하고싶은일들을위해서로다른자유의정도를가진다. 인텔 80386 구조는 4개의모드를가지는데각각을 ring이라부른다. 유닉스는오직두개의 ring을가지는데 ( 두개의모드를가지는데 ) 가장높은 ring, 즉 superviser 모드 (ring 0) 와가장하부의링, user 모드 (ring 3) 을의미한다. 다시 system call과라이브러리함수의이야기로돌아가보자. 일반적인경우에여러분은 user mode에서라이브러리함수들을사용할것이다. 그리고사용된라이브러리함수는여러개의 system call을호출할것이며이러한 system call은라이브러리함수의뒤에서실행될것인데커널의한부분에서 superviser mode로실행이될것이다. 호출된 system call은각자의작업이끝나면다시 user mode로돌아오게된다. 3.1.4 Name Space 여러분이작은 c 프로그램을작성할때, 여러분은가독성을보장하는변수를사용할것이다. 만일반면에, 여러분이다른사람의전역변수에해당하는어떠한전역변수명을다시사용한다면서로의변수이름때문에충돌이일어나프로그램에큰문제가될것이다. 만약여러분이쉽게구별할수없는이상한이름의전역변수가많은프로그램을작성중이라면여러분은 namespace pollution 이라는문제를가진프로그램을작성하고있는것이다. 큰프로젝트에서변수에대한여분의이름에대해기억하고, 각자의변수명과심볼명을만드는틀에대한고민이반드시필요하다. 커널코드를작성할때도, 심지어그것이작디작은모듈이라할지라도작성된모듈은전체의커널로 linking 될것인데, 위에서언급한부분을고려한다면이것은분명히주목할만한문제가된다. 위와같은문제점을피하는가낭좋은방법은여러부만의잘정의된 prefix를사용하고모든변수를 static 으로선언하는것이다. 전통적으로커널의 prefix는소문자로정의되었다. 여러분이모든변수들을 static 으로선언하기싫다면여러분의 symbol table을정의해커널에등록하는방법이있다. 우리는이것에대해차후에다루어볼것이다. Linux Kernel Module Programming Guide 17
/proc/kallsyms 파일은커널에서사용하고있는모든 symbol 에관한데이터를가지고있기때문에위를통해서사용 가능한 symbol 들을알아낼수있다. 3.1.5 Code Space 메모리관리는매우복잡한주제이다. 우리는메모리관리에대해전문가가아니지만, 실제모듈작성을위해 몇가지의사실에대해알고있어야한다. 만일여러분이 segfault의실제의미에대한생각을해본적이없다면포인터가실제로메모리의위치를가리키지않는다는사실에대해놀랄것이다. 프로세스가만들어졌을때, 커널은실제메모리를프로세스에게할당한다. ( 우리가알고있는코드의실행, 변수, 스택, 힙등의것들을위해 ) 메모리는 0x00000000 으로시작해필요한만큼주소를확장한다. ( 여기서는 " 내컴퓨터는 4GB의메모리를가지고있는데, 무한정확장될수있다는말인가? 라고말하는것이아니다. 어떤프로세스를위해메모리가 0x00000000 이라는물리적주소로부터시작된다는그사실이중요하고그것을말하는것이다.) 프로세스를위해할당된메모리주소는서로겹치지않는다. ( 겹치게되면심각한문제가발생하게될것이다.) 다시풀어서말하면, 각각의프로세스가실제로 0xbffff978 이라는메모리주소를접근하지만, 실제접근하는물리적인메모리의위치는다르다는것이다. 즉, 프로세스는각각을위해할당된실제물리적메모리의접근을위해 offset의개념으로위와같은주소를사용하는것이다. 대부분의경우에서각각의프로세스는다른프로세스의메모리영역에접근하지못한다. 하지만그러한방법들이있는데그것들은차후에다루도록하자. 커널은위의논리에맞게자신만의메모리영억을가지고있다. 모듈은커널에동적으로탑재되고제거될수있으므로 (semi-autonomous라고할수있다 :: 사용자의요구에의해서탑재혹은제거되므로 ) 각각의모듈들은자신만의메모리보다커널의 code space를공유한다. 그러므로, 여러분의모듈이 segfault 상태를발생시킨다면우리들의커널역시 segfault의상태에빠지게된다. 그리고만약여러분이이러한상태에서데이터를저장한다면커널의데이터는손상될것이다. 위의상태는실제로엄청나게나쁜상황이므로, 여러분은위와같은상황이발생하지않도록매우주의해야한다. 그런데, 단일커널을사용하는커널이라면위와같은상황이분명발생할수있다. 그리고 microkernel 이라불리는 것이있는데위의커널은각자만의작은 codespace 를가지고있다. GNU hurd 와 QNX Neutrino 가 microkernel 의 한예이다. 3.1.6 Device Drivers 드라이버는모듈의종류중하나인데, 시리얼포트나 TV 수신가트와같이하드웨어를위한기능을제공한다. 유닉스에서각각의하드웨어는 /dev 폴더에위치해있는파일이름으로나타내지고, 그파일들은하드웨어와의의사소통수단을제공한다. 그리하여, 드라이버는유저프로그램의안보이는곳에서하드웨어와의통신이가능하도록하는역할을한다. 3.1.6.1 Major and Minor Numbers 몇개의장치파일들을살펴보자. 아래에 3 개의 IDE hard drive 에대한정보가나타나있다. # ls -l /dev/hda[1-3] brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1 brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3 Linux Kernel Module Programming Guide 18
컴마이후의숫자가보이는가? 첫번째번호는장치의 major number 을나타낸다. 두번째번호는 minor number을나타낸다. major number는여러분에게어떤드라이버가하드웨어에접근하기위해사용되는가를나타낸다. 각각의드라이버는고유의 major number가부여되어있다. 만일모든 major number가같다면해당 major number를가진장비들은모두같은드라이버에의해컨트롤됨을의미한다. 위의예에서, 모든 major number 가 3이므로, 하드드라이브는모두같은드라이버에의해컨트롤됨을알수있다. minor number 는같은드라이버에의해컨트롤되는장치들을구별하기위한용도로사용된다. 위의예로 돌아가서, 비록모든하드들이같은드라이버에의해컨트롤되지만각자가고유의하드웨어로구분됨을알수있다. 장치들은두가지의타입 : character device 와 block device로나뉜다. block device는요청에대한버퍼가존재해서각각의요청에대해가장최적의요청을고를수가있다. 이부분은저장장치에서매우중요한부분인데읽거나쓰는요청이들어왔을때현제의섹터위치보다먼쪽이아닌가까운쪽을먼저처리하는것이더욱빠르게일을처리해낼수있기때문이다. 또다른차이점은 block device는 input에대해 block 단위의 output ( 각각의장비에따라 block 의크기는다르다. ) 을출력하지만, character device는많거나혹은단순히몇바이트정도의출력물을낼수있다는것이다. ( block device보다결과물의단위가유동적이다. ) 현제존재하는대부분의장치들은 character device 인데고정된 block size 단위의출력물이나버퍼링같은동작이필요하지않기때문이다. 여러분은어떠한디바이스파일이 character device를위한것인지, 아니면 block device를위한것인지에대해 ls -l 명령어의출력물의첫번째문자를보고알수있는데, b' 는 block device를, 'c' 는 character device를의미한다. 위의예는, 모든디바이스가 block device인데, 아래의예선모든디바이스가 character device이다. ( 시리얼포트를의미함 ) # ls -l /dev/hda[1-3] brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1 brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3 디바이스에할당된 major number 들을확인하고싶다면, /usr/src/linux/documentation/devices.txt 를확인하면된다. 시스템에설치된모든디바이스파일들은 mknod 명령어를통해생성되었다. 우리가 major number로 12, minor number로 2를가지는 coffee 라는이름의 character device를생성하기위해선간단하게 mknod /dev/coffee c 12 2 라는명령어를입력하면된다. 여러분이직접디바이스파일을 /dev 폴더에넣을필요는없다. 보통의경우에 /dev 폴더에디바이스파일을넣지만, 여러분이테스트목적으로디바이스파일을만들었을경우엔여러분이커널모듈의컴파일작업을하는폴더에집어넣어도관계없다. ( 즉, 어떠한폴더에서작업을하더라도관계없다.) 다만, 작성이완료된디바이스파일만올바른폴더에서관리하면된다. 위의주제에서명확하게다뤄지지않았던몇가지의사항들에대해조금더구체적으로언급을해보자. 디바이스파일에어떠한장치가접근하려할때, 커널은디바이스파일의 major number를사용해어떤드라이버가위의디바이스파일에대한접근을제어하기위해사용되는지판단한다. 이말은, 커널이 minor number을사용하지않거나, 혹은이숫자가커널에아무런의미가없는숫자라고말할수있다. 유일하게드라이버자신만이 minor number를사용해여러가지다른하드웨어들을구별한다. 그리고한편, 여기서저자가말한하드웨어란것은 PCI 카드와같은것이아니라조금은추상적인상태의하드웨어 자체를의미한다. 아래애두가지디바이스파일을살펴보자. % ls -l /dev/fd0 /dev/fd0u1680 brwxrwxrwx 1 root floppy 2, 0 Jul 5 2000 /dev/fd0 brw-rw---- 1 root floppy 2, 44 Jul 5 2000 /dev/fd0u1680 여러분은위의두개의디바이스가 block device 이며, 같은드라이버를통해컨트롤됨을알수있다. 또한위의장치는플로피디스크를의미함을알수있다. 여러분이하나의플로피디스크를가지고있다고하더라도위와같은결과가나올것이다. 왜일까? Linux Kernel Module Programming Guide 19
하나는플로피드라이브가 1.44MB의저장소임을의미한다. 나머지의파일은 1.68MB의 'superformatted' 플로피디스크를의미하고, 1.44MB의 'formatted' 플로피드라이브보다조금더많은양의데이터를저장할수있다. 위의예로부터우리는두개의다른 minor number를가지고있는하나의플로피디스크드라이브라는사실을알수있다. 그래서저자가조금은추상적인의미의하드웨어라는말을위에서언급한것이다. Linux Kernel Module Programming Guide 20
Chapter4. Character Device Files 4.1 Character Device Drivers 4.1.1 The file_operations Structure file_operations 구조체는 /linux/fs.h 에정의되어있고, 함수를가리키는포인터들을가지고있는데위의함수들은 디바이스에서수행되는다양한기능을구현한것이다. 구조체의각각의부분은드라이버가정의한어떤함수들의 주소에대응되는데이는디바이스에요청된여러가지의작업들을다루기위해서이다. 예를들어, 모든 character driver 는디바이스로부터읽는역할을하는함수를필요로한다. file_operations 구조체는 이러한기능을수행하는함수들의주소를가지고있다. 아래에커널 2.6.5 버전의 file_operations 구조체를살펴보자. struct file_operations struct module owner; loff_t(llseek) (struct file, loff_t, int); ssize_t(read) (struct file, char user, size_t, loff_t ); ssize_t(aio_read) (struct kiocb, char user, size_t, loff_t); ssize_t(write) (struct file, const char user, size_t, loff_t ); ssize_t(aio_write) (struct kiocb, const char user, size_t, loff_t); int (readdir) (struct file, void, filldir_t); unsigned int (poll) (struct file, struct poll_table_struct ); int (ioctl) (struct inode, struct file, unsigned int, unsigned long); int (mmap) (struct file, struct vm_area_struct ); int (open) (struct inode, struct file ); int (flush) (struct file ); int (release) (struct inode, struct file ); int (fsync) (struct file, struct dentry, int datasync); int (aio_fsync) (struct kiocb, int datasync); int (fasync) (int, struct file, int); int (lock) (struct file, int, struct file_lock ); ssize_t(readv) (struct file, const struct iovec, unsigned long, loff_t ); ssize_t(writev) (struct file, const struct iovec, unsigned long, loff_t ); ssize_t(sendfile) (struct file, loff_t, size_t, read_actor_t, void user ); ssize_t(sendpage) (struct file, struct page, int, size_t, loff_t, int); unsigned long (get_unmapped_area) (struct file, unsigned long,unsigned long, unsigned long, unsigned long); ; 드라이버가구현하지않는몇몇기능들도존재한다. 예를들어, 비디오카드를다루는드라이버는 directory 구조체를읽어올필요가없다. file_operations 구조체에서도이에대응하는기능의진입점은 NULL로셋팅되어야할것이다. 아래애구조체를조금더편하게할당하는 gcc extension을살펴보자. 여러분은최근의드라이버들에서이러한형태들을발견할수있을것이다. struct file_operations fops = read: device_read, write: device_write, open: device_open, release: device_release ; 그러나 C99 라는다른방법도있는데, GNU extension 이전에선호되던방식이다. 저자가사용한 gcc 의버전은 2.95 인데, 새로운 C99 문장들을허용한다. 여러분은다른어떤이가여러분의드라이버를복사하길원한다면아래와 같은문장을사용해야한다. struct file_operations fops =.read = device_read,.write = device_write,.open = device_open,.release = device_release ; 좀더의미가분명해졌다. 그리고구조체의멤버중명확히하지않은모든멤버들은 gcc 에의해 NULL 로초기화될것이다. Linux Kernel Module Programming Guide 21
4.1.2 The file structure 각각의디바이스들은 linux/fs.h 에정의된커널의 file 구조체로표현될수있다. 하지만위의구조체는커널수준에서사용되므로유저레벨의사용환경에서는확인할수없음에유의하자. glibc에정의되고커널공간의함수에서사용되지않는 FILE과는다른것이다. 그리고위에서도볼수있듯이 file 이라는이름은잘못지어졌다. 위의 file은디스크상의파일이아니라추상적으로파일을 ' 여는 ' 것을나타낸다. file 구조체의객체는보통 flip 이라불린다. 그리고여러분은 struct file file 이라는구조체역시확인할수있을 것이다. 햇갈리지않도록주의하자. 이제 file 의정의를살펴보자. struct dentry 와같이디바이스드라이버가사용하지않는많은진입점들은무시해도 좋다. 왜나면드라이버는 file 에포함된구조체들만사용할뿐, 직접적으로 file 에접근하지않기때문이다. 4.1.3 Registering A Device 이미언급하였지만, character device 들은주로 /dev 파일에있는디바이스파일들을통해접근할수있다. 디바이스 파일의 major number 는어떤드라이버가어떤디바이스파일을다루는지나타내준다고했고, minor number 는 사용중인장치에서드라이버가하나이상의디바이스를관리할때의미가있다고했다. 여러분의시스템에드라이버를설치한다는것은커널에등록을해야한다는것을의미한다. 이말은모듈의 초기화동안 major number 를드라이버에할당한다는것과같은의미이다. 여러분은 linuxx/fs.h 에정의되어있는 register_chrdev 함수를통해위와같은동작을수행할수있다. int register_chrdev(unsigned int major, const char name, struct file_operations fops); 인자중에서 unsigned int major 는여러분이요청한 major number를의미하고, const char name 은 /proc/devices 에나타날디바이스의이름을의미하며 struct file_operations fops 는여러분의드라이버를위한 file_operations 테이블을가리키는포인터를의미한다. 이함수의리턴값중음수의의미는드라이버의등록이정상적으로수행되지않았음을의미한다. 우리가 register_chrdev 으로 minor number를넘기지않았음을떠올려보자. 그이유는커널은 minor number를사용하지않고단지드라이버자신의구별을위해서만사용되기때문이였다. 이제질문은 어떻게하면중복되지않는 major number를알아낼수있을까? 이다. 가장쉬운방법중하나는 Documentation/devices.txt 파일을살펴서사용되지않는 major number를알아내는것인데, 이방법은 여러분이선택한번호가다른장치에의해서조금나중에등록되지않는다 라는점을확실히보장할수없기때문에좋은방법이아니라고말할수있다. 가장좋은방법은커널의 dynamic major number를확인하는것이다. 여러분이 register_chrdev 함수를통해커널에 0을전달하면, 커널은동적으로할당한 major number를리턴할것이다. 아직남아있는문제점은여러분이 major number를알지못한다면디바이스파일을만들수없다는점이다. 이문제에대해여러가지해결책들이있는데, 그중하나는드라이버가새로할당한번호를출력할수있다는점을이용해여러분이디바이스파일을직접만드는것이다. 두번째는새로등록된디바이스는 /proc/devices 에엔트리를가지고있을것이다. 그정보를활용해직접디바이스파일을제작하거나혹은쉘스크립트를작성해파일을읽고디바이스파일을만드는것이다. 저자가소개할마지막세번째방법은디바이스등록후 mknod system call을이용해디바이스파일을만들고 cleanup_module를호출할동안지우는것이다. Linux Kernel Module Programming Guide 22
4.1.4 Unregistering A Device 커널모듈이메모리에올라가있는중에다시그커널모듈을언로드하는것을허용하는건매우위험한생각이다. 만약디바이스파일을프로세스가사용중일때여러분이적절한함수를특정메모리위치에호출하는파일을통해여러분이커널모듈을제거한다면어떤결과가일어날까? 운이좋다면, 다른어떤코드가그위치에로딩되어있지않아서다행히에러메시지를하나쯤보고끝날수도있지만, 운이없는경우라면, 어떤다른커널이현커널내부의어떤함수의한중간의자리로로딩됨을의미하고, 이것에대한결과는상상할수없을만큼시스템에치명적일것이다. 일반적으로, 여러분이뭔가를허락하고싶지않을경우엔함수를통해에러코드를리턴할것이다. 물론 cleanup_module 함수는 void 함수이기때문에에러코드를리턴할수는없다. 하지만시스템엔어떤프로세스가여러분의모듈을사용하는가에대해기록하는부분이있는데, 여러분은그정보를 /proc/modules 의세번쩨필드를통해확인할수있다. 만약그필드가 0 이라면, rmmod 는실행되지않을것이다. 이렇게확인하는경우에여러분이 cleanup_module 에대한필드를체크할필요가없음을기억하자. 그이유는위의필드값의확인은 linux/module.c 에정의되어있는 sys_delete_module 함수의호출을통해수행될것이기때문이다. 여러분은또한위의값의변경을위해매번위와같은파일에직접적으로접근할필요가없다. 위의기능을위해 linux/module.h 에몇가지함수가정의되어있기때문이다. l try_module_get(this_module): Increment the use count. l module_put(this_module): Decrement the use count. 위의필드값을정확하게유지하는것은매우중요한일이다. 만약여러분이위의필드값을정확하게유지하거나 사용하지못한다면, 여러분은모듈을절대로언드로하지못할것이다. 이문제는곧여러분이실제모듈들을 개발하는과정에서실질적인문제로다가올것이다. 4.1.5 chardev.c 아래에있는예제코드는 chardev 라고불리는 character device를만드는코드이다. 여러분은이장치의디바이스파일을 cat을사용하거나어떤프로그램을통해열수가있고, 드라이버는위의디바이스파일이몇번이나사용됬는지 ( 읽기를목적으로 ) 그에대한정보를기록할것이다. 기본적으로디바이스파일을 echo "hi" >/dev/hello" 와같은형식으로직접적으로수정혹은쓸수있는기능은지원되지않지만 ( 루트권한이없는이상쓰기가불가능하다 ) 이러한시도를할경우에위와같은기능을지원하지않는다고알려주는메시지가출력될것이다. 우리가버터를통해읽는데이터가보이지않는다고걱정할필요는없다. 이가이드북에선그렇게깊은내용을다루지는않을것이고단지우리가받을수있는형태로간단한데이터와메시지를읽고출력하는정도의실습을할것이기때문이다. Example 4-1. chardev.c chardev.c: Creates a read-only char device that says how many times you've read from the dev file #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <asm/uaccess.h> for put_user Prototypes - this would normally go in a.h file Linux Kernel Module Programming Guide 23
int init_module(void); void cleanup_module(void); static int device_open(struct inode, struct file ); static int device_release(struct inode, struct file ); static ssize_t device_read(struct file, char, size_t, loff_t ); static ssize_t device_write(struct file, const char, size_t, loff_t ); #define SUCCESS 0 #define DEVICE_NAME "chardev" Dev name as it appears in /proc/devices #define BUF_LEN 80 Max length of the message from the device Global variables are declared as static, so are global within the file. static int Major; Major number assigned to our device driver static int Device_Open = 0; Is device open? Used to prevent multiple access to device static char msg[buf_len]; The msg the device will give when asked static char msg_ptr; static struct file_operations fops =.read = device_read,.write = device_write,.open = device_open,.release = device_release ; This function is called when the module is loaded int init_module(void) Major = register_chrdev(0, DEVICE_NAME, &fops); if (Major < 0) printk(kern_alert "Registering char device failed with %d\n", Major); return Major; printk(kern_info "I was assigned major number %d. To talk to\n", Major); printk(kern_info "the driver, create a dev file with\n"); printk(kern_info "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); printk(kern_info "Try various minor numbers. Try to cat and echo to\n"); printk(kern_info "the device file.\n"); printk(kern_info "Remove the device file and module when done.\n"); return SUCCESS; This function is called when the module is unloaded void cleanup_module(void) Unregister the device int ret = unregister_chrdev(major, DEVICE_NAME); if (ret < 0) printk(kern_alert "Error in unregister_chrdev: %d\n", ret); Methods Called when a process tries to open the device file, like "cat /dev/mycharfile" Linux Kernel Module Programming Guide 24
static int device_open(struct inode inode, struct file file) static int counter = 0; if (Device_Open) return -EBUSY; Device_Open++; printf(msg, "I already told you %d times Hello world!\n", counter++); msg_ptr = msg; try_module_get(this_module); return SUCCESS; Called when a process closes the device file. static int device_release(struct inode inode, struct file file) Device_Open--; We're now ready for our next caller Decrement the usage count, or else once you opened the file, you'll never get get rid of the module. module_put(this_module); return 0; Called when a process, which already opened the dev file, attempts to read from it. static ssize_t device_read(struct file filp, see include/linux/fs.h char buffer, buffer to fill with data size_t length, length of the buffer loff_t offset) Number of bytes actually written to the buffer int bytes_read = 0; If we're at the end of the message, return 0 signifying end of file if (msg_ptr == 0) return 0; Actually put the data into the buffer while (length && msg_ptr) The buffer is in the user data segment, not the kernel segment so "" assignment won't work. We have to use put_user which copies data from the kernel data segment to the user data segment. put_user((msg_ptr++), buffer++); length--; bytes_read++; Most read functions return the number of bytes put into the buffer return bytes_read; Called when a process writes to dev file: echo "hi" > /dev/hello static ssize_t device_write(struct file filp, const char buff, size_t len, loff_t off) printk(kern_alert "Sorry, this operation isn't supported.\n"); return -EINVAL; Linux Kernel Module Programming Guide 25
4.1.6 Writing Modules for Multiple Kernel Version 커널이프로세스에대한정보를출력하는가장주요한방식인 system call 은일반적으로여러커널버전에서똑같게유지되어왔다. 어떠한새로운시스템이추가되더라도, 이전의시스템은본래하던방식과동일하게동작할것이다. 새로운커널버젼이일반적으로실행되던프로세스의실행을방해하지않는이러한방식은시스템간호환을위해필수적이다. 대부분의경우에서디바이스파일은커널의버전이바뀌더라도동일하게유지된다. 반면에커널내부의인터페이스들은버전에따라달라지게된다. 리눅스커널버전은 stable version ( 배포판, 안정된버전 :: n.$ 짝수번호 #.m) 과 development version ( 개발버전, 테스트중인버전 :: n.$ 홀수번호 $.m) 으로나뉜다. 개발버전은새로운개념을포함하고있는데, 당연히새로적용되는기능들에대해여러가지테스트목적으로배포되어다음버전을위해지속적으로재구현된다. 결과적으로여러분들은지속적으로바뀌는개발버전들이동일한내부인터페이스를가질것이라는것을보장할수가없게된다. 물론이러한가이드북역시매번버전이바뀔때마다업데이트될수없는이유도여기에있다. 반면에배포판에서는, 내부적으로동일한커널인터페이스를가질것이라고생각해도된다. 커널버전마다여러가지차이점이있고, 여러분이여러가지커널버전을지원하는모듈을작성하고싶다면, 여러분은우리가흔히말하는조건부컴파일지시자 (c에서 #if, #ifdef 와같은 ) 를사용해서코드를작성해야한다. 이렇게하는이유는 LINUX_VERSION_CODE와 KERNEL_VERSION 매크로의비교를위해서이다. 커널버전 a / b / c 에서는위의매크로버전이 $ 2^(16)a + 2^(8)b + c $ 일것이다. 반면에이가이드의이전버전에서는이러한하위호환성을위한코드작성방법에대해세밀하게다루었지만, 이번엔그러한내용을다루지않았다. 모듈작성에관심이있는사람은자신의커널버전에맞는 LKMPG 가이드북을알아서찾아볼것이다. 그리하여우리도커널과비슷하게 LKMPG의업데이트를하기로결정했다. ( 물론배포판커널의세부버전차이까지는고려하지않을것이다.) 우리는 LKMPG의 versioning을위해 patchlevel을사용할것이고, 그렇다면 LKMPG version 2.4.x 는커널 2.4.x를, LKMPG version 2.6.x 는커널 2.6.x 의내용을다루게될것이다. 또한각각 LKMPG의버전에맞추어여러분의커널을업데이트하는등의준비를할것을당부하겠다. Linux Kernel Module Programming Guide 26
Chapter5. The /proc File System 5.1 The /proc File System 리눅스에는커널과커널모듈을위한새로운매커니즘이추가되었는데, 이는프로세스들에게정보를전달하기위한것이다. 이를 /proc 파일시스템이라고하는데, 원래프로세스정보에대해쉽게접근하기위해제작되었고최근엔커널에서 /proc/meminfo 의메모리사용통계내용이나 /proc/modules 와같이모듈의리스트들에대한정보와같이사용가자관심을가질만한거의대다수의정보를다루는데사용된다. proc 파일시스템의사용방법은디바이스드라이버들과상당부분유사하다. 핸들러함수들을 ( 우리가배울 proc 파일시스템의구조체는오직한개의포인터만을제공하는데, 이것은사용자가 /proc 파일을읽으려고할때호출된다.) 조작하기위한포인터를포함해 /proc 파일이필요로하는모든정보를가진구조체가만들어진다. 그리고 init_module 이커널에위의구조체를등록하고 cleanup_module을이용해제거한다. 우리가 proc_register_dynamic을사용하는이유는우리가실제로사용되는 inode 번호를정하기싫어서가아니라커널스스로 inode 번호로인한충돌을방지하기위해서이다. 일반적인파일시스템들은일반적인메모리 (/proc 이위치한 ) 보다디스크에위치하는경우가많고그래서 inode 번호는위의파일들의 index-node가위치한디스크의어떤위치를가리키는포인터의역할을한다. inode 는파일의허가에관려된정보와같은파일정보를디스크위치정보혹은파일의데이터가위치한정보와함께가지고있다. 사용자가매번파일이열리고닫힐때마다신경을쓰는경우가없도록 try_module_put 이나 try_module_get 과 같은매크로는존재하지않고, 또한파일이열린후모듈이제거되더라도그에따르는결과를막을방법역시 존재하지않는다. 아래에 /proc 파일을어떻게사용하는것인가에대한간단한예를실었다. 아래의예는 /proc 파일시스템을위한 HelloWorld와같은간단한예인데, ( 본문에서는 Hello World를보통명사처럼사용함..) 세부분으로나누어져있다 :: /proc 폴더에 init_module 함수를포함한 helloworld 파일을만들고, procfs_read 함수를통해 /proc/helloworld 파일이사용중일때값을리턴하는부분, 그리고 cleanup_module을사용해 /proc/helloworld 파일을삭제하는부분이다. 매번 /proc/helloworld 파일이사용될때마다, procfs_read 함수가호출될것이다. 이함수의인자중첫번째와세번째인자값은매우중요한데, 첫번째인자는버퍼를, 세번째인자는오프셋을의미한다. 버퍼의내용은위파일을사용할어플리케이션 (cat과같은 ) 으로들어가게될버퍼값이고, 오프셋은사용중인파일에서의위치를의미한다. 만약위의함수 (procfs_read) 의리턴값이 null 이아니라면, 또다시호출될것이다. 즉, 리턴값에주의해서위의함수를사용해야한다는것이다. % cat /proc/helloworld HelloWorld! Example 5-1. procfs1.c procfs1.c - create a "file" in /proc #include <linux/module.h> Specifically, a module #include <linux/kernel.h> We're doing kernel work #include <linux/proc_fs.h> Necessary because we use the proc fs Linux Kernel Module Programming Guide 27
int init_module() Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL); if (Our_Proc_File == NULL) remove_proc_entry(procfs_name, &proc_root); printk(kern_alert "Error: Could not initialize /proc/%s\n", procfs_name); return -ENOMEM; Our_Proc_File->read_proc = procfile_read; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 37; printk(kern_info "/proc/%s created\n", procfs_name); return 0; everything is ok void cleanup_module() remove_proc_entry(procfs_name, &proc_root); printk(kern_info "/proc/%s removed\n", procfs_name); 5.2 Read and Write a /proc File 우리는 /proc/helloworld 파일을읽는아주간단한 /proc 파일의예를살펴보았다. (Example 5.1) 또한 /proc 내부에파일을쓰는것역시가능하다. 이땐위의예와같이파일을읽는경우와비슷하게동작하는데, /proc 내부의파일이읽어질때함수가호출된다는것이다. 그러나위의경우와파일의읽기혹은유저로부터넘겨진데이터간엔약간의차이점이존재하는데, 이러한차이점때문에여러분은 copy_from_user 혹은 get_user 함수를통해유저공간의데이터들을커널공간으로 import 해야만한다. copy_from_user 혹은 get_user 함수가존재하는이유는리눅스메모리가단편화되어있기때문이다. ( 다른프로세서는다를수도있지만일단인텔아키텍쳐를기준으로하자.) 이것의의미는포인터자체만으로는메모리의특정한부분을참조할수없고단지메모리세그먼트에위치한부분만참조가가능하며이때문에여러분이어떤부분의메모리세그먼트가사용가능한지알아야만하는이유가된다. 커널을위한메모리세그먼트는단하나만존재하며, 나머지는프로세스들을위해할당된다. 하나의프로세스엔단하나의메모리세그먼트만할당되고사용가능하기때문에, 여러분이실행을위한일반적인프로그램을작성중이라면, 메모리세그먼트에대해걱정할필요가없다. 여러분이커널모듈을작성할때, 일반적으로여러분은시스템에의해자동적으로조정되는커널메모리세그먼트에접근하고싶을것이다. 그러나메모리버퍼의내용이현제작동중인프로세스에서커널로전달될때, 커널함수는프로세스세그먼트안에존재하는메모리버퍼포인터를전달받는다. put_user 와 get_user 매크로 ( 함수 ) 는여러분이메모리에접근할수있도록하는역할을하는데, 이러한함수들은하나의문자만다루며, 여러분은 copy_to_user 와 copy_from_user 함수를통해여러개의문자들을다룰수있다. 여러분이유저공간의데이터를커널공간으로 import 하기위한쓰기함수를위해커널공간에버퍼가제공되는데읽기함수를위해제공되는것이아니라는걸알자. ( 이미데이터가커널에존재하므로버퍼를읽기용으로제공하는건무의미하다.) Example 5-2. procfs2.c procfs2.c - create a "file" in /proc Linux Kernel Module Programming Guide 29
#include <linux/module.h> Specifically, a module #include <linux/kernel.h> We're doing kernel work #include <linux/proc_fs.h> Necessary because we use the proc fs #include <asm/uaccess.h> for copy_from_user #define PROCFS_MAX_SIZE 1024 #define PROCFS_NAME "buffer1k" This structure hold information about the /proc file static struct proc_dir_entry Our_Proc_File; The buffer used to store character for this module static char procfs_buffer[procfs_max_size]; The size of the buffer static unsigned long procfs_buffer_size = 0; This function is called then the /proc file is read int procfile_read(char buffer, char buffer_location, off_t offset, int buffer_length, int eof, void data) int ret; printk(kern_info "procfile_read (/proc/%s) called\n", PROCFS_NAME); if (offset > 0) we have finished to read, return 0 ret = 0; else fill the buffer, return the buffer size memcpy(buffer, procfs_buffer, procfs_buffer_size); ret = procfs_buffer_size; return ret; This function is called with the /proc file is written int procfile_write(struct file file, const char buffer, unsigned long count, void data) get buffer size procfs_buffer_size = count; if (procfs_buffer_size > PROCFS_MAX_SIZE ) procfs_buffer_size = PROCFS_MAX_SIZE; if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) ) return -EFAULT; return procfs_buffer_size; This function is called when the module is loaded Linux Kernel Module Programming Guide 30
int init_module() create the /proc file Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL); if (Our_Proc_File == NULL) remove_proc_entry(procfs_name, &proc_root); printk(kern_alert "Error: Could not initialize /proc/%s\n", PROCFS_NAME); return -ENOMEM; Our_Proc_File->read_proc = procfile_read; Our_Proc_File->write_proc = procfile_write; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 37; printk(kern_info "/proc/%s created\n", PROCFS_NAME); return 0; everything is ok This function is called when the module is unloaded void cleanup_module() remove_proc_entry(procfs_name, &proc_root); printk(kern_info "/proc/%s removed\n", PROCFS_NAME); 5.3 Manage /proc file with standard filesystem 여러분은 /proc 인터페이스를통한 /proc 파일을어떻게쓰고읽는지에대해살펴보았다. 그러나 inodes 를통한 /proc 파일관리가가능하다. 가장흥미로운부분은허가와같은고급기능의사용에있다. 리눅스는파일시스템등록을위한표준화된메커니즘이존재한다. 모든파일시스템이 inode 와파일동작들을다루기위한자신만의함수를가지게된이후로, 위의모든함수, file_operations 구조체의포인터를포함하는 inode_operations 구조체의모든포인터들을관리하고다루기위한특별한구조체가존재한다. /proc 내부에서우리가새로운파일을등록할때, 어떤 inode_operations 구조체가사용될것인지구체화할수있다. inode_operations 는 procfs_read 와 procfs_write 함수의포인터를가지고이쓴 file_operations 구조체의포인터를포함하는구조체이고, 위의메커니즘을우리가사용하는것이다. 또하나흥미로운점은 module_permission 함수인데, 이함수는 /proc 파일과함께프로세스가뭔가를하려할때호출되며, /proc 파일에접근을허용할것인가, 아닌가를결정한다. 위의모듈은기본적으로현사용유저의 uid 와실행을기반을두지만, 우리가원하는어떠한것이라도 ( 어떤프로세스가지금접근하려는파일을사용했는지, 오늘의시스템시간혹은우리가입력한마지막 input value 등 ) 판단의기반으로삼을수있다. 커널에서읽기와쓰기의기본적인역할이반대라는걸아는것은매우중요하다. 읽기함수는 output 을위해사용되는반면쓰기함수들은 input 을위해사용된다. 이러한이유는읽기와쓰기함수는유저의관점에서작업을하기때문이다. - 만약프로세스가무언가를읽으려고한다면, 그의미는커널에게출력을요구하는것이고, 그리고만약프로세스가무엇인가를쓰는행동을한다면, 커널은이것을 input 으로받기때문이다. Example 5-3. procfs3.c procfs3.c - create a "file" in /proc, use the file_operation way to manage the file. Linux Kernel Module Programming Guide 31