리눅스 커널모듈 프로그램 가이드 · web view[1] 스레드 프로세스가 예외다,...

140
리리리 리리리리 리리리리 리리리 Peter Jay Salzman Ori Pomerantz Copyright © 2001 Peter Jay Salzman 2003-04-04 ver 2.4.0 번번 번번 [email protected] 2003-07-11 ver 2.4.0-trans-0.1 Table of Contents Foreword 1. Acknowledgements 2. Nota Bene 1. Introduction 1.1. What Is A Kernel Module? 1.2. How Do Modules Get Into The Kernel? 2. Hello World 2.1. Hello, World (part 1): The Simplest Module 2.2. Compiling Kernel Modules 2.3. Hello World (part 2) 2.4. Hello World (part 3): The __init and __exit Macros 2.5. Hello World (part 4): Licensing and Module Documentation 2.6. Passing Command Line Arguments to a Module 2.7. Modules Spanning Multiple Files 3. Preliminaries 3.1. Modules vs Programs 4. Character Device Files 4.1. Character Device Drivers 5. The /proc File System 5.1. The /proc File System 6. Using /proc For Input 6.1. Using /proc For Input

Upload: others

Post on 28-Dec-2019

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

리눅스 커널모듈 프로그램 가이드Peter Jay SalzmanOri Pomerantz

Copyright © 2001 Peter Jay Salzman

2003-04-04 ver 2.4.0

번역 운형 [email protected]

2003-07-11 ver 2.4.0-trans-0.1

Table of Contents Foreword

1. Acknowledgements 2. Nota Bene

1. Introduction 1.1. What Is A Kernel Module? 1.2. How Do Modules Get Into The Kernel?

2. Hello World 2.1. Hello, World (part 1): The Simplest Module 2.2. Compiling Kernel Modules 2.3. Hello World (part 2) 2.4. Hello World (part 3): The __init and __exit Macros 2.5. Hello World (part 4): Licensing and Module Documentation 2.6. Passing Command Line Arguments to a Module 2.7. Modules Spanning Multiple Files

3. Preliminaries 3.1. Modules vs Programs

4. Character Device Files 4.1. Character Device Drivers

5. The /proc File System 5.1. The /proc File System

6. Using /proc For Input 6.1. Using /proc For Input

Page 2: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

7. Talking To Device Files 7.1. Talking to Device Files (writes and IOCTLs)}

8. System Calls 8.1. System Calls

9. Blocking Processes 9.1. Blocking Processes

10. Replacing Printks 10.1. Replacing printk

11. Scheduling Tasks 11.1. Scheduling Tasks

12. Interrupt Handlers 12.1. Interrupt Handlers

13. Symmetric Multi Processing 13.1. Symmetrical Multi-Processing

14. Common Pitfalls 14.1. Common Pitfalls

A. Changes: 2.0 To 2.2 A.1. Changes between 2.0 and 2.2

B. Where To Go From Here B.1. Where From Here?

IndexList of Examples 2-1. hello-1.c 2-2. Makefile for a basic kernel module 2-3. hello-2.c 2-4. Makefile for both our modules 2-5. hello-3.c 2-6. hello-4.c 2-7. hello-5.c 2-8. start.c 2-9. stop.c 2-10. Makefile for a multi-filed module 4-1. chardev.c 4-2. some title 5-1. procfs.c 6-1. procfs.c

Page 5: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

1. 감사 말Ori Pomerantz 는 Yoav Weiss 의 많은 조언과 문선 공개전에 문서에서 잘못된 부분들을 찾아

준 것에 감사의 말을 전한다. 또한 네덜란드의 Frodo Looijaard, 뉴질랜드의 Stehpen Judd, 스웨덴의 Ahltorp, 그리고 캐나다 퀘벡의 Emmanuel Papirakis 에게도 감사한다.

내가 이 문서를 관리할 수 있도록 해준 점에 대해 Ori Pomerantz 에게 감사한다. 그에게 있어

이것은 상당한 노력이었다. 내가 이 문서에 작업한 것을 그가 좋아하길 바란다.

나를 지도해준 Jeff Newmiller 와 Rhonda Bailey 에게도 감사한다. 그들이 바쁨에도 불구하고

인내심을 가지고 나에게 그들의 경험을 전해 주었다. David Porter 는 LaTex 소스를 Docbook으로 컨버팅하는 지루하고 골치 아픈 직업을 가지고 있지만 누군가는 그 일을 해야만 한다. David 에게도 감사한다.

http://www.tldp.org/LDP/lkmpg/www.kernelnewbies.org 의 사람들에게도 감사를 한다. In particular, Mark McLoughlin and John Levon who I'm sure have much better things to do than to hang out on kernelnewbies.org and teach the newbies.(무슨 소릴까?) If this guide teaches you anything, they are partially to blame. 만약 이 문서가 당신에게

별 의미가 없다면 그들에게도 책임이 있다(역주: 그만큼 그들의 역할이 중요했음 ).

Ori 와 나는 Richard M. Stallman 과 Linus Tovalds 에게 성능 좋은 OS 를 사용하게 해준 것과

그것이 어떻게 작동하는지 자세히 알 수 있는 기회를 준 점에 대해 감사한다. 나는 Linus 를 만난

적도 없고, 앞으로도 못 만나겠지만, 그는 나의 인생에 상당한 변화를 주었다.

다음은 나에게 좋은 제안을 하거나 오류수정의 메시지를 보낸 사람들이다. Ignacio Martin and David Porter

Page 6: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2. Nota BeneOri's original document was good about supporting earlier versions of Linux, going all the way back to the 2.0 days. I had originally intended to keep with the program, but after thinking about it, opted out. My main reason to keep with the compatibility was for Linux distributions like LEAF, which tended to use older kernels. However, even LEAF uses 2.2 and 2.4 kernels these days.

Both Ori and I use the x86 platform. For the most part, the source code and discussions should apply to other architectures, but I can't promise anything. One exception is Chapter 12, Interrupt Handlers, which should not work on any architecture except for x86.

Page 7: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 1. IntroductionTable of Contents 1.1. What Is A Kernel Module? 1.2. How Do Modules Get Into The Kernel?

Page 8: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

1.1. 커널 모듈이란 무엇인가?당신은 커널 모듈을 작성하려고 한다. C 를 알고 프로세스로 작동하는 일반적인 프로그램을

작성해 봤고 이제는 어디서 실질 적인 작업이 행해지는 지, 하나의 와일드 포인터가 파일

시스템을 완전히 지울 수 있고 코어 덤프가 시스템의 리부팅을 의미한다는 것을 알고 있다.

커널 모듈이란 정확히 무엇인가? 모듈이란 요구에 따라 커널에 적재 되거나 해제 될 수 있는

코드다. 시스템의 재 가동 없이 커널의 기능을 확장을 가능케하는 것이다. 예로 모듈의 한

종류는 디바이스 드라이버다. 그리고 그것은 시스템에 연결된 하드웨어에 커널이 접근할 수

있도록 해 준다. 모듈이 없다면 우리는 모놀리틱 커널을 다시 빌드 해야 하며, 커널 이미지에

새로운 기능을 직접적으로 추가 시켜야 한다. 대규모 커널에서는, 우리가 원하는 새로운 기능을

추가 하기 위해서 매번 커널을 다시 빌드해야 하고, 다시 부팅해야하는 단점을 갖게 된다.

Page 9: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

1.2. 어떻게 모듈을 커널에 넣을 것인가? lsmod 을 실행함으로써 당신은 이미 커널에 적재되있는 모듈을 볼수 있으며, lsmod 는

/proc/modules 파일을 읽어 정보를 얻어 온다.

모듈은 커널에서 어떻게 자신의 위치를 찾아낼까? 커널 내부에 존재하지 않는 특징을 커널이 알

필요가 있을 때, 커널 모듈 데몬인 kmod[1] 모듈을 로드 시키기 위해 modprobe 를 실행

시킨다. modprobe 에 다음의 두 형태로 문자열이 전달된다.

softdog 혹은 ppp 같은 모듈이름

char-major-10-30 같은 일반적인 아이덴티파이어

modprobe 가 일반적인 아이덴티파이어를 전달 받는다면, 그것은 /etc/modules.conf 파일에서 그 문자열을 찾는다. 다음과 같은 알리어스 행을 찾는 다면

alias char-major-10-30 softdog

일반적인 아이덴티파이어는 softdog.o 라는 모듈을 참조한다는 사실을 알게 된다. /

다음으로 modprobe 는 /lib/modules/version/modules.dep 파일을 조사해서 다른 모듈이

요구되는 모듈이 적재되기 전에 먼저 적재되야 하는 가를 본다. 이 파일은 depmod -a 에 의해

생성되며, 모듈의 의존성을 담고 있다. 예를 들어 msdos.o 는 fat.o 모듈이 먼저 커널에 적재된

상태를 요구한다. 요청된 모듈이 사용하는 심볼(변수나 함수)을 다른 모듈이 정의 했는가라는, 다른 모듈에 대한 의존성을 갖는다.

마지막으로 modprobe 는 선행 모듈을 커널에 적재하기 위해 insmode 를 사용하고, 요구된 모듈을 적재한다. modprobe 는 insmode 에 표준 모듈 디렉토리인 /lib/modules/version/을 사용하도록 지시한다. insmode 는 모듀의 위치에 대한 정보를 전혀 모르게 되있다. 반면에 modprobe 는 모듈의

Page 10: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

기본 위치를 알고 있다. 예를 들어 msdos 모듈을 적재하기 원한다면 다음의 두 가지를 실행해야 한다.

insmod /lib/modules/2.5.1/kernel/fs/fat/fat.o insmod /lib/modules/2.5.1/kernel/fs/msdos/msdos.o

혹은 "modprobe -a msdos"를 실행하자.

리눅스는 modprobe, insmode, depmod 를 modutils 혹은 mod-utils 라 불리는 패키지로

제공한다.

이장을 마치기 전에 /etc/modules.conf 를 살펴보자.

#This file is automatically generated by update-modules path[misc]=/lib/modules/2.4.?/local keep path[net]=~p/mymodules options mydriver irq=10 alias eth0 eepro

#으로 시작하는 행은 주석이며, 빈 행은 무시된다.

path[misc] 행은 modprobe 에 /lib/modules/2.4.?/local 디렉토리에서 misc 모듈에 대한

경로를 찾아 대체하도록 한다. 보듯이, 쉘 메타 캐릭터가 사용 가능하다.

path[net] 행은 modprobe 로 하여금 net 모듈을 ~p/modules/ 디렉토리에서 찾도록 한다. 그러나 path[net]에 바로 선행되는 “keep”은 modprobe 에게 misc 모듈에서 한 것처럼 표준

검색 경로를 대체지 않고, 해당 디렉토리를 net 모듈을 찾을 때 표준 검색 경로로 추가하도록

한다.

kmod 가 일반적인 아이덴티파이어인 ‘eth0’를 적재하라는 요구할 때마다, eepro.o 를

적재하라고 한다.

Page 11: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/etc/modules.conf 에서 "alias block-major-2 floppy"같은 행은 발견하지 못할 것이다. 왜냐햐면, modprobe 는 임 대부분의 시스템에서 사용되는 표준 드라이버들에 대해 알고 있기

때문이다.

이제 모듈이 어떻게 커널에 적재되는지 알았을 것이다. ‘stacking modules’라고 부르는 모듈에

의존적인 모듈을 작성한다면 몇 가지 더 언급할 것들이 있다. 이것은 다음으로 미룬다. 상대적으로 고난이의 쟁점을 다루기 전에 다뤄야 할 부분이 많이 있다.

1.2.1. 시작하기 전에

코드를 파헤치기 전에 우리가 다뤄야할 몇몇 주제가 더 있다. 모든 사람의 시스템이 상이하고

그들만의 관습이 있다. “hello world” 프로그램을 컴파일하고 올바르게 적재하는 것은 때론

trick(?)일수 있다. 처음 몇몇 장애를 극복하고 나면, 순풍에 돛을 단 듯, 진행될 것이라

생각한다.

1.2.1.1. 모듈 버전

CONFIG_MODVERSIONS 를 커널에서 활성화 시키지 않았다면, 특정 커널에서 컴파일한

모듈은 다른 커널로 부팅한 시스템에는 적재 되지 않는다. 이문서 후반부 까지는 모듈 버전에

관한 문제를 다루지 않을 것이다. 이 문서의 예제는 modversioning 기능을 활성화한 커널을

사용한다면 작동하지 않을 것이다. 대부분의 배포판 커널은 이 기능을 활성화 시킨 상태로

나온다. 만약 모듈을 적재할 때 버전 문제로 에러가 난다면 modversioning 기능을 비활성화

시킨 후 커널을 다시 컴파일 해야 할 것이다.

1.2.1.2. X 사용하기

이 문서에 나온 모든 예제를 타이핑하고 컴파일하고 적재해보기를 강력히 권하다. console 에서

작업하기 역시 권한다. X 환경에서의 작업을 하지 마라.

모듈은 printf()처럼 화면에 출력을 하지 못하고 로그 정보와 경고들을 콘솔로만 출력한다. 만일

xterm 에서 모듈을 적재한다면, 정보와 경고들은 당신의 로그 파일에만 기록될 것이다. 로그

파일을 확인 하기 전까지는 그것을 보지 못할 것이다. 즉각적인 정보를 위해서 모든 작업을

콘솔에서 하라.

1.2.1.3. 컴파일 문제와 커널 버전

종종 리눅스는 표준이 아닌 방식으로 다양하게 패치되서 배포되고 그것은 문제를 야기한다.

Page 12: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

더 흔한 문제는 몇몇 배판들이 불완전한 커널 헤더를 가지고 배포된다는 점이다. 당신의 코드를

컴파일 할 때 리눅스 커널의 다양한 헤더 파일을 사용해서 컴파일 해야 할 것이다. 머피의

법칙은 모듈이 작동하기에 필요한 헤더만 빠져있다고 말한다.(썰렁 조크-_-“ – 필자가 쓴것임)

이런 문제를 피하기 위해서는 리눅스 커널 미러 사이트에서 다운 받아 컴파일하고 부팅시켜

시스템을 사용할 것을 강력히 권한다. 자세한 사항은 Linux Kernel HOWTO 를 참고하기

바란다.

역설적이지만 이것 역시 무제를 만든다. 기본적으로 , 당신의 시스템에 있는 GCC 는 당신이

설치한 버전의 커널 헤더파일을 찾는 것이 아니고 기본으로 설치됐던 커널 헤더를 찾을 것이다. (보통 /usr/src/) This can be fixed by using gcc's -I switch. 이것은 gcc 의 –l 옵션을

사용해서 수정할 수 있다.

Notes

[1]이전 버전의 리눅스에서는 kerneld 로 알려져 있다.

[2]커널을 수정한다면, 존재하는 모듈을 재 작성하는 것을 피하기 위해 EXTRAVERSION 을

당신의 Makefile 에 추가해주어야 한다.

Page 13: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 2. Hello WorldTable of Contents 2.1. Hello, World (part 1): The Simplest Module 2.2. Compiling Kernel Modules 2.3. Hello World (part 2) 2.4. Hello World (part 3): The __init and __exit Macros 2.5. Hello World (part 4): Licensing and Module Documentation 2.6. Passing Command Line Arguments to a Module 2.7. Modules Spanning Multiple Files

Page 14: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.1. Hello, World (part 1): The Simplest Module원시 프로그래머가 처음으로 원시 컴퓨터에 첫 프로그램을 새겨 넣었을 때, 그것은 ‘Hello World’라는 문자를 양떼 그림에 새겨 넣은 프로그램이었다. 로마의 프로그램 책은 ‘Salut Mundi’(hello world 의 로마어인 듯.. J)로 시작한다. 이런 관습을 깨뜨린 사람에게 어떤 일이

일어났는지 잘은 모르지만, 별로 신경 쓰지 않아도 될 듯 하다. 커널 모듈을 작성하는 기본이

되는 다른 측면에서의 예로 Hello World 류의 프로그램을 가지고 시작해보자(어색한 번역…).

다음은 가장 간단한 모듈이다. 아직 컴파일하지 말자; 다음 장에서 모듈을 컴파일하는 것에 대해

다룰 것이다..

Example 2-1. hello-1.c

/* hello-1.c - The simplest kernel module. */#include <linux/module.h> /* 모든 모듈에 필요 */#include <linux/kernel.h> /* KERN_ALERT 에 필요 */

int init_module(void){ printk("<1>Hello world 1.\n");

// 0 이 아닌 값을 리턴하는 것은 init_module 이 실패한 것을 의미한다. 고로 모듈은

로드되지 못한다. return 0;}

void cleanup_module(void){ printk(KERN_ALERT "Goodbye world 1.\n");}

Page 15: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

커널 모듈은 최소 두 개의 함수를 포함해야 한다: 모듈이 커널에 적재될 때 호출되는 “시작”(초기화) 함수인 init_module() 그리고 모듈이 해제되기 직전에(rmmod) 호출되는 종료(해제) 함수인 cleanup_module()이 바로 그것이다. 실제, 커널 2.3.13 부터 이런 부분에 많은 변화가

있었다. 당신은 시작하거나 종료할 때 마음에 드는 것을 사용할 수 있으며, 이런 것들을 Section 2.3 에서 배울 것이다. 사실 새로운 방식이 더 낫다. 그러나 많은 사람들이 여전히 시작과 끝에

init_module() 과 cleanup_module()함수를 사용한다.

전형적으로 init_module()함수는 커널에 어떤 헨들러를 등록하거나, 커널이 가지고 있는

함수의 코드를 대체한다. 일반적으로 특정한 작업을 수행한 후, 원래의 함수를 다시 호출한다.). cleanup_module()함수는 init_module()함수가 행한 것을 복귀시켜 모듈을 안전하게 해제

(unload)할 수 있게 한다.

마지막으로 모든 커널 모듈은 linux/module.h 를 포함해야 한다. linux/kernel.h 는 printk()함수의 log level 인 KERN_ALERT 를 위해서만 여기서 필요하며 이런 내용은 Section 2.1.1에서 배울 것이다.

2.1.1. printk()함수 소개

당신의 생각과 다르게 printk()는 사용자와 정보 교환을 위한 것은 아니다.(우리가 이런

목적으로 hello-1 에서 printk()를 사용할 지라도) 이것은 커널에 기록(logging)하는 방식이며, 경고를 하거나 정보를 남기기 위해 사용된다. 각각의 printk()는 우선 순위를 가지고

사용되며, /which is the <1> and KERN_ALERT you see /이것은 <1> 혹은 KERN_ALERT 와 같은 형태로 사용된다. 8 개의 우선순위가 있으며, 커널은 그에 상응하는 매크로를 가지고

있어 이해하기 어려운 번호를 사용할 필요가 없다. 이들은 linux/kernel.h 에서 찾아볼 수 있다. 우선 순위를 정하지 않고 사용한다면 기본적인 우선순위인 DEFAULT_MESSAGE_LOGLEVEL이 사용될 것이다.

우선 순위 매크로를 읽기를 권한다. 헤더 파일은 각 우선 순위가 의미하는 바도 설명해 준다. 실제 <4>와 같은 번호는 쓰이지 않으며 대신 KERN_WARNNING 과 같은 매크로를 사용한다.

만약 int console_loglevel 보다 우선 순위가 낮으면, 해당 메시지는 당신의 현재 터미널에

보여질 것이다. syslogd 와 klogd 가 실행 중이라면 메지시가 콘솔로 출력여부와 관계 없이

/var/log/messages 에 추가된다. printk()의 메시지가 로그 파일에만 남겨지지 않고 콘솔로

반듯이 출력되도록 하기 위해 KERN_ALERT 와 같이 높은 우선순위를 사용한다. 실제 모듈을

작성할 때 적절히 상황에 맞는 우선순위를 사용하기 원할 것이다.

Page 16: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.2. 커널 모듈 컴파일커널 모듈은 재대로 작동하기 위해서 GCC 의 특정한 옵션과 함께 컴파일 되야 한다. 정의된

특정한 심볼과 함께 컴파일 될 필요도 있다. 실행 파일을 컴파일하는지 커 널 모듈을

컴파일하는지에 따라 커널 헤더 파일이 다르게 동작해야 하기 대문이다. GCC 의 –D옵션을

사용하거나 #define 프리프로세서 명령을 사용해 심볼을 정의 할 수 있다. 이 장에서 커널

모듈을 컴파일하기 위해 필요한 것들을 모두 다룬다.

-c: 커널 모듈이 독립적으로 실행 가능하지 않고, insmod 를 사용하는 실행 시간 중에

커널에 링크되는 오브젝트 파일 이다. 결론적으로 모듈은 -c옵션을 주고 컴파일 해야

한다. -O2: 커널은 인라인 함수를 주로 사용하기 때문에 모듈은 이 옵션 플래그를 사용해야

한다. 이 옵션을 사용하지 않은 경우 어떤 어셈블러 매크로는 함수 호출 시 정상적으로

작동하지 않을 것이다. insmod 는 커널에서 원하는 함수를 찾지 못하고 결국 모듈의

적재는 실패할 것이다. -W -Wall: 프로그램에서의 실수는 당신의 시스템을 다운 시킬 수도 있다. 컴파일러 경고

기능은 항상 켜둬라, 이것은 모듈 컴파일 뿐 아니라 당신의 모든 컴파일 행위에

적용된다. -isystem /lib/modules/`uname -r`/build/include: 컴파일 대상이 되는 커널의

헤더를 사용해야만 한다. 기본적인 /usr/include/linux 를 사용하는 것은 작동하지 않을

것이다. -D__KERNEL__:이 심볼을 정의 하는 것은 헤더 파일에 이 코드가 유저 프로세스로

동작하지 않고 커널 모드에서 작동한다는 사실을 알린다. -DMODULE: 이 심볼은 헤더 파일에 커널 모듈을 위한 올바른 정의를 하게 한다.

module.h 를 포함 시킬 때 –W –Wall 이 유발시키는 사용하지 않는 변수에 대한 경고를 GCC 로

하여금 막기 위해, -I 옵션 대신 GCC 의 -isystem옵션을 사용해야 한다. GCC-3.0 하에서 –

isystem 을 사용함으로써 커널 헤더를 주의 깊게 다루어지고 경고는 절재 된다. -I옵션을 대신

사용한다면 (gcc 2.9x 하에서 -isystem옵션을 사용하는 것도 ) 사용하지 않는 변수에 대한

경고가 출력될 것이다. 그렇다면 무시하면 된다.

hello-1.c 모듈 컴파일을 위한 Makefile 을 살펴보자

Example 2-2. Makefile for a basic kernel module

Page 17: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

TARGET := hello-1WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypesINCLUDE := -isystem /lib/modules/`uname -r`/build/includeCFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}CC := gcc-3.0

${TARGET}.o: ${TARGET}.c

.PHONY: clean

clean: rm -rf {TARGET}.o

연습으로 hello-1.c 를 컴파일하고 insmod ./hello-1.o 를 커널에 올려보자. 잘되는가? 커널에

적재된 모든 모듈은 /proc/modules 에 리스트된다. 당신의 모듈이 커널의 일부가 됐는지 알아

보기 위해 그 파일을 출력해보자. 축하한다. 당신은 리눅스 커널코드의 작성자가 됐다. 즐거움은

잠시 미뤄두고 rmmod hello-1 를 사용해 커널로부터 당신의 모듈을 제거 하자. 당신의 시스템

로그파일에 기록이 됐는지 보기 위해 /var/log/messages 파일을 살펴 보자.

독자를 위한 또 다른 예제가 있다. 앞서 언급한 init_module()함수의 리턴 값을 0 이 아닌 다른

값으로 변경한 후 다시 컴파일 하고 로드해 보자 어떤 일이 일어나는가

Page 18: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.3. Hello World (part 2)여러분은 여러분의 init 와 cleanup 함수 이름을 바꿀 수 있다. 이후로 그들의 이름이 반듯이

init_moduel(), cleanup_module()일 필요는 없다. 이는 module_init()와 module_exit()매크로에 의해 이뤄진다. 이 매크로는 linux/init.h 에 저의 되있다. 선행되야 할 것은 매크로를

호출하기 전에 init 와 cleanup 함수라 정의 돼있어야 한다 것 뿐, 그렇지 않다면 컴파일 에러를

만날 것이다. 이런 테크닉의 예가 있다.

Example 2-3. hello-2.c

/* hello-2.c - Demonstrating the module_init() and module_exit() macros. This is the * preferred over using init_module() and cleanup_module(). * hello-2.c – module_init()와 module_exit()매크로의 예. 이것은 init_module()과

cleanup_moduel() 보다 낫다. */#include <linux/module.h> // Needed by all modules#include <linux/kernel.h> // Needed for KERN_ALERT#include <linux/init.h> // Needed for the macros

static int hello_2_init(void){ printk(KERN_ALERT "Hello, world 2\n"); return 0;}

static void hello_2_exit(void){ printk(KERN_ALERT "Goodbye, world 2\n");}

Page 19: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

module_init(hello_2_init);module_exit(hello_2_exit);

이제 우리는 두개의 실제 커널 모듈을 체험했다. 생산성을 높이기 위해 우리는 Makefile 을

활용해야 한다. 다음은 앞 두 개의 모듈을 모두 컴파일 할 수 있는 개선된 Makefile 이다. 간결성, 규모면에서 최적화된 것이다. 다음을 이해할 수 없다면 GNU Makefile 매뉴얼 혹은, makefile info페이지를 읽기 바란다.

Example 2-4. Makefile for both our modules

WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypesINCLUDE := -isystem /lib/modules/`uname -r`/build/includeCFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE}CC := gcc-3.0OBJS := ${patsubst %.c, %.o, ${wildcard *.c}}

all: ${OBJS}

.PHONY: clean

clean: rm -rf *.o

독자들을 위한 예로, 만약 같은 디렉토리에 hello-3.c 같은 모듈이 하나 더 있다면 그 모듈을

자동으로 컴파일 하기 위해 당신은 Makefile 을 어떻게 수정할 것인가?

Page 20: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.4. Hello World (part 3): The __init and __exit Macros음은 커널 2.2 나 혹은 그 후 버전에서의 예다. init 와 cleanup 함수 의 정의에서의 변화를

주목하라. 내장된 드라이버에 대해 init 함수가 수행되면, __init 매크로는 init 함수가 버려지고(-_-“) 메모리가 반환된다. 그러나 모듈은 적재 불가능하다. 언제 init 함수가 호출되는 가를

생각한다면 이것은 완전히 타당하다고 느낄 것이다.

init 함수 자체에 대한 것이라기 보다는 init 변수에 대해 작용하는 __init 와 유사한 매크로인

__initdata 라는 것도 있다.

__exit 매크로는 모듈이 커널로 빌트인(모듈로 컴파일 하는 것이 아닌 커널 일부로 컴파일 하는

것)될 때, 함수의 호출을 생략하며 __exit처럼 적재 가능한 모듈에 어떤 영향도 미치지 않는다. 마찬가지로 언제 cleanup 함수가 작동하는 고려한다면 이해될 것이다. 내장된 드라이버는

cleanup 함수가 필요 없다. 반면에 적재 가능한 모듈은 필요하다.

이 매크로들은 linux/init.h 에 정의 돼있고 사용된 커널 메모리를 해제하는데 사용된다. 부팅 후

‘Freeing unused kernel memory: 236k freed’ 와 같은 메시지를 볼 때 , 커널이 메모리를

해제하는 것이다.

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_ALERT */#include <linux/init.h> /* Needed for the macros */

static int hello3_data __initdata = 3;

static int __init hello_3_init(void){ printk(KERN_ALERT "Hello, world %d\n", hello3_data);

Page 21: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

return 0;}

static void __exit hello_3_exit(void){ printk(KERN_ALERT "Goodbye, world 3\n");}

module_init(hello_3_init);module_exit(hello_3_exit);

리눅스 2.2 커널을 위한 드라이버 모듈에서 __initfunction() 같은 함수를 보았을 것이다.

__initfunction(int init_module(void)){ printk(KERN_ALERT "Hi there.\n"); return 0;}

이 메크로는 __init 와 같은 동작을 한다. 그러나 __init 에 비해 매우 적게 사용된다. 그저, 커널에서 당신이 보았을 수도 있기에 언급한 것이다. 2.4.18 버전의 커널에 __initfunction()은

38 군데에서 보이고, 2.4.20 에서는 37 군데에서만 보인다. 그러나 당신의 코드에서는 사용하지

말 것을 권한다.

Page 22: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.5. Hello World (prt 4): 저작권과 모듈 문서커널 버전 2.4 나 그 후 버전을 사용한다면, 앞서 언급한 모듈 예를 적재할 때 다음과 같은

메시지를 보게 될 것이다.

# insmod hello-3.oWarning: loading hello-3.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modulesHello, world 3Module hello-3 loaded, with warnings

2.4 버전 이후의 커널에서 GPL 라이센스 코드를 식별하기 위한 메커니즘이 고안돼서, 오픈

소스가 아닌 경우 사용자들에게 경고를 할 수 있게 돼있다. 이는 다음 코드에서 제시된

MODULE_LICENSE() 매크로에 의해 충족된다. GPL 라이센스로 설정함으로써 경고가 출력되는

것을 방지 할 수 있다. 이런 라이센스 메커니즘은 linux/module.h 에 정의되있고 문서화

되있다.

비슷하게 MOUDLE_DESCRIPTION() 은 모듈이 무엇을 하는가를 , MOUDLE_AUTHOR()은

모듈의 저자를 MODULE_SUPPORTED_DEVICE()는 어떤 타입의 장치를 모듈이 지원하는 가를

알려준다.

이런 매크로들은 linux/module.h 에 정의되있고 커널 자체에서는 사용되지 않는다. 이것들은

문화를 간단히 하며, objdump 와 같은 툴을 이용해 볼 수 있다. 독자들의 연습을 위해

linux/drivers 에서 어떻게 모듈 저자들이 모듈을 문서화 하기 위해 이 매크로들을 사용했는가

찾아 보길 바란다.

Example 2-6. hello-4.c

/* hello-4.c - Demonstrates module documentation. */#include <linux/module.h>

Page 23: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#include <linux/kernel.h>#include <linux/init.h>#define DRIVER_AUTHOR "Peiter Jay Salzman <[email protected]>"#define DRIVER_DESC "A sample driver"

int init_hello_3(void);void cleanup_hello_3(void);

static int init_hello_4(void){ printk(KERN_ALERT "Hello, world 4\n"); return 0;}

static void cleanup_hello_4(void){ printk(KERN_ALERT "Goodbye, world 4\n");}

module_init(init_hello_4);module_exit(cleanup_hello_4);

/* You can use strings, like this: */MODULE_LICENSE("GPL"); // Get rid of taint message by declaring code as GPL..

/* Or with defines, like this: */MODULE_AUTHOR(DRIVER_AUTHOR); // Who wrote this module?MODULE_DESCRIPTION(DRIVER_DESC); // What does this module do?

Page 24: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* 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");

Page 25: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.6. 커맨드 라인 인자 모듈에 넘기기모듈은 커맨드라인 인자를 받을 수 있다. 기존에 사용하던 것처럼 argc/argv 를 가지고 할 수는

없다.

인자를 당신의 모듈로 넘기기 위해서는 커맨드라인 인자의 값을 저장할 변수를 전역으로 선언한

후 메커니즘을 활성화 시키기 위해 MODULE_PARAM()매크로를 사용한다. 실행 시간에

insmod 는 주어진 인자로 변수를 채울 것이다. 변수의 선언과 매크로들은 명확성을 위해 모듈

서두에 위치해야 한다. 다음의 예는 어눌한 내 설명을 명백히 해준다.(내 번역도 ^^)

MODULE_PARAM() 매크로는 2 개의 인자를 받는다 변수의 이름과 타입 지원하는 타입은

1byte 인”b”, short int 인 “h”, integer 인 “i”, long 인 “l”, string 인 “s” 등 이다. 문자열

(strings – string 과 strings 와 구분)은 “char *”로 해야 하며, insmod 는 그 문자열(strings)을 위한 메모리를 할당한다. 늘 변수를 초기화하는 습관을 갖길 권한다. 이것은 커널 코드이다. 당신은 반듯이 방어적으로 프로그래밍 해야 한다. 다음은 예제다.

int myint = 3; char *mystr;

MODULE_PARM (myint, "i"); MODULE_PARM (mystr, "s");

배열 역시 지원된다. MODULE_PARM 에 선행되는 integer 값은 배열의 최대 길이를 알려준다. ‘-‘에 의해 분리된 두 번호는 최대 최소 값을 알려 준다. 예를 들어 최소값 2 과 최대값 4 를 갖는

short 형의 배열은 다음과 같이 선언될 수 있다.

Int myshortArray[4]; MODULE_PARM (myintArray, "2-4i");

어떤 IO 포트 혹은 IO 메모리가 사용되는지와 같이 모듈의 변수 값은 디폴트 값으로 세팅 되는

것이 좋다. 만약 변수가 기본값을 갖는다면 자동인식(차후에 설명이 나옴)를 수행 할 수 있다.

Page 26: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

그렇지 않다면 현재의 값을 유지한다. 이것은 앞으로도 명확하다. 지금은 모듈로 인자를 넘기는

것만을 예로 들겠다.

Example 2-7. hello-5.c

/* hello-5.c - Demonstrates command line argument passing to a module. */#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("Peiter Jay Salzman");

static short int myshort = 1;static int myint = 420;static long int mylong = 9999;static char *mystring = "blah";

MODULE_PARM (myshort, "h");MODULE_PARM (myint, "i");MODULE_PARM (mylong, "l");MODULE_PARM (mystring, "s");

static int __init hello_5_init(void){ printk(KERN_ALERT "Hello, world 5\n=============\n"); printk(KERN_ALERT "myshort is a short integer: %hd\n", myshort); printk(KERN_ALERT "myint is an integer: %d\n", myint); printk(KERN_ALERT "mylong is a long integer: %ld\n", mylong); printk(KERN_ALERT "mystring is a string: %s\n", mystring); return 0;}

static void __exit hello_5_exit(void)

Page 27: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

{ printk(KERN_ALERT "Goodbye, world 5\n");}

module_init(hello_5_init);module_exit(hello_5_exit);

Supercalifragilisticexpialidocious(저자가 쓴 암호 중 하나... -_-)

Page 28: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

2.7. 다중 파일 모듈때로는 커널 모듈을 여러 개의 소스파일로 나누는 것이 좋을 때가 있다. 이런 경우 다음과 같은

사항이 필요하다.

1. 하나의 파일을 제외하고 모든 소스 파일에 #define __NO_VERSION__ 이 필요하다. module.h 는 일반적으로 kernel_version, 모듈이 컴파일되는 커널의 전역 변수를

포함하기 때문에 이것은 중요하다. 만약 version.h 가 필요하다면 그것을 포함시켜야

한다. 왜냐하면 __NO_VERSION__를 가진 module.h 은 이것을 해주지 않는다.2. 일반적으로 모든 소스 파일을 컴파일 해야 한다.3. 모든 오브젝트 파일을 하나로 컴파일 해야 한다. X86환경에서는 ld -m elf_i386 -r -

o <module name.o> <1st src file.o> <2nd src file.o>를 사용한다.

다음은 앞서 언급한 것에 대하나 예제다.

Example 2-8. start.c

/* 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("Hello, world - this is the kernel speaking\n"); return 0;}

다음 파일

Example 2-9. stop.c

/* stop.c - Illustration of multi filed modules */

Page 29: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) #include <linux/modversions.h> /* Will be explained later */ #define MODVERSIONS#endif #include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */#define __NO_VERSION__ /* It's not THE file of the kernel module */#include <linux/version.h> /* Not included by module.h because of

__NO_VERSION__ */

void cleanup_module(){ printk("<1>Short is the life of a kernel module\n");}

마지막으로 Makefile

Example 2-10. Makefile for a multi-filed module

CC=gccMODCFLAGS := -O -Wall -DMODULE -D__KERNEL__ hello.o: hello2_start.o hello2_stop.o ld -m elf_i386 -r -o hello2.o hello2_start.o hello2_stop.o start.o: hello2_start.c ${CC} ${MODCFLAGS} -c hello2_start.c stop.o: hello2_stop.c ${CC} ${MODCFLAGS} -c hello2_stop.c

Page 30: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 3. 준비 단계

Page 31: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

3.1. 모듈 vs 프로그램3.1.1. 모듈은 어떻게 시작하고 끝나는가

일반적으로 프로그램은 main()함수에서 시작한다, 명령어들의 집합을 실행하고 명령어들의

완료 시 종료된다. 커널 모듈은 약간 다르게 작동한다. 모듈은 항상 init_module()함수, 혹은

당신이 module_init()라는 함수에 지정한 곳에서 시작한다. 이것이 모듈의 진입 함수다(entry function); 모듈이 필요해질 때 이 함수들은 커널에게 그 모듈의 기능은 무엇이며 어떻게

설정되는 가를 알려 준다. 이런 설정이 이뤄지면 모듈을 시작하는 함수는 리턴하고 모듈이

제공하는 코드를 가지고 커널이 무엇인가를 하려할 때까지 모듈은 아무런 일을 하지 않는다.

모든 모듈은 cleanup_module(), 혹은 module_exit()에 지정되 있는 함수를 호출할 때

종료된다. 이것이 모듈의 종료 함수(exit function)가 된다. 이 함수는 진입 함수가 무엇을 하던

종료 시킨다. 진입 함수에 등록되 있는 기능을 해제한다..

모든 모듈은 진입함수와 종료함수를 갖는다. 하나 이상의 진입함수와 종료함수를 지정하는

방법이 있기에, `진입함수(entry function)'와 `종료함수(exit function)'라는 단어를

사용하겠다. 그러나 내가 간단히 init_module() 그리고 cleanup_module()이라 하더라도, 독자들은 내가 무엇을 의미하는지 알 것이라 생각하겠다.

3.1.2. 모듈에 사용 가능한 함수들

프로그래머들은 자신들이 정의한 함수만을 사용하는 것은 아니다. 대표적인 예가 printf()다. 프로그래머들은 표준 C 라이브러리에서 제공하는 이런 라이브러리 함수들을 사용한다. 실제

이런 함수들의 정의는 링킹 스테이지(linking stage)가 될 때까지 프로그램에 들어가지 않는다. 그리고 이런 것은 코드가 사용 가능하다는 것을 보장해주고 그 위치에 명령어(instruction)를

위치 시켜준다.

여기서도 커널 모듈은 다는 양상을 띤다. 전술한 예제에서, 우리는 printk()라는 함수를

사용했다 그리고 그것은 표준 라이브러리 함수가 아님을 알 것이다. 모듈은 insmod 에 의해

해석되지는 심볼을 갖는 오브젝트 파일이기 때문이다. 심볼의 정의는 커널 자체에 있다. 당신이

사용 가능한 유일한 외부 함수는 커널에 의해 제공되는 것들에 한정되어진다. 어떤 심볼(역주-모듈에서 직접 정의하지 않고 사용할 수 있는 함수)들이 사용가능한지 궁금하다면 /proc/ksyms을 살펴봐라.

Page 32: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

라이브러리 함수와 시스템 콜의 차이점을 명심하자. 라이브러리 함수들은 상위 레벨에

있으면서, 완벽하게 유저 레벨에서 실행되며, 프로그래머가 실제 작업을 하는데 편의를

제공해준다. 시스템 콜은 커널에 의해 제공되며 사용자의 행위에 따라 커널 레벨에서 실행된다. 라이브러리 함수인 printf()는 가장 일반적인 출력함수처럼 보인다. 그러나 그것이 실제로 하는

일은 데이터를 스트링으로 포맷하고 스트링 데이터를 로우레벨 시스템 콜인 write()를 이용하는

것이다, 그리고 그것은 그 데이터를 표준 출력으로 내보낸다.

Printf()가 무으로 구성되있는 보고 싶은가? 그것을 알아보는 것은 굉장히 쉽다. 다음의 프로그램을 컴파일 해보자.

#include <stdio.h> int main(void) { printf("hello"); return 0; }

gcc -Wall -o hello hello.c. strace hello 을 실행해보자. 놀랐는가? 당신이 보는 각각의

행이 시스템 콜에 해당된다. strace[1]는 어떤 시스템콜로 프로그램이 구성됬는가, 어떤

아규먼트가 넘겨지는가, 무엇을 리턴하는가를 보여주는 간단한 프로그램이다. 이 프로그램은

프로그램이 어떤 파일에 접근하는가와 같은 것들을 설명하는데 매우 중요한 도구다. 끝 부분에

write(1, "hello", 5hello)라는 부분을 볼수 있을 것이다. 이것이 printf()의 정체다. 대부분의

사람들은 파일 입출력에 fopen(), fputs(), fclose()와 같은 라이브러리 함수를 사용하기

때문에, write()에 익숙하지 않을 것이다. 만약 당신도 그렇다면 , man 2 write 을 보라. 매뉴얼 페이지의 2 번째 섹션은 kill(), read()와 같은 시스템 콜에 할당되있다. 3 번째 섹션은

독자들이 익숙한 cosh(), random() 등의 라이브러리 함수에 할당 되있다.

당신도 커널의 시스템 콜을 대체하기 위해 모듀을 쓸 것이다.(우리가 간단히 할것이다) 크래커들이 백도어용으로 이런 종류의 것들을 사용한다. 누군가 당신의 시스템에 있는 파일을

지우려 시도할 때마다 당신은 모듈을 이용해서 “간지러 장난 치지마”(역주 Tee hee, that

tickles! 의역)라는 식의 온화한 대응을 할 수 있을 것이다.

3.1.3. 사용자 공간 vs 커널 공간

커널은 비디오 카드, 하드 드라이브, 심지어 메모리까지 모든 리소스에 접근한다. 프로그램은

종종 같은 리소스에 대해 경쟁한다. 내가 이 문서를 저장할 때, updatedb 가 위치에대한 데이터

베이스를 갱신하기 시작한다. 나의 vim 세션과 updatedb 가 같은 하드 드라브를 동시에

사용하는 것이다. 커널은 이런 것들을 순서대로 유지할 필요가 있으며, 사용자가 그들이 원하는

Page 33: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

자원에 직접 접근하도록 권한을 주어서는 안 된다. 이런 이유로 CPU 는 다른 방식으로 작동한다. 각 모드는 당신이 시스템에 하고자 하는 일에 대한 서로 다른 레벨의 권한을 준다. 인텔의

30386 아키텍쳐는 4 개의 모드를 가지고 있다. 유닉스는 모든 것이 가능한 관리자 모드로

알려진 0 모드(최상위 모드 – ring 의 단어선택이... -_-)와 유저 모드로 불리는 최하위 모드, 두

가지만을 사용한다.

라이브러리 함수와 시스템 콜에 대한 논의를 상기하자. 당신은 전형적으로 사용자유저모드에서

라이브러리 함수를 사용한다. 라이브러리 함수는 하나 혹은 그 이상의 시스템 콜을 호출한다. 그리고 이런 시스템 콜은 라이브러리 함수처럼 행동한다. 그런 관리자 모드에서는 커널의

일부이기 때문에 그렇게 행동한다. 시스템콜이 그 작업을 완료하면 복귀(return)하고

유저보드로 복귀한다.

3.1.4. 이름 공간(이런 번역을 보면 욕을 했는데 나도... -_-)

당신이 C 프로그램을 작성할 때, 당신은 코드를 보는 사람들의 가독성을 위해 변수이름을

사용했을 것이다. 규모가 큰 프로그램의 일부를 작성하고 있을 때, 당신의 전역변수가 같이

일하는 사람의 전역변수의 이름과 같다면 변수이름으로 인해 문제가 될 수 있다. 서로 구분할

만큼 의미 있는 이름을 갖지 못한 전역변수가 많이 존재한다면 이름공간 이 오염(-_-“)될 것이다. 대규모 프로젝트에서 사용 보류된 이름에 대해 이런 노력은 반듯이 있어야 하며, 심볼과 변수의

유니크한 명명 법에 대한 계획이 필요하다.

커널 코드를 작성할 때 그것이 아무리 작은 모듈이라도 커널 전체에 링크되기 때문에 이것은

분명 문제가 된다. 이것을 해결하는 가장 좋은 방법은 변수를 정적 변수로 선언하고, 잘 정의된

접두어를 당신의 심볼에 사용하는 것이다. 관습적으로 커널의 접두어는 소문자다. 모든 것을

정적으로 선언하기 원치 않는다, symbol table 을 선언하고 그것을 커널에 등록시키는 방법을

쓴다. 차후에 이것을 하게 될 것이다.

/proc/ksyms 는 커널이 알고 있는 모든 심볼을 가지고 있고, 그 심볼은 커널 코드 스페이스에서

공유되기 때문에, 당신의 모듈이 접근/사용할 수 있다.

3.1.5. 코드 영역

메모리 관리는 매우 복잡한 주제다. `Understanding The Linux Kernel 의 상당 부분이 메모리

관리에 중점을 둔다. 우리는 비록 메모리 관리에 전문가는 아니지만 실제 모듈을 작성하는데

고려해야 할 두 가지 사실에 대해 알 필요는 있다.

Page 34: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

실제로 세그멘트 폴트가 무엇을 의미하는지 생각해보지 않았다면 포인터가 실제 메모리 위치를

지시하고 있지 않다는 것을 듣게 되면 놀랄 것이다. 어쨌든 어느 포인터도 그렇지 않다. 프로세스가 생성될 때, 커널은 실제 메모리의 일부를 할당해, (컴퓨터 학자나 알법한 것들) 프로세스가 사용하는 실행코드, 변수, 스택, 힙 등으로 사용하도록 프로세스에게 넘긴다. 이

메모리는 $0$에서 시작해 프로세스가 필요한 만큼 확장된다. 서로 다른 두 프로세스의 메모리

영역은 겹치지 않기 때문에 0xbffff978 에 접근하는 모든 프로세스는 실제 물리적 메모리의

서로 다른 지점에 접근할 것이다. 프로세스들은 특정 프로세스에게 할당된 메모리 영역으로의

오프셋의 한 종류를 지시하는 0xbffff978 로 이름 지어진 인덱스에 접근하려 할 것이다. 이후에

다루게 될 방법이 있음에도 불구하고, 우리의 Hello, World 와 같은 대부분의 경우 다른

프로세스의 영역에 접근할 수 없다.

커널 역시 자신만의 메모리 영역이 있다. 모듈은 동적으로 커널에 적재 되거나 제거될 수 있는

코드기 때문에, 모듈은 자신만의 커널 코드 영역을 가지기 보다는 커널 코드 영역을 공유한다. 즉, 반 독립적 객체에 상반되는 개념이다. (as opposed to a semi-autonomous object 의

의역) 그러므로 우리의 세그먼트 폴트는 커널의 세그먼트 폴트가 된다. off-by-one 에러 때문에

데이터를 겹쳐 쓰기 시작한다면, 커널 코드를 망가트릴 것이다. 이건 생각보다 심각하므로

주의를 기울여야 한다. ( off-by-one error : n=0 에서 시작할 것을 n=1 에서 시작함으로 해서

일어나는 류의 에러를 의미함)

위에서 언급한 사실들은 모놀리틱 커널을 사용하는 모든 오퍼레이팅 시스템에서 적용된다는

것을 지적하고 싶다. 자기 자신만의 코드 영역을 갖는 마이크로 커널이라는 것도 있다. GNU Hurd 와 QNX Neutrino 가 마이크로 커널의 예다.

3.1.6. 장치 드라이버

모듈의 한 종류가 장치 드라이버이며, 그들은 TV카드나 시리얼 포트 같은 하드웨어의 기능을

제공한다. 유닉스에서 하나의 하드웨어는 하드웨어와 의사 통신하는 수단을 제공하는 named device 파일(/dev/ 아래 위치한다.)로 보여진다. 디바이스 드라이버는 유저 프로그램을 대신해, 의사 소통 수단을 제공한다. es1370 사운드카드 드라이버는 Ensoniq IS1370 사운드 카드에

연결하기 위해 /dev/sound device 에 연결한다. mp3blaster 와 같은 유저 스페이스

프로그램은 어떤 종류의 사운드 카드가 설치됐는지 모른 체 /dev/sound 라는 장치를 사용할 수

있다.

3.1.6.1. 장치 주 번호와 장치 부 번호

디바이스 파일을 살펴보자. 다음은 프라이머리 마스터 IDE 하드 드라이버의 첫 세 개 파티션을 나타내는 디바이스 파일이다.

Page 35: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

# 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

콤마에 의해 구분된 번호의 열을 주시하자 첫 번째 번호를 장치 주 번호라 부르며 두 번째 번호는

부 번호라 부른다. 주 번호는 드라이버가 어떤 하드웨어에 엑세스하는지 알려준다. 각

드라이버는 유일한 주 번호를 할당 받으며, 동일한 주 번호를 갖는 모든 디바이스 파일은 같은

드라이버에 의해 컨트롤 된다. 위의 주 번호가 모두 3 인 것은, 그들이 같은 드라이버에 의해

콘트롤 되기 때문이다.

부 번호는 드라이버가 자신이 컨트롤하는 하드웨어를 구분하기 위해 사용된다. 위의 예로

돌아가 보자, 세 개의 장치들이 같은 드라이버에 의해 운영될지라도 서로 다른 고유의 부 번호를

갖는데, 이는 드라이버가 그들(하드 디스크 파티션들)을 서로 다른 하드웨어로 인식하기

때문이다.

디바이스는 캐릭터 디바이스와 블록 디바이스의 두 타입으로 나뉜다. 블록 디바이스는 버퍼를

가지고 있어, 어떤 순서로 응답하는 것이 가장 좋은 것인가 선택할 수 있다는 것이 차이점이다. 이점은 물리적으로 떨어져있는 섹터보다 가까이 있는 섹터에 읽기/쓰기를 하는 것이 빠르다는

점에서 저장 장치에 있어 중요하다. 또 다른 차이점은 블록 디바이스만이 입출력 시 블록 단위로

접근할 수 있다는 점이다(블록의 크기는 장치에 따라 다르다). 반면에 캐릭터 디바이스는 몇

바이트 되지 않는 크기만을 허용한다. 대부분의 장치는 캐릭터 디바이스이다. 왜냐하면 장치들

대부분이 이런 종류의 버퍼일을 필요로 하지 않고 고정된 블록 크기에 대해 작동하지 않기

때문이다. ls –l 의 결과에서 첫 번째 문자를 살펴 봄으로써 디바이스가 블록 디바이스인지 캐릭터 디바이스인지 구분할 수 있다. 만인 첫 문자가 ‘b’이면 블록디바이스고, ‘c’이면 캐릭터 디바이스이다. 다음은 캐릭터 디바이스의 예다(시리얼 포트) .

crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0 crw-r----- 1 root dial 4, 65 Nov 17 10:26 /dev/ttyS1 crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2 crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3

Page 36: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

만일 어떤 주 번호가 할당됐는가 알기를 원한다면, /usr/src/linux/Documentation/devices.txt 파일을 참고하기 바란다.

시스템이 설치될 때, 이런 디바이스 파일들은 mknod 에 의해 생성된다. 주번호/부번호 12, 2의 ‘coffee’라는 새로운 캐릭터 디바이스를 생성하고자 한다면, 단순히 mknod /dev/coffee c 12 2 만 실행시키면 된다. 디바이스 파일을 /dev/에 넣을 필요는 없다. 단순히 관습일 뿐이다. 리누즈가 그의 디바이스 파일을 /dev/에 넣었기 때문에 당신도 그렇게 하는 것이 낫을 것이다. 그러나 테스트 목적으로 디바이스 파일을 생성한다면, 커널 모듈을 컴파일한 작업 디렉토리에

디바이스 파일을 넣어도 무방하다. 디바이스 드라이버 작성이 완료됐을 때 올바른 위치에만

넣으면 된다.

나는 이전에 언급했던 것들과 상충되는 몇 가지 지적을 하기를 좋아 한다. 하지만 그것들은 단지

몇몇 경우에만 국한된다. 디바이스 파일이 액세스될 때 커널은 장치 주 번호를 어떤 드라이버를

이용해 그 액세스를 처리할 것인가를 결정하기 위해 사용한다. 이 이야기는 커널은 장치 부

번호를 사용할 필요가 없고, 심지어 몰라도 된다는 말이다. 장치 부 번호에 관심을 갖는 것은

드라이버 자체 뿐이다. 동일한 종류의 하드웨어 중에 어떤 장치인가를 구분하기 위해서만 장치

부 번호는 사용된다.

그런데 내가 ‘하드웨어’라고 했을 때 , 난 당신의 손에 있는 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

이제 두 개의 디바이스 파일을 보고 그들이 블록 디바이스라는 것과 동일한 드라이버에 의해

처리된다는 것을 즉시 알 수 있을 것이다. 두 개의 디바이스 파일은 당신의 플로피 드라이브를

나타낸다는 것을 알 것이다. 그런데 당신은 하나의 플로피 드라이버만을 가지고 있지 않은가. 왜

두 개인가? 하난 1.44MB 의 플로피 드라이브를 나타낸다. 다른 하나는 흔히 말하는

‘superformatted’ 드라이브에 해당되는 1.68MB 의 동일한 저장 장치다. 표준 포맷 플로피

보다 많은 양의 데이터를 저장하는 것이다. 이것이 동일한 실제 하드웨어에 서로 다른 장치 부

번호를 갖는 경우다. 우리의 논의 에서 ‘하드웨어’라고 하는 단어가 상당히 추상적이라는 것을

알기 바란다.

Page 37: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Notes

[1]프로그램이 액세스 하고 있는 파일이 어떤 파일인가와 같은 것들을 설명해주는 값진 도구다. 그것이 파일을 찾지 못 하기 때문에 프로그램이 베일에 가렸다고 느끼는가? 그것은 PITA 이다.

[2]Jim 난 컴퓨터 과학자가 아니고 물리학자야!

[3]비록 생각은 같은 것이지만, 이것은 당신의 모든 모듈을 커널에 포함 시켜 만드는 것과 분명히 다르다.

Page 38: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 4. 문자 장치 파일

Page 39: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

4.1. 문자 디바이스 드라이버4.1.1. file_operations 구조체

file_operations 구조체는 linux/fs.h 에 정의되 있으며 장치들에 대해 다양한 작동을 하는

드라이버에 의해 정의된 함수들을 가지고 있다. 구조체의 각 필드는 요구되는 작동을 하기 위해

드라이버에 의해 정의된 함수들의 주소에 대응된다.

예를 들어, 모든 문자 장치 드라이버는 장치로부터 데이터를 읽어 오는 함수를 정의 할 필요가

있다. file_operations 구조체는 그런 기능을 하는 함수 모듈의 주소를 가지고 있는 것이다. 커널 2.4.2 에서 그 정의 들은 다음과 같다.

struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, 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 (*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 *); };

Page 40: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

어떤 동작들은 드라이버에 의해 구현되지 않는다. 예로 비디오카드를 다루는 드라이버는

디렉토리 구조체에서 데이터를 읽어올 필요는 없다. 이 경우 file_operations 구조체의 해당

필드는 NULL 로 세팅된다..

gcc 의 확장 버전은 이 구조체에 인자 할당을 좀더 편하게 해준다. 최근 만들어진 드라이버들에서 이런 것들을 볼 수 있다, 아마도 (사용 방법 때문에) 놀라움을 줄 수도 있다. 다음은 이런 새로운 할당 방식을 보여준다.

struct file_operations fops = { read: device_read, write: device_write, open: device_open, release: device_release };

그러나 C99방식의 구조체 멤버 할당방식도 여전히 존재하며 GNU 확장 버전을 사용함에

오히려 이 방식이 선호되고 있다. 내가 현재 사용하고 있는 gcc 2.95 버전은 C99 문법의 새로운 부분을 지원하고 있다. 누군가 당신의 드라이버를 포팅하기 원하는 경우 당신은 이 문법을 사용해야 할 것이다. 이 경우 두 종류의 신텍스가 공존할 수 있다.

struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };

의미는 명확하다 그리고 당신이 할당하지 않은 구조체의 멤버는 gcc 에 의해 NULL 로

초기화된다는 사실을 기억하자. file_operations 구조체의 포인터는 일반적으로 fops 라는

이름을 갖는다.

Page 41: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

4.1.2. file 구조체

각각의 장치들은 file 구조체에 의해 커널 내부에 보여진다. 그리고 그것은 linux/fs.h 에 정의

되있다. 구조체는 커널 내부에 존재하며 사용자 공간의 프로그램에서는 절대 보이지 않는다는

것을 명심하자. 구조체와 같지 않다. FILE 구조체는 glibc 에 정의되 있고 커널영역에는 절대

보이지 않는다. 이름 때문에 잘못 이해되기도 한다. 그것은 열린 파일을 의미하지 디스크상의

inode 구조체에 이해 지시되는 파일을 의미하는 것은 아니다.

file 구조체의 포인터는 일반적으로 filp 라는 이름을 갖는다. struct file file 와 혼돈하지 말자(완전 통밥-_-)

file 구조체의 정의를 살펴보자. 대부분의 멤버들이 dentry 구조체처럼 디바이스 드라이버에서

사용되지 않는다. 또한 그런 부분은 무시해도 좋다. 디바이스 드라이버는 file 구조체를

직접적으로 채우지 않고 어디선가 생성한 파일 구조체의 내용을 단지 이용하기만 할 뿐이다.

4.1.3. Registering A Device

앞서 논의한 것처럼 문자장치는 보통 dev[1] 에 있는 디바이스 파일을 통해 접근된다. 주장치

번호는 어느 드라이버가 어는 디바이스 파일을 사용하는가 알려준다. 부 장치번호는 드라이버가

하나 이상의 장치를 작동시킬 때, 어떤 장치가 가동되는가를 구분하기 위해 드라이버 내부에서

사용된다.

시스템에 드라이버를 추가한다는 것은 커널에 그것을 등록시키는 것을 의미한다. 모듈이

초기화되는 동안 주장치 번호를 부여한다는 것과도 같은 의미다. linux/fs.h 에 정의된

register_chrdev()함수를 사용함으로써 이런 작업을 수행한다.

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

unsigned int major 는 요청할 주장치 번호이고, const char *name 은 /proc/devices 에

나오는 장치 이름이고 struct file_operations *fops 는 드라이버를 위한 file_operations table 의 포인터다. 음수를 리턴 할 경우 장치 드라이버 등록에 실패한 것이다. 부장치 번호를

register_chrdev()에 넘기지 않는다는 것에 유의하자. 커널은 부장치 번호에 전혀 신경쓰지

않기 때문이다. 우리의 드라이버만 그 번호를 사용한다.

Page 42: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

이제 어떻게 이미 사용중인 번호를 가로채지 않고 주장치 번호를 얻어오느냐는 생각이 든다. 가장 쉬운 방법은 Documentation/devices.txt 를 보고 사용하지 않는 것을 선택하는 것이다. 그러나 이 번호가 나중에 사용될 수 있으므로 좋은 방법은 아니다. 해결책은 커널에게 동적으로

주장치 번호를 할당해 줄 것을 요청하는 것이다.

register_chrdev()에 주장치 번호 0 으로 넘기면, 동적으로 할당된 주장치 번호를 리턴 해준다. 주장치 번호를 알 수 없기 때문에 미리 디바이스 파일을 만들 수 없다는 단점이 있다. 몇 가지

해결 책이 있다. 우선, 드라이버가 자신에게 할당된 주장치 번호를 출력하고 수작업으로 그 장치

파일을 만드는 것이다. 다음으로, /proc/devices 에 새로 등록된 장치가 있을 것이다. 우리는

직접 디바이스 파일을 만들던지 혹은 파일을 읽어 들여 디바이스 파일을 만드는 쉘 스크립트

만들면 된다. 세 번째로 장치 드라이버를 등록한 후mknod 를 이용해 디바이스 파일을 만들고

cleanup_module()을 호출한 후 rm 으로 삭제하는 것이다.

4.1.4. 장치의 등록해제

root 가 그러고 싶다고 해서 커널 모듈을 rmmod 하게 할 수는 없다. 프로세스에 의해 장치

파일을 열고 모듈을 제거한 후, 장치 파일의 사용시도는 read/write등 올바른 함수가 사용하던

메모리위치를 다른 것으로 하여금 사용하게 할 것이다. 만일 운이 좋다면 그곳에 아무런 코드도

로드되지 않을 것이고 에러 메시지를 받을 것이다. 우리가 운이 없다면 같은 위치로 다른 커널

모듈이 올라올 것이고 그것은 커널 내의 다른 함수의 중간 어딘가로 실행위치를 바꿔 버릴

것이다. 결과는 예상할 수 없으며 매우 부정적이다.

일반적으로 우리가 무엇인가 허가 하고 싶지 않다면 그 일을 처리하는 함수에서 에러코드(음수)를 리턴하도록 한다. void 타입이기 때문에 cleanup_module()에서는 이것이 불가능 하다. 그러나 얼마나 많은 프로세스가 그 모듈을 이용하고 있는지 추적하는 카운터가 있다. /proc/modules 파일의 세 번째 필드의 값이 바로 이것이다. 이 번호가 0 이 아니라면, rmmod는 실패한다. linux/module.c 에 정의 되있는 sys_delete_module()에 의해 그 수가 체크되고

있으므로 cleanup_module()에서 카운트 할 필요가 없다. 직접적으로 카운터를 사용하지 말자, linux/modules.h 에 정의된 매크로가 있다. 그것은 이 카운터를 증가시키거나 감소시키는 일을

한다.

MOD_INC_USE_COUNT: 카운터를 증가시킨다. MOD_DEC_USE_COUNT: 카운터를 감소시킨다.

MOD_IN_USE: 카운터를 보여준다.

Page 43: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

카운터를 정확하기 유지시키는 것이 중요하다. 만약 정확한 카운터의 수를 잃어 버린다면, 모듈을 해제(unload)하는 것은 불가능하다. 부팅을 다시 하자. 모듈 개발을 하는 동안 언젠가는

닥칠 일이다.

4.1.5. chardev.c

다음의 코드는 chardev 라는 이름의 문자 드라이버를 만드는 간단한 예제다. 장치파일의 내용을

화면에 출력 (혹은 다른 프로그램을 이용해 열어 볼 수 있다)할 수 있다. 그러면 드라이버는

파일이 읽혀진 횟수를 파일에 기록한다. echo "hi" > /dev/hello 와 같이 파일에 기록하는

것을 지원하지 않는다. 그러나 이런 시도를 감지해 사용자에게 쓰기 기능을 지원하지 않는 다는

것을 알려 준다. 우리가 읽어 버퍼에 기록하는 데이터를 가지고 우리가 하는 일을 볼 수 없다고

걱정하지 마라; 우리는 그것을 가지고 많은 일을 하지 않는다. 단지 데이터를 읽어서 그

데이터를 받았다는 메시지만 출력한다.

Example 4-1. chardev.c

/* chardev.c: 얼마나 많이 디바이스 파일에 접근했는지

* 알려주는 모듈

*/

#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) #include <linux/modversions.h> #define MODVERSIONS#endif#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 */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 *);

Page 44: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0#define DEVICE_NAME "chardev" /* /proc/devices 에 나타나는 장치 이름 */#define BUF_LEN 80 /* 장치로부터 메시지의 최대 길이 */

/* 정적 변수로 전역변수 선언 */

static int Major; /* 주장치 번호 */static int Device_Open = 0; /* 장치가 열렸는가? 중복사용 방지 */ /* access to the device */static char msg[BUF_LEN]; /* 요청이 있을 때 장치가 보내는 메시지 */static char *msg_Ptr;

static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release};

/* Functions */

int init_module(void){ Major = register_chrdev(0, DEVICE_NAME, &fops);

if (Major < 0) { printk ("Registering the character device failed with %d\n", Major); return Major; }

Page 45: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

printk("<1>I was assigned major number %d. To talk to\n", Major); printk("<1>the driver, create a dev file with\n");

printk("'mknod /dev/hello c %d 0'.\n", Major); printk("<1>Try various minor numbers. Try to cat and echo to\n");

printk("the device file.\n"); printk("<1>Remove the device file and module when done.\n");

return 0;}

void cleanup_module(void){ /* Unregister the device */ int ret = unregister_chrdev(Major, DEVICE_NAME); if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);}

/* Methods */

/* * "cat /dev/mycharfile" 처럼 프로세스가 디바이스 파일을 열려고 할 때 호출됨

*/static int device_open(struct inode *inode, struct file *file){ static int counter = 0; if (Device_Open) return -EBUSY; Device_Open++; sprintf(msg,"I already told you %d times Hello world!\n", counter++"); msg_Ptr = msg; MOD_INC_USE_COUNT;

return SUCCESS;

Page 46: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

}

/* 디바이스 파일이 닫힐 때 호출됨. */static int device_release(struct inode *inode, struct file *file){ Device_Open --; /* We're now ready for our next caller */

/* 사용 카운트 감소 혹은 한번 열어본 파일이 아니라면 모듈을 제거할 수 없음 */ MOD_DEC_USE_COUNT;

return 0;}

/* 이미 열린 장치 파일에서 무엇인가 읽으려 할 때 호출 */static ssize_t device_read(struct file *filp, char *buffer, /* The buffer to fill with data */ size_t length, /* The length of the buffer */ loff_t *offset) /* Our offset in the file */{ /* 버퍼에 실제 쓰여진 바이트 수*/ int bytes_read = 0;

/* 메시지의 끝에 메시지의 끝임을 알리기 위해 0 리턴 */ if (*msg_Ptr == 0) return 0;

/* 버퍼에 실제 데이터를 입력 */ while (length && *msg_Ptr) {

/* 버퍼는 커널 세그먼트가 아니고 사용자 세그먼트다; * 할당은 일어 나지 않는다. 커널 데이터영역에서 사용자 영역으로

* 데이터를 복사하는 put_user() 사용

*/

Page 47: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

put_user(*(msg_Ptr++), buffer++);

length--; bytes_read++; }

/* 읽혀진 데이터의 바이트 수를 버퍼에 기록 */ 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 ("<1>Sorry, this operation isn't supported.\n"); return -EINVAL;}

4.1.6. 여러 커널 버전을 위한 모듈 작성

커널과 프로세스 사이의 주요한 인터페이스인 시스템 콜은 일반적으로 버전을 일치시켜

사용한다. 새 버전의 시스템 콜이 추가되더라도 구 버전의 시스템 콜은 여전히 동작한다. 이것은

구 버전과의 공존을 위해 필요하다. –새 버전의 커널은 일반적인 프로세스를 중지시키려 하지

않는다. 대부분의 경우, 장치 파일은 그대로 남아있다. On the other hand 반면에 , 커널

내부의 인터페이스는 버전마다 바뀐다.

리눅스 커널은 안정버전과 (n.$<$짝수$>$.m) 개발버전으로 (n.$<$홀수$>$.m) 나뉜다. 개발 버전은 다음 버전에서 다시 구현해야 한다거나 오류로 취급될 수도 있는 것을 내포한 모든

참신한 아이디어를 포함하고 있다. 결과적으로, 이런 개발버전의 인터페이스는 신뢰할 수 없다

(내가 이 글에서 그것들을 다루지 않는 이유이기도 하다. 그것은 고된 일이고 바뀌기도 빨리

바뀐다.). 반면에 안정버전에서, 버그 수정 여부와 관계없이 같은 인터페이스를 기대할 수 있다.

Page 48: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

서로 다른 버전의 커널에는 다른 차이점들이 있다. 만일 여러 버전의 커널을 지원하려 한다면, 조건 지향적인 코드 컴파일 방식이 필요함을 알게 될 것이다.. LINUX_VERSION_CODE 와

KERNEL_VERSION 를 비교하는 방법. 커널의 a.b.c 버전에서, 매크로의 값은

$2^{16}a+2^{8}b+c$ 이다. 커널 2.0.35 이전 에는 이 매크로가 없다는 것을 알아두자. 구식 커널을 지원하는 모듈을 작성하고자 한다면, 다음과 같이 정의 해야 한다.

Example 4-2. some title

#if LINUX_KERNEL_VERSION >= KERNEL_VERSION(2,2,0) #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif

물론 이런 매크로가 있기 때문에, 당신은 매크로의 존재여부를 테스트하기 위해 커널 버전을

테스트 하는 것보다 #ifndef KERNEL_ VERSION 을 사용하는 것이 좋을 것이다.

Notes

[1]관습적인 것이다. 드라이버 작성시, 장치파일을 당신의 디렉토리에 위치시키는 것은

무방하다. 실제 사용하기 위해 /dev 에 드라이버를 위치 시켜야 한다는 점을 명심하자

Page 49: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 5. /proc 파일 시스템

Page 50: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

5.1. /proc File System리눅스에는 커널과 커널 모듈이 프로세스로 정보를 보내는 추가적인 방법이 있다. /proc 파일

시스템이 그것이다. 원래 이것은 프로세스에 관한 정보에 쉽게 접근하도록 설계된 것이다(이름을 보라.). 모듈의 리스트를 가지고 있는 /proc/modules, 그리고 메모리 사용량을

보여주는 /proc/meminfo등과 같이 커널이 관심을 갖는 것들에 상용된다.

/proc 파일 시스템을 사용하는 방법은 디바이스 드라이버를 이용하는 방법과 매우 유사하다. 여러분은 함수의 핸들러 포인터를 포함해 /proc 파일에 필요한 모든 정보를 가지고 구조체를

생성한다.( 우리의 경우 한가지 정보, 누군가 /proc 파일로부터 무엇인가 읽으려 시도할 때

그것이 호출된다.). 그런 후 커널에 init_module()이 그 구조체를 커널에 등록하고

cleanup_module 이 등록을 해제한다.

우리가 우리의 파일이 이용할 inode 번호를 결정하기를 원하지 않고 커널이 결정하게 해서

시스템 다운을 방지하기 원하기 때문에 우리는 proc_register_dynamic()[1]함수를 사용한다. 일반적인 파일은 메모리에 존재하기보다는 디스크에 존재한다.(그것이 /proc 이다.), 또한 그런

경우 파일의 인덱스 노드(index-node, inode 는 약어다)가 위치한 디스크를 inode 번호가

지시한다. inode 구조체는 파일에 대한 정보를 담고 있다, 예를 들어 파일의 퍼미션, 디스크

어디서 파일의 데이터를 찾을 수 있는가 등등.

파일이 열리거나 닫힐 때 우리가 호출하지 않았기 때문에 , 모듈 내부에서

MOD_INC_USE_COUNT 와 MOD_DEC_USE_COUNT 에 접근할 방법이 없다. 또한 파일이

열린 상태에서 모듈이 제거될 때 생기는 문제를 피할 방법이 없다. 다음 장에서 우리는 더

복잡한 구현을 본다, 그러나 그것은 /proc 파일을 다루는데 있어 더 유연한 방법을 제시해줄

것이다. 그리고 그것은 문제에 봉착하지 않게 해주기도 한다.

Example 5-1. procfs.c

/* procfs.c - create a "file" in /proc */

#include <linux/kernel.h> /* 커널 작업 */#include <linux/module.h> /* 모듈 작업 */

/* Deal with CONFIG_MODVERSIONS */

Page 51: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

/* proc fs 를 다룬다 */#include <linux/proc_fs.h>

/* 2.2.3 버전 이전에는 다음의 매크로가 없다. 필요하다면 여기서 정의 한다.*/#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

/* proc fs 에 데이터 전달.

Argument ========= 1. 데이터가 삽입되는 버퍼, 만일 사용하기 원한다면

2. 문자열에 대한 포인터, 만일 커널이 버퍼를 할당하기 원하지 않는 경우 사용

3. 파일에서 현재 위치

4. 첫 인자 버퍼의 크기

5. 0(미래의 사용을 위해?).

Usage and Return Value======================만일 자신만의 버퍼를 원한다면 내가 한 것처럼 두번 째 인자에 넣고 버퍼에서 사용한

바이트 수를 리턴하라

리턴 값이 NULL 인것은 더 이상의 정보가 없음을 의미한다(end of file). 또한 음수인 경우

에러 상황이다.

Page 52: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

For More Information ==================== The way I discovered what to do with this function 내가 이 함수에서 무엇인가를

발견한 방법은 문서를 읽는 것이 아니고, 사용되는 코드를 읽는 것이었다. proc_dir_entry구조체의 get_info 필드를 어디다 쓰는가를 살펴봤다. 또한 /fs/proc/array.c 파일이 사용되는

것을 살펴 본 것이다.

If something is unknown about the kernel, this is 무언가 커널에서 잘 모르는 부분이

있다면 이런 방식이 일반적으로 행해진다. 리눅스에서 우리는 커널 코들를 얻을 수 있다는 것은

대단한 장점이다. 활용하라. */int procfile_read(char *buffer,

char **buffer_location, off_t offset, int buffer_length, int zero)

{ int len; /* 실제 사용되는 바이트 수 */

/* 우리가 이 함수를 나갈 때도 이 스테틱 변수이기 때문에 메모리에 남겨 진다.*/ static char my_buffer[80];

static int count = 1;

/* 우리(?직역)는 한번에 모든 정보를 주기 때문에 유저(?직역)가 추가적인 정보를 요구할 때

우리의 대답은 NO 다. * 표준 라이브러리 함수는 커널이 더 이상 줄 정보가 없다고 말할 때까지, 혹은 표준

라이브러리 함수의 버퍼가 다 찰 때까지 read 시스템 콜을 하기 때 문에 중요하다. */ if (offset > 0) return 0;

/* Fill the buffer and get its length */ len = sprintf(my_buffer,

Page 53: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

"For the %d%s time, go away!\n", count, (count % 100 > 10 && count % 100 < 14) ? "th" : (count % 10 == 1) ? "st" : (count % 10 == 2) ? "nd" : (count % 10 == 3) ? "rd" : "th" ); count++;

/* 함수에 버퍼의 위치를 알린다. */ *buffer_location = my_buffer;

/* Return the length */ return len;}

struct proc_dir_entry Our_Proc_File = { 0,/* Inode 번호 –여기서는 무시한다. 이것은 proc_register[_dynamic]에 의해 채워진다. */ 4, /* 파일의 이름 길이 */ "test", /* 파일이름 */ S_IFREG | S_IRUGO, /* File mode – 일반 파일, 소유자, 그룹, 모든 사람들이 있을 수 있다.*/ 1, /* 파일이 참고하는 디렉토리의 링크 개수*/ 0, 0, /* uid, gid 루트에게 권한을 준다.*/ 80, /* ls 에 의해 보고되는 파일의 크기. */ NULL, /* link, remove등 inode 상에 행해지는 함수- 우리는 아무것도 지원하지 않는다. */ procfile_read, /* 읽기 함수, 무엇인가를 읽으려 할 때 호출되는 함수

NULL /* 퍼미션, 소유권 등을 다루는 inode 를 채우는 함수의 포인터*/ };

Page 54: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* Initialize the module - register the proc file */int init_module(){ /* proc_register[_dynamic]이 성공하면 성공, 아니면 실패 */#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0) /* 구조체에서 그 값이 0 이면 2.2 버전에서는 proc_register()함수가 inode 번호를 동적으로

할당한다. * proc_register_dynamic()함수가 필요없게 됬다. */ return proc_register(&proc_root, &Our_Proc_File);#else return proc_register_dynamic(&proc_root, &Our_Proc_File);#endif

/* proc_root 는 proc 파일 시스템의 루트(/proc) * 우리가 파일을 위치시키기 원하는 위치다. */}

/* Cleanup - unregister our file from /proc */void cleanup_module(){ proc_unregister(&proc_root, Our_Proc_File.low_ino);}

Notes

[1]버전 2.0 과 2.2 에서 inode 를 0 으로 설정하면 이것은 자동으로 된다.

Page 55: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 6. 입력을 위한 /proc 의 사용

Page 56: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

6.1. 입력을 위한 /proc 의 사용지금까지 우리는 커널 모듈로부터 출력을 얻는 두 가지 방법을 알아 보았다. 우리는 장치

드라이버와 mknod 를 등록하거나 /proc 파일을 생성할 수 있다. 이것은 커널 모듈로 하여금

우리에게 무엇인가를 전달할 수 있도록 해준다. 그 반대의 경우가 불가능하다는 것이 문제가

된다. 우선 커널 모듈에 데이터를 입력하는 것은 /proc 파일에 데이터를 쓰는 방법을 택한다.

/proc 파일 시스템은 커널이 프로세스의 상태를 보고할 수 있도록 한 것이기 때문에 , 입력에

대한 특별한 배려는 없다. proc_dir_entry 구조체에는 출력 함수와 같은 방식의 입력 함수에

대한 포인터가 없다. /proc 파일에 쓰기를 하는 대신, 표준 파일 시스템 메커니즘을 사용할

필요가 있다.

리눅스에는 파일 시스템 등록을 위한 표준 메커니즘이 있다. 모든 파일 시스템은 inode 와 file operation 을 다루기 위한 고유한 함수를 가지고 있어야 하기 때문에 이 모든 함수의 포인터를

가지고 있는 inode_operations 라는 구조체가 있으며, 그리고 그것은 file_operations구조체를 지시하는 포인터를 포함하고 있다. 새 파일을 등록할 때마다, /proc 에서 그것에

접근하기 위해 어느 inode_operations 구조체를 사용할 것인지 결정한다. 우리가 사용할

module_input(), module_output()함수를 지시하는 포인터를 포함한 file_operation 구조체를 지시하는 포인터를 포함한 inode_operations 구조체, 이것이 우리가 사용하는

메커니즘이다.

읽기와 쓰기를 위한 표준 롤 셋이 커널에서는 반대로 지정돼있다는 것을 명심하자. 읽기 함수는

출력을 위해, 반대로 쓰기함수는 입력을 위해 사용 된다. 일기와 쓰기는 사용자의 관점에서 본

것이기 때문이다. --- 만일 프로세스가 커널에서 무언가 읽는다면 커널은 그것을 출력해 주어야

한다. 그리고 프로세스가 커널에 무언가를 쓴다면 커널은 그것을 입력으로 받아 들여야 한다.

흥미 있는 또 다른 것은 module_permissions() 함수다. 프로세스가 /proc 파일에 무언가를

하려고 할 때마다, 이 함수는 호출 된다. 그리고 이 함수는 이 접근을 허용할 것인가 말 것인가를

결정하게 된다. 당장은 이것이 현재 사용자의 uid 와 행위(operation) 자체에만 근거 하지만, (그리고 이것은 현재 실행되는 프로세스의 정보를 담고 있는 구조체의 포인터에서 찾아 질 수

있는 것들) 이것은 프로세스가 무엇을 하는 것인가, 작업 시각, 마지막으로 무엇을 입력

받았는가 등, 우라기 원하는 어떤 것에 근거할 수도 있다.

인텔 아키텍쳐에서(물론 다른 프로세서 하에서는 달라질 수도 있지만) put_user() 매크로와

get_user()매크로를 사용하는 이유는 리눅스의 메모리가 세그먼트 이기 때문이다. 포인터

Page 57: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

자체는 유니크한 메모리 위치를 지시할 수 없고, 메모리 세그먼트 위치만 지시할 수 있다. 또한

어느 메모리 세그먼트를 사용할 수 있는지 알 필요가 있다. 커널을 위한 메모리 하나의

세그먼트가 있고, 각 프로세스들마다 하나의 세그먼트가 존재한다.

프로세스 자신의 메모리 세그먼트에만 접근이 가능하다. 그래서 일반적인 프로그램이

프로세스로서 실행될 때 세그먼트에 관해 신경 쓰지 않아도 된다. 커널 모듈을 작성할 때, 일반적으로 시스템에 의해 자동으로 다뤄지는 커널 메모리 세그먼트에 접근하기를 원한다. 그러나 메머리 버퍼의 내용이 현재 실행 중인 프로세스와 커널 사이에서 전달 되야 할 때, 커널

함수는 프로세스 세그먼트에 있는 메모리 포인터를 받게 된다. put_user() 매크로와

get_user() 매크로가 그 메모리에 접근 가능케 해준다.

Example 6-1. procfs.c

/* procfs.c - create a "file" in /proc, which allows both input and output. */

#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Necessary because we use proc fs */#include <linux/proc_fs.h>

/* In 2.2.3 /usr/include/linux/version.h includes a * macro for this, but 2.0.35 doesn't - so I add it * here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)#include <asm/uaccess.h> /* for get_user and put_user */#endif

Page 58: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* The module's file functions ********************** */

/* Here we keep the last message received, to prove * that we can process our input */#define MESSAGE_LENGTH 80static char Message[MESSAGE_LENGTH];

/* Since we use the file operations struct, we can't * use the special proc output provisions - we have to * use a standard read function, which is this function */#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(2,2,0)static ssize_t module_output( struct file *file, /* The file read */ char *buf, /* The buffer to put data to (in the * user segment) */ size_t len, /* The length of the buffer */ loff_t *offset) /* Offset in the file - ignore */#elsestatic int module_output( struct inode *inode, /* The inode read */ struct file *file, /* The file read */ char *buf, /* The buffer to put data to (in the * user segment) */ int len) /* The length of the buffer */#endif{ static int finished = 0; int i; char message[MESSAGE_LENGTH+30];

/* We return 0 to indicate end of file, that we have * no more information. Otherwise, processes will * continue to read from us in an endless loop. */

Page 59: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

if (finished) { finished = 0; return 0; }

/* We use put_user to copy the string from the kernel's * memory segment to the memory segment of the process * that called us. get_user, BTW, is * used for the reverse. */ sprintf(message, "Last input:%s", Message); for(i=0; i&lt;len && message[i]; i++) put_user(message[i], buf+i);

/* Notice, we assume here that the size of the message * is below len, or it will be received cut. In a real * life situation, if the size of the message is less * than len then we'd return len and on the second call * start filling the buffer with the len+1'th byte of * the message. */ finished = 1;

return i; /* Return the number of bytes "read" */}

/* This function receives input from the user when the * user writes to the /proc file. */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t module_input( struct file *file, /* The file itself */ const char *buf, /* The buffer with input */ size_t length, /* The buffer's length */ loff_t *offset) /* offset to file - ignore */#else

Page 60: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

static int module_input( struct inode *inode, /* The file's inode */ struct file *file, /* The file itself */ const char *buf, /* The buffer with the input */ int length) /* The buffer's length */#endif{ int i;

/* Put the input into Message, where module_output * will later be able to use it */ for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(Message[i], buf+i); /* In version 2.2 the semantics of get_user changed, * it not longer returns a character, but expects a * variable to fill up as its first argument and a * user segment pointer to fill it from as the its * second. * * The reason for this change is that the version 2.2 * get_user can also read an short or an int. The way * it knows the type of the variable it should read * is by using sizeof, and for that it needs the * variable itself. */ #else Message[i] = get_user(buf+i);#endif Message[i] = '\0'; /* we want a standard, zero * terminated string */ /* We need to return the number of input characters * used */ return i;

Page 61: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

}

/* This function decides whether to allow an operation * (return zero) or not allow it (return a non-zero * which indicates why it is not allowed). * * The operation can be one of the following values: * 0 - Execute (run the "file" - meaningless in our case) * 2 - Write (input to the kernel module) * 4 - Read (output from the kernel module) * * This is the real function that checks file * permissions. The permissions returned by ls -l are * for referece only, and can be overridden here. */static int module_permission(struct inode *inode, int op){ /* We allow everybody to read from our module, but * only root (uid 0) may write to it */ if (op == 4 || (op == 2 && current->euid == 0)) return 0;

/* If it's anything else, access is denied */ return -EACCES;}

/* The file is opened - we don't really care about * that, but it does mean we need to increment the * module's reference count. */int module_open(struct inode *inode, struct file *file)

Page 62: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

{ MOD_INC_USE_COUNT; return 0;}

/* The file is closed - again, interesting only because * of the reference count. */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)int module_close(struct inode *inode, struct file *file)#elsevoid module_close(struct inode *inode, struct file *file)#endif{ MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) return 0; /* success */#endif}

/* Structures to register as the /proc file, with * pointers to all the relevant functions. ********** */

/* File operations for our proc file. This is where we * place pointers to all the functions called when * somebody tries to do something to our file. NULL * means we don't want to deal with something. */static struct file_operations File_Ops_4_Our_Proc_File = { NULL, /* lseek */

Page 63: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

module_output, /* "read" from the file */ module_input, /* "write" to the file */ NULL, /* readdir */ NULL, /* select */ NULL, /* ioctl */ NULL, /* mmap */ module_open, /* Somebody opened the file */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) NULL, /* flush, added here in version 2.2 */#endif module_close, /* Somebody closed the file */ /* etc. etc. etc. (they are all given in * /usr/include/linux/fs.h). Since we don't put * anything here, the system will keep the default * data, which in Unix is zeros (NULLs when taken as * pointers). */ };

/* Inode operations for our proc file. We need it so * we'll have some place to specify the file operations * structure we want to use, and the function we use for * permissions. It's also possible to specify functions * to be called for anything else which could be done to * an inode (although we don't bother, we just put * NULL). */static struct inode_operations Inode_Ops_4_Our_Proc_File = { &File_Ops_4_Our_Proc_File, NULL, /* create */ NULL, /* lookup */ NULL, /* link */ NULL, /* unlink */ NULL, /* symlink */

Page 64: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

NULL, /* mkdir */ NULL, /* rmdir */ NULL, /* mknod */ NULL, /* rename */ NULL, /* readlink */ NULL, /* follow_link */ NULL, /* readpage */ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ module_permission /* check for permissions */ };

/* Directory entry */static struct proc_dir_entry Our_Proc_File = { 0, /* Inode number - ignore, it will be filled by * proc_register[_dynamic] */ 7, /* Length of the file name */ "rw_test", /* The file name */ S_IFREG | S_IRUGO | S_IWUSR, /* File mode - this is a regular file which * can be read by its owner, its group, and everybody * else. Also, its owner can write to it. * * Actually, this field is just for reference, it's * module_permission that does the actual check. It * could use this field, but in our implementation it * doesn't, for simplicity. */ 1, /* Number of links (directories where the * file is referenced) */ 0, 0, /* The uid and gid for the file - * we give it to root */ 80, /* The size of the file reported by ls. */

Page 65: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

&Inode_Ops_4_Our_Proc_File, /* A pointer to the inode structure for * the file, if we need it. In our case we * do, because we need a write function. */ NULL /* The read function for the file. Irrelevant, * because we put it in the inode structure above */ };

/* Module initialization and cleanup ******************* */

/* Initialize the module - register the proc file */int init_module(){ /* Success if proc_register[_dynamic] is a success, * failure otherwise */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) /* In version 2.2, proc_register assign a dynamic * inode number automatically if it is zero in the * structure , so there's no more need for * proc_register_dynamic */ return proc_register(&proc_root, &Our_Proc_File);#else return proc_register_dynamic(&proc_root, &Our_Proc_File);#endif}

/* Cleanup - unregister our file from /proc */void cleanup_module(){ proc_unregister(&proc_root, Our_Proc_File.low_ino);

Page 66: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

} Notes

[1]이 두 가지의 차이는 file operations 는 파일 자체를 다룬다는 것이고 , inode operation은 파일에 링크를 생성하는 것 같은 파일을 참조하는 방법을 다룬다는 점에 있다.

Page 67: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 7. Talking To Device Files

Page 68: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

7.1. Talking to Device Files (writes and IOCTLs)디바이스 파일은 물리적 장치를 나타내는 것으로 간주된다. 대부분의 물리적 장치들은 입출력을

위해 사용된다. 그래서 커널 안에서 디바이스 드라이버가 프로세스로부터 디바이스로 보낼

아웃풋을 얻기 위해 특별한 메커니즘이 필요하다. 파일에 쓰기를 하는 것처럼, 아웃풋을 쓰기

위해 디바이스 파일을 오픈 하는 것으로 된다. 다음의 예에서 이것은 device_write()함수에

의해 구현된다.

이것으로 항상 충분한 것은 아니다. 모뎀에 연결된 시리얼 포트가 있다고 상상해보자. (내장

모뎀이라 해도 CPU 의 관점에서 모뎀에 시리얼 포트가 연결된 것으로 구현된다. 너무 지나치게

깊이 상상하지 말자). 해야 할 것은 디바이스 파일을 이용해 모뎀에 무언가(모뎀 명령어 혹은

전화선을 통해 나가는 데이터)를 기록하는 것이고, 모뎀으로부터 무언가(명령에 대한 응답, 혹은

전화선을 통한 데이터)를 읽어 오는 것이다. 예를 들어 어떤 속도록 데이터를 보낼 것인가? 와

같은 질문은 여전히 남는다.

유닉스에서 Input Output Control 의 준 말인 ioctl()이라는 특수한 함수를 호출하는 것이 답이

된다. 모든 디바이스드라이버는 자신만의 ioctl 명령을 갖을 수 있고, 그리고 그것은 읽기를 위한

ioctl 커맨드(프로세스로부터 커널로 정보를 보내는 )이거나 쓰기를 위한 ioctl 커맨드(프로세스로 정보를 돌리는), 혹은 둘 모두, 혹은 둘 모드 아니다. ioctl 함수는 다음과 같은 세

개의 인자를 가진다. 해당되는 디바이스 파일의 디스크립터, ioctl 번호, 롱형으로 어떤

타입으로던지 케스팅후 사용할 수 있는 인자[2]

ioctl 의 번호는 장치 주번호, ioctl 의 타입(읽기/쓰기), 커맨드, 인자의 타입으로 만들어 진다. 이

ioctl 번호는 헤더파일에 있는 _IO, _IOR, _IOW or _IOWR(이들은 보통 타입에 따라 달리 쓰임) 와 같은 매크로에 의해 보통 만들어진다. 헤더 파일은 ioctl 을 사용하는 프로그램(적당한 ioctl명령을 만들기 위해 )이나, 커널 모듈(만들어 진 ioctl명령을 이해하기 위해) 모두에 포함되야

한다. 다음의 예에서, chardev.h 가 헤더 파일이고 그것을 사용하는 프로그램은 ioctl.c 이다.

만일 당신의 커널 모듈에서 ioctl 을 사용하기 원한다면, ioctl 명령을 할당하는 것이 가장 좋다. 그래서 우연히 다른 사람의 ioctl 커맨드를 받게 되거나, 다른 사람이 당신의 ioctl 커맨드를

받아도 그것이 잘못됐다는 것을 알게 된다. 좀더 많은 정보를 위해서 커널소스의

Documentation/ioctl-number.txt 파일을 참고하길 바란다.

Page 69: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Example 7-1. chardev.c

/* chardev.c - Create an input/output character device */

#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

/* For character devices */

/* The character device definitions are here */#include <linux/fs.h>

/* A wrapper which does next to nothing at * at present, but may help for compatibility * with future versions of Linux */#include <linux/wrapper.h>

/* Our own ioctl numbers */#include "chardev.h"

/* In 2.2.3 /usr/include/linux/version.h includes a * macro for this, but 2.0.35 doesn't - so I add it * here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

Page 70: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)#include <asm/uaccess.h> /* for get_user and put_user */#endif

#define SUCCESS 0

/* Device Declarations ******************************** */

/* The name for our device, as it will appear in * /proc/devices */#define DEVICE_NAME "char_dev"

/* The maximum length of the message for the device */#define BUF_LEN 80

/* Is the device open right now? Used to prevent * concurent access into the same device */static int Device_Open = 0;

/* The message the device will give when asked */static char Message[BUF_LEN];

/* How far did the process reading the message get? * Useful if the message is larger than the size of the * buffer we get to fill in device_read. */static char *Message_Ptr;

Page 71: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* This function is called whenever a process attempts * to open the device file */static int device_open(struct inode *inode, struct file *file){#ifdef DEBUG printk ("device_open(%p)\n", file);#endif

/* We don't want to talk to two processes at the * same time */ if (Device_Open) return -EBUSY;

/* If this was a process, we would have had to be * more careful here, because one process might have * checked Device_Open right before the other one * tried to increment it. However, we're in the * kernel, so we're protected against context switches. * * This is NOT the right attitude to take, because we * might be running on an SMP box, but we'll deal with * SMP in a later chapter. */

Device_Open++;

/* Initialize the message */ Message_Ptr = Message;

MOD_INC_USE_COUNT;

return SUCCESS;}

Page 72: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* This function is called when a process closes the * device file. It doesn't have a return value because * it cannot fail. Regardless of what else happens, you * should always be able to close a device (in 2.0, a 2.2 * device file could be impossible to close). */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static int device_release(struct inode *inode, struct file *file)#elsestatic void device_release(struct inode *inode, struct file *file)#endif{#ifdef DEBUG printk ("device_release(%p,%p)\n", inode, file);#endif /* We're now ready for our next caller */ Device_Open --;

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) return 0;#endif}

/* This function is called whenever a process which * has already opened the device file attempts to * read from it. */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

Page 73: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

static ssize_t device_read( struct file *file, char *buffer, /* The buffer to fill with the data */ size_t length, /* The length of the buffer */ loff_t *offset) /* offset to the file */#elsestatic int device_read( struct inode *inode, struct file *file, char *buffer, /* The buffer to fill with the data */ int length) /* The length of the buffer * (mustn't write beyond that!) */#endif{ /* Number of bytes actually written to the buffer */ int bytes_read = 0;

#ifdef DEBUG printk("device_read(%p,%p,%d)\n", file, buffer, length);#endif

/* If we're at the end of the message, return 0 * (which signifies end of file) */ if (*Message_Ptr == 0) return 0;

/* Actually put the data into the buffer */ while (length && *Message_Ptr) {

/* Because the buffer is in the user data segment, * not the kernel data segment, assignment wouldn't * work. Instead, we have to use put_user which * copies data from the kernel data segment to the * user data segment. */ put_user(*(Message_Ptr++), buffer++);

Page 74: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

length --; bytes_read ++; }

#ifdef DEBUG printk ("Read %d bytes, %d left\n", bytes_read, length);#endif

/* Read functions are supposed to return the number * of bytes actually inserted into the buffer */ return bytes_read;}

/* This function is called when somebody tries to * write into our device file. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t device_write(struct file *file, const char *buffer, size_t length, loff_t *offset)#elsestatic int device_write(struct inode *inode, struct file *file, const char *buffer, int length)#endif{ int i;

#ifdef DEBUG printk ("device_write(%p,%s,%d)", file, buffer, length);#endif

Page 75: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

for(i=0; i<length && i<BUF_LEN; i++)#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(Message[i], buffer+i);#else Message[i] = get_user(buffer+i);#endif

Message_Ptr = Message;

/* Again, return the number of input characters used */ return i;}

/* This function is called whenever a process tries to * do an ioctl on our device file. We get two extra * parameters (additional to the inode and file * structures, which all device functions get): the number * of the ioctl called and the parameter given to the * ioctl function. * * If the ioctl is write or read/write (meaning output * is returned to the calling process), the ioctl call * returns the output of this function. */int device_ioctl( struct inode *inode, struct file *file, unsigned int ioctl_num,/* The number of the ioctl */ unsigned long ioctl_param) /* The parameter to it */{ int i; char *temp;#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) char ch;

Page 76: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#endif

/* Switch according to the ioctl called */ switch (ioctl_num) { case IOCTL_SET_MSG: /* Receive a pointer to a message (in user space) * and set that to be the device's message. */

/* Get the parameter given to ioctl by the process */ temp = (char *) ioctl_param; /* Find the length of the message */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(ch, temp); for (i=0; ch && i<BUF_LEN; i++, temp++) get_user(ch, temp);#else for (i=0; get_user(temp) && i<BUF_LEN; i++, temp++)

;#endif

/* Don't reinvent the wheel - call device_write */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) device_write(file, (char *) ioctl_param, i, 0);#else device_write(inode, file, (char *) ioctl_param, i);#endif break;

case IOCTL_GET_MSG: /* Give the current message to the calling * process - the parameter we got is a pointer, * fill it. */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) i = device_read(file, (char *) ioctl_param, 99, 0);

Page 77: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#else i = device_read(inode, file, (char *) ioctl_param, 99); #endif /* Warning - we assume here the buffer length is * 100. If it's less than that we might overflow * the buffer, causing the process to core dump. * * The reason we only allow up to 99 characters is * that the NULL which terminates the string also * needs room. */

/* Put a zero at the end of the buffer, so it * will be properly terminated */ put_user('\0', (char *) ioctl_param+i); break;

case IOCTL_GET_NTH_BYTE: /* This ioctl is both input (ioctl_param) and * output (the return value of this function) */ return Message[ioctl_param]; break; }

return SUCCESS;}

/* Module Declarations *************************** */

/* This structure will hold the functions to be called * when a process does something to the device we * created. Since a pointer to this structure is kept in * the devices table, it can't be local to * init_module. NULL is for unimplemented functions. */

Page 78: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

struct file_operations Fops = { NULL, /* seek */ device_read, device_write, NULL, /* readdir */ NULL, /* select */ device_ioctl, /* ioctl */ NULL, /* mmap */ device_open,#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) NULL, /* flush */#endif device_release /* a.k.a. close */};

/* Initialize the module - Register the character device */int init_module(){ int ret_val;

/* Register the character device (atleast try) */ ret_val = module_register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

/* Negative values signify an error */ if (ret_val < 0) { printk ("%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; }

printk ("%s The major device number is %d.\n",

Page 79: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

"Registeration is a success", MAJOR_NUM); printk ("If you want to talk to the device driver,\n"); printk ("you'll have to create a device file. \n"); printk ("We suggest you use:\n"); printk ("mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); printk ("The device file name is important, because\n"); printk ("the ioctl program assumes that's the\n"); printk ("file you'll use.\n");

return 0;}

/* Cleanup - unregister the appropriate file from /proc */void cleanup_module(){ int ret;

/* Unregister the device */ ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME); /* If there's an error, report it */ if (ret < 0) printk("Error in module_unregister_chrdev: %d\n", ret);}

Example 7-2. chardev.h

/* chardev.h - the header file with the ioctl definitions. * * The declarations here have to be in a header file, because * they need to be known both to the kernel module * (in chardev.c) and the process calling ioctl (ioctl.c) */

Page 80: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

#ifndef CHARDEV_H#define CHARDEV_H

#include <linux/ioctl.h>

/* The major device number. We can't rely on dynamic * registration any more, because ioctls need to know * it. */#define MAJOR_NUM 100

/* Set the message of the device driver */#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)/* _IOR means that we're creating an ioctl command * number for passing information from a user process * to the kernel module. * * The first arguments, MAJOR_NUM, is the major device * number we're using. * * The second argument is the number of the command * (there could be several with different meanings). * * The third argument is the type we want to get from * the process to the kernel. */

/* Get the message of the device driver */#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) /* This IOCTL is used for output, to get the message * of the device driver. However, we still need the * buffer to place the message in to be input,

Page 81: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* as it is allocated by the process. */

/* Get the n'th byte of the message */#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) /* The IOCTL is used for both input and output. It * receives from the user a number, n, and returns * Message[n]. */

/* The name of the device file */#define DEVICE_FILE_NAME "char_dev"

#endif

Example 7-3. ioctl.c

/* ioctl.c - the process to use ioctl's to control the kernel module * * Until now we could have used cat for input and output. But now * we need to do ioctl's, which require writing our own process. */

/* device specifics, such as ioctl numbers and the * major device file. */#include "chardev.h"

#include <fcntl.h> /* open */ #include <unistd.h> /* exit */#include <sys/ioctl.h> /* ioctl */

Page 82: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* Functions for the ioctl calls */

ioctl_set_msg(int file_desc, char *message){ int ret_val;

ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);

if (ret_val < 0) { printf ("ioctl_set_msg failed:%d\n", ret_val); exit(-1); }}

ioctl_get_msg(int file_desc){ int ret_val; char message[100];

/* Warning - this is dangerous because we don't tell * the kernel how far it's allowed to write, so it * might overflow the buffer. In a real production * program, we would have used two ioctls - one to tell * the kernel the buffer length and another to give * it the buffer to fill */ ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

if (ret_val < 0) { printf ("ioctl_get_msg failed:%d\n", ret_val); exit(-1); }

Page 83: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

printf("get_msg message:%s\n", message);}

ioctl_get_nth_byte(int file_desc){ int i; char c;

printf("get_nth_byte message:");

i = 0; while (c != 0) { c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);

if (c < 0) { printf( "ioctl_get_nth_byte failed at the %d'th byte:\n", i); exit(-1); }

putchar(c); } putchar('\n');}

/* Main - Call the ioctl functions */main(){ int file_desc, ret_val; char *msg = "Message passed by ioctl\n";

Page 84: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

file_desc = open(DEVICE_FILE_NAME, 0); if (file_desc < 0) { printf ("Can't open device file: %s\n", DEVICE_FILE_NAME); exit(-1); }

ioctl_get_nth_byte(file_desc); ioctl_get_msg(file_desc); ioctl_set_msg(file_desc, msg);

close(file_desc); }

Notes

[1]여기서의 읽기 쓰기 롤셋도 바뀌어 있어 ioctl 의 읽기는 정보를 커널로 보내는 것이고

쓰기는 정보를 커널로부터 받는 것임을 인지하자.[2]

이것은 명확하지 않다. 예를 들어 ioctl 을 통해 구조체를 전달할 수는 없다. 그러나

구조체의 포인터를 전달할 수는 있다. /

Page 85: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 8. 시스템 콜

Page 86: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

8.1. 시스템 콜지금까지 우리가 한 것은 /proc 파일과 디바이스 헨들러를 등록하기 위해 잘 정의된 커널

메커니즘을 사용한 것이다. 장치 드라이버를 작성하는 것과 같은 커널 프로그램을 하는 것에는

지금까지의 것도 좋다. 그러나 특정한 방식으로 시스템의 행위를 변경하는 것과 같은 독특한

것을 원하는가? 그런 것들은 전적으로 당신의 몫이다.

이 부분이 커널 프로그램에서 주의해야 할 부분이다. 아래 예제에서 open() 시스템 콜을

죽였다. 이것은 어떤 파일도 열거나, 어떤 프로그램을 실행하거나, 컴퓨터를 셧다운 시키지도

못하게 됨을 의미한다. 전원 스위치를 강제로 꺼야 한다. 다행히도 어떤 파일도 손상을 입지

않았다. 어떤 파일도 잃지 않기 위해서는 insmod 를 하거나 rmmod 를 실행하기 전에 sync 를

실행 시키자.

/proc 파일이나 디바이스 파일은 잊자. 그들은 간단한 문제다. 모든 프로세스가 사용하는 단

하나의 실제 프로세스에서 커널로의 통신 메커니즘은 시스템 콜이다. 파일을 열거나 새로운

프로세스를 생성하거나 추가적인 메모리를 요구하는 등, 프로세스가 커널에게 서비스를 요구할

때, 이 메커니즘이 사용된다. 원하는 방식으로 커널이 움직이기를 원한다면 바로 여기가

작업해야 할 부분이다. 어떤 시스템 콜이 프로그램에 의해 사용되는 가를 알기 원한다면, strace <arguments> 실행 시키자.

일반적으로 프로세스는 커널에 직접적으로 접근할 수 없다. 프로세스는 커널 메모리에

접근한다거나 커널의 함수를 호출할 수 없다. 하드웨어의 CPU 가 그렇게 하며 이것이

보호모드라 부르는 이유다.

시스템 콜은 일반적인 규칙에서의 예외다. 프로세스가 적당한 값으로 레지스터를 채우고, 커널

안에 미리 정의 되있는 위치로 점프하는 인스트럭션(명령)을 호출하는 것이 바로 이것이다. 인텔

CPU 에서는 이것이 인터럽트 0x80 을 의미한다. 일단 이 위치로 이동하면 하드웨어는 제한

적인 사용자 모드가 아니고 운영체계의 커널로 인식한다. 비로소 원하는 것을 할 수 있다.

커널 내부의 위치에서 프로세스는 시스템 콜로 점프가 가능하다. 이 위치에서 프로시져는

시스템 콜의 번호를 확인한다. 그리고 그것은 커널에게 어떤 서비스를 프로세스가 요구했는지

알려준다. 그런 후, 시스템 콜 테이블(sys_chall_tabel)을 커널 함수를 호출하기 위한 주소를

알아보기 위해 조사한다. 그 후 함수를 호출하며, 함수가 리턴한 후 몇 가지 시스템 체크를 하고, 프로세스로 돌아온다. 이 코드를 보기를 원한다면, /$<$architecture$>$/kernel/entry.S 의

다음 라인인 ENTRY(system_call)에 있으니 보기 바란다. /

Page 87: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

특정 시스템 콜의 작동을 바꾸고 싶다면, 해야 할 일은 그 함수를 우리의 것으로 새로 구현하는

것이다.(보통 몇 라인되는 우리의 코드를 넣고 원래의 함수를 호출한다.) 그리고 sys_call_table에 우리가 만든 함수의 포인터로 대체하면 된다. 나중에 우리가 만든 것을 제거하고 시스템이

불안정한 상태로 남기를 바라지 않는 다면, cleanup_module()에서 원래의 상태로

sys_call_table 을 돌려 놓는 것이 필요하다.

여기의 소스코드는 그런 커널 모듈의 예다. 특정 사용자에 대한 ‘스파이’를 원해 그 사용자가

어떤 파일을 열 때마다 prink()로 메시지를 출력한다. 이것을 위해 open 시스템 콜의 코드를

우리의 함수인 our_sys_open()으로 바꿔야 한다. 이 함수는 현재 프로세스의 uid 를 검사해본

후 우리가 감시하고자 하는 uid 와 일치 할 경우, printk()를 호출해 열린 파일의 이름을

출력한다. 같은 방식으로 실제 파일을 열기 위해 같은 인자를 가지고 open()함수를 호출한다.

init_module() 함수는 sys_call_table 에서 맞는 위치를 대체하고 원래 함수의 포인터를 변수에

보관한다. cleanup_module() 함수는 그 변수를 원상 복구해 모두 정상 상태로 돌려 놓는 다. 두 개의 커널 모듈이 동시에 같은 시스템 콜을 변경할 가능성 대문에 이 접근은 위험하다. A 와 B라는 커널 모듈이 있다고 가정하자. 그리고 A 의 open()함수인 A_open 그리고 B 의 B_open이라 하자. A 모듈이 커널에 적재 될 때, 시스템 콜은 A_open 으로 바뀌고, 일을 마쳤을 때 원래

것인 sys_open 으로 되돌아 갈 것이다. 다음으로 B 모듈이 적재 되면, 시시템 콜을 B_open으로 바꾸고, 그것은 해제 시 복구 시켜야 할 시스템 콜을 A_open 으로 생각할 것이다.

B 모듈이 먼저 제거 될 경우 모든 것은 괜찮다. --- 그것은 단순히 시스템 콜을 A_open 으로

되돌려 놓을 것이다. 그런 A 모듈이 먼저 제거되고 B 가 제거되면, 시스템은 다운될 것이다. A 모듈을 제거하는 것은 원래의 시스템 콜을 복구 시키고 B 는 이 루프를 빠져 나온다. B 모듈이

제거 될 때, B 는 원래의 시스템 콜을 A_open 이라고 생각할 것이다. 그러나 이것은 더 이상

메모리에 존재하지 않는다. 간단히 보아, 시스템 콜이 우리가 작성한 함수와 같은가, 변화가

없는가를 확인함으로써 간단히 해결할 수 있을 것 같아 보인다.(B 모듈이 제거 될 때 아무것도

제거하지 않음으로써) 그러나 이것은 더 나쁜 문제를 유발한다. A 모듈이 제거 될 때, 시스템

콜이 B_open 으로 바뀐 것으로 보고 더 이상 A_open 을 지시하지 않을 것이다. 이는

메모리에서 제거되기 전에 sys_open()을 복구 시기지 않을 것이다. 불행히도 B_open 은 더

이상 존재하지 않는 A_open 을 호출하려고 하고 B 모듈을 제거하지 않은 상태에서 시스템은

다운될 것이다.

두 가지 방법으로 이를 해결할 수 있다. 첫 방법은 원래 시스템 콜인 sys_open()을 복구 하는

것이다. 불행하게도 sys_open 은 더 이상 커널 시스템 콜 테이블인 /proc/ksyms 의 일부가

아니어 우리가 접근할 수 없다. 다른 방법은 레퍼런스 카운터를 사용해 모듈이 사용중이라면

Page 88: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

root 가 rmmod 를 실행 할 수 없게 하는 것이다. 이것은 상용제품에서는 좋은 방법이나

교육용으로는 부적합하다. --- 내가 여기서 이것을 사용하지 않는 이유다.

Example 8-1. procfs.c

/* syscall.c * * System call "stealing" sample. */

/* Copyright (C) 2001 by Peter Jay Salzman */

/* The necessary header files */

/* Standard in kernel modules */#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

#include <sys/syscall.h> /* The list of system calls */

/* For the current (process) structure, we need * this to know who the current user is. */#include <linux/sched.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

Page 89: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* macro for this, but 2.0.35 doesn't - so I add it * here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)#include <asm/uaccess.h>#endif

/* The system call table (a table of functions). We * just define this as external, and the kernel will * fill it up for us when we are insmod'ed */extern void *sys_call_table[];

/* UID we want to spy on - will be filled from the * command line */int uid;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)MODULE_PARM(uid, "i");#endif

/* A pointer to the original system call. The reason * we keep this, rather than call the original function * (sys_open), is because somebody else might have * replaced the system call before us. Note that this * is not 100% safe, because if another module * replaced sys_open before us, then when we're inserted

Page 90: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* we'll call the function in that module - and it * might be removed before we are. * * Another reason for this is that we can't get sys_open. * It's a static variable, so it is not exported. */asmlinkage int (*original_call)(const char *, int, int);

/* For some reason, in 2.2.3 current->uid gave me * zero, not the real user ID. I tried to find what went * wrong, but I couldn't do it in a short time, and * I'm lazy - so I'll just use the system call to get the * uid, the way a process would. * * For some reason, after I recompiled the kernel this * problem went away. */asmlinkage int (*getuid_call)();

/* The function we'll replace sys_open (the function * called when you call the open system call) with. To * find the exact prototype, with the number and type * of arguments, we find the original function first * (it's at fs/open.c). * * In theory, this means that we're tied to the * current version of the kernel. In practice, the * system calls almost never change (it would wreck havoc * and require programs to be recompiled, since the system * calls are the interface between the kernel and the * processes). */

Page 91: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

asmlinkage int our_sys_open(const char *filename, int flags, int mode){ int i = 0; char ch;

/* Check if this is the user we're spying on */ if (uid == getuid_call()) { /* getuid_call is the getuid system call, * which gives the uid of the user who * ran the process which called the system * call we got */

/* Report the file, if relevant */ printk("Opened file by %d: ", uid); do {#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(ch, filename+i);#else ch = get_user(filename+i);#endif i++; printk("%c", ch); } while (ch != 0); printk("\n"); }

/* Call the original sys_open - otherwise, we lose * the ability to open files */ return original_call(filename, flags, mode);}

Page 92: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* Initialize the module - replace the system call */int init_module(){ /* Warning - too late for it now, but maybe for * next time... */ printk("I'm dangerous. I hope you did a "); printk("sync before you insmod'ed me.\n"); printk("My counterpart, cleanup_module(), is even"); printk("more dangerous. If\n"); printk("you value your file system, it will "); printk("be \"sync; rmmod\" \n"); printk("when you remove this module.\n");

/* Keep a pointer to the original function in * original_call, and then replace the system call * in the system call table with our_sys_open */ original_call = sys_call_table[__NR_open]; sys_call_table[__NR_open] = our_sys_open;

/* To get the address of the function for system * call foo, go to sys_call_table[__NR_foo]. */

printk("Spying on UID:%d\n", uid);

/* Get the system call for getuid */ getuid_call = sys_call_table[__NR_getuid];

return 0;}

/* Cleanup - unregister the appropriate file from /proc */void cleanup_module(){ /* Return the system call back to normal */

Page 93: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

if (sys_call_table[__NR_open] != our_sys_open) { printk("Somebody else also played with the "); printk("open system call\n"); printk("The system may be left in "); printk("an unstable state.\n"); }

sys_call_table[__NR_open] = original_call;}

Page 94: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 9. Blocking Processes

Page 95: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

9.1. Blocking Processes9.1.1. printk() 대체하기

누군가 지금 당장 무얼 해달라고 하면 어떻게 하겠는가? 당신이 사람이고 다른 사람이 귀찮게

한다면 당신은 “지금은 않돼, 바빠, 저리가!” 라고 하면 된다. 그러나 당신이 커널 모듈이고

프로세스가 귀찮게 할 때는 다른 방식을 취해야 한다. 당신이 프로세스의 요구를 수행할 수 있을

때까지 프로세스를 잠들게 해야 한다. 프로세스는 커널에 의해 잠들거나 깨어날 수 있다. (이것이 하나의 CPU 에서 여러 개의 프로세스가 실행되는 방식이다.)

다음의 커널 모듈은 이런 예다. /proc/sleep 파일은 오직 하나의 프로세스에 의해서만 사용될 수

있다. 만약 파일이 이미 열려 있다면, 커널 모듈은 module_interruptible_sleep_on()함수를

호출한다. 이 함수는 작업의 상태(task – 작업은 프로세스와 호출되는 시스템 콜에 관한 정보를

가진 커널의 데이터 구조를 말한다.)를 TASK_INTERRUPTIBLE 로 변경한다. 이것은 작업이

깨어나 파일에 접근할 수 있는 작업 대기 큐에 진입됨을 의미한다. 그리고 이 함수는 스케줄러를

호출해 다른 프로세스로 문맥전환(context switch)를 하게 하며, 그 프로세스는 CPU 를

사용한다.

프로세스가 파일에 대한 작업을 마치면, 파일을 닫고, module_close()함수를 호출한다. 이

함수는 큐에 있는 프로세스를 깨운다. (대기 중인 프로세스를 깨우는 특별한 메커니즘은 없다) 파일을 닫은 프로세스는 다른 작업을 계속 수행한다. 이때 스케줄러는 그 프로세스가 충분히

수행됐으며, 다른 프로세스에게 CPU 점유권을 넘기도록 결정한다. 결국 프로세스 중 하나는

스케줄러에 의해 CPU 점유를 허가 받게 된다. 이런 작업은 module_interruptible_sleep_on()함수를 호출한 직후 수행된다. 이 프로세스는 전역 변수 값을 설정하고, 여전히 해당 파일을

사용하려고 기다리는 프로세스에게 통보를 한다. 다른 프로세스들이 CPU 를 점유하면, 해당

전역변수의 값을 확인하고 다시 대기 상태로 돌아간다.

흥미롭게도, module_close()함수는 파일에 접근하기 위해 기다리는 프로세스를 깨우는 것에

대해 독점권을 가지지 못한다. Ctrl+c (SIGINT)같은 시그널도 프로세스를 깨울 수 있다.[3] 이런 경우 우리는 –EINTR 로 설정된 값을 즉각 돌려 받는다. 일례로 프로세스가 파일을 열기

전에 사용자가 그것을 죽일 수 있다는 점에서 중요하다.

기억해 야할 사항이 하나 더 있다. 가끔 대기 상태로 바뀌는 것을 원치 않는 프로세스가 있다. 그들은 그들이 원하는 것을 즉각적으로 갖기를 원하거나 작업을 진행할 수 없다고 보고 하길

원한다. 그런 프로세스들은 파일을 열 때 O_NONBLOCK 플래그를 설정한다. 다음의 예와 같이

Page 96: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

파일을 열 때 블록 당하기 보다 -EAGAIN 이라는 에러코드를 리턴함으로써 커널이 응답한다. 이

장에서 예로 볼 수 있는 cat_noblock 프로그램은 O_NONBLOCK 플래그를 설정한 체, 파일을

열게 될 것이다.

Example 9-1. sleep.c

/* sleep.c - create a /proc file, and if several processes try to open it at * the same time, put all but one to sleep */

#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

/* Necessary because we use proc fs */#include <linux/proc_fs.h>

/* For putting processes to sleep and waking them up */#include <linux/sched.h>#include <linux/wrapper.h>

/* In 2.2.3 /usr/include/linux/version.h includes a macro for this, but 2.0.35 * doesn't - so I add it here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)#include <asm/uaccess.h> /* for get_user and put_user */#endif

Page 97: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* The module's file functions */

/* Here we keep the last message received, to prove that we can process our * input */#define MESSAGE_LENGTH 80static char Message[MESSAGE_LENGTH];

/* Since we use the file operations struct, we can't use the special proc * output provisions - we have to use a standard read function, which is this * function */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t module_output ( struct file *file, /* The file read */ char *buf, /* The buffer to put data to (in the user segment) */ size_t len, /* The length of the buffer */ loff_t *offset) /* Offset in the file - ignore */#elsestatic int module_output ( struct inode *inode, /* The inode read */ struct file *file, /* The file read */ char *buf, /* The buffer to put data to (in the user segment) */ int len) /* The length of the buffer */#endif{ static int finished = 0; int i; char message[MESSAGE_LENGTH+30];

/* Return 0 to signify end of file - that we have nothing more to say at this * point. */ if (finished) {

Page 98: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

finished = 0; return 0; }

/* If you don't understand this by now, you're hopeless as a kernel * programmer. */ sprintf(message, "Last input:%s\n", Message); for (i = 0; i < len && message[i]; i++) put_user(message[i], buf+i);

finished = 1; return i; /* Return the number of bytes "read" */}

/* This function receives input from the user when the user writes to the /proc * file. */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)static ssize_t module_input ( struct file *file, /* The file itself */ const char *buf, /* The buffer with input */ size_t length, /* The buffer's length */ loff_t *offset) /* offset to file - ignore */#elsestatic int module_input ( struct inode *inode, /* The file's inode */ struct file *file, /* The file itself */ const char *buf, /* The buffer with the input */ int length) /* The buffer's length */#endif{ int i;

/* Put the input into Message, where module_output will later be able to use

Page 99: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* it */ for(i = 0; i < MESSAGE_LENGTH-1 && i < length; i++)#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(Message[i], buf+i);#else Message[i] = get_user(buf+i);#endif /* we want a standard, zero terminated string */ Message[i] = '\0'; /* We need to return the number of input characters used */ return i;}

/* 1 if the file is currently open by somebody */int Already_Open = 0;

/* Queue of processes who want our file */static struct wait_queue *WaitQ = NULL;

/* Called when the /proc file is opened */static int module_open(struct inode *inode, struct file *file){ /* If the file's flags include O_NONBLOCK, it means the process doesn't want * to wait for the file. In this case, if the file is already open, we * should fail with -EAGAIN, meaning "you'll have to try again", instead of * blocking a process which would rather stay awake. */ if ((file->f_flags & O_NONBLOCK) && Already_Open) return -EAGAIN;

/* This is the correct place for MOD_INC_USE_COUNT because if a process is * in the loop, which is within the kernel module, the kernel module must

Page 100: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* not be removed. */ MOD_INC_USE_COUNT;

/* If the file is already open, wait until it isn't */ while (Already_Open) {#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) int i, is_sig = 0;#endif

/* This function puts the current process, including any system calls, * such as us, to sleep. Execution will be resumed right after the * function call, either because somebody called wake_up(&WaitQ) (only * module_close does that, when the file is closed) or when a signal, * such as Ctrl-C, is sent to the process */ module_interruptible_sleep_on(&WaitQ); /* If we woke up because we got a signal we're not blocking, return * -EINTR (fail the system call). This allows processes to be killed or * stopped. */

/* * Emmanuel Papirakis: * * This is a little update to work with 2.2.*. Signals now are contained in * two words (64 bits) and are stored in a structure that contains an array of * two unsigned longs. We now have to make 2 checks in our if. * * Ori Pomerantz: * * Nobody promised me they'll never use more than 64 bits, or that this book * won't be used for a version of Linux with a word size of 16 bits. This code

Page 101: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* would work in any case. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) for (i = 0; i < _NSIG_WORDS && !is_sig; i++) is_sig = current->signal.sig[i] & ~current->blocked.sig[i];

if (is_sig) {#else if (current->signal & ~current->blocked) {#endif /* It's important to put MOD_DEC_USE_COUNT here, because for processes * where the open is interrupted there will never be a corresponding * close. If we don't decrement the usage count here, we will be left * with a positive usage count which we'll have no way to bring down * to zero, giving us an immortal module, which can only be killed by * rebooting the machine. */ MOD_DEC_USE_COUNT; return -EINTR; } }

/* If we got here, Already_Open must be zero */

/* Open the file */ Already_Open = 1; return 0; /* Allow the access */}

/* Called when the /proc file is closed */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)int module_close(struct inode *inode, struct file *file)#elsevoid module_close(struct inode *inode, struct file *file)#endif

Page 102: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

{ /* Set Already_Open to zero, so one of the processes in the WaitQ will be * able to set Already_Open back to one and to open the file. All the other * processes will be called when Already_Open is back to one, so they'll go * back to sleep. */ Already_Open = 0;

/* Wake up all the processes in WaitQ, so if anybody is waiting for the * file, they can have it. */ module_wake_up(&WaitQ);

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) return 0; /* success */#endif}

/* This function decides whether to allow an operation (return zero) or not * allow it (return a non-zero which indicates why it is not allowed). * * The operation can be one of the following values: * 0 - Execute (run the "file" - meaningless in our case) * 2 - Write (input to the kernel module) * 4 - Read (output from the kernel module) * * This is the real function that checks file permissions. The permissions * returned by ls -l are for referece only, and can be overridden here. */static int module_permission(struct inode *inode, int op){ /* We allow everybody to read from our module, but only root (uid 0) may * write to it

Page 103: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

*/ if (op == 4 || (op == 2 && current->euid == 0)) return 0;

/* If it's anything else, access is denied */ return -EACCES;}

/* Structures to register as the /proc file, with pointers to all the relevant * functions. */

/* File operations for our proc file. This is where we place pointers to all * the functions called when somebody tries to do something to our file. NULL * means we don't want to deal with something. */static struct file_operations File_Ops_4_Our_Proc_File = { NULL, /* lseek */ module_output, /* "read" from the file */ module_input, /* "write" to the file */ NULL, /* readdir */ NULL, /* select */ NULL, /* ioctl */ NULL, /* mmap */ module_open, /* called when the /proc file is opened */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) NULL, /* flush */#endif module_close}; /* called when it's classed */

/* Inode operations for our proc file. We need it so we'll have somewhere to * specify the file operations structure we want to use, and the function we * use for permissions. It's also possible to specify functions to be called * for anything else which could be done to an inode (although we don't bother, * we just put NULL).

Page 104: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

*/static struct inode_operations Inode_Ops_4_Our_Proc_File = { &File_Ops_4_Our_Proc_File, NULL, /* create */ NULL, /* lookup */ NULL, /* link */ NULL, /* unlink */ NULL, /* symlink */ NULL, /* mkdir */ NULL, /* rmdir */ NULL, /* mknod */ NULL, /* rename */ NULL, /* readlink */ NULL, /* follow_link */ NULL, /* readpage */ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ module_permission}; /* check for permissions */

/* Directory entry */static struct proc_dir_entry Our_Proc_File = {

0, /* Inode number - ignore, it will be filled by * proc_register[_dynamic] */ 5, /* Length of the file name */ "sleep", /* The file name */

/* File mode - this is a regular file which can be read by its owner, its * group, and everybody else. Also, its owner can write to it. * * Actually, this field is just for reference, it's module_permission that * does the actual check. It could use this field, but in our * implementation it doesn't, for simplicity. */

Page 105: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

S_IFREG | S_IRUGO | S_IWUSR, 1, /* Number of links (directories where the file is referenced) */ 0, 0, /* The uid and gid for the file - we give it to root */ 80, /* The size of the file reported by ls. */

/* A pointer to the inode structure for the file, if we need it. In our * case we do, because we need a write function. */ &Inode_Ops_4_Our_Proc_File,

/* The read function for the file. Irrelevant, because we put it in the * inode structure above */ NULL};

/* Module initialization and cleanup */

/* Initialize the module - register the proc file */int init_module(){ /* Success if proc_register_dynamic is a success, failure otherwise */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) return proc_register(&proc_root, &Our_Proc_File);#else return proc_register_dynamic(&proc_root, &Our_Proc_File);#endif

/* proc_root is the root directory for the proc fs (/proc). This is where * we want our file to be located. */}

/* Cleanup - unregister our file from /proc. This could get dangerous if * there are still processes waiting in WaitQ, because they are inside our * open function, which will get unloaded. I'll explain how to avoid removal

Page 106: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* of a kernel module in such a case in chapter 10. */void cleanup_module(){ proc_unregister(&proc_root, Our_Proc_File.low_ino);}

Notes

[1]파일을 열어 놓은 상태를 유지하는 가장 쉬운 방법은 tail –f 파일을 여는 것이다.

[2]이것은 프로세스가 여전히 커널 모드에 있음을 의미한다. -- 아직까지 프로세스는 작업

중이고 프로세스는 open() 시스템 콜을 사용 중이며, 아직 리턴하지 않는 것이다. 프로세스가 해당 시스템 콜을 호출하고 리턴하는 순간까지의 대부분의 시간동안 다른

프로세스가 CPU 를 사용하는지 알지 못한다. [3]

이것이 module_interrupibie_sleep_on()함수를 사용하는 이유다. module_sleep_on()함수를 사용할 수도 있지만 이것은 Ctrl+c 가 무시당하는 사용자들을 매우 화나게 만들

것이다.

Page 107: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 10. Printk 대체하기

Page 108: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

10.1. printK()대체하기섹션 Section 1.2.1.2 에서 X 와 커널 모듈 프로그래밍을 함께 사용하지 말라고 했다. 커널

모듈을 개발하는 과정에서는 맞는 말이다. 그러나 실제의 경우에서는 모듈을 적재하기 위한 tty명령을 보낼 수 있기를 원할 것이다.

현재 실행 중인 현재 태스크의 tty 구조체를 구하기 위해, 태스크를 지시하는 current 매크로를

사용해서 해결할 수 있다. 문자열을 쓸 수 있는 함수의 포인터를 찾기 위해 tty 구조체를

들여다본다, 그것을 이용해 tty 에 문자열을 출력 한다.

Example 10-1. print_string.c

/* print_string.c - Send output to the tty you're running on, regardless of whether it's * through X11, telnet, etc. We do this by printing the string to the tty associated * with the current task. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/sched.h> // For current#include <linux/tty.h> // For the tty declarationsMODULE_LICENSE("GPL");MODULE_AUTHOR("Peter Jay Salzman");

void print_string(char *str){ struct tty_struct *my_tty; my_tty = current->tty; // The tty for the current task

/* If my_tty is NULL, the current task has no tty you can print to (this is possible, * for example, if it's a daemon). If so, there's nothing we can do. */ if (my_tty != NULL) {

Page 109: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* my_tty->driver is a struct which holds the tty's functions, one of which (write) * is used to write strings to the tty. It can be used to take a string either * from the user's memory segment or the kernel's memory segment. * * The function's 1st parameter is the tty to write to, because the same function * would normally be used for all tty's of a certain type. The 2nd parameter * controls whether the function receives a string from kernel memory (false, 0) or * from user memory (true, non zero). The 3rd parameter is a pointer to a string. * The 4th parameter is the length of the string. */ (*(my_tty->driver).write)( my_tty, // The tty itself 0, // We don't take the string from user space str, // String strlen(str)); // Length

/* ttys were originally hardware devices, which (usually) strictly followed the * ASCII standard. In ASCII, to move to a new line you need two characters, a * carriage return and a line feed. On Unix, the ASCII line feed is used for both * purposes - so we can't just use \n, because it wouldn't have a carriage return * and the next line will start at the column right after the line feed. * * BTW, this is why text files are different between Unix and MS Windows. In CP/M * and its derivatives, like MS-DOS and MS Windows, the ASCII standard was strictly * adhered to, and therefore a newline requirs both a LF and a CR. */ (*(my_tty->driver).write)(my_tty, 0, "\015\012", 2); }}

Page 110: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

int print_string_init(void){ print_string("The module has been inserted. Hello world!"); return 0;}

void print_string_exit(void){ print_string("The module has been removed. Farewell world!");}

module_init(print_string_init);module_exit(print_string_exit);

Notes

[1]Teletype, 원래는 키보드와 프린터를 조합해 유닉스 시스템과 통신하기 위해 사용했으며, 물리적인 터미널이던, X 디스플레이상의 xterm 이던, 텔넷을 이용한 네트워크 커넥션이던

관계없는 유닉스상의 텍스트 스트림을 지칭하는 추상적인 개념이다.

Page 111: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 11. 작업 스케줄링

Page 112: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

11.1. 작업 스켈줄링하기우리는 종종 “가정부” 일을 한다. 그리고 그 일은 자주이던 가끔이던 특정 시각에 행해져야 한다. 그 일을 프로세스가 한다면, crontab 파일에 입력하는 것으로 해결할 수 있다. 만일 이런 일을

커널 모듈이 해야 한다면, 두 가지 방법이 있다. 첫째로 필요할 때, 시스템 콜에 의해 모듈을

깨우는 프로세스를 crontab 파일에 집어 넣는 것이다. 그러나 이것은 끔찍하게도 비효율적이다. 이것은 새 프로세스를 crontab 에서 실행 시키고, 실행 가능 코드를 메모리에서 읽는다. 단지

메모리에 이미 존재하는 커널 모듈을 깨우기 위해서 말이다.

이렇게 하는 것 말고, 타이머 인터럽트 때마다 호출되는 함수를 만들 수 있다. 이런 일을 하는

방법은 새로운 작업(task)를 만들고, 함수의 포인터를 가지고 있는 tq_struct 구조체에

보관하는 것이다. 그리고나서 queue_task() 함수를 tq_timer(이것은 다음 타임 인터럽트 때

실행될 작업의 리스트를 가지고 있다.)라고 불리는 작업 리스트에 집어 넣기 위해 사용한다. 이

함수가 실행 중인 것으로 유지되기 바라기 때문에, 이것이 호출될 때마다 다음 시간 인터럽트를

대비해, 다시 tq_timer 에 집어넣는다.

기억할 사항이 하나 더 있다. 모듈이 rmmod 에 의해 제거 될 때, 레퍼런스 카운트가 점검된다. 그 값이 0 일 때, module_cleanup()이 호출된다. 그제서야 모듈이 이 함수에 의해 메모리에서

제거된다. 어느 누구도 timer’s task list 에 이런 더 이상 사용하지 않는 함수의 포인터가

있는지 확인하지 않는다. 한참 전에( 이것은 어디까지나 컴퓨터의 관점이다. 인간의 관점에서

보면 많아야 수 백 초 전이다.) 커널은 타이머 인터럽트를 받았고, 작업리스트에 있는 함수를

호출했다. 불행히도 그 함수는 더 이상 그곳에 존재하지 않는다. 대부분의 경우, 함수가

존재하던 메모리의 페이지는 더 이상 사용되지 않으며, 당신은 지저분한 에러 메시지를 받을

것이다. 그러나 같은 메모리의 위치에 새로운 코드가 자리잡았다면, 사태는 더 나빠진다. 불행히도 우리는 작업리스트(task list)에서 작업을 제거하는 쉬운 방법이 없다.

cleanup_module()함수는 에러코드를 리턴할 수 없기 때문에(이것은 void 를 리턴한다.), 해결책은 아무것도 리턴하게 하지 못하는 것이다. 대신 sleep_on() 혹은 module_sleep_on()을 호출해 rmmod 프로세스를 잠들게 한다. 그러기 전에, 타이머 인터럽트에서 호출된 함수에게

전역변수를 설정함으로써 자신을 더 이상 추가하지 말도록 알린다. 다음 번 타임 인터럽트 때에

rmmod 프로세스가 깨어나고, 그때는 우리의 함수가 더 이상 큐에 있지도 않고, 모듈을

제거하는데도 안전하게 된다.

Example 11-1. sched.c

Page 113: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* sched.c - scheduale a function to be called on every timer interrupt. * * Copyright (C) 2001 by Peter Jay Salzman */

/* The necessary header files */

/* Standard in kernel modules */#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

/* Necessary because we use the proc fs */#include <linux/proc_fs.h>

/* We scheduale tasks here */#include <linux/tqueue.h>

/* We also need the ability to put ourselves to sleep and wake up later */#include <linux/sched.h>

/* In 2.2.3 /usr/include/linux/version.h includes a macro for this, but * 2.0.35 doesn't - so I add it here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

/* The number of times the timer interrupt has been called so far */static int TimerIntrpt = 0;

Page 114: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* This is used by cleanup, to prevent the module from being unloaded while * intrpt_routine is still in the task queue */static struct wait_queue *WaitQ = NULL;

static void intrpt_routine(void *);

/* The task queue structure for this task, from tqueue.h */static struct tq_struct Task = { NULL, /* Next item in list - queue_task will do this for us */ 0, /* A flag meaning we haven't been inserted into a task * queue yet */ intrpt_routine, /* The function to run */ NULL /* The void* parameter for that function */};

/* This function will be called on every timer interrupt. Notice the void* * pointer - task functions can be used for more than one purpose, each time * getting a different parameter. */static void intrpt_routine(void *irrelevant){ /* Increment the counter */ TimerIntrpt++;

/* If cleanup wants us to die */ if (WaitQ != NULL) wake_up(&WaitQ); /* Now cleanup_module can return */ else /* Put ourselves back in the task queue */ queue_task(&Task, &tq_timer); }

Page 115: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* Put data into the proc fs file. */int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int zero){ int len; /* The number of bytes actually used */

/* It's static so it will still be in memory when we leave this function */ static char my_buffer[80];

static int count = 1;

/* We give all of our information in one go, so if the anybody asks us * if we have more information the answer should always be no. */ if (offset > 0) return 0;

/* Fill the buffer and get its length */ len = sprintf(my_buffer, "Timer called %d times so far\n", TimerIntrpt); count++;

/* Tell the function which called us where the buffer is */ *buffer_location = my_buffer;

/* Return the length */ return len;}

struct proc_dir_entry Our_Proc_File = { 0, /* Inode number - ignore, it'll be filled by proc_register_dynamic */ 5, /* Length of the file name */ "sched", /* The file name */ S_IFREG | S_IRUGO, /* File mode - this is a regular file which can be

Page 116: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* read by its owner, its group, and everybody else */ 1, /* Number of links (directories where the file is referenced) */ 0, 0, /* The uid and gid for the file - we give it to root */ 80, /* The size of the file reported by ls. */ NULL, /* functions which can be done on the inode (linking, removing, * etc). - we don't * support any. */ procfile_read, /* The read function for this file, the function called

* when somebody tries to read something from it. */ NULL /* We could have here a function to fill the file's inode, to * enable us to play with permissions, ownership, etc. */ };

/* Initialize the module - register the proc file */int init_module(){ /* Put the task in the tq_timer task queue, so it will be executed at * next timer interrupt */ queue_task(&Task, &tq_timer);

/* Success if proc_register_dynamic is a success, failure otherwise */#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0) return proc_register(&proc_root, &Our_Proc_File);#else return proc_register_dynamic(&proc_root, &Our_Proc_File);#endif}

/* Cleanup */void cleanup_module()

Page 117: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

{ /* Unregister our /proc file */ proc_unregister(&proc_root, Our_Proc_File.low_ino); /* Sleep until intrpt_routine is called one last time. This is necessary, * because otherwise we'll deallocate the memory holding intrpt_routine * and Task while tq_timer still references them. Notice that here we * don't allow signals to interrupt us. * * Since WaitQ is now not NULL, this automatically tells the interrupt * routine it's time to die. */ sleep_on(&WaitQ);}

Notes

[1]그들은 실제로 같은 것이다.

Page 118: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 12. 인터럽트 핸들러

Page 119: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

12.1. 인터럽트 핸들러12.1.1. 인터럽트 핸들러

최신 컴퓨터를 제외하고, 프로세스가 요청한 것에 대해, 특수 파일을 다루거나 ioctl()을

사용하거나, 시스템 콜을 사용함으로써 우리가 지금까지 커널에 한 모든 작업을 했다. 그러나

커널의 역할은 프로세스의 요청에 응답하는 것만은 아니다. 모든 순간 중요한 또 다른 커널의

작업은 장비에 연결된 하드웨어와 의사 소통하는 것이다.

CPU 와 나머지 하드웨어간에 의사 소통을 하는 방식은 두 가지가 있다. 첫 방식은 CPU 가

하드웨어에 명령을 내리고, CPU 에게 하드웨어가 무언가 필요하다고 말하는 방식이다. 인터럽트라 불리는 두 번째 방식은 CPU 가 아닌 하드웨어의 편의를 다루는 것이기 때문에

구현이 더 어렵다. 하드웨어 장치는 보편적으로 매우 작은 양의 RAM 을 가지고 있고, 만약

가능할 때 당신이 그 정보를 읽지 않는 다면 그것은 사라 진다.

유닉스에서는 하드웨어 인터럽트를 IRQ 라 부른다. short 와 long 타입의 두 가지 IRQ 가

존재한다. short IRQ 는 매우 짧은 시간이 걸리는 것이고, 그 시간 동안 다른 인터럽트가

처리되지 않고 장비의 나머지 장비가 모두 블록된다. long IRQ 는 비교적 긴 시간이 걸리고, 그

시간 동안 다른 인터럽트가 발생할 수도 있다. (그렇지만 같은 장치로부터 발생할 수는 없다.) 이

모든 가능성을 고려한다면, 인터럽트 핸들러를 long 타입으로 서언 하는 것이 낫다.

CPU 가 인터럽트를 받으면, 그것은 하던 모든 일을 멈추고(더 중요한 인터럽트 처리를 하고

있지 않다면, 이 경우, 좀더 중요한 인터럽트 처리가 처리된 후 그것을 처리한다.), 스택에 특정

파라미터를 저장한 후 인터럽트 핸들러를 호출한다. 이것은 시스템이 어떤 상태에 있는지 알지

못하기 때문에, 어떤 것들은 인터럽트 핸들러 내부에서는 허가 되지 않음을 의미한다. 이 문제의

해결책은 즉각 처리되어야 하는 인터럽트 핸들러가 일반적으로 하드웨어에서 무엇인가를

읽거나 하드웨어에 어떤 것을 전달하고 나서 새로운 정보를 처리하는 스케줄러를 실행 시키는

것이다.(우리는 이것을 bottom half 라 부른다. 커널은 가능한 빨리 bottom half 를 호출할

것이라 여겨진다. -- 그리고 그렇게 됐을 때, 커널 모듈에서 허가된 모든 것이 수행 될 수 있다.

이 방식은 상대적인 IRQ 를 받았을 때, 인터럽트 핸들러를 찾기 위해 request_irq()함수를

호출함으로써 구현된다. 이 함수는 IRQ 번호, 함수의 이름, 플래그 값, /proc/interupts 에 대한

이름, 인터럽트 핸들러에 전달될 인자를 전달 받는다. 이 때, 플래그는 다른 인터럽트 핸들러와

IRQ 를 공유하겠다는 것을 알리는 SA_SHIRQ(보통, 하드웨어 디바이스의 번호가 같은 IRQ 에

Page 120: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

의해 발생하기 때문에), 빠른 처리돼야 함을 알리는 SA_INTERRUPT 를 포함한다. 서로

공유하려고 하거나, IRQ 에 다른 핸들러가 존재하지 않을 때만 이 함수는 성공한다.

인터럽트 핸들러 내부로부터, 우리는 하드웨어와 통신할 수 있고, bottom half 를 스케줄 하기

위해, queue_task_irq()함수와 mark_bh(BH_IMMEDIATE)를 사용한다. 우리가 표준의

queue_task()함수를 버전 2.0 에서 사용하지 못하는 것은 인터럽트가 다른 사람의

queue_task()함수 실행 중에 발생할 수 있기 때문이다. 초기 버전의 리눅스만이 32 개 배열의

bottom half 를 가졌고, 그 중 하나(BH_IMMEDIATE)는 bottom half 의 엔트리를 할당하지

않은 드라이버의 bottom half 의 링크드 리스트를 위해 사용한다.

12.1.2. 인텔 아키텍쳐에서의 키보드

이 장의 나머지 부분의 전적으로 인텔 계열에만 적용된다. 다른 플렛폼에서 작동시켰다면, 작동하지 않을 것이다. 만일 그렇다면 컴파일 시도도 하지 말자.

이 장의 예제 코드를 작성하는데 문제가 있었다. 한편으로는 모든 사람의 컴퓨터에서 작동

하면서 유용한 예제, 다른 한편으로는 커널이 이미 포함하고 있는 일반적인 디바이스 드라이버, 그리고 내가 지금 작성하려고 하는 것과 일치하지 않는 것. 찾아낸 해결책은 키보드 인터럽트에

대해 무엇인가 작성하는 것이고, 우선은 일반적인 키보드 인터럽트를 중지 시키는 것이다. static symbol 로 이미 커널 내부에 정의 돼있기 때문에(특히 drivers/char/keyboard.c), 그것을 복구할 방법은 없다. 이 코드를 적재하기 전에 120 동안 다른 터미널을 잠재우자.(sleep 120); 만일 당신의 파일 시스템을 중요하게 여긴다면 reboot 하자.

이 코드는 자신을 IRQ1 에 바인드 시키며, IRQ1 은 인텔 아키텍쳐 아래서 키보드 IRQ 이다. 이

코드가 키보드 인터럽트를 받을 때, 키보드의 상태를 읽어오고(inb(0x64)를 하기 위한

것이다.), 코드를 스캔한다. 그리고 그 코드는 키보드에 의해 리턴되는 값이다. 커널이 이것을

실행 가능한 것이라고 판단하자마자, got_char()함수를 실행 시키며, 이 함수는 사용된 키의

코드를 주고(스캔한 코드의 첫 7비트), 그것이 눌렸는지(이 경우 8 번째 비트가 0 이다), 눌렸다

떨어졌는지(이 경우는 1) 알려준다.

Example 12-1. intrpt.c

/* intrpt.c - An interrupt handler. * * Copyright (C) 2001 by Peter Jay Salzman */

Page 121: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

/* The necessary header files */

/* Standard in kernel modules */#include <linux/kernel.h> /* We're doing kernel work */#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include <linux/modversions.h>#endif

#include <linux/sched.h>#include <linux/tqueue.h>

/* We want an interrupt */#include <linux/interrupt.h>

#include <asm/io.h>

/* In 2.2.3 /usr/include/linux/version.h includes a macro for this, but * 2.0.35 doesn't - so I add it here if necessary. */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif

/* Bottom Half - this will get called by the kernel as soon as it's safe * to do everything normally allowed by kernel modules. */static void got_char(void *scancode){ printk("Scan Code %x %s.\n", (int) *((char *) scancode) & 0x7F, *((char *) scancode) & 0x80 ? "Released" : "Pressed");

Page 122: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

}

/* This function services keyboard interrupts. It reads the relevant * information from the keyboard and then scheduales the bottom half * to run when the kernel considers it safe. */void irq_handler(int irq, void *dev_id, struct pt_regs *regs){ /* This variables are static because they need to be * accessible (through pointers) to the bottom half routine. */ static unsigned char scancode; static struct tq_struct task = {NULL, 0, got_char, &scancode}; unsigned char status;

/* Read keyboard status */ status = inb(0x64); scancode = inb(0x60); /* Scheduale bottom half to run */#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0) queue_task(&task, &tq_immediate);#else queue_task_irq(&task, &tq_immediate);#endif mark_bh(IMMEDIATE_BH);}

/* Initialize the module - register the IRQ handler */int init_module(){ /* Since the keyboard handler won't co-exist with another handler, * such as us, we have to disable it (free its IRQ) before we do * anything. Since we don't know where it is, there's no way to

* reinstate it later - so the computer will have to be rebooted

Page 123: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

* when we're done. */ free_irq(1, NULL);

/* Request IRQ 1, the keyboard IRQ, to go to our irq_handler. * SA_SHIRQ means we're willing to have othe handlers on this IRQ.

* SA_INTERRUPT can be used to make the handler into a fast interrupt. */ return request_irq(1, /* The number of the keyboard IRQ on PCs */ irq_handler, /* our handler */ SA_SHIRQ, "test_keyboard_irq_handler", NULL);}

/* Cleanup */void cleanup_module(){ /* This is only here for completeness. It's totally irrelevant, since

* we don't have a way to restore the normal keyboard interrupt so the* computer is completely useless and has to be rebooted.

*/ free_irq(1, NULL);}

Notes

[1]이는 인텔 아키텍처하에서 리눅스가 지향하는 표준 nomenclature(nomenclature 저자의 오타인 듯: 용어)다.

[2]queue_task_irq()는 글로벌 락에 의해 이것으로부터 보호된다.-- 2.2 버전에는

queue_task_irq()가 없으며, queue_task()가 락에 의해 보호 받는다.

Page 124: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 13. 대칭형 다중 프로세싱

Page 125: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

13.1. 대칭적 다중 처리(프로세싱)하드웨어 성능 향상을 위한 가장 쉽고 저렴한 방법 중 하나가 보드에 CPU 를 하나 더 설치하는

것이다. 서로 다른 CPU 가 서로 다른 작업을 수행하게 하는 것(비대칭적 다중 처리)으로 실현될

수 있으며, 같은 일을 병행적으로 실행하게 함(대칭적 다중 처리)으로써도 가능하다. 효과적인

비대칭 다중 처리를 위해서는 컴퓨터가 하는 작업에 대한 특별한 지식이 필요하며, 리눅스 같은

일반적인 목적의 운영체계에서는 사용할 수 없는 것이다. 반면에, 대칭적 다중 처리는

상대적으로 구현하기 쉽다.

상대적으로 쉽게, 실제 쉽지 않았음을 말할 수 있다. 대칭 다중 프로세스 환경에서, CPU 는 같은

메모리를 공유하고 결과적으로, 하나의 CPU 에서 실행되는 코드는 다른 CPU 가 사용하는

메모리를 변경시킬 수 있다. 이전에 값을 넣은 변수가 여전히 같은 값을 가지고 있다고 확신할

수 없다. 당신이 주의를 기울이지 않는 사이에 다른 CPU 가 그 변수를 어떻게 했을 수도 있다. 명백하게도, 프로그램을 이런 식으로 할 수는 없다.

프로세스 프로그래밍의 경우에서, 프로세스는 일반적으로 하나의 CPU 에서 실행되기 때문에

일반적으로 이런 것은 문제가 되지 않는다. 반면에 커널은 서로 다른 CPU 에서 실행되는

프로세스에 의해 호출 될 수 있다.

전체 커널이 하나의 거대한 spinlock() 이기 때문에, 2.0.x 버전에서는 이것이 문제가 되지

않았다. 예를 들어, 하나의 CPU 가 커널을 사용할 때, 시스템 콜에 의해 다른 CPU 가 커널을

요청하면, 그것은 처음 CPU 가 일을 마칠 때까지, 기다려야 한다. 이것은 SMP 를 안전하게

만들었지만 효율을 떨어뜨렸다.

버전 2.2.x 에서는 여러 개의 CPU 가 동시에 커널을 사용할 수 있다. 이것이 모듈을 작성하는

사람이 주의 해야 할 것이다.

Notes

[1]스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU 에서 작동한다.

[2]SMP 를 사용할 때 안전함을 의미한다.

Page 126: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Chapter 14. 일반적으로 주의할 사항

Page 127: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

14.1. 흔한 함정당신이 세상으로 나가 커널 모듈을 작성하기 전에, 당신에게 주의를 주어야 할 것들이 몇 가지

있다. 내가 충고를 주지 못해 나쁜 일이 발생한다면, 그 문제를 나에게 알려주길 바란다.

표준 라이브러리를 사용하는 것

표준 라이브러리는 사용할 수 없다. 당신이 커널 모듈에서 사용 할 수 있는 함수는

/proc/ksyms 에서 보이는 함수들 뿐이다.

인터럽트를 불가능하게 하는 것

이것은 잠시 동안 써야 할 필요도 있고, 그런 것은 괜찮다. 그러나 다시 인터럽트를

가능하게 해놓지 않는 다면, 당신의 시스템에 문제가 발생할 것이고, 전원을 내려야만

할 것이다.

머리 속의 거대한 식충식물을 찔러라 ( 이문서 번역한 것 중, 가장 최악 -_-“)

이것에 대해 충고하지 않아야 할 것 같다. 그러나 설명한다.. 어째든… (무슨 소리지 -_-“)

Page 128: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Appendix A. Changes: 2.0 To 2.2 / 2.0 에서 2.2 로의 변화

Page 129: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

A.1. Changes between 2.0 and 2.2A.1.1. Changes between 2.0 and 2.2

I don't know the entire kernel well enough do document all of the changes. In the course of converting the examples (or actually, adapting Emmanuel Papirakis's changes) I came across the following differences. I listed all of them here together to help module programmers, especially those who learned from previous versions of this book and are most familiar with the techniques I use, convert to the new version.

An additional resource for people who wish to convert to 2.2 is located on Richard Gooch's site .

asm/uaccess.h

If you need put_user or get_user you have to #include it.

get_user

In version 2.2, get_user receives both the pointer into user memory and the variable in kernel memory to fill with the information. The reason for this is that get_user can now read two or four bytes at a time if the variable we read is two or four bytes long.

file_operations

This structure now has a flush function between the open and close functions.

close in file_operations

In version 2.2, the close function returns an integer, so it's allowed to fail.

Page 130: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

read,write in file_operations

The headers for these functions changed. They now return ssize_t instead of an integer, and their parameter list is different. The inode is no longer a parameter, and on the other hand the offset into the file is.

proc_register_dynamic

This function no longer exists. Instead, you call the regular proc_register and put zero in the inode field of the structure.

Signals

The signals in the task structure are no longer a 32 bit integer, but an array of _NSIG_WORDS integers.

queue_task_irq

Even if you want to scheduale a task to happen from inside an interrupt handler, you use queue_task, not queue_task_irq.

Module Parameters

You no longer just declare module parameters as global variables. In 2.2 you have to also use MODULE_PARM to declare their type. This is a big improvement, because it allows the module to receive string parameters which start with a digits, for example, without getting confused.

Symmetrical Multi-Processing

The kernel is no longer inside one huge spinlock, which means that kernel modules have to be aware of SMP.

Page 131: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

Appendix B. 여기서 더 무엇을 해야하나

Page 132: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

B.1. Where From Here?I could easily have squeezed a few more chapters into this book. I could have added a chapter about creating new file systems, or about adding new protocol stacks (as if there's a need for that -- you'd have to dig underground to find a protocol stack not supported by Linux). I could have added explanations of the kernel mechanisms we haven't touched upon, such as bootstrapping or the disk interface.

However, I chose not to. My purpose in writing this book was to provide initiation into the mysteries of kernel module programming and to teach the common techniques for that purpose. For people seriously interested in kernel programming, I recommend Juan-Mariano de Goyeneche's list of kernel resources . Also, as Linus said, the best way to learn the kernel is to read the source code yourself.

If you're interested in more examples of short kernel modules, I recommend Phrack magazine. Even if you're not interested in security, and as a programmer you should be, the kernel modules there are good examples of what you can do inside the kernel, and they're short enough not to require too much effort to understand.

I hope I have helped you in your quest to become a better programmer, or at least to have fun through technology. And, if you do write useful kernel modules, I hope you publish them under the GPL, so I can use them too.

Page 133: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

IndexSymbols

/etc/conf.modules, How Do Modules Get Into The Kernel? /etc/modules.conf, How Do Modules Get Into The Kernel? /proc filesystem, The /proc File System /proc/interrupts, Interrupt Handlers /proc/ksyms, Functions available to modules, Name Space, Common Pitfalls /proc/meminfo, The /proc File System /proc/modules, How Do Modules Get Into The Kernel?, The /proc File System 2.2 changes, Changes between 2.0 and 2.2 _IO, Talking to Device Files (writes and IOCTLs)} _IOR, Talking to Device Files (writes and IOCTLs)} _IOW, Talking to Device Files (writes and IOCTLs)} _IOWR, Talking to Device Files (writes and IOCTLs)} _NSIG_WORDS, Changes between 2.0 and 2.2 __exit, Hello World (part 3): The __init and __exit Macros __init, Hello World (part 3): The __init and __exit Macros __initdata, Hello World (part 3): The __init and __exit Macros __initfunction(), Hello World (part 3): The __init and __exit Macros __NO_VERSION__, Modules Spanning Multiple Files A

asm uaccess.h, Changes between 2.0 and 2.2

asm/uaccess.h, Changes between 2.0 and 2.2 B

BH_IMMEDIATE, Interrupt Handlers blocking processes, Blocking Processes blocking, how to avoid, Replacing printk bottom half, Interrupt Handlers busy, Replacing printk C

carnivore large, Common Pitfalls

Page 134: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

cleanup_module(), Hello, World (part 1): The Simplest Module close, Changes between 2.0 and 2.2 code space, Code space coffee, Major and Minor Numbers CPU

multiple, Symmetrical Multi-Processing crontab, Scheduling Tasks ctrl-c, Replacing printk current task, Replacing printk D

DEFAULT_MESSAGE_LOGLEVEL, Introducing printk() defining ioctls, Talking to Device Files (writes and IOCTLs)} device file

character, Character Device Drivers device files

input to, Talking to Device Files (writes and IOCTLs)} write to, Talking to Device Files (writes and IOCTLs)}

E

EAGAIN, Replacing printk EINTR, Replacing printk elf_i386, Modules Spanning Multiple Files ENTRY(system call), System Calls entry.S, System Calls F

file, The file structure filesystem

/proc, The /proc File System registration, Using /proc For Input

filesystem registration, Using /proc For Input file_operations, The file_operations Structure file_operations structure, Using /proc For Input flush, Changes between 2.0 and 2.2 G

get_user, Using /proc For Input, Changes between 2.0 and 2.2 H

handlers

Page 135: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

interrupt, Interrupt Handlers housekeeping, Scheduling Tasks Hurd, Code space I

inb, Keyboards on the Intel Architecture init_module(), Hello, World (part 1): The Simplest Module inode, The file structure, The /proc File System inode_operations structure, Using /proc For Input input

using /proc for, Using /proc For Input insmod, Compiling Kernel Modules, System Calls Intel architecture

keyboard, Keyboards on the Intel Architecture interrupt 0x80, System Calls interrupt handlers, Interrupt Handlers interruptible_sleep_on, Replacing printk interrupts, Changes between 2.0 and 2.2

disabling, Common Pitfalls ioctl, Talking to Device Files (writes and IOCTLs)}

defining, Talking to Device Files (writes and IOCTLs)} official assignment, Talking to Device Files (writes and IOCTLs)}

irqs, Changes between 2.0 and 2.2 K

kernel versions, Changes between 2.0 and 2.2

kernel versions, Writing Modules for Multiple Kernel Versions kerneld, How Do Modules Get Into The Kernel? kernel\_version, Modules Spanning Multiple Files KERNEL_VERSION, Writing Modules for Multiple Kernel Versions keyboard, Keyboards on the Intel Architecture kmod, How Do Modules Get Into The Kernel? L

ld, Modules Spanning Multiple Files libraries

standard, Common Pitfalls library function, Functions available to modules

Page 136: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

LINUX_VERSION_CODE, Writing Modules for Multiple Kernel Versions M

major number, Major and Minor Numbers dynamic allocation, Registering A Device

mark_bh, Interrupt Handlers memory segments, Using /proc For Input microkernel, Code space minor number, Major and Minor Numbers mknod, Major and Minor Numbers modem, Talking to Device Files (writes and IOCTLs)} module

parameters, Changes between 2.0 and 2.2 module parameters, Changes between 2.0 and 2.2 module.h, Modules Spanning Multiple Files modules.conf

alias, How Do Modules Get Into The Kernel? comment, How Do Modules Get Into The Kernel? keep, How Do Modules Get Into The Kernel? options, How Do Modules Get Into The Kernel? path, How Do Modules Get Into The Kernel?

MODULE_AUTHOR(), Hello World (part 4): Licensing and Module Documentation module_cleanup, Scheduling Tasks MODULE_DESCRIPTION(), Hello World (part 4): Licensing and Module Documentation module_exit, Hello World (part 2) module_init, Hello World (part 2) module_interruptible_sleep_on, Replacing printk MODULE_LICENSE(), Hello World (part 4): Licensing and Module Documentation MODULE_PARM, Changes between 2.0 and 2.2 module_permissions, Using /proc For Input module_sleep_on, Replacing printk, Scheduling Tasks MODULE_SUPPORTED_DEVICE(), Hello World (part 4): Licensing and Module Documentation module_wake_up, Replacing printk MOD_DEC_USE_COUNT, Unregistering A Device MOD_INC_USE_COUNT, Unregistering A Device, System Calls MOD_IN_USE, Unregistering A Device

Page 137: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

monolithic kernel, Code space multi-processing, Symmetrical Multi-Processing multi-tasking, Replacing printk multitasking, Replacing printk N

namespace pollution, Name Space Neutrino, Code space non-blocking, Replacing printk O

official ioctl assignment, Talking to Device Files (writes and IOCTLs)} O_NONBLOCK, Replacing printk P

permission, Using /proc For Input pointer

current, Using /proc For Input printk

replacing, Replacing printk printk(), Introducing printk() proc

using for input, Using /proc For Input proc file

ksyms, Common Pitfalls processes

blocking, Blocking Processes killing, Replacing printk waking up, Replacing printk

processing multi, Symmetrical Multi-Processing

proc_dir_entry, Using /proc For Input proc_register, The /proc File System, Changes between 2.0 and 2.2 proc_register_dynamic, The /proc File System, Changes between 2.0 and 2.2 putting processes to sleep, Replacing printk put_user, Using /proc For Input, Changes between 2.0 and 2.2 Q

queue_task, Scheduling Tasks, Interrupt Handlers, Changes between 2.0 and 2.2 queue_task_irq, Interrupt Handlers, Changes between 2.0 and 2.2

Page 138: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

R

read, Changes between 2.0 and 2.2 in the kernel, Using /proc For Input

reference count, Scheduling Tasks refund policy, Common Pitfalls register_chrdev, Registering A Device request_irq(), Interrupt Handlers rmmod, System Calls, Scheduling Tasks

preventing, Unregistering A Device S

SA_INTERRUPT, Interrupt Handlers SA_SHIRQ, Interrupt Handlers scheduler, Replacing printk scheduling tasks, Scheduling Tasks segment

memory, Using /proc For Input serial port, Talking to Device Files (writes and IOCTLs)} shutdown, System Calls SIGINT, Replacing printk signal, Replacing printk signals, Changes between 2.0 and 2.2 sleep

putting processes to, Replacing printk sleep_on, Replacing printk, Scheduling Tasks SMP, Symmetrical Multi-Processing, Changes between 2.0 and 2.2 source file

chardev.c, Talking to Device Files (writes and IOCTLs)} chardev.h, Talking to Device Files (writes and IOCTLs)} hello-1.c, Hello, World (part 1): The Simplest Module hello-2.c, Hello World (part 2) hello-3.c, Hello World (part 3): The __init and __exit Macros hello-4.c, Hello World (part 4): Licensing and Module Documentation hello-5.c, Passing Command Line Arguments to a Module intrpt.c, Keyboards on the Intel Architecture ioctl.c, Talking to Device Files (writes and IOCTLs)} print_string.c, Replacing printk

Page 139: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

procfs.c, System Calls sched.c, Scheduling Tasks sleep.c, Replacing printk start.c, Modules Spanning Multiple Files stop.c, Modules Spanning Multiple Files

source files multiple, Modules Spanning Multiple Files

ssize_t, Changes between 2.0 and 2.2 standard libraries, Common Pitfalls strace, Functions available to modules, System Calls struct

tty, Replacing printk struct file_operations, Using /proc For Input struct inode_operations, Using /proc For Input structure

file_operations, Changes between 2.0 and 2.2 symbol table, Name Space symmetrical multi-processing, Symmetrical Multi-Processing, Changes between 2.0 and 2.2 sync, System Calls system call, Functions available to modules, System Calls

open, System Calls system calls, System Calls sys_call_table, System Calls sys_open, System Calls T

task, Scheduling Tasks current, Replacing printk

tasks scheduling, Scheduling Tasks

TASK_INTERRUPTIBLE, Replacing printk tq_immediate, Interrupt Handlers tq_struct, Scheduling Tasks tq_timer, Scheduling Tasks tty_structure, Replacing printk

Page 140: 리눅스 커널모듈 프로그램 가이드 · Web view[1] 스레드 프로세스가 예외다, 그것은 동시에 여러 개의 CPU에서 작동한다. [2] SMP를 사용할 때

V

version.h, Modules Spanning Multiple Files W

waking up processes, Replacing printk write, Changes between 2.0 and 2.2

in the kernel, Using /proc For Input