본문 바로가기

프로그램

리눅스 커널 모듈 프로그래밍 안내서

리눅스 커널 모듈 프로그래밍 안내서

kernel ver. 2.0.x(~ 2.2.x)


Version 1.1.0, 1999년 4월 26일.

원저자   : Ori Pomerantz

역자      : 채병철(dataeng@chollian.net)

번역시작 : 1999년 8월 16일.

수정      : 2000년 6월 8일.

            오류를 지적해주신 김우현님께 감사드립니다.


이 책의 모든 번역 내용은 아래에 언급한, 그리고 원문의 GPL을 따릅니다. 누구나 이 책을 GPL하에서 자유롭게 배포할 수 있습니다. 다만 수정의 경우나 번역상의 오류는 본인에게 알려주시면 즉시 이를 반영할 것이며, 이 안내서가 일관된 내용을 유지하는데 도움을 줄 것입니다.

부록 C,D는 이 책의 내용과 직접적인 관련이 없기에 번역하지 않았습니다.

부록 E의 GPL은 송창훈님의 번역을 그대로 인용했습니다.


이 가이드의 내용은 리눅스의 커널 모듈에 관한 것이다. 이것은 커널 모듈을 만드는 방법을 알기 원하는 그리고  C언어를 어느 정도 이해하는 프로그래머에게 유용할 것이다. 이것은 중요한 기법에 대한 모든 예제와 함께, How-To 명령 매뉴얼로 쓰여진 것이다. 이 가이드는 커널 설계의 많은 부분을 다루고 있지만, 그것에 대해 많은 것을 알 필요는 없을 것이다 --- 이러한 주제에 대한 내용은 리눅스 문서 프로젝트, 출간된 자료에서 찾을 수 있을 것이다. 당신은 이 책의 내용을 허가된 조건아래서 자유롭게 재 배포하고 복사할 수 있다. 카피라이트와 배포에 관한 아래의 내용을 살펴보길 바란다.


여기에 언급된 모든 제품의 명칭은 단지 제품에 대한 인식을 명확히 하기 위한 목적이며, 등록 상표는 모두 각 소유자에 속해 있음을 밝힌다. 나는 제품에 관계된 회사, 소유회사에 어떠한 소유권도 주장하지 않는다


Copyright(c) 1999 Ori Pomerantz

Ori Pomerantz

Apt. #1032

2355 N Hwy 360

Grand Prairie

TX 75050

USA

Email: mpg@simple-tech.com


리눅스 커널 모듈 프로그래밍 안내서는 무료이다. 당신은 자유 소프트웨어 재단의 GNU 공개 라이선스하(Ver2)에서 이를 수정, 재 배포할 수 있다. 버전 2는 이 책의 부록 E에 기술되어 있다. 이 책이 쓸모 있게 여러 사람에 배포되기를 바란다, 그러나 어떤 사후 보장도 없을 것이다; 상품성 있는 제품의 사후 보장은 없을 것이며, 특별한 목적에 적합하게 바뀌지도 않을 것이다. 저자는 이 책이 위에 언급한 카피라이트에 준하여 사람들에게, 혹은 상업적으로 널리 퍼지는 것에 용기를 얻을 것이다. 다시 말하면, 당신은 이 책을 무료로 복사하고 재 배포할 수 있다. 물리적, 전자적, 또는 그 중간의 매체로서 재 생산되는 것에 저자의 어떤 명시적 허가도 필요 없다. 주의할 것은, 파생된 결과 그리고 이 문서의 변환은 반드시 GNU의 공개 라이센스하에서만 가능하다는 것이며, 원 카피라이트 조건은 유지되어져야만 한다. 당신이 이 책에 새로운 내용을 첨부하기 원한다면, 새로운 교정을 위해 유용한 원문의 소스 코드와 함께(LATEX와 같은) 이 문서의 유지 보수자인, Ori Pomerantz에게 그 내용을 알리기를 바란다. 이렇게 함으로서 새로운 내용이 추가될 것이며, 리눅스의 세계에 일관된 교정을 제공하게 된다. 이 책을 상업적으로 배포하고 출판할 계획이라면, 약간의 기부금, 로열티, 출판료는, 리눅스 문서 프로젝트와 저자는 당사자에게 큰 감사를 느낄 것이다. 이러한 방법은 자유 소프트웨어와 리눅스 문서 프로젝트에 기여하는 방법을 보여준다. 질문이나 혹은 주석이 있다면, 위의 주소로 연락하기 바란다.


내용

0. 소개

0.1 누가 이것을 읽어야 하는가?

0.2 문체에 대한 주의

0.3 변화된 것들

0.3.1 문서 버전 1.0.1에서의 변화

0.3.2 문서 버전 1.1.0에서의 변화

0.4 감사의 말

0.4.1 1.0.1을 위한

0.4.2 1.1.0을 위한


1. Hello, world

  hello.c 

1.1 커널 모듈을 위한 Makefiles

  Makefile

1.2 다중 파일

  start.c

  stop.c

  Makefile


2. 문자 장치 파일들

  chardev.c

2.1 다중 버전 소스 파일


3. /proc 파일 시스템 

  procfs.c


4. 입력을 위한 /proc의 이용

  procfs.c


5. 장치 파일의 제어(IOCTL 다루기)

  chardev.c

  chardev.h

  ioctl.c


6. 초기 조건들   

  param.c


7. 시스템 호출

  syscall.c


8. Blocking Processes 

  sleep.c


9. printk's의 대치 

  printk.c


10. 태스크 스케줄링

  sched.c


11. 인터럽트 처리기

11.1 인텔구조에서의 키보드

  intrpt.c


12. 대칭형 다중 처리 


13. 범하기 쉬운 실수 


A. 2.0에서 2.2.107사이의 변화

B. 어디에서 추가적인 내용을 얻을 수 있는가?

C. 상품과 서비스

C.1 출판물 얻기

D. 당신이 호의를 보이려면(기부를 원하면) 

E. The GNU General Public License 



제 0 장


소개

자 이제 당신은 커널 모듈을 작성하기를 원한다. 당신은 C언어를 알고 있고, 프로세스로써 실행되는 많은 일반적인 프로그램들을 작성한 적이 있으며, 이제는 실질적인 실행이 어디서 이루어 지는지, 일개의 엉뚱한 포인터가 당신의 파일 시스템을 지워버릴 수 있으며 코어 덤프가 재부팅을 꾀하려하는 곳이 어디인지를 알고 싶어한다. 이제, 클럽(역주:모듈을 작성하는)에 온 것을 환영한다. 나는 DOS(고맙게도, 이제 나쁜 운영체제를 견뎌야 한다 - Dead Operating System)하에서 중요한 디렉토리를 지울 수 있는 와일드 포인터를 가진 적이 있다. 그리고 리눅스하에서 생활하는 것이 더 안전할 것인지에 대해서 모른다.

주의: 여기의 코드는 인텔 펜티엄에서 실행되는 버전 2.0.35에서 2.2.3에서 작성되고 검사되었다. 대부분의 경우에, 버전이 2.0.x에서 2.2.x이라면 다른 CPU의 다른 커널에 있어서도 제대로 동작할 것이나 그 어떤 약속할 수는 없다. 한 가지 예외는 11장이며, 이것은 x86을 제외한 구조에서는 적절하게 동작하지 않을 것이다.


0.1 누가 이것을 읽어야 하는가?

이 문서는 커널 모듈에 대해 알기 원하는 사람을 위한 것이다. 여러 부분에서 커널의 내부에 대한 내용을 약간씩 다룰 것이나, 이것은 이 문서의 목적이 아니다. 내가 여기서 설명한 것 보다 많은 훌륭한 내용을 커널에서 알 수 있다. 이 문서는 또한 커널 모듈을 작성하기 원하는 사람을 위한 것이나, 버전 2.2의 커널에서 아직 적합하지 않다. 당신이 이러한 경우라면, 부록 A의 예제들을 살펴보길 권한다. 이 목록의 어떤 것도 이해하기 쉽지는 않지만, 대부분의 기본적인 기능을 포함하고 있으며 당신이 시작하기에 충분한 내용을 제공할 것이다.


커널은 많은 조각 프로그램의 집합이고, 필자는 프로그래머들이 적어도 몇몇 커널 소스 파일들쯤은 읽어야하며, 그것들을 이해하여야 한다고 믿는다. Having said that, 또한 먼저 시스템을 가지고 놀고 나중에 질문하는 것이 가치 있다고 믿는다. 새로운 언어를 배울때, 필자는 라이브러리 코드를 읽는 것으로 시작하지 않는다, 하지만 그 대신에 작은 'hello, world' 프로그램을 작성한다. 커널을 가지고 노는 것이 어떠한 차이가 있을 것인지는 모른다.


0.2 문체에 대한 주의

나는 문서에 가능한 한 많은 조크를 넣는 것을 좋아한다. 내가 이렇게 하는 것은 그것을 즐기기 때문이며, 그리고 대부분의 경우 당신도 동일한 이유로서 이것을 읽는 것이라고 생각한다. 당신이 문서의 중요한 점만 알기 원한다면, 보통의 문장은 무시하고, 소스 코드만을 읽으면 된다. 중요한 내용에 대해 빠짐없이 주석을 달아 놓았다.


0.3 변화된 것들

0.3.1 문서 버전 1.0.1에서의 변화

1. 섹션의 변경 - 0.3 장

2. 어떻게 부 디바이스 번호를 찾는가? - 2 장

3. 문자 디바이스와 디바이스 파일과의 차이점에 대한 설명이 교정되었다. - 2 장

4. 커널 모듈을 위한 Makefiles - 1.1 장

5. 대칭형 다중 프로세싱 - 12 장 

6. 그릇된 생각 또는 잘못된 생각 - 13 장


0.3.2 문서 버전 1.1.0에서의 변화

1. 커널 버전 2.2의 지원, 모든 문서에서.

2. 다중 커널 버전 소스 파일들 -  2.1 장

3. 2.0과 2.2사이의 변화들 -  부록 A.

4. 다중 소스 파일에서 커널 모듈들 -  1.2 장 

5. 시스템 콜과 충돌을 일으키는 모듈들이 rmmod 되지 않도록 하기 위한 제안 - 7 장


0.4  감사의 말

많은 아이디어와 토론에 도움을 준 Yoav Weiss에 감사드리며, 출간 전에 많은 실수들을 교정해준 많은 사람들에게 또한 감사한다. 물론 여전히 남아 있는 실수들은 순전히 필자의 실수이다. 이 책의 TEX초고는 'Linux Installation and Getting Started'에서 부끄러움을 느끼면서 훔쳐(?)왔으며, Matt Welsh에 의해 TEX의 작업이 이루어졌다. 리누스 토발즈에게 경의를 표하며, 리차드 스톨만과 나의 컴퓨터에 뛰어난 운영체제를 갖추도록 해준 다른 많은 사람들과 아무런 조건 없이 소스 코드를 얻도록 해준(맞아 --- 내가 왜 이유를 말해야 하지: 역주-GNU의 소스이기에) 사람들에게 감사한다.


0.4.1 버전 1.0.1을 위한

나에게 전자우편으로 도움을 준 사람들 모두에게 사의와 감사를 드린다. 아래의 사람들은 특별한 도움을 주었다:

네덜란드의 Frodo Looijaard는 호스트를 위한 유용한 제안과 2.1.x커널에 대한 정보를 주었다.

뉴질랜드의 Stephen Judd은 철자 교정을 해주었다..

스웨덴의 Magnus Ahltorp는 문자와 블록 장치에 사이의 차이점에 대한 나의 실수를 교정해 주었다.


0.4.2 버전 1.1.0을 위한

캐나다 퀴벡의 Emmanuel Papirakis, 2.2커널에 모든 예제들을 포팅해 주었다.

네덜란드의 Frodo Looijaard는 다중 파일 커널 모듈 작성 방법을 알려 주었다.

물론, 나 자신의 부주의로 여전히 실수가 남아있다, 그리고 만약 당신이 이 책을 불필요하게 생각하고 당신의 기부금을 돌려 받기를 원한다면 그렇게 해줄 것이다.



제 1 장


Hello, world

처음의 원시 프로그래머가 첫 번째 프로그램을 최초의 동굴 컴퓨터의 화면에 조각했을때, 그것은 사슴 그림 안에 'Hello, world'란 문자열을 그린 것이었다. 로마인의 프로그램 교본은 'Salut, Mundi'의 프로그램과 함께 시작되었다. 누가 이러

한 전통을 깨뜨렸는지 사람들에게 어떤 일이 일어났는지 모르겠다. 이러한 일이 발견되지 않는 것이 좀더 안전하지 않았을까 생각한다.

하나의 커널 모듈은 적어도 두개의 함수를 가진다; 모듈이 커널 안에 삽입될 때 init 모듈이 호출되고, 제거되기 전에 cleanup 모듈이 호출된다. 전형적으로, init 모듈은 커널과 함께 무언가 일을 수행하기 위한 처리기의 등록과 자신의 코드와 함께 커널 함수의 하나를 대치한다.(일반적으로 코드는 무언인가 수행하고 원래의 함수를 호출한다). cleanup모듈은 init모듈이 무엇을 했던 간에 원래대로 되돌릴것이고,그래서 모듈은 안전하게 재적재 가능하게 된다.


<hello.c>

/* hello.c

*  Copyright (C) 1998 by Ori Pomerantz

*  "Hello, world" - 커널 모듈의 버전.

*/


/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다 */

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기*/

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 모듈의 초기화 */

int init_module()

{

   printk("Hello, world - this is the kernel speaking\n");

  

    /* 0이 아닌 값을 리턴하면, init_module이 실패했음을 나타내고 커널 모듈은 적재되지 않는다 */

   return 0;

}


/* Cleanup - init_module이 무엇을 했던 간에 되돌린다 */

void cleanup_module()

{

    printk("Short is the life of a kernel module\n");

}


1.1 커널 모듈을 위한 Makefiles 

커널 모듈은 독립적인 실행파일이 아니라, 실행시에 커널에 링크될 오브젝트 파일이다. 결론을 말하자면, 모듈들은 -c 플래그와 함께 컴파일 되어야 한다. 또한, 모든 커널 모듈은 아래의 정의된 심벌들과 함께 컴파일 되어야 한다.


*. __KERNEL__

이는 헤더 파일에 이 코드가 사용자 프로세스가 아닌, 커널 모드에서 실행됨을 알린다,

               

*. MODULE

이는 헤더 파일에 커널 모듈을 위한 적절한 정의들을 포함하도록 한다.


*. LINUX

기술적으로 말하자면, 이것은 필요하지 않다. 그러나, 여러 개의 운영체제상에서 컴파일하기에 아주 조심스럽게 커널 모듈을 작성하기 원하면, 이것이 좋은 결과를 가져다 줄 것이다. 이는 운영체제에 종속적인 조건부 컴파일을 허용할 것이다.


커널 컴파일시에 이용하하였던 플래그들에 따라 포함되어야 하거나 그렇지 않은 다른 심볼들이 있다. 만일 커널이 어떻게 컴파일 되었는지 확신할 수 없다면, /usr/include/linux/config.h을 살펴보라.

                

*. __SMP__

대칭형 다중 프로세싱. 이는 커널이 대칭형 다중 프로세싱을 지원할 경우에 포함되어져야 한다(단지 하나의 CPU상에서 수행될지라도). 대칭형 다중 프로세싱을 사용한다면, 다른 사항들이 필요하다(12장을 살펴보라).


*. CONFIG_MODVERSIONS

CONFIG_MODVERSIONS이 컴파일때 활성화하려면, 커널 모듈 컴파일시에 정의된 것을 가져야하며 /usr/include/linux/modversions.h를 인클루드하여야 한다. 또한 코드 자신에 의해 완료되는 것이 가능하다.


Makefile

# 기본적인 커널 모듈을 위한 Makefile 

CC=gcc

MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX

hello.o: hello.c /usr/include/linux/version.h

   $(CC) $(MODCFLAGS) -c hello.c

   @echo insmod hello.o가 이를 실행한다.

   @echo rmmod hello가 이를 종료한다.

   @echo

   @echo X와 커널 프로그램을 혼용하지 말라.

   @echo insmod와 rmmod는 X의 밖에서 실행한다.


자, 이제 루트의 su권한을 얻는 일만 남았다(루트의 권한으로 컴파일을 하지는 않았나?).(*1)


각주1 **************************************************

루트에서 직접 컴파일하지 않는 이유는 좀더 안전하게 시스템을 관리하고자 함이다.

나는 오랫동안 보안관계의 일을 했으며, 따라서 매우 편집광적이다.

******************************************************


그러면 조심스럽게 두근거리며 insmod hello와 rmmod hello를 해보자. 이를 수행하는 동안, /proc/modules에 있는 당신의 새로운 커널 모듈을 주목하라. 그건 그렇고, 왜 X의 바깥에서 insmod를 실행시켜야 하는 걸까? 이는 커널이 메시지를 표시할 때 printk를 이용하기 때문이다. X를 이용하지 않을 때, 이 메시지는 당신이 이용하는 가상 터미널(ALT-F(n)에 의해 선택된)에 표시되며, 볼 수 있게 된다. 반면에, X를 이용하면, 두 가지의 가능성이 존재한다. xterm -C로 콘솔을 연 경우, 올바르게

메시지는 표시될 것이다. 그렇지 않았다면, 가상 터미널 7로 메시지의 출력이 전달된다 --- X에 의해 온통 뒤덮인. 만약 커널이 불안정하다면 X없이 디버그 메시지를 얻을 필요가 있다. X의 바깥에서, printk는 커널에서 콘솔로 직접 메시지를 전달한다. 반면에, X의 안에서는, printk의 메시지는 사용자 프로세스(xterm -C)로 전달된다. CPU시간에 이 프로세스가 수신되면, X서버 프로세스에 이를 전달할 것이다. 그래서, X서버가 이를 수신하면, 그것을 표시할 것이다 --- 그러나, 불안정한 시스템으로 인해 커널이 적절하게 수행되지 못하는 경우, 한참 동안이나 지연된 오류 메시지를 보게 될 것이다. 무엇이 잘 못 되었는지 오랜 후에야 알게 되는 것이다.


1.2 커널 모듈을 위한 다중 파일

때때로 커널 모듈을 여러 개의 소스 파일로 나눌 경우가 생긴다. 이런 경우에, 아래의 과정이 필요하다.


1. 하나를 제외한 모든 소스 파일내에, #define __NO_VERSION__을 삽입한다. 이것은 module.h가 kernel_version의 정의를 포함하기에 중요하며. 컴파일동안 모듈의 커널 버전과 함께하는 전역 변수로서 중요하다. __NO_VERSION__과 함께 module.h는 수행되지 않으므로 version.h가 필요하다면 이것을 포함시킨다.


2. 오브젝트 코드를 합한다. 파일들은 하나로 된다.

x86하에서, 이는 ld -m elf_i386 -r -o <name of module>.o <1st source file>.o <second sourcefile>.o로 수행된다.


여기에 이러한 커널 모듈의 예가 있다.


<start.c>

/*

* start.c

* Copyright (C) 1999 by Ori Pomerantz

* "Hello, world" - 커널 모듈 버전.

* 이 파일은 단지 시작 루틴만을 포함한다.

*/


/* 필요한 헤더 파일들 */

/* 커널 모듈안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다.*/

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 모듈의 초기화 */

int init_module()

{

    printk("Hello, world - this is the kernel speaking\n");

   

    /* 0이 아닌 값을 리턴 하면, init_module가 실패했음을 나타내고 커널 모듈은 적재되지 않는다 */

    return 0;

}


<stop.c>

/*

* stop.c

* Copyright (C) 1999 by Ori Pomerantz

* "Hello, world" - 커널 모듈 버전. 

* 이 파일은 단지 종료 루틴만을 포함한다.

*/


/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다.*/

#define __NO_VERSION__   /* 커널 모듈의 파일이 아니다.*/

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */

#include <linux/version.h>   /* __NO_VERSION__때문에, module.h에 의해서 포함되지 않았다. */


/* CONFIG_MODVERSIONS 다루기*/

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 제거 - init_module이 무엇을 했던 간에 되돌린다 */

void cleanup_module()

{

    printk("Short is the life of a kernel module\n");

}


<Makefile>

# 다중 커널 모듈을 위한 Makefile 

CC=gcc

MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX


hello.o: start.o stop.o

   ld -m elf_i386 -r -o hello.o start.o stop.o


start.o: start.c /usr/include/linux/version.h

   $(CC) $(MODCFLAGS) -c start.c


stop.o: stop.c /usr/include/linux/version.h

   $(CC) $(MODCFLAGS) -c stop.c



제 2 장


문자 장치 파일들

자, 이제 우리는 대담한 프로그래머들이다. 그리고 아무런 일도 하지 않는 커널 모듈을 어떻게 작성하는 지를 알게 되었다. 우리는 자신에 자부심을 느끼고 머리를 곧게 쳐든다. 그러나, 어쩐지 무엇인가 빠뜨린 기분이 든다. 모듈들은 사실 재미있지는 않다.

커널 모듈과 프로세스가 통신하는 방법은 두 가지가 있다. 하나는 장치 파일을 이용하는 것이고, 다른 하나는 proc 파일 시스템을 이용하는 것이다. 커널을 이용하는 주된 이유가 하드웨어 장치를 지원하기 위해서 이기에, 장치 파일과 함께 내용을 시작할 것이다. 장치 파일의 본래 목적은 커널 안에서 프로세스들과 장치 드라이버 사이의 통신을 허락하는 일이고, 그리고 그들을 통해서 물리적 장치(모뎀, 터미널 등등)들과의 통신이 가능해진다.


이러한 방법이 아래에 잘 설명되어 있다.

각 장치 드라이버는, 어떤 형태의 하드웨어를 위해 응답하는, 자신의 주 번호를 할당 받는다. 이 드라이버들의 목록과 주 번호는 /proc/devices에 나타난다. 장치 드라이버에 의해 관리되는 각 물리적 장치들은 부 번호를 할당받는다. /dev 디렉토리는 각각의 장치들을 위한, 장치 파일로 호출될, 그것이 시스템 상에 실제로 설치되어 있지 않더라도, 특별한 파일들을 포함한다. 예를 들면, ls -l /dev/hd[ab]* 수행해보면, 시스템에 연결된 모든 IDE 하드 디스크의 파티션을 볼 수 있을 것이다. 모두가 동일한 주 번호 3을 가지는 것에 주목하라. 그러나, 부 번호는 다른 장치 사용자들중 하나로부터 변경된다. 이것은 PC구조를 사용하는 한 확실하다. 다른 구조의 시스템에서 리눅스상의 장치에 대해서는 잘 모르겠다. 시스템이 설치되었을 때, 장치 파일 전부가 mknod 명령에 의해 만들어진다. 그들이 /dev 디렉토리에 위치하는 기술적인 이유는 없다. 실험을 위해서 장치 파일을 만들 때, 여기의 예제에서처럼, 커널 모듈을 컴파일 하는 곳의 디렉토리 안에 그것을 위치시켜도 전혀 문제되지 않는다. 


장치들은 크게 두 가지 형태로 나눌 수 있다: 문자 장치와 블록 장치. 이 둘의 차이는 블록 장치가 데이터의 요청을 위한 버퍼를 가지며, 그래서, 응답된 순서되로 선택된다는 것이다. 이것은 데이터 저장 장치에서, 보다 멀리 떨어져 있는 것들보다, 서로 가까이 있는 것들에서 좀더 빠르게 섹터를 쓰거나 읽는 면에서 중요하다. 다른 차이는 블록 장치가 단지 입력만을 받아들이고 블록들의 출력을 리턴 하는(크기는 장치에 의존적이다) 반면에, 문자 장치는 그냥 많거나 또는 적은 수의 버퍼의 이용만이 가능하다. 대부분의 장치들은 버퍼링 형태를 필요로 하지 않기 때문에 문자 장치이고, 고정된 블록 크기로서 동작하지 않는다. ls -l의 출력의 첫 번째 문자를 확인함으로서 장치 파일이 문자 장치인지 블록 장치인지를 알 수 있다. 'b'라면 블록장치이며, 'c'라면 문자 장치이다. 모듈은 두 부분으로 나뉜다: 모듈을 등록하는 부분과 장치 드라이버의 부분. init_module 함수가 module_register_chrdev를 호출하여 커널의 장치 테이블에 장치 드라이버를 등록한다. 이것은 또한 장치를 위한 주 번호를 리턴한다. claenup_module 함수가 장치의 해제를 한다. 이러한(장치의 등록과 해제)것이 이들 두 함수들의 일반적인 기능이다. 모듈은 커널의 내부에서 자신을 초기화하지 않으며, 프로세스처럼, 그러나 호출되어지면, 시스템 호출을 경유하여 프로세스에 의해, 또는 인터럽트를 경유하여 하드웨어 장치에 의해, 또는 커널의 다른 부분에 의해(단순히 지정된 함수의 호출에 의해), 바로 초기화되어 사용된다. 결론을 말하자면 모듈은, 커널에 코드를 추가하면, 어떤 형태의 사건을 위한 처리기로서 동작될 수 있으며 그것을 제거하면 해제될 것이다.


역주 ****************************************************************************

여기서는 블록 장치와 문자 장치의 의미가 꽤 어렵게 설명되있다. 일반적으로 블록 장치는 블록이라 불리는 일정 크기의 버퍼(512, 1K Bytes등, 장치 의존적) 단위로 데이터의 읽기 쓰기가 행해진다. 반면에, 문자 장치는 하나(이런 경우는 거의 없지만 가능은 하다), 혹은 수십 내지 수백 개의 가변 크기의 버퍼(비록 이 크기가 고정되어 있을지라도 이는 바꿀 수 있다)를 가진다. 블록 장치의 예는 하드 디스크, CDROM 장치, 플로피 디스크등 주로 대용량의 데이터를 다루는 장치들이며, 문자 장치는 직렬 통신 포트, 모뎀 등이다.

*******************************************************************************


장치 드라이버는 4개의 device_<action>함수로서 이루어지며, 장치 파일의 주 번호와 함께 누군가가 어떤 작업을 하려고 할 때 호출된다. 커널이 그들이 호출된 것을 아는 방법은 장치가 등록될 때 주어지는, 이들 4개의 함수를 포함한, file_operations 구조체, Fops, 를 경유해서이다. 여기서 기억해야할 또 다른 점은 루트가 하는 것처럼 우리가(루트가 아닌)커널 모듈을 해제(rmmoded)하는 것은 허가되지 않는다. 장치가 프로세스에 의해 열리고 커널 모듈을 제거하면, 적정한 함수들 (read/write)이 이용되는 곳의 메모리 호출을 일으키게 된다. 좋은 경우에는 아무런 코드가 적재되지 않고, 약간의 지저분한 오류 메세지만을 보게 되지만, 최악의 경우 다른 커널 모듈이 동일한 장소를 이용하게 된다. 이러한 결과는 전혀 예측할 수 없게 된다. 보통, 무언가를 허가하고 싶지 않은 경우라면, 함수가 작업을 수행하는 곳에서 오류 코드(음수)를 리턴 한다. cleanup_module와 함께라면 이것이 void 함수이기에 가능하다. cleanup_module이 호출 되자마자, 이 모듈은 종료된다. 그러나, 카운터-얼마나 많은 커널 모듈들이 이 커널 모듈을 이용하는지에 대한-는 참조 카운터(/proc/modules 마지막 줄의)로 호출된다. 0이 아니라면, rmmod는 실패한 것이다. 이 모듈의 참조 카운터는 mod_use_count의 변수안에서 유용하다. 이 변수(MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT)의 처리를 위해 매크로가 정의되어 있으며, mod_use_count를 직접 이용하는 것 보다는 이들을 참조하는 것이 향후의 기능 변경에 대비할 수 있다.


<chardev.c>

/*

* chardev.c

* Copyright (C) 1998,1999 by Ori Pomerantz

* 문자 장치 만들기(읽기 전용)

*/

/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>     /* 커널 작업을 수행한다.*/

#include <linux/module.h>    /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 문자 장치를 위해 */

#include <linux/fs.h>          /* 문자 장치 정의들을 여기에 */

#include <linux/wrapper.h>  /* 현재는 아무런 일도 하지 않는다. 그러나 향후의 호환성을 위해 */


/* 2.2.3 /usr/include/linux/version.h을 위해 포함한다. 그러나, 2.0.35에서는 필요하지 않다. 필요하다면 추가한다. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif


/* 조건부 컴파일, LINUX_VERSION_CODE가 현재 버전의 코드이다 */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h> /* for put_user */

#endif


#define SUCCESS 0


/* 장치 선언자 **************************** */

/* 이 장치를 위한 이름, /proc/devices에 나타날 것이다 */

#define DEVICE_NAME "char_dev"


/* 이 장치로부터의 최대 메시지 크기 */

#define BUF_LEN 80


/* 이 장치를 지금 열 것인가? 동일 장치에의 동시 접근을 방지하기 위해 이용된다 */

static int Device_Open = 0;


/* 요청할 때 주어질 디바이스 메시지 */

static char Message[BUF_LEN];


/* 프로세스가 얼마나 많은 메시지를 얻었는가? 얻고자 하는 /device_read의 버퍼 크

  기보다 메시지가 클 경우 유용하다 */

static char *Message_Ptr;


/* 이 함수는 장치 파일을 열려고 시도하는 어느 때나 호출되어 진다 */

static int device_open(struct inode *inode, struct file *file)

{

    static int counter = 0;

      

   #ifdef DEBUG

   printk ("device_open(%p,%p)\n", inode, file);

   #endif

      

   /* 하나의 물리적 장치보다 많은 장치를 얻어야 하는 경우에 부 장치 번호를 얻는 방법을 보인다*/

   printk("Device: %d.%d\n", inode->i_rdev >> 8, inode->i_rdev & 0xFF);

      

   /* 동시에 두 프로세스의 통신을 원하지 않는다면.*/

   if (Device_Open)  return -EBUSY;

      

   /* 이것이 하나의 프로세스라면, 여기에서 좀더 조심스럽게 접근해야 한다. 프로세스들의 경우에서, 위험은 하나의

   프로세스가 Device_Open을 검색하고, 다른 프로세스가 스케줄러에 의해 이 함수를 실행할 때이다. 처음의

   프로세스가 CPU상에서 돌아갈 때, 프로세스는 이 장치는 아직 열리지 않았다고 확신한다. 그러나, 리눅스는

   프로세스가 커널 컨텍스트안에서 수행되는 동안 대치되지 않도록 한다.

   SMP의 경우에, 다른 CPU가 점유하는 동안 검색 과정의 후에 바로 Device_Open을 증가시킬 것이다. 그러나,

   2.0버전의 커널에서는 동시에 커널 모듈은 단지 하나의 CPU에 의해 점유되기에 문제되지 않는다. 성능상의 저하

   때문에 2.2버전에서 이것은 변경되어 졌다, 불행히도, SMP와 함께 이러한 작업이 어떻게 SMP 기계에서 수행

   되는지 확인해보지 못했다. */

          

   Device_Open++;

      

   /* 메시지의 초기화 */

   sprintf(Message, "If I told you once, I told you %d times - %s", counter++, "Hello, world\n");

      

   /* 여기에서 sprintf를 쓸 수 있게 허가되는 이유는 메시지의 최대 길이가 BUF_LEN보다 적기 때문이다.

   특별히 커널의 경우에 있어 버퍼 오버플로우가 일어나지 않도록 주의해야 한다. */


   Message_Ptr = Message;

      

   /* 파일이 사용 카운터를 증가시키는 것에 의해 열려 있는 동안 모듈은 제거되지 않는다(모듈에서 참조되어진 수들,

   만약 rmmod에서 0이 아니면 rmmod는 올바르게 수행되지 못한다). */

      

   MOD_INC_USE_COUNT;

   return SUCCESS;

}


/* 이 함수는 프로세스가 장치 파일을 종료할 때 호출된다. 버전 2.0.x에서 결과 값을 가지지 않기 때문에 항상 종료된다. 버전 2,2,x에서는 종료 실패시 결과를 되돌린다. 그러나 이것을 남겨 둘 필요는 없다. */


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static int device_release(struct inode *inode, struct file *file)

#else

static void device_release(struct inode *inode, struct file *file)

#endif

{

   #ifdef DEBUG

   printk("device_release(%p,%p)\n", inode, file);

   #endif


   /* 다음 호출자을 위한 대기 */  

   Device_Open--;


   /* 사용 카운터의 증가, 다시 말하면 파일을 연 횟수, 결코 이 모듈을 제거하지 말라. */     

   MOD_DEC_USE_COUNT;

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   return 0;

   #endif

}


/* 이 함수는 이미 열려진 장치 파일에서 읽으려 할 때 언제나 호출된다. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_read(struct file *file,

                          char *buffer,   /* 데이터를 채울 버퍼 */

                          size_t length,  /* 버퍼의 길이 */

                          loff_t *offset) /* 파일에서의 상대위치 */

#else

static int device_read(struct inode *inode,

                      struct file *file,

                      char *buffer, /* 데이터를 채울 버퍼 */

                      int length)     /* 버퍼의 길이(이것을 초과해서 쓰지 말라!) */

#endif

{

   /* 실제 버퍼에 쓰여진 데이터의 개수 */

   int bytes_read = 0;


   /* 메시지의 끝이라면, 0을 리턴 한다(파일의 끝을 나타낸다) */

   if (*Message_Ptr == 0)  return 0;

      

   /* 실제로 버퍼 안에 쓰여진 데이터 */

   while (length && *Message_Ptr)

   {

       /* 버퍼는 커널 데이터 세그먼트가 아닌 사용 데이터 세그먼트의 내에 있기 때문에 작업에 할당되지 않는다.

       대신에, put_user를 이용해서 커널 데이터 세그먼트에서 사용자 데이터 세그먼트로 복사해야만 한다. */

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

       length--;

       bytes_read ++;

   }


   #ifdef DEBUG 

   printk ("Read %d bytes, %d left\n", bytes_read, length);

   #endif

      

   /* 읽기 함수들은 버퍼에 실제로 쓰여진 개수를 리턴 한다. */

   return bytes_read;

}


/* 이 함수는 누군가 장치 파일에 쓰기를 시도할 때 호출된다 - 이 예제 안에서는 지원되지 않음 */

#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)      /* 파일에서의 상대 위치 */

#else

static int device_write(struct inode *inode,

                       struct file *file,

                       const char *buffer,

                       int length)

#endif

{

       return -EINVAL;

}


/* 모듈 선언 */

/* 장치를 위한 주 장치 번호. 이것은 등록, 해제시 모두에서 사용되기에 전역 변수(물론, 정적, 이 안에서  컨텍스트는 이 파일 내에서 전역)이다. */

static int Major;


/* 이 구조체는 프로세스가 만든 장치에서 무엇인가 하려고 할 때 함수들을 유지하기 위해 호출된다. 이 구조체에서의 포인터는 장치 테이블에 유지되고, init_module에서 지역 변수로 사용되지 않는다. NULL은 아직 기능이 정의되진 않은 함수들을 위한 것이다. */

struct file_operations Fops =

{

    NULL,            /* 찿기 */

   device_read,

   device_write,   

   NULL,            /* 디렉토리 읽기 */

   NULL,            /* 선택 */

   NULL,            /* 입출력 제어 */

   NULL,            /* 메모리 맵 */

   device_open,

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   NULL,            /* 버퍼 비우기*/

   #endif

   device_release   /* 해제 */

};


/* 모듈의 초기화 - 문자 장치의 등록 */

int init_module()

{

   /* 문자 장치의 등록(적어도 시도 정도는) */

   Major = module_register_chrdev(0, DEVICE_NAME, &Fops);


   /* 음의 값은 오류를 나타낸다. */

   if (Major < 0)

   {

       printk ("%s device failed with %d\n", "Sorry, registering the character", Major);

      

       return Major;

   }


   printk ("%s The major device number is %d.\n", "Registeration is a success.", Major);

   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 <name> c %d <minor>\n", Major);

   printk ("You can try different minor numbers %s", "and see what happens.\n");

  

   return 0;

}


/* 제거 -  /proc로 부터 관계된 파일을 해제한다.*/

void cleanup_module()

{

    int ret;


   /* 장치의 해제 */

   ret = module_unregister_chrdev(Major, DEVICE_NAME);

      

   /* 오류가 생기면, 보고한다. */

   if (ret < 0)  printk("Error in unregister_chrdev: %d\n", ret);

}


2.1 다중 커널 버전 소스 파일

시스템 호출은, 프로세스에서 커널과의 주된 접속 수단인, 일반적으로 버전에 무관하게 유지된다. 새로운 시스템 호출이 추가될지라도, 보통은 이전의 것과 동일하게 동작한다. 이것은 하위 호환성을 위해 필요하다 --- 새로운 커널 버전이 정규 프로세스를 손상시키지 않게 한다. 대부분의 경우에서, 장치 파일들은 여전히 동일하게 유지된다. 반면에, 커널 안의 내부 인터페이스들과 기능은 버전에 따라 변경된다. 리눅스의 커널 버전들은 안정 버전(짝수 번호의)과 개발 버전(홀수 버전의)으로 나누

어진다. 개발 버전은 새로운 모든 아이디어를 포함하고, 고려해야할 실수들을 포함하여 재 작성된다. 결과적으로, 이들 버전들에서 동일한 인터페이스를 기대할 수 없게된다(이것이 이 책에서 이의 지원을 꺼리는 이유이며, 또한 이것은 너무 많은 작업과 너무 빠른 변경을 가진다). 반면에, 안정 버전에서는, 많은 버그 수정 버전에도 불구하고 여전히 동일한 인터페이스를 기대할 수 있다. 이 가이드의 내용은 버전 2.0.x와 2.2,x의 커널을 둘 다 포함한다. 이 둘 사이의 차이점으로 인해, 커널 버전에 의존적인 컴파일 조건을 요구된다. 이러한 일에 매크로 LINUX_VERSION_CODE가 이용된다. 커널 버전의 a.b.c에 대한, 이 매크로의 값은 2^16a+2^8b+c가 될 것이다. 지정된 커널 버전의 값을 얻기 위해, KERNEL_VERSION 매크로를 이용한다. 2.0.35에서는 정의되지 않았으므로, 필요한 경우 이를 정의한다.



제 3 장

/proc 파일 시스템

리눅스에서 커널 모듈을 위한 프로세스들에 정보를 전달하는 추가적인 방법이 있다. --- /proc 파일 시스템. 원래는 프로세스들에 대한 정보를 쉽게 인식하기 위해 설계되었지만, 지금은 모든 커널에서, 모듈의 목록을 표시하는 /proc/modules, 메모리의 이용 상태를 표시하는 /proc/meminfo와 같은, 이용된다.  /proc 파일 시스템을 이용하는 이러한 방법은 장치 드라이버를 이용하는 방법과 매우 유사하다 --- /proc 파일을 위한 필요한 모든 정보와 함께 구조체의 생성, 처리 함수를 위한 포인터의 포함(우리의 경우에 이것은 한 번이다, /proc 파일에서 누군가가 읽기를 시도할 때 한 번 호출되어 진다). init_module이 커널과 함께 구조체의 등록과 cleanup_module이 이를 해제할 때이다. 우리가 proc_register_dynamic을* 이용하는 이유는 나은 파일 성능을 위해 이를 결정하기 원하지 않기 때문이며, 커널에서 위험한 충돌을 막기 위해 결정한다. 보통의 파일 시스템은 메모리(/prco가 위치하는)보다는 디스크 상에 위치하며, 이러한 경우의 inode번호는 파일의 index-node가 위치하는 곳의 디스크상의 위치를 나타낸다. inode는 파일에 관한 정보를, 예를 들면 파일의 소유권, 디스크 위치를 나타내는 포인

터 또는 파일의 데이터를 발견할 수 있는 위치 등을 포함한다. /proc 시스템상의 파일이 열리거나 닫혔을 때 어떠한 호출도 얻을 수 없으므로, 모듈에서 MOD_INC_USE_COUNT와 MOD_DEC_USE_COUNT는 어디에 있는지 알 수 없으며, 그리고 만약 파일이 열리고 모듈이 제거되었으면, 결과를 전혀 예측할 수 없게 된다. 다음 장에서 좀더 자세한 내용을 볼 수 있을 것이며, 그러나 좀더 유연한 내용들을, /proc파일들의 이러한 문제점을 막는 방법을 제공한다.


각주1 ***************************************

2.0, 2.2 버전에서 inode를 0으로 설정하면 자동적으로 완료된다.

*******************************************


<procfs.c>

/*

* procfs.c - /proc에 파일 생성하기 

* Copyright (C) 1998-1999 by Ori Pomerantz

*/

/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다.*/

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* /proc 파일 시스템을 이용하기에 필요함 */

#include <linux/proc_fs.h>


/* 버전 2.2.3에서 /usr/include/linux/version.h에 매크로 포함하나, 2.0.35에서는

   그렇지 못하다. 그래서 필요하다면 이곳에서 추가한다. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif


/* proc 파일 시스템의 파일에 데이터 쓰기 */

/*

인자들

====================

1. 데이터를 삽입할 곳의 버퍼, 이용할 것이라면 지정하라.

2. 문자들을 지정할 포인터. 커널에 의해 할당된 버퍼를 이용하지 않을 경우 유용하다.

3. 파일에서의 현재 위치.

4. 첫 번째 인자의 버퍼 크기.

5. Zero (향후에 이용을 위해?).


사용법과 리턴 값

====================

자신만의 버퍼를 이용할 경우, 나는 그렇게 하는 것을 좋아한다, 두 번째 인자에 위치를 지정하고 버퍼에서 이용된 크기를 리턴 받는다. 결과 값이 0이면 이번에 더이상의 정보(파일의 끝)를 가지지 못함을 뜻한다. 음수의 값은 오류 조건이다.


상세한 정보

====================

문서를 읽는 것만으로는 이 함수와 함께 무엇을 해야하는지 발견하지 못 할 것이다. 그러나 코드를 살펴보는 것에 의해 방법을 알 수 있다. get_info 필드와 proc_dir_entry가 무엇에 이용되었는지 살펴봄으로(주로 필자는 find와 grep의 조합을 이용한다)으로서 이것을 이해하게 되었고, 그리고 <커널 소스 디렉토리> /fs/proc/array.c에 이용되는 것을 보았다. 커널에 대해 모르는 것이 있다면, 이러한 것이 통상적인 방법을 제공한다. 리눅스에서의 커널 소스로부터 아주 커다란 무료인 이익을 얻을 수 있다 - 그것을 이용하라.

*/


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;

      

   /* 필요한 정보 모두가 한 번에 주어진다, 그래서 사용자가 정보를 좀더 요구하면, 그 대답은 항상 "아니오"이다.

   이것은 라이브러리 표준 read함수가 시스템 호출을 통해 커널이 응답 할 때까지, 즉 더 이상의 정보가 없거나,

   버퍼가 찰 때까지 계속 read 시스템 호출을 계속하기에 중요하다. */

   if (offset > 0)  return 0;


   /* Fill the buffer and get its length */

   len = sprintf(my_buffer,

                "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 len;

}


struct proc_dir_entry Our_Proc_File =

{

   0,                           /* Inode 번호 - 무시, proc_register[_dynamic]에 의해 채워진다. */

   4,                           /* 파일 이름의 길이 */

   "test",                     /* 파일 이름 */

   S_IFREG | S_IRUGO,   /* 파일 모드 - 소유자, 그룹, 다른 모두가 읽을 수 있는 정규 화일 */  

   1,                           /* 링크의 수(파일이 참조되어진 곳에서의 디렉토리들) */

   0, 0,                       /* 파일을 위한 uid 와 gid  - 루트에서 주어진 */

   80,                         /* ls에 의해 나타나는 파일의 크기 */

   NULL,                      /* inode상에서 수행 가능한 함수(링킹, 제거 등등)? 아무 것도 지원하지 않는다. */

   procfile_read,           /* 이 파일을 위한 읽기 함수, 누군가 읽기를 시도할 때 호출되어진다.*/

   NULL                      /* 파일의 inode을 지정하기 위한 여기에 함수를 가진다. 퍼미션, 소유자 등과 함께 수행한다 */

};


/* 모듈의 초기화 - proc파일 등록 */

int init_module()

{

    /* proc_register[_dynamic]이 성공이면 성공, 아니면 실패. */

      

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

   /* 버전 2.2에서, proc_register는 구조체안에서 값이 0이면 자동적으로 동적 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)을 위한 루트 디렉토리. 원하는 곳에 위치할 수 있다. */

}


/* 모듈의 제거 - /proc에 파일을 해제한다. */

void cleanup_module()

{

   proc_unregister(&proc_root, Our_Proc_File.low_ino);

}



제 4 장 


입력을 위한 /proc의 이용

이제 커널의 모듈로부터 출력을 얻는 두 가지 방법을 알게 되었다. 장치 드라이버를 등록하고 mknod를 이용하여 장치 파일을 만드는 것이 가능하며, 또한 /proc 파일을 만드는 것도 가능하다. 이것은 입력에서도 이와 유사함을 보여준다. 문제라면, 응답을 얻을 어떤 방법도 없다는 것이다. 커널 모듈에 입력을 보내기 위한 첫 번째 방법은 /proc 파일에서 쓰여진 것을 통해서이다. 때문에 /proc 파일 시스템은 주로 프로세스들에 대한 상황을 나타내는 데에 쓰이며, 입력을 위한 어떤 특별한 형식은 없다. proc_dir_entry 구조체는 입력 함수에서 포인터를 포함하지 않으며, 출력 함수에서만 포인터를 포함한다. 대신, /proc 파일에 쓰기 위해서, 표준 파일 시스템 방식의 이용을 필요로 한다. 리눅스에서 그것은 파일 시스템의 등록을 위한 표준적인 절차이다. 모든 파일 시스템이 파일의 동작과 inode 동작을* 처리하기 위해 자신만의 함수들을 가지며, struct_inode와 struct_file에 대한 포인터를 포함하여, 이들이 이러한 함수들에서 사용되는 특별한 구조체이다.


각주1 ***************************************************************************

둘 사이의 차이는  파일 동작이  파일 자신과 함께 다루어지는 것이고, inode 동작이 파일에 대한 링크(연결고리)를 생성하는 것처럼, 파일에 대한 참조의 방법으로 다룬다는 것이다.

********************************************************************************


/proc에서, 새로운 파일을 등록할 때마다, 이에 대한 접근을 위해 struct_inode을 지정하게 된다. 이것이 우리가 이용하는 절차이며, struct_inode는 모듈의 입력과 출력을 위한 포인터를 포함하는 struct_file에 대한 포인터를 포함한다. 유의할 점은 읽기와 쓰기의 표준적인 규칙들이 커널에서는 바뀐다는 점이다. 읽기 함수는 쓰기를 위해 이용되며, 반면에 쓰기 함수는 읽기를 위해 이용된다. 이는 읽기, 쓰기가 입력을 위한 사용자의 관점에서 참조되기 때문이다 --- 프로세스가 커널에서 무언가 읽으려 하면 커널은 그것을 출력할 필요가 있으며, 그리고 프로세스가 커널에 무언가 쓰게되면 커널은 입력처럼 그것을 받아들인다.


또 한가지 흥미로운 점은 module_permission 함수이다. 이 함수는 프로세스가 /proc 파일에 어떤 일을 하려고 할 때 호출되어지며, 그것에 대한 접근의 허가 또는 불허를 결정한다. 이것은 파일의 동작과 현재 이용된 uid에 근거하며(현재 가능한, 현재실행되고 있는 프로세스의 정보를 포함하는 구조체의 포인터), 그러나 다른 것들, 다른 프로세스들이 동일한 파일과 함께 무엇을 하는지, 날짜와 시간, 또는 수신된 마지막 입력 따위의 것들에서 근거하기도 한다.


put_user와 get_user을 쓰는 이유는 리눅스의 메모리(적어도 인텔 구조에서, 다른 구조에서는 다를지도 모르지만)가 세그먼트로 구성되기 때문이다. 이것은 포인터가 메모리에서 단일한 위치를 참조하지 않으며, 단지 메모리상의 세그먼트 상에 위치하고, 그것이 이용 가능한 세그먼트인지 알 필요가 있다는 것이다. 커널을 위한 하나의 세그먼트가 있으며, 프로세스들에도 각각 하나씩 할당된다. 프로세스를 실행하려 할 때, 단지 자신에게 할당된 세그먼트 만을 이용할 수 있으므로, 세그먼트에 대해서는 걱정할 필요가 없다. 커널 모듈에 쓰려고 할 때, 보통은 시스템에 의해 자동적으로 관리되는 커널 메모리 세그먼트에 접근하길 원한다. 그러나, 현재 실행되고 있는 프로세스와 커널 사이에 필요한 메모리의 양이 넘겨질 때, 커널 함수는 프로세스 세그먼트 안에 위치한 메모리 버퍼의 포인터를 수신한다. put_user와 get_user 매크로는 이 메모리에 접근하도록 허용한다.


<procfs.c>

/* procfs.c - /proc안에 생성되는 파일, 입력, 출력이 모두 허가된다. */

/* Copyright (C) 1998-1999 by Ori Pomerantz */


/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다.*/

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* /proc 파일 시스템을 이용하기에 필요함 */

#include <linux/proc_fs.h>


/* 버전 2.2.3에서 /usr/include/linux/version.h에 매크로를 포함하나, 2.0.35에서는 그렇지 못하다. 그래서 필요하다면 이곳에서 추가한다. */

#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> /* get_user와 put_user를 위해 */

#endif


/* 모듈의 파일 함수들 */

/* 여기서 수신된 마지막 메시지를 유지하며, 시험을 위해 입력을 처리할 수 있다.*/

#define MESSAGE_LENGTH 80


static char Message[MESSAGE_LENGTH];


/* 파일 동작 구조체를 이용하므로, 특별한 proc 출력 시험들은 할 수 없다 - 여기에 쓰여진 것처럼, 표준 읽기 함수를 이용해야 한다.*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_output(

   struct file *file,    /* 파일 읽기 */

   char *buf,           /* 사용자 세그먼트 안에서 데이터를 저장을 위한 버퍼 */

   size_t len,           /* 버퍼의 길이 */

   loff_t *offset)      /* 파일에서의 상대 위치 - 무시 */

#else

static int module_output(

   struct inode *inode,  /* inode 읽기 */

   struct file *file,        /* 파일 읽기 */

   char *buf,               /* 사용자 세그먼트 안에서 데이터를 저장을 위한 버퍼 */ 

   int len)                   /* 버퍼의 길이 */       

#endif   

{

   static int finished = 0;

   int i;

   char message[MESSAGE_LENGTH+30];


   /* 파일의 끝이면 0을, 더 이상의 정보를 가지고 있지 않다. 반면에, 프로세스들은 무한 루프 안에서 읽기를 계속한다. */

   if (finished)

   {

       finished = 0;

       return 0;

   }

      

   /* put_user는 커널 메모리 세그먼트에서 호출한 프로세스의 세그먼트로 문자열을 복사한다. get_user는

   반대의 동작을...*/

   sprintf(message, "Last input:%s", Message);

   for(i=0; i<len && message[i]; i++) put_user(message[i], buf+i);


   /* 주의, 메시지의 크기가 아래의 길이를 가진다고 확신한다. 그렇지 않으면, 잘려질 것이다. 실제의 상황에서,

   메시지의 크기가 버퍼의 길이보다 작다면, 길이를 리턴 할 것이고, 다음의 호출에서 len+1의 크기의 메시지와 함께

   버퍼는 채워진다.

    */

   finished = 1;

      

   return i; /* "read",에 의한 크기를 리턴 */

}



/* 이 함수는 사용자가 /proc 파일에 쓸 때 사용자로부터 입력을 수신한다.*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_input(

    struct file *file,    /* 파일 자신 */

   const char *buf,    /* 입력 버퍼 */

   size_t length,        /* 버퍼의 길이 */

   loff_t *offset)       /* 파일서의 상대위치 - 무시 */

#else

static int module_input(

    struct inode *inode, /* 파일의 inode */

   struct file *file,        /* 파일 자신 */

   const char *buf,      /* 입력 버퍼 */

   int length)               /* 버퍼의 길이 */

#endif

{

   int i;

      

   /* 메시지 안에 입력을 집어넣기, module_output의 곳에서 나중에 이용된다.*/

   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);

      

   /* 버전 2.2에서 get_user의 의미는 변경되었다. 더이상 문자를 리턴하지 않도록, 그러나 첫 번째 인자에 변수를

   지정하길 기대하고, 다음의 인자에 사용자 세그먼트의 포인터를 지정한다.

   버전 2.2에서 get_user의 이러한 변경의 이유는 short 또는 int의 읽기를 가능하게 한다는 것이다. 읽기 위한 변수의

   형태를 아는 방법은 sizeof을 이용해서이며, 변수 자신을 위해 필요하다.

    */   

#else

   Message[i] = get_user(buf+i);

#endif


   Message[i] = '\0'; /* 표준을 원한다, 0은 문자열 종료 문자 */

  

    /* 이용된 입력 문자들의 개수를 리턴할 필요가 있다 */

   return i;

}


/* 이 함수는 동작을 허가(0을 리턴), 불허(왜 허가하지 않는데 이유를 나타내는 양수의 값을 리턴)결정한다.

아래의 값들중 하나로서 동작한다:

0 - 실행(파일의 실행 - 여기서는 무의미하다)

2 - 쓰기(커널 모듈에서의 입력)

4 - 읽기(커널 모듈로부터의 출력)

이것은 파일의 퍼미션을 검사하는  실제 함수이다. ls -l에 의해 리턴 되어지는 퍼미션들은 단지 참조될 뿐이며, 이곳에서 중첩되는 것이 가능하다. */

static int module_permission(struct inode *inode, int op)

{

    /* 모두에게 모듈에서의 읽기를 허가하고, 루트(uid 0)에게만 쓰기를 허가한다.*/

   if (op == 4 || (op == 2 && current->euid == 0))

   return 0;

      

   /* 어떤 것도 아니라면, 접근은 불허된다. */

   return -EACCES;

}


/* 파일은 열려 진다. -  이것에 대해 크게 조심하지 않아도 되지만, 모듈의 참조 카운터를 증가할 필요는 있다. */

int module_open(struct inode *inode, struct file *file)

{

   MOD_INC_USE_COUNT;

   return 0;

}


/* 파일은 닫혀진다. - 다시 말하지만 참조 카운터의 값 때문에 이것이 흥미로울 뿐이다 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   int module_close(struct inode *inode, struct file *file)

#else

   void module_close(struct inode *inode, struct file *file)

#endif

{

    MOD_DEC_USE_COUNT;


   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   return 0; /* 성공 */

   #endif

}


/* /proc에 등록하기 위한 구조체, 모든 관계된 함수들의 포인터와 함께 */ 

/* proc 파일을 위한 연산들. 파일에 무슨 일을 수행하려 할 때 호출되는 모든 함수들의 포인터가 위치한다. NULL의 아무 것도 수행하지 않는다는 의미이다.*/

static struct file_operations File_Ops_4_Our_Proc_File =

{

   NULL,               /* lseek */

   module_output,  /* 파일로부터의 "읽기" */

   module_input,    /* 파일에 "쓰기" */

   NULL,               /* readdir */

   NULL,               /* select */

   NULL,               /* ioctl */

   NULL,               /* mmap */

   module_open,    /* 누군가 파일을 열었다. */

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   NULL,               /* flush, 버전 2.2에서 추가 */

   #endif

   module_close,    /* 누군가 파일을 닫았다. */

      

   /* 기타 등등. (/usr/include/linux/fs.h에 주어진 모든 것들). 여기에 어떤 것도 주지 않았으므로, 시스템은 기본 값을

   유지한다. 유닉스에서 이것은 0이다(포인터일 경우 이것은 NULL). */

};


/* proc 파일을 위한 inode 연산들. 이용하기 원하는 곳에 이 파일 연산 구조체를 위치시키고, 이 함수는 퍼미션을 위해 이용한다. 이것은 또한 inode의 작업 말고도 다른 일에 함수들을 지정할 수 있다.(이런 일은 그다지 까다롭지 않다, 필요치 않는 경우 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) */

   NULL,          /* 디렉토리 생성(mkdir) */

   NULL,          /* 디렉토리 삭제(rmdir_ */

   NULL,          /* 노드 생성(mknod) */

   NULL,          /* 이름 변경(rename) */

   NULL,          /* readlink */

   NULL,          /* follow_link */

   NULL,          /* 페이지 읽기(readpage) */

   NULL,          /* 페이지 쓰기(writepage) */

   NULL,          /* bmap */

   NULL,         /* 절단(truncate) */

   module_permission /* 접근 허가를 위한 검사 */

};



/* 디렉토리 등록 */

static struct proc_dir_entry Our_Proc_File =

{

   0,    /* Inode 번호 - 무시, proc_register[_dynamic]에 의해 지정될 것이다. */

   7,    /* 파일명의 길이 */

   "rw_test", /* 파일 이름 */

   S_IFREG | S_IRUGO | S_IWUSR, /* 파일 모드 - 소유자, 그룹, 그리고 모두에 읽기가 가능한 정규 파일. 실제로,

                                               이 필드는 단지 참조 될 뿐이며, module_permission이 실제로 이를 이용한다.

                                               이것이 이 필드를 이용하기는 하지만, 여기서는 필요치 않다. */ 

   1,         /* 링크의 개수(파일이 참조되는 곳의 디렉토리들) */

   0, 0,      /* 파일의 uid와 gid - 루트에 의해 주어진다. */

   80,        /* ls에 의해 나타나는 파일의 크기 */

   &Inode_Ops_4_Our_Proc_File,   /* 필요하다면, 파일을 위한 inode 구조체의 포인터 쓰기 함수가 필요하므로

                                              여기서는 필요하다.*/

   NULL  /* 파일을 위한 읽기 함수. 불필요, 위의 inode 구조체에서 사용하기에*/

};


/* 모듈의 초기화와 삭제 */

/* 모듈의 초기화 - proc에 등록 */

int init_module()

{

   /* proc_register[_dynamic]이 0이면 성공, 아니면 실패 */

      

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   /* 버전 2.2에서 proc_register는 구조체 안의 값이 0이면, 동적으로 inode번호를 지정한다. 그래서

   proc_register_dynamic이 더이상 필요치 않다. */

   return proc_register(&proc_root, &Our_Proc_File);

   #else

   return proc_register_dynamic(&proc_root, &Our_Proc_File);

   #endif

}


/* 삭제  - /proc에서 파일을 제거  */

void cleanup_module()

{

   proc_unregister(&proc_root, Our_Proc_File.low_ino);

}



제 5 장


장치 파일의 제어(IOCTL 다루기)

장치 파일들은 물리적 장치를 나타낸다. 대부분의 물리적 장치들은 입력은 물론 출력도 이용하며, 커널 내에서 프로세스들에 장치 드라이버의 입, 출력을 제공하기 위한 절차를 가진다. 이것은 출력를 위한 장치 파일의 열기에 의해 완료되며, 일반적인 파일에 쓰는 것과 같이 쓰기 작업을 한다. 아래의 예제에서, 이것은 device_write에 의해 수행된다. 그러나, 이것만으로는 충분하지 않다. 모뎀에 연결된 직렬 통신 포트를 상상해 보자(내장형 모뎀을 가지고 있을지라도, CPU에 있어 그것은 여전히 원격으로 모뎀에 연결된 직렬포트일 뿐, 하지만 너무 어렵게 생각하지는 않아도 된다). 본질은 모뎀에 쓰기 위해(모뎀 명령은 물론 전화선을 통해 데이터를 보내는 것까지), 그리고 모뎀으로부터 읽는 것을 위해(명령에 대한 응답은 물론 전화선을 통한 데이터의 수신) 장치 파일을 이용하는 것이다. 일단은, 언제 직렬 포트를 제어하는지 무엇을 하는지, 예를 들면 데이터를 송수신 할 때의 통신속도를 보내는 것 따위의, 질문은 뒤로 남겨두자. 유닉스에서 이의 응답은 ioctl이라 불리는 특별한 함수를 통해서이다(input output control의 줄임 말인). 모든 장치는 자신의 ioctl 명령을 가지며, ioctl의 읽기(프로세스에서 커널로 정보를 보내기 위한), ioctl의 쓰기(*1)(프로세스에서 정보를 리턴하기 위한)가 둘 다 쓰일 수도 혹은 아닐 수도 있다.


각주 1 **************************************************************************

주의할 점은 읽기와 쓰기의 규칙이 다시 바뀐다는 것, ioctl의 읽기는 커널에 정보를 보내는 것이고 쓰기는 커널에서 정보를 수신한다는 것이다.

*******************************************************************************


ioctl 함수는 3개의 인자와 함께 호출되어진다: 접근하려는 장치 파일의 파일 기술자, ioctl 번호, 그리고 하나의 인자, 이것을 통해 어떤 것이라도 전달하기 위해 캐스트(cast:형변화 연산자)를 이용하는 것이 가능하다.(*2) 


각주 2 **************************************************************************

이것은 엄밀하게 따지자면 정확하지는 않다. 예를 들면 구조체등은 전달할 수는 없다. --- 그러나, 구조체의 포인터를 전달 할 수는 있다.

*******************************************************************************


ioctl 번호는 주 장치 번호, ioctl의 형태, 명령, 그리고 인자의 형태 등을 변환한다. ioctl 번호는 헤더 파일에 정의된 보통 하나의 매크로 호출(_IO, _IOR, _IOW, _IOWR --- 형태에 의존적인)에 의해 만들어진다. 이 헤더 파일은 이용되는 ioctl(이렇게 해야 접근할 수 있는 ioctl 함수를 만들 수 있다)과 커널 모듈(이것의 이해를 가능하게 한다) 모두에 의해 #inlcude되어 포함되어져야 한다. 아래의 예에서, 헤더 파일은 chardev.h와 ioctl.c를 이용하는 프로그램에 포함된다. 자신의 커널 모듈에 ioctl의 이용을 원한다면, 이것은 가장 좋은 공식적인 ioctl 지정 방법이다, 이렇게 함으로서 허가되지 않은 누군가가 우연히 ioctl의 제어를 얻으면, 잘못된 것을 알게 될 것이다. 상세한 정보를 원하면, 커널 소스의 `Documentation/ioctl-number.txt'을 참조하라.


<chardev.c>

/*

* chardev.c

* 입력/출력 문자 장치의 생성

*

* Copyright (C) 1998-9 by Ori Pomerantz

*/


/* 필요한 헤더 파일들 */

/* 커널 모듈 안에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널 작업을 수행한다.*/

#include <linux/module.h>   /* 특별히, 하나의 모듈에서 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 문자 장치들을 위해 */

/* 문자 장치의 정의들을 여기에 */

#include <linux/fs.h>


/* wrapper.h는 현재에는 유용하지 않다. 그러나, 향후의 호환성을 위해 추가한다. */

#include <linux/wrapper.h>


/* 자신의 ioctl 번호들 */

#include "chardev.h"


/* 버전 2.2.3에서 /usr/include/linux/version.h에 매크로를 포함하나, 2.0.35에서는 그렇지 못하다.

   그래서 필요하다면 이곳에서 추가한다. */

#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> /* get_user와 put_user를 위해 */

#endif


#define SUCCESS 0


/* 장치 선언 */

/* 장치를 위한 이름, /proc/devices에 나타날 것이다. */

#define DEVICE_NAME "char_dev"


/* 장치를 위한 메시지의 최대 길이 */

#define BUF_LEN 80


/* 장치의 열기가 적절한지 - 동일한 장치에의 동시 접근을 막기 위해 */

static int Device_Open = 0;


/* 요청될 때 주어지는 장치 메시지 */

static char Message[BUF_LEN];


/* 얻어진 메시지가 얼마나 멀리 있는가 - device_read로 얻어진 메시지가 버퍼의 크기보다 클 때 유용하다.*/

static char *Message_Ptr;


/* 장치 파일을 열려할 때 언제든지 호출된다. */

static int device_open(struct inode *inode, struct file *file)

{

   #ifdef DEBUG

   printk ("device_open(%p)\n", file);

   #endif

      

   /* 동시에 두개의 프로세스와의 대화를 원하지 않는다. */

   if (Device_Open) return -EBUSY;


   /* 이것이 하나의 프로세스였다면, 다른 하나의 프로세스가 이것을 증가하기 전에 Device_Open이 적절하게 수행되어야

   하기 때문에, 이 곳에서  매우 조심해야 한다. 그러나, 커널의 내부에서, 컨텍스트 사이의 교환은 방지된다. 이것은

   SMP기계에서 수행할 때 적절치 못 한 결과를 가져온다. SMP에 대해서는 나중에 다룰 것이다. */

      

   Device_Open++;


   /* 메시지의 초기화 */

   Message_Ptr = Message;

      

   MOD_INC_USE_COUNT;

      

   return SUCCESS;

}


/* 이 함수는 프로세스가 장치 파일을 종료할 때 호출된다. 절대 실패할 수 없기에 결과 값은 없다. 무엇이 일어났든 간에, 항상 장치를 종료가능하게 해야한다(2.0. 2.2 에서 장치 파일은 종료 불가능할 수도 있다). */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static int device_release(struct inode *inode, struct file *file)

#else

static void device_release(struct inode *inode,struct file *file)

#endif

{

   #ifdef DEBUG

   printk ("device_release(%p,%p)\n", inode, file);

   #endif

      

   /* 다음 호출을 위한 준비 */

   Device_Open--;

      

   MOD_DEC_USE_COUNT;

      

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   return 0;

   #endif

}


/* 이 함수는 프로세스가 이미 열린 장치 파일을 읽을 때 호출된다.*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_read(

   struct file *file,

   char *buffer,   /* 데이터를 저장하기 위한 버퍼 */

   size_t length,  /* 버퍼의 크기 */

   loff_t *offset) /* 파일의 상대 위치 */

#else

static int device_read(

   struct inode *inode,

   struct file *file,

   char *buffer,   /* 데이터를 저장하기 위한 버퍼 */

   int length)      /* 버퍼의 크기(이 크기를 초과해서 쓰지 말것!) */

#endif

{

   /* 실제 버퍼에 쓰여진 데이터의 개수 */

   int bytes_read = 0;


   #ifdef DEBUG

   printk("device_read(%p,%p,%d)\n", file, buffer, length);

   #endif

      

   /* 메시지의 끝이라면, 0을 리턴(파일의 끝이라는 의미로) */

   if (*Message_Ptr == 0)  return 0;

      

   /* 실제 버퍼에 쓰여진 데이터 */

   while (length && *Message_Ptr)

   {

   /* 버퍼는 커널 데이터 세그먼트가 아닌 사용자 데이터 세그먼트에 위치하므로, 버퍼의 할당이 이루어지지 않는다.

   대신에, put_user함수로서 커널 데이터 세그먼트에서 사용자 데이터 세그먼트로 데이터를 복사한다. */

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

   length --;

   bytes_read ++;

   }

      

   #ifdef DEBUG

   printk ("Read %d bytes, %d left\n", bytes_read, length);

   #endif

      

   /* 읽기 함수는 실제 버퍼에 기록된 개수를 리턴할 것이다.*/

    return bytes_read;

}



/* 이 함수는 장치 파일에 쓰려할 때 호출된다. */

#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)

#else

static 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


   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;

      

   /* 입력에 이용된 문자의 수를 리턴 */

   return i;

}


/* 이 함수는 프로세스가 장치 파일상의 ioctl작업을 수행할 때 호출된다. 두개의 여분의 인자(추가하여 inode와 피일 구조체, 모든 장치 함수들이 얻는)들을 얻게된다: 호출된 ioctl의 수와 ioctl에 의해 주어진 인자. ioctl이 쓰기 혹은 읽기/쓰기(출력은 호출 프로세스에서 리턴된다.)이면, ioctl 호출은 이 함수의 출력을 리턴한다. */

int device_ioctl(struct inode *inode, struct file *file,

                unsigned int ioctl_num,      /* ioctl의 번호 */

                unsigned long ioctl_param) /* ioctl의 인자 */

{

   int i;

   char *temp;

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   char ch;

   #endif


   /*ioctl 호출에 의해서 교체 */

   switch (ioctl_num)

   {

   case IOCTL_SET_MSG:

        /* 메시지(사용자 영역)의 포인터  수신, 그리고 장치의  메시지를 지정 */

        /* 프로세스에 의해 주어진 ioctl의 인자를 얻는다. */

        temp = (char *) ioctl_param;

              

           /* 메시지의 길이를 찾는다. */

           #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

              

           /* 바퀴를 다시 발명하지 말라 -  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:

           /* 호출된 프로세스에 현재 메시지를 준다  - 인자는 포인터이다.여기를 채워라. */

           #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

           i = device_read(file, (char *)ioctl_param, 99, 0);

           #else

           i = device_read(inode, file, (char *)ioctl_param, 99);

           #endif

           /* 주의 - 버퍼의 길이가 100임을 확인하라. 버퍼의 오버플로우가 생기면 프로세스는 코어 덤프를 야기한다.

           99개의 문자가 허락되기에 NULL 종료 문자를 위한 영역이 필요하다.*/

           /* 버퍼의 마지막에 0을 추가하면, 적절한 종료가 될 것이다. */

           put_user('\0', (char *) ioctl_param+i);

           break;

      case IOCTL_GET_NTH_BYTE:

           /* ioctl이 입력(ioctl_param), 출력(이 함수의 결과값) 둘 다의 기능을 가지므로 */

           return Message[ioctl_param];

           break;

    }

      

    return SUCCESS;

}


/* 모듈 선언 */

/* 이 구조체는 프로세스가 만든 장치에서 무엇인가 하려고 할 때 함수들을 유지하기 위해 호출된다. 이 구조체에서의 포인터는 장치 테이블에 유지되고, init_module에서 지역 변수로 사용되지 않는다. NULL은 아직 기능이 정의되지 않은 함수들은 위한 것이다 */

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  /* 종료 */

};



/* 모듈의 초기화 - 문자 장치의 등록 */

int init_module()

{

    int ret_val;

      

   /* 문자 장치의 등록 (적어도 시도는) */

   ret_val = module_register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);


   /* 음수의 값은 오류를 나타낸다. */

   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",

           "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;

}


/* 삭제 - /proc로부터 관계된 파일의 등록 삭제 */

void cleanup_module()

{

   int ret;


   /* 장치의 등록 삭제 */

   ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

      

   /* 오류이면, 표시한다. */

   if (ret < 0)

   printk("Error in module_unregister_chrdev: %d\n", ret);

}


<chardev.h>

/* chardev.h - ioctl의 정의를 위한 헤더 파일. 커널의 모듈(chardev.c)과 ioctl을 호출하는 프로세스(ioctl.c) 둘 다에 알려져야 하므로 선언은 헤더파일에 있어야 한다. */

#ifndef CHARDEV_H

#define CHARDEV_H

#include <linux/ioctl.h>


/* 주 장치 번호. ioctl이 이것을 알 필요가 있기 때문에, 동적인 등록은 더이상 신뢰할 수 없다. */

#define MAJOR_NUM 100


/* 장치 드라이버의 메시지 지정 */

#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)


/* _IOR은 사용자 프로세스에서 커널 모듈까지 정보를 전달하기 위한 ioctl 명령의 개수를 만든다는 것이다.

* 첫 번째 인자들은, MAJOR_NUM, 이용하는 주 장치 번호이다.

* 두 번째 인자는 명령의 개수이다.(여러 가지의 다른 의미를 함께 가진다)

* 세 번째 인자는 프로세스에서 커널로부터 얻기 원하는 데이터의 형태이다. */


/* 장치 드라이버의 메시지 얻기 */

#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)

/* 이 IOCTL은 장치 드라이버의 메시지를 얻기 위한 출력을 위해 이용된다. 그러나, 입력하기위해 이 메시지를 프로세스에 의해 할당된 버퍼에 여전히 유지할 필요가 있다. */


/* 메시지의 n번째 내용을 얻기 */

#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)

/* 이 IOCTL은 입/출력 둘 다에 이용된다. 사용자로부터 n번째의 하나의 수를 수신하고, Message[n]을 리턴한다. */


/* 장치 파일의 이름 */

#define DEVICE_FILE_NAME "char_dev"

#endif



<ioctl.c>

/* ioctl.c - 커널 모듈의 ioctl을 제어하기 위한 프로세스. 이제 입/출력을 위해 cat을 이용할 것이다. 그러나, 지금은 자신의 프로세스에 쓰기가 요구되어, ioctl이 필요하다. */


/* Copyright (C) 1998 by Ori Pomerantz */

/* 장치의 규격들, ioctl 번호와 주 장치 파일들 같은  */

#include "chardev.h"

#include <fcntl.h>       /* 열기 */

#include <unistd.h>      /* 종료 */

#include <sys/ioctl.h>   /* ioctl */


/* ioctl 호출을 위한 함수 */

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];

      

   /* 주의 - 커널에 어느 크기의 버퍼를 쓸지 말하지 않았기에 위험하다. 이것은 오버 플로우를 야기할 수도 있다.

   실제 응용 프로그램에서는, 두 개의 ioctl을 이용한다 - 하나는 커널에 버퍼의 크기를 알리고 다른 하나는 이 버퍼를

   이용한다. */

   ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

   if (ret_val < 0)

   {

   printf ("ioctl_get_msg failed:%d\n", ret_val);

   exit(-1);

   }

      

   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 - ioctl 함수를 호출 */

main()

{

   int file_desc, ret_val;

      

   char *msg = "Message passed by ioctl\n";

   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);

}



제 6 장


초기 변수들 

많은 이전의 예제들에서, 커널 모듈과 밀착된, /proc 파일을 위한 파일 이름 또는 장치를 위한 주 장치 번호등을 다루고 ioctl에 이를 적용했다. 이는 유닉스와 리눅스에서 유연한 프로그램을 만들기 위해 사용자가 최적화를 가능하게 하도록 하는 철학에 상충된다. 명령행 인자에 의해 작업을 하기 전에 프로그램 또는 커널에 알리는 방법이 필요하다. 커널 모듈의 경우에서는, argc와 argv의 인자를 얻지 못한다 ---대신에, 더 좋은 것을 얻는다. 커널 모듈 안에 전역변수를 정의하는 것이 가능하고, insmod는 이들 변수에 값을 지정할 것이다. 이 커널 모듈에서, 두 가지를 정의한다; str1와 str2. 필요로 하는 모든 일이 커널 모듈로 컴파일 되고, insmod str1=xxx와 str2=yyy를 실행한다. init_module이 호출되면, str1은 문자열 xxx로 str2는 문자열 yyy로 지정된다. 버전 2.0에서 이들 인자는 아무런 형검사도(*1) 받지 않는다. str1와 str2의 첫 번째 문자가 숫자이면 커널은 문자열의 포인터가 아닌 integer의 값을 변수에 대입할 것이다. 실제 적용되는 상황에서는 이를 검사해야 한다. 한편 버전 2.2에서 매크로 MACRO_PARM이 insmod에 인자들의 이름과 형태 등을 알리기 위해 이용된다. 예를 들면, 이것

은 자료형의 문제와 커널 모듈이 숫자와 함께 시작하는 문자열의 수신을 허가하는 문제를 해결할 수 있다.


각주1 **************************************************************************

C의 목적 코드가 자료형이 아닌 단지 전역 변수들의 위치만을 가지므로, 이는 불가능하다. 이것이 헤더 파일이 필요한 이유이다.

*******************************************************************************


<param.c>

/* 

* 모듈의 설치 시에 명령행 인자들을 수신 

*/


/* Copyright (C) 1998-9 by Ori Pomerantz */


/* 필요한 헤더 파일들 */

/* 커널 모듈에서의 표준 헤더들 */

#include <linux/kernel.h>     /* 커널 작업을 수행 */

#include <linux/module.h>    /* 특별히, 하나의 모듈 */


/* CONFIG_MODVERSIONS의 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


#include <stdio.h>           /* NULL이 필요하다 */


/* 2.2.3에서 /usr/include/linux/version.h에 이를 위한 매크로를 포함한다. 그러나, 2.0.35에서는 포함하지 않으며 필요한 경우에 이곳에 추가한다.*/

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif


/* Emmanuel Papirakis: 인자의 이름은 이제(2.2에서) 매크로로 다루어진다. 커널은 심벌 이름을 이것이 단 한번 사용되기에 매크로 확장하지 않는다. 모듈에 인자를 전달하기 위해, include/linux/modules.h(line 176)에 정의된 매크로를 이용해야 한다. 이 매크로는 두개의 인자를 취한다. 인자의 이름과 자료형. 이 자료형은 겹 따옴표(")이다. 예를 들면, "i"는 정수자료형이고 "s"는 문자열이다. */

char *str1, *str2;


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

MODULE_PARM(str1, "s");

MODULE_PARM(str2, "s");

#endif


/* 초기화 모듈 - 인자들을 보라 */

int init_module()

{

    if (str1 == NULL || str2 == NULL)

   {

        printk("Next time, do insmod param str1=<something>");

        printk("str2=<something>\n");

   }

   else

        printk("Strings:%s and %s\n", str1, str2);


   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   printk("If you try to insmod this module twice,");

   printk("(without rmmod'ing\n");

   printk("it first), you might get the wrong");

   printk("error message:\n");

   printk("'symbol for parameters str1 not found'.\n");

   #endif

      

   return 0;

}



/* 삭제 */

void cleanup_module()

{

}



제 7 장


시스템 호출 

자 이제. 겨우 /proc 파일과 장치 처리기의 등록 절차를 알게되었다. 당신이 커널 프로그래머로서 원하는 것을, 장치 드라이버의 작성 따위, 할 수 있다. 그러나, 무언가 비정상적인 경우에서는 어떻게 해야, 어떤 방법으로 시스템의 동작을 변경할 수 있을까?. 


이것이 커널 프로그래밍에서 위험에 처하는 부분이다. 아래의 예제에서, 나는 open 시스템 호출을 강제로 종료시켰다. 이는 어떤 파일도 열 수 없으며, 어떤 프로그램도 실행할 수 없다는 뜻이며, 컴퓨터를 셧다운하지도 못하게 된다. 결국은 전원 스위치를 내려야 한다. 아무 파일도 손상되지 않으면 운이 좋은 것이다. 어떤 파일도 잃지 않도록 하기 위해, insmod와 rmmod를 실행하기 전에 sync 명령을 실행하는 좋다. /proc와 장치 파일들은 일단 잊어라. 그들은 단지 사소한 것들이다. 커널 통신 절차에서의 실제 프로세스는, 모든 프로세스들에 의해 하나만 이용되는, 시스템 호출이다. 프로세스가 커널로부터 서비스를 요청 받으면(파일의 열기, 새로운 프로세스의 생성, 더 많은 메모리의 요구등), 이 때 이러한 절차가 이용된다. 작업 중에 커널의 동작을 변경하길 원하면, 이러한 일도 할 수 있다. 그리고, 프로그램에서 실행되는 프로세스가 어떤 것인지 알기 원하면, strace <command> <arguments>를 실행하라. 일반적으로, 프로세스는 커널에 접근하지 못하도록 되어있다. 커널 메모리의 접근은 불가능하며 커널 함수의 호출도 불가능하다. CPU의 하드웨어에서 강제로 이러한 일을 금지한다(이것이 '보호 모드'를 호출하는 이유이다). 시스템 호출은 이러한 일반적인 규칙에서 예외의 경우이다. 레지스터에 적절한 어떤 값을 채워 프로세스에 전달하거나, 특별한 명령이 커널내의 이미 정의된 위치에서 분기하도록 하는 일이 발생한다(물론, 그 위치는 사용자 프로세스에 의해 읽는 것이 가능하며, 그러나, 쓰는 것은 불가능하다). 인텔 CPU하에서 이것은 인터럽트 0x80에 의해 수행된다. 하드웨어는 이 위치에 단지 한번 분기하는 것을 알며, 사용자 모드에서 더이상의 실행은 제한된다. 그러나 시스템 커널에서는 다르다 --- 그래서 당신이 원하던 일을 할 수 있게 된다. 커널에서 프로세스의 위치로 분기하는 것은 시스템 호출로서 가능하다. 시스템 호출 번호에 의한 위치를 검사하고, 커널에 프로세스에 의해 요청된 서비스가 무엇인지 알린다. 이

때, 호출된 커널 함수의 주소를 알기 위해 시스템 호출 테이블(sys_call_table)을 알아본다. 이후 함수를 호출하고, 몇 가지 시스템 검사 후, 프로세스에 결과 값을 리턴한다(또는 다른 프로세스에, 프로세스 시간이 종료되면). 이 코드를 읽기 원한다면, 이 소스는 arch/architecture/kernel/entry.S의 ENTRY(시스템 호출)이후의 행을 읽어 보라.


시스템 호출의 작업을 변경하려면, 함수를 조금 수정하는 일이 필요하며(보통은 약간의 코드를 추가하고. 원래의 함수를 호출), sys_call_table에서 지정된 함수의 포인터를 변경한다. 나중에 지우거나 불안정한 상태에서 시스템을 종료하기 원하지 않으므로, cleanup_module에서 원래의 테이블 위치로 되돌리는 일이 매우 중요하다. 여기의 소스 코드가 이러한 커널 모듈의 예이다. 우리는 정상의 사용자를 관찰('spy')하길 원하고, 사용자가 파일을 열 때 printk 메시지를 나타나길 원한다. 결론을 말하면, 자신의 함수와 함께 파일을 여는 시스템 호출을 대치하는, our_sys_open이 호출된다. 이 함수는 현재 프로세스의 uid(사용자 id)를 검사하고, 관찰 대상의 uid와 일치하면, printk를 호출하여 열려진 파일의 이름을 표시한다. 그때, 어느쪽이든. 동일한 매개변수와 함께 원래의 open 함수를 호출하여 파일을 실제로 열 수 있다.  


init_module 함수는 sys_call_table 내의 적절한 위치로 대치되고 변수내의 원 포인터는 유지된다. cleanup_module 함수의 모든 변수는 평상시의 상태로 되돌려진다. 이러한 접근은 동일한 시스템 호출로 두개의 커널 모듈의 변경 가능성 때문에 위험하다. 두 개의 커널 모듈 A와 B를 가진다고 생각해보자. A는 A_open을 B는 B_open를 시스템 호출한다. 이제, A는 커널에 추가되었으며, 시스템 호출은 A_open를 대치하고, 호출이 완료될 때 원래의 sys_open이 호출될 것이다. 다음으로, B가 커널에 추가된다. 시스템 호출에 의해 B_open이 대치되고, 완료될 때, A_open, 원래의 시스템 호출이 무엇인지

를 생각할 것이다.


지금, B가 첫 번째로 제거되면, 모든 것이 정상이다 -- 이것은 단순히 원래의 호출인 A_open 시스템 호출로 재 적재될 것이다, 그러나, A가 제거되고, B가 제거되면, 시스템은 고장을 일으킨다. A의 제거는 원래의 시스템 호출 재 적재하고, sys_open, 루프의 밖으로 B를 밀어낸다. 이때, B가 제거되었으면, 원래의 호출이라고 생각한 것을, 더이상 메모리에 존재하지 않는, A_open, 시스템 호출하여 재 적재한다. 우선, 이 특별한 문제를 해결하고, 더이상 악화시키지 않기 위해 시스템 호출이 open 함수와 동일하면 그리고 전혀 변경되지 않았(그래서 B가 제거되었을 때 시스템호출을 변경하지 않았다)는지 검사를 해야할 것이다. A가 제거되었을 때, 이것은 시스템 호출이 B_open으로 변경되었음을 그래서 더이상 A_open을 가리키지 않음을 보여주며, 이것이 메모리로부터 제거되기 전에는 sys_open에 의해 재 적재되지 않는다. 불행히도, B_open은 여전히 더이상 아무 것도 없는 곳으로 A_open을 호출하려고 시도하며, 따라서 시스템은 B의 제거 없이도 고장을 일으키게 된다. 


이러한 문제를 막는 방법은 두 가지 방법을 생각해 볼 수 있다. 첫 번째는, sys_open에 의해 호출된 원래의 값을 재 적재하는 것이다. 불행히도, sys_open은 /proc/ksyms내의 커널 시스템의 부분이 아니며, 이것에 접근할 수 없다. 다른 하나의 방법은 루트가 한번 적재된 모듈을 제거하는 것을 막기 위해 참조 카운터를 이용하는 것이다. 이것은 실제의 모듈에서는 적합하나, 여기서의 예로는 적합하지 않다 --- 이것이 여기서 이러한 일을 하지 않는 이유이다.


<syscall.c>

/* syscall.c

* 시스템 호출 "훔쳐온" 예제 

*/


/* Copyright (C) 1998 -  by Ori Pomerantz */

/* 필요한 헤더 파일들 */

/* 커널 모듈에서의 표준 헤더 */

#include <linux/kernel.h>     /* 커널 작업을 한다. */

#include <linux/module.h>    /* 특별히, 모듈 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


#include <sys/syscall.h>     /* 시스템 호출의 목록 */


/* 현재(프로세스)의 구조체를 위해, 현재 사용자가 누구인지 알 필요가 있다  */

#include <linux/sched.h>


/* 2.2.3에서 /usr/include/linux/version.h에 이를 위한 매크로를 포함한다. 그러나, 2.0.35에서는 포함하지 않으며 필요한 경우에 이곳에 추가한다.*/

#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


/* 시스템 호출 테이블(함수들의 테이블), 이것은 외부실행으로 정의하며 커널은 insmod를 할 때 이것을 채울 것이다. */

extern void *sys_call_table[];


/* 알아보기 원하는 UID  - 명령 행으로부터 지정된다. */

int uid;


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

MODULE_PARM(uid, "i");

#endif


/* 원 시스템 호출에서 지정된 포인터. 호출된 원래의 함수(sys_open)보다 이것을 유지해야하는 이유는 누군가 먼저 시스템 호출을 대치할지도 모르기 때문이다. 이 모듈에 함수를 삽입하기 이전에 sys_open으로 다른 모듈을 대치할 수 있기 때문에, 이것이 100% 안전하지 않다는 것에 주의하라 - 그리고, 이전에 제거될 수도 있다. 또 다른 이유는, sys_open을 얻기 못하기 때문이다. 이것은 정적 변수이며, 따라서 외부에서 참조되지 않는다 */

asmlinkage int (*original_call)(const char *, int, int);


/* 몇 가지의 이유로, 2.2.3에서 current->uid는 실제 사용자의 ID가 아닌, 0으로 주어진다. 이것으로 인해 잠시 무엇이 잘못된 것인지 찾기 위해 노력한 적이 있다 - 그래서 uid를 얻기 위해 단지 시스템 호출을 이용할 수 있으며, 이것은 프로세스가 하는 방법이란 것을 알게 되었다. 또 다른 몇 가지 이유로, 후에 이러한 문제점을 없애기 위해 커널을 재컴파일 하였다. */

asmlinkage int (*getuid_call)();


/* 이 함수는 sys_open 함께 대치될 것이다(함수는 시스템 호출을 호출하였을 때 호출되어진다). 정확한 함수 원형을 찾기 위해, 인자의 개수와 자료형, fs/open의 처음을 살펴 보라. 이론적으로는, 커널의 현재 버전에 묶여 있다는 뜻이다. 실제적으로, 시스템 호출은 대부분 거의 변경되지 않는다(재컴파일 할 것이 요구된다. 시스템 호출이 커널과 프로세스사이의 통신을 담당하므로) */

asmlinkage int our_sys_open(const char *filename, int flags, int mode)

{

    int i = 0;

   char ch;

      

   /* 사용자가 누구인지 알기 원하면, 검사한다.*/

   if (uid == getuid_call())

   {

   /* getuid_call은 getuid 시스템 호출이며, 호출한 시스템 호출을 사용하는 사용자의 uid를 준다. */

       /* 적절하다면, 파일을 표시한다. */

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

    }


   /* 원래의 sys_open을 호출 - 반면에, 파일을 열 수 있는 능력을 잃게 된다.*/

   return original_call(filename, flags, mode);

}


/* 모듈의 초기화 - 시스템 호출의 대치 */

int init_module()

{

    /* 주의 - 실행이 지금보다 늦어진다. 그러나 다음을 위해서... */

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


   /* original_call내의 원 함수의 포인터를 유지하고, our_sys_open과 함께 시스

      템 호출 테이블의 시스템 호출로 대치한다. */

   original_call = sys_call_table[__NR_open];

   sys_call_table[__NR_open] = our_sys_open;

      

   /* 시스템 호출 foo를 위한 함수의 주소를 얻기 위해, sys_call_table[__NR_foo]로 간다. */

   printk("Spying on UID:%d\n", uid);

      

   /* getuid를 위한 시스템 호출 얻기 */

   getuid_call = sys_call_table[__NR_getuid];

   return 0;

}


/* 삭제 -  /proc에 사용된 파일을 제거 */

void cleanup_module()

{

    /* 시스템 콜백을 보통의 상태로 되돌린다. */

   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;

}



제 8 장


프로세스의 실행 금지(Blocking Processes)

누군가 올바르지 못한 일을 당신에게 요구하면 어떻게 행동하겠는가? 다른 사람이 귀찮게 한다면, 그저 '지금은 안돼, 바쁘단 말이야. 저리가라구!' 라고 말하면 된다. 그러나, 커널 모듈에서는 다른 프로세스가 귀찮게(?)하면, 다른 방식으로 말해야 한다. 서비스 가능할 때까지 대기 상태로 프로세스를 만드는 것이 가능하다. 결국, 프로세스는 커널에 의해 대기되고, 모든 시간에(다중 프로세스가 하나의 CPU상에서 동시에 실행되는 방법이다) 깨어날 것이다.  


이 커널 모듈이 이것의 예이다. 파일(/proc/sleep로 호출된)은 동시에 하나의 프로세스에서만 열린다. 파일이 이미 열려있으면, 커널 모듈은 module_interruptible_sleep_on을(*1) 호출한다. 이 함수는 태스크(태스크는 프로세스와 시스템 호출에 대한 정보를 가지고 있는 커널의 데이터 구조체이다)에 대한 상태 변수 TASK_INTERRUPTIBLE을 변경하고, 어떤 방식으로든 깨어날 때까지 실행되지 않도록 WaitQ에 추가하고, 태스크들은 큐에서 파일로의 접근을 기다린다. 


각주1 ******************************************

열려진 파일을 유지하는 가장 쉬운 방법은 tail -f로 파일을 여는 것이다.

***********************************************


프로세스가 파일과 함께 종료되면, 태스크도 종료되며, 그리고 module_close가 호출된다. 이 함수는 큐안의 모든 프로세스들을 깨우게 된다(이들을 깨우기 위한 단 하나의 절차). 이로서 프로세스는 방금 종료된 파일의 실행을 계속할 수 있다. 제 시간에, 스케쥴러는 이 프로세들에 충분한 시간을 결정하고 다른 프로세스에 CPU의 제어권을 준다. 결국, 큐안의 프로세스들 중 하나에 스케쥴러가 CPU의 제어권을 넘겨준다. 이것은 modulue_interruptible_sleep_on(*2)에서 호출된 직후에 시작된다. 모든 다른 프로세들에 위의 전역 변수의 값을 지정하여 자신이 실행중임을 알리며, 파일은 여전히 열려있고 자신의 삶을 살아간다. 다른 프로세스들은 CPU 실행시간의 일부를 가질 때, 전역 변수를 살펴보고 다시 대기상태로 돌아간다. 


각주2 *************************************************************************

프로세스는 여전히 커널 모드 안에 있다 --- 프로세스에 관한 한, open 시스템 호출은 수행되었고 시스템 호출은 아직 리턴되지 않았다. 프로세스는 호출된 사건(open)과 리턴된 사건 사이에서 누가 CPU 시간의 대부분을 사용하는지 알지 못한다. 

*******************************************************************************


삶을 좀더 활기차게 하기 위해, module_close는 파일에 접근하려는 프로세스들의 깨움을 독점하지 않는다. 적절한 신호가, Ctrl-C(SIGINT)와 같은, 프로세스를 깨운다(*3). 이런 경우에, -EINTR이 바로 리턴 되길 원한다. 이것은 사용자들에게 중요하며, 예를 들면, 파일을 받기 전에 프로세스를 강제로 종료하는 경우에서. 중요하게 기억해야 할 것이 하나 더 있다. 때때로 프로세스들은 대기상태를 원하지 않는다. 이런 프로세스들은 파일을 열 때 O_NONBLOCK 플래그를 이용한다. 커널은 다른 영역에 대한 동작에 대해, 아래의 예제의 파일을 여는 것 같은, -EAGAIN 오류 코드를 리턴할 것이다. cat_noblock 프로그램이, 이장의 소스 디렉토리에 있는, O_NONBLOCK로 파일을 열 수 있게 할 것이다.  


각주3 **************************************************************************

이것 때문에 module_interruptible_sleep_on를 이용한다. 대신에, module_sleep_on을 이용할 수도 있지만, Ctrl-C를 무시하는 매우 화난 사용자에게는 소용이 없다.

*******************************************************************************


<sleep.c>

/* sleep.c /proc에 만든다, 만약 여러 프로세스가 동시에 파일을 열려하면, 하나를 제외한 모두를 대기 상태로 만든다. */

/* Copyright (C) 1998-9 by Ori Pomerantz */


/* 필요한 헤더 파일들 */

/* 커널 모듈에서의 표준 헤더 */

#include <linux/kernel.h>     /* 커널 작업을 한다. */

#include <linux/module.h>    /* 특별히, 모듈 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* proc fs을 이용하기에 필요 */

#include <linux/proc_fs.h>


/* 프로세스를 대기 상태로 만들기 위해, 그리고 나중에 깨우기 위해 */

#include <linux/sched.h>

#include <linux/wrapper.h>


/* 2.2.3에서 /usr/include/linux/version.h에 이를 위한 매크로를 포함한다. 그러나, 2.0.35에서는 포함하지 않으며 필요한 경우에 이곳에 추가한다.*/

#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>     /* get_user와 put_user를 위해 */

#endif


/* 모듈의 함수들 */

/* 입력 프로세스가 가능함을 증명하기 위해, 마지막 메시지를 유지한다.  */

#define MESSAGE_LENGTH 80


static char Message[MESSAGE_LENGTH];



/* 파일에 관한 구조체를 이용하므로, 특별한 proc출력을 이용할 수 없다 - 이 함수와 같은 표준 읽기 함수를 이용해야만 한다. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_output(

    struct file *file,   /* 파일 읽기 */

   char *buf,           /* 데이터를 저장할 버퍼(사용자 세그먼트에서) */

   size_t len,           /* 버퍼의 길이 */

   loff_t *offset)      /* 파일의 상대 위치 - 무시 */

#else

static int module_output(

    struct inode *inode, /* inode 읽기 */

   struct file *file,        /* 파일 읽기 */

   char *buf,               /* 데이터를 저장할 버퍼(사용자 세그먼트에서) */

   int len)                   /* 버퍼의 길이 */

#endif

{

   static int finished = 0;

   int i;

   char message[MESSAGE_LENGTH+30];

      

   /* 파일의 끝이면 0을 리턴한다 - 이 위치에서 더 이상 가질 것이 없다. */

   if (finished)

   {

       finished = 0;

       return 0;

   }


   /* 이것을 이해할 수 없다면, 커널 프로그래머의 희망을 버려야 될지도 */

   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; /* "read"에 의한 데이터의 개수를 리턴 */

}


/* /proc 파일에 사용자가 쓰려하면, 이 함수는 사용자로부터의 입력을 수신한다.*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_input(

    struct file *file,    /* 파일 자신 */

   const char *buf,    /* 입력 버퍼 */

   size_t length,        /* 버퍼의 크기 */

   loff_t *offset)       /* 파일의 상대 위치 - 무시*/

#else

   static int module_input(

   struct inode *inode, /* 파일의 inode */

   struct file *file,       /* 파일 자신 */

   const char *buf,     /* 입력 버퍼 */

   int length)              /* 버퍼의 크기 */

#endif

{

    int i;


   /* module_output이 나중에 사용 가능하게, 메시지 안에 입력을 넣는다. */

   for(i=0; i<MESSAGE_LENGTH-- && 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


   /* 우리는 하나의 표준을 원한다. 0은 문자열 종료문자 */

   Message[i] = '\0';


   /* 사용된 입력 문자의 개수가 필요하다. */

   return i;

}


/*  파일이 누군가에 의해 현재 열려있으면 1 */

int Already_Open = 0;


/* 파일을 사용하길 원하는 프로세스들의 큐 */

static struct wait_queue *WaitQ = NULL;


/* /proc 파일이 열릴 때 호출  */

static int module_open(struct inode *inode, struct file *file)

{

   /* 파일의 플래그가 O_NONBLOCK을 포함하면, 프로세스가 파일의 접근을 기다리길 원하지 않는다는 의미이다.

   이런 경우, 파일이 이미 열려 있으면, -EAGAIN으로 실패를 표시하며, "다시 시도해야 한다"는 의미의, 대신에

   대기중인 다른 프로세스들의 실행은 금지한다. */

   if ((file->f_flags & O_NONBLOCK) && Already_Open)

   return -EAGAIN;


   /* 커널의 모듈 안에서 프로세스가 루프  안이라면, MOD_INC_USE_COUNT을 위한 올바

      른 위치이며, 커널은 제거되지 않을 것이다. */

   MOD_INC_USE_COUNT;


   /* 파일이 이미 열려 있으면, 닫힐 때까지 기다린다. */

   while (Already_Open)

   {

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

       int i, is_sig=0;

       #endif

              

       /* 이 함수는 현재 프로세스를 대기하기 위해, 시스템 호출을 포함하여, 여기에서처럼, 추가된다. 이 함수의 호출 후에

       실행이 재개될 것이고, 누군가에의해 wake_up(&WaitQ)또는 Ctrl-C와 같은 시그널이 수신될 때, 프로세스에

       보내진다. */

       module_interruptible_sleep_on(&WaitQ);


       /* 깨어 있으면, 이미 시그널을 수신하여 실행금지 되지 않았으므로, -EINTR을 리턴한다(시스템 호출의 실패).

       이것은 프로세스들에 종료 또는 정지를 허가한다. */

              

       /* Emmanuel Papirakis: 2.2.*에서 약간의 수정이 있었다. 시그널은 두개의 워드(64비트)를 포함하고 두개의

       unsigned long의 배열을 포함하는 구조체안에 저장된다. 2번의 검사를 여기에서 수행해야 한다.

       Ori Pomerantz: 누구도 64비트 보다 큰 값을 사용하지 않을 것이고, 또한, 이문서는 16비트 크기의 워드와

       함께 하는 리눅스 버전은 사용하지 않을 것이다. 이 코드는 어디에서나 적용 가능하다. */

       #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

           /* 프로세스들이 인터럽트로서 실행되고 close와 연결되는 통신할 수 없으므로 여기에서

           MOD_DEC_USE_COUNT를 추가하는 것이 중요하다. 여기에서 사용 카운터를 감소하지 않으면, 양수의 값으로

           남겨지게되고 0으로 감소할 어떤 방법도 없게되어, 영구적인 모듈로서 남겨지게 되며, 리부팅에 의해서만

           종료할 수 있다. */

           MOD_DEC_USE_COUNT;

                      

           return -EINTR;

       }

    }


   /* 여기에서 얻었다면, Already_Open 0일 것이다. */

   /* 파일 열기 */

   Already_Open = 1;


   return 0; /* 접근 허가 */

}


/* /proc 파일이 종료되었을 때 호출되어진다. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

int module_close(struct inode *inode, struct file *file)

#else

void module_close(struct inode *inode, struct file *file)

#endif

{

    /* Already_Open을 0으로 지정, WaitQ안의 프로세스들 중 하나는 Already_Open을 1로 할 것이고 파일을 열게 된다.

    Already_Open이 1로 될 때 모든 다른 프로세스들은 대기상태로 된다. */

   Already_Open = 0;


   /* WaitQ안의 모든 프로세스를 깨우게 되며, 만약 누군가 파일을 기다리고 있었다면, 파일을 열 수 있게된다. */   

   module_wake_up(&WaitQ);

   MOD_DEC_USE_COUNT;


   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   return 0; /* 성공 */

   #endif

}


/* 이 함수는 동작의 허가(0을 리턴), 불허(왜 허가하지 않는데 이유를 나타내는 양수의 값을 리턴)를 결정한다.

아래의 값들 중 하나로서 동작한다:

0 - 실행(파일의 실행 - 여기서는 무의미하다)

2 - 쓰기(커널 모듈에서의 입력)

4 - 읽기(커널모듈로부터의 출력)


이것은 파일의 퍼미션을 검사하는 실제 함수이다. ls -l에 의해 리턴 되어지는 퍼미션들은 단지 참조될 뿐이며, 이곳에서 중첩되는 것이 가능하다. */

static int module_permission(struct inode *inode, int op)

{

    /* 모두에게 모듈에서의 읽기를 허가하고, 루트(uid 0)에게만 쓰기를 허가한다.*/

   if (op == 4 || (op == 2 && current->euid == 0)) return 0;


   /* 어떤 것도 아니라면, 접근은 불허된다. */

   return -EACCES;

}


/* /proc에 등록하기 위한 구조체, 모든 관계된 함수들의 포인터와 함께 */ 

/* proc 파일을 위한 연산들. 파일에 무슨 일을 수행하려 할 때 호출되는 모든 함수들의 포인터가 위치한다. NULL은 아무 것도 수행하지 않는다는 의미이다.*/

static struct file_operations File_Ops_4_Our_Proc_File =

{

    NULL,           /* lseek */

   module_output,  /* 파일로 부터의 "읽기" */

   module_input,   /* 파일에 "쓰기" */

   NULL,           /* readdir */

   NULL,           /* select */

   NULL,           /* ioctl */

   NULL,           /* mmap */

   module_open,    /* /proc 파일이 열려지면 호출 */

   #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

   NULL,           /* flush */

   #endif

   module_close    /* 파일이 닫힐 때 호출 */

};


/* proc 파일을 위한 inode 연산들. 이용하기 원하는 곳에 이 파일 연산 구조체를 위치시키고, 이 함수는 퍼미션을 위해 이용한다. 이것은 또한 inode의 작업 말고도 다른 일에 함수들을 지정할 수 있다.(이런 일은 그다지 까다롭지 않다, 필요치 않는 경우 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) */

   NULL,                /* 디렉토리 만들기(mkdir) */

   NULL,                /* 디렉토리 지우기(rmdir) */

   NULL,                /* 노드 생성(mknod) */

   NULL,                /* 이름 변경(rename) */

   NULL,                /* readlink */

   NULL,                /* follow_link */

   NULL,                /* 페이지 읽기(readpage) */

   NULL,                /* 페이지 쓰기(writepage) */

   NULL,                /* bmap */

   NULL,                /* 절단(truncate) */

   module_permission /* 접근 허가를 위한 검사 */

};


/* 디렉토리 등록 */

static struct proc_dir_entry Our_Proc_File =

{

    0,        /* Inode 번호 - 무시, proc_register[_dynamic]에 의해 지정될 것이다. */

   5,    /* 파일명의 길이 */

   "sleep",/* 파일 이름 */

   S_IFREG | S_IRUGO | S_IWUSR, /* 파일 모드 - 소유자, 그룹, 그리고 모두에 읽기가 가능한 정규 파일. 실제로,

                                               여기서 이 필드는 단지 참조될 뿐이며, module_permission에서 실제로 이를

                                               이용한다. 이것이 이 필드를 이용하기는 하지만, 여기서는 필요치 않다. */ 

   1,         /* 링크의 개수(파일이 참조되는 곳의 디렉토리들) */

   0, 0,      /* 파일의 uid와 gid - 루트에 의해 주어진다. */

   80,        /* ls에 의해 나타나는 파일의 크기 */

   &Inode_Ops_4_Our_Proc_File, /* 필요하다면, 파일을 위한 inode 구조체의 포인터 쓰기 함수가 필요하므로

                                            여기서는 필요하다.*/

   NULL  /* 파일을 위한 읽기 함수. 불필요, 위의 inode 구조체에서 사용하기에 */

};


/* 모듈의 초기화와 삭제 */

/* 모듈의 초기화 - proc에 등록 */

int init_module()

{

    /* proc_register_dynamic이 success이면 성공, 아니면 실패 */

      

   #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은 proc fs(/proc)를 위한 루트 디렉토리이다. 원하는 곳에 파일을 위치시킨다. */

}


/* 삭제 - /proc로부터 파일을 등록 제거. 프로세스가 여전히 WaitQ안에서 대기중이라면 open 함수의 내부에서 미 적재된 것을 얻을 수도 있기에 위험해진다. 제 10 장에서 이런 경우의 처리 방법을 설명할 것이다. */

void cleanup_module()

{

    proc_unregister(&proc_root, Our_Proc_File.low_ino);

}



제 9 장 


printk의 대치 

문서의 처음에서(제 1 장), X와 커널 모듈 프로그래밍을 혼합하여 작성하지 말라고 했다. 커널 모듈을 작성하는 동안 이것은 확실히 지켜져야 하지만, 실제로는 모듈로 부터의 메세지를 보내도록 tty(*1) 명령을 어느 곳에서든 사용하길 원할 것이다. 이것은 커널 모듈이 해제된 후에 오류를 인식하기 위해 중요하며, 이것은 그들의 모두에서 이용될 수 있다. 여기의 방법이 현재 실행중인 tty의 포인터, 현재 태스크의 tty 구조체를 얻기 위해 현재 이용되는 것이다. 이제, tty에서 문자열을 쓰기 위해 이용하는 문자열 쓰기 함수의 포인터를 찾기 위해 tty 구조체의 안을 들여다보게 된다. 


각주1 *************************************************************************

Teletype, 원래는 유닉스 시스템의 통신에 이용되던 키보드 --- 프린터의 조합기계, 오늘날에는 유닉스 프로그램에서 이용되는 텍스트 스트림의 추상적 의미, 물리적 터미널, X 화면 장치상의 xterm, 네트웍 상에 연결된 telnet, 기타 등등.

*******************************************************************************


<printk.c>

/* printk.c -  실행중인 tty에, X를 통해서든, telnet, 또는 다른 것, 텍스트를 출력하기. */ 

/* Copyright (C) 1998 by Ori Pomerantz */


/* 필요한 헤더 파일들 */

/* 커널 모듈에서의 표준 헤더 */

#include <linux/kernel.h>    /* 커널에 대한 작업을 한다 */

#include <linux/module.h>   /* 특별히, 모듈에서 */


/* CONFIG_MODVERSIONS와 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* 여기에서 필요한 헤더 */

#include <linux/sched.h>     /* For current */

#include <linux/tty.h>         /* tty 선언들을 위해 */


/* 현재 태스크에서 이용되는 적절한 tty에 문자열을 표시한다. */

void print_string(char *str)

{

    struct tty_struct *my_tty;

      

   /* 현재 태스크를 위한 tty */

   my_tty = current->tty;


   /* my_tty이 NULL이면, 프린트할 tty가 없다는 의미(예를 들어, 만약 데몬이라면). 이 경우에는, 아무 것도 할 수

   없다.  */

   if (my_tty != NULL)

   {

    /* my_tty->driver가 tty의  함수들의 구조체이며, 이들 중 하나(write)가 tty에 문자열을 쓰는데 이용된다.

    사용자 메모리 세그먼트, 커널 메모리 세그먼트, 모두에서 문자열을 가져올 수 있다.      

    이 함수의 첫 번째 인자는, 보통 동일한 함수가 확실한 형태의 모든 tty를 위해서 이용되기 때문에, 쓰려는 tty이다.

    두 번째 인자는 함수가 커널 메모리(0) 또는 사용자 메모리(0이 아닌 값)에서 문자열을 수신할 것인지를 정한다.

    세 번째 인자는 문자열의 포인터이며, 네 번째 인자는 문자열의 길이이다. */

              

       (*(my_tty->driver).write)(

           my_tty,         /* tty 자신 */

           0,                 /* 사용자 영역에서 문자열을 가져오지 않는다. */

           str,               /* 문자열 */

           strlen(str));    /* 길이 */

   /* tty는 원래 ASCII 표준을 준수(통상적으로)하는 하드웨어 장치이다. ASCII에 의하면, 새로운 줄로 이동하기 위해

   두 문자가, CR & LF, 필요하다. 반면에, 유닉스에서는 ASCII의 LF가 두 가지 목적에 이용된다 -  CR을 이용할 수 없기에

   \n을 이용 할 수 없고 LF의 후에 다음 줄이 칼럼의 오른쪽에 위치하게 된다. 이것이 유닉스와 윈도우에서 텍스트 파일이

   다른 이유이다. CP/M에서 이것이 파생되었으며, MS-DOS와 윈도우처럼, ASCII 표준은 엄격하게 준수되기에, 새로운

   줄은 LF와 CR모두가 요구된다. */

       (*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);

   }

}


/* 모듈의 초기화와 삭제 */

/* 모듈의 초기화 - /proc에 파일 등록 */

int init_module()

{

   print_string("Module Inserted");

   return 0;

}


/* 삭제 - /proc에서 파일 제거 */

void cleanup_module()

{

   print_string("Module Removed");

}



제 10 장 


태스크 스케줄링 

매우 자주, housekeeping:'문제 해결에 직접 관여하지 않는 운영 루틴' 태스크를 지정한 시간에 수행하도록 해야 한다. 태스크가 프로세스에 의해 완료하게 하려면, 이것을 crontab 파일에 넣어 수행한다. 태스크가 커널 모듈에 의해 완료하게 하려면, 두 가지의 가능성을 가진다. 첫 번째는 필요할 때 시스템 콜에 의해 모듈이 깨어나도록 crontab 파일에 프로세스를 넣는 것이고, 예를 들면 파일 열기에 의한.  이것은 아주 비효율적이다, 그러나 --- crontab의 새로운 프로세스를 제거하거나,  메모리에서 새로운 실행 가능한 프로세스를 읽거나, 메모리의 어디에서든 커널 모듈을 깨우고자 하는 경우에는 유용하다.

이러한 일 대신에, 타이머 인터럽트의 호출에 의해 함수를 만들 수 있다. 이것이 태스크를 만든 방법이며, 구조체 tq_struct에 의해 만들어지고,  여기에 함수의 포인터를 지정하게 된다. 이때, tq_timer에 의해 호출되도록 태스크 목록상의 태스크를 queue_task에 넣게되며, 이 목록의 태스크는 다음 타이머 인터럽트에서 실행된다. 함수가 계속 실행되도록 유지해야 하기 때문에, 다음 타이머 인터럽트에서 실행되도록 호출될 때마다 tq_timer상에 이것을 다시 넣는 일이 필요하다. 여기서 기억해야 할 중요한 점이 하나 있다. 모듈이 rmmod에 의해 제거될 때, 첫 번째로 참조 카운터를 검사한다. 이것이 0이면, module_cleanup이 정상적으로 호출된 것이다. 이때, 모듈은 메모리에서 관계된 모든 함수를 제거한다.  만약  타이머의 태스크 목록 상에 더 이상 유용하지 않은 이들 함수들 중 하나의 포인터를 포함하게되면, 누구도 이를 알지 못한다. 나중에 나이가 든 후(컴퓨터의 관점에서, 인간의 관점이 아닌, 1/100초 보다 적을 것이다), 커널은 타이머를 가지고 태스크 목록상의 태스크를 호출하려 할 것이다. 불행히도, 이 함수는 더 이상 존재하지 않는다. 대부분의 경우, 이것은 사용되지 않게 된 메모리 페이지이며, 오류 메시지를 보게 될 것이다. 그러나, 만약 다른 코드가 동일한 메모리의 위치에 존재하면, 매우 잘못된 결과를 얻게된다. 불행히도, 태스크의 목록으로부터 태스크를 제거할 쉬운 방법을 갖고 있지 못하다. cleanup_module이 에러 코드와 함께 리턴되지 않으므로(void 함수이다), 이의 해결책은 모든 것이 리턴되지 않게 하는 것이다. 대신에, 이것은 대기 프로세스에 rmmod를 넣기 위해 sleep_on또는 module_sleep_on(*1)을 호출한다. 그전에, 이것은 전역 변수를 지정하여 함수(rmmod)가 타이머 인터럽트로 호출된 후 제거되도록 한다. 다음 타이머 인터럽트에서 rmmod 프로세스는 깨어나고, 큐안에 더 이상의 함수가 없도록 제거하고 모듈에서 안전하게 제거된다.


각주1 ************

실제로 똑같은 함수이다.

*****************


<sched.c>

/* sched.c - 타이머 인터럽트 상에서 호출되어지는 함수들의 스케줄링 */

/* Copyright(C) 1998 by Ori Pomerantz */

/* 필요한 헤더 파일들 */

/* 커널 모듈에서의 표준 헤더 */

#include <linux/kernel.h>     /* 커널 작업을 수행한다 */

#include <linux/module.h>    /* 특별히, 모듈에서 */


/* CONFIG_MODVERSIONS 다루기*/

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif


/* proc fs에 이용되기에 */

#include <linux/proc_fs.h>


/* 스케줄 가능한 태스크를 여기에 */

#include <linux/tqueue.h>


/* 대기 상태와 나중에 깨우도록 하기 위해 필요하다 */

#include <linux/sched.h>


/* 2.2.3에서 /usr/include/linux/version.h에 이를 위한 매크로를 포함한다. 그러나, 2.0.35에서는 포함하지 않으며 필요한 경우에 이곳에 추가한다.*/

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif


/* 호출된 타이머 인터럽트의 횟수 */

static int TimerIntrpt = 0;


/* cleanup에 의해 이용된다. intrpt_routine이 task_queue에 남아있는 동안 모듈에서 제거되는 것을 막기위해 */

static struct wait_queue *WaitQ = NULL;

static void intrpt_routine(void *);


/* 이 태스크를 위한 task_queue 구조체, tqueue.h로 부터 */

static struct tq_struct Task = {

   NULL,            /* 목록의 다음 항목 - queue_task가 이 일을 한다. */

   0,                 /* task_queue에 아직 아무 것도 추가되지 않았다. */

   intrpt_routine, /* 실행 함수 */

   NULL              /* 함수를 위한 void* 인지 */

};


/* 이 함수는 타이머 인터럽트에서 호출된다. void* 포인터에 유의 - 태스크 함수들은 여러 목적에 이용되며, 매번 다른 인자를 가진다. */

static void intrpt_routine(void *irrelevant)

{

   /* 카운터를 증가 */

   TimerIntrpt++;


   /* 제거되길 원하면 */

   if (WaitQ != NULL)

   wake_up(&WaitQ); /* 이제 cleanup_module은 리턴 가능하다. */

   else

   queue_task(&Task, &tq_timer); /* task_queue안에 다시 집어넣는다. */

}


/* proc fs 파일에 데이터 쓰기 */

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;


   /* 모든 정보를 한번에 주게되며, 더 이상의 정보가 없느냐고 물으면 대답은 항상 '없다'이다. */

   if (offset > 0) return 0;


   /*버퍼를 채우고 그 크기를 얻는다. */

   len = sprintf(my_buffer, "Timer was called %d times so far\n", TimerIntrpt);

   count++;


   /* 함수에 버퍼를 어디에서 호출했는지 알려준다. */

   *buffer_location = my_buffer;

      

   /* 크기를 리턴 */

   return len;

}


struct proc_dir_entry Our_Proc_File =

{

   0,                         /* Inode 번호  - 무시, proc_register_dynamic에 의해 수정된다. */

   5,                         /* 파일 이름의 길이 */

   "sched",                 /* 파일 이름 */

   S_IFREG | S_IRUGO, /* 파일 모드 - 소유자, 그룹, 모두가  읽을 수 있는 정규 파일 */

   1,                         /* 링크의 개수(파일이 참조되는 곳의 디렉토리들) */

   0, 0,                      /* 파일의 uid와 gid - 루트에 의해 주어진다. */

   80,                        /* ls에 의해 표시되는 파일의 크기 */

   NULL,                     /* inode을 다루는 함수(linking, removing, etc.) 어떤 것도 지원하지 않는다. */

   procfile_read,          /* 이 파일을 위한 읽기 함수, 누군가 이것을 읽을려 할 때 호출된다. */

   NULL                      /* 퍼미션, 소유자등, 파일의 inode를 바꾸기 위한 함수가 위치한다. */

};


/* 모듈 초기화  - /proc 파일에 등록 */

int init_module()

{

   /* tq_timer 태스크 큐안에 태스크를 넣는다. 다음 타이머 인터럽트에서 실행될 것이다. */

   queue_task(&Task, &tq_timer);


   /* proc_register_dynamic이 성공이면 성공, 아니면 실패 */

   #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

}


/* 제거 */

void cleanup_module()

{

   /* /proc에서 제거하기 */

   proc_unregister(&proc_root, Our_Proc_File.low_ino);


   /* intrpt_routine이 마지막에 호출될 때까지 대기. intrpt_routine에 할당된 메모리를 해제하고 태스크가 작업들을

   참조하는 tq_timer동안 대기하기 위해 필요하다. 여기에서 시그널이 허가되지 않음에 주의하라. WaitQ가 NULL이

   아니므로, 자동적으로 인터럽트 루틴에 종료될 시간을 알린다. */

   sleep_on(&WaitQ);

}



제 11 장


인터럽트 처리기

마지막장은 제외하고, 커널내의 프로세스의 요청에 대한 응답 처리의 거의 전부를 다루었다, 특별한 파일의 다루기, ioctl에 보내기, 시스템 호출 등. 그러나, 커널의 작업은 프로세스의 요청에 대한 응답만이 전부는 아니다. 다른 작업은, 이것은 매우 중요하다, 컴퓨터에서의 하드웨어 장치를 연결하는 것이다. CPU와 컴퓨터의 나머지 하드웨어사이의 상호 작용에는 두 가지 형태가 있다. 그 첫 번째 형태는 CPU가 하드웨어에 명령을 줄 때와,하드웨어가 CPU에 무언가 말할 필요가 있을 때이다. 두 번째 인터럽트 호출은, CPU가 아닌 하드웨어에 대해 편하게 취급되어야 하기에 하드웨어에 밀접하게 연결된다. 전형적으로 하드웨어 장치는 매우 적은 양의 메모리만을 가지며, 이 내용을 유용한 시점에서 읽지 않으면 잃어버리게 된다. 리눅스에서, 하드웨어 인터럽트는 IRQ(Iinterrupt Request의 약어)(*1)라 불리어진다. 두 가지 형태의, 짧은(short) 그리고 긴(long), 인터럽트가 있다.  짧은 IRQ는 매우 짧은 기간의 시간에 실행되어야 하며, 나머지 시간동안 컴퓨터는 금지되고 다른 인터

럽트는 수행되지 않는다. 긴 IRQ는 좀 더 긴 시간을 가지며, 실행되는 동안 다른 인터럽트가 발생할 수 있다(동일한 장치에서가 아닌). 모든 인터럽트가 가능하다면, 긴 형태로 선언되는 것이 좋다. CPU가 인터럽트를 수신하면, 무엇을 하건 일단 멈추고(좀더 중요한 인터럽트는 제외하고, 순위가 높은 인터럽트가 완료되면 이것이 처리된다), 스택상의 인자들을 대피하고 인터럽트 처리기를 호출한다. 이는 시스템이 불안정한 상태에 빠지지 않게 중요한 것 들은 스택에 대피시키고 인터럽트 처리기에서 허가되지 않는다는 의미이다. 인터럽트 처리기에서의 이러한 문제의 해결책은 필요한 일을, 보통은 하드웨어로부터 읽거나 하드웨어에 무언가를 보내는, 그리고 나중에 새로운 정보의 처리를 위한 스케줄(이것은 'bottom half'라 불린다)등을, 즉시 수행하고 리턴하는 것이다. 커널은 가능한 한 'bottom half'의 호출을 보증한다 --- 이것이 수행되면, 모든 것이 커널 모듈 안에서 허가된 모든 것이 허가되어 진다. 이러한 것을 수행하는 방법은 관련된 IRQ가 수신되었을 때 호출된 인터럽트 처리기를 얻기 위해 request_irq를 호출하는 것이다(인텔 구조에서 이것은 16개중 하나이다). 이 함수는 IRQ 번호, 함수의 이름, 플래그, /proc/interrupts를 위한 이름과 인터럽트 처리기에서 전달된 인자를 수신한다. 이 플래그는 IRQ를 공유하고자 하는 다른 인터럽트를 표시하기 위해 SH_SHIRQ를 포함할 수 있고 SA_INTERRUPT는 이것이 빠른 인터럽트임을 표시한다. 이 함수는 이 IRQ상에 처리기가 있지 않을 때나 둘의 공유를 허가하였을 때만 수행될 것이다. 그리고 나면, 인터럽트 처리기의 안에서, 하드웨어와 통신하게 되고 'bottom half'를 스케줄하기 위해 queue_task_irq와 tq_immediate와 mark_bh(BH_IMMEDATE)를 이용하게 된다. 버전 2.0에서 표준 queue_task를 이용할 수 없는 이유는 인터럽트가 누군가의 queue_task(*2)의 중간에서 발생할 수도 있어서이다. 초기 버전의 리눅스에서는 단지 32개의 'bottom half'배열만이 가졌으므로 mark_bh가 필요하고 그들 중 하나(BH_IMMEDATE)가 'bottom-half'를 얻지 못한 장치들을 위한 'bottom half'들의 연결 리스트에 이용된다.


각주1 ********************

이것은 인텔 구조의 표준 명명법이다.

*************************


각주2 **************************************************

queue_task_irq 는 전역 잠금에 의해 이것으로부터 보호된다. --- 2.2에서는 없다 

queue_task_irq 와 queue_task는 잠금에 의해 보호된다.

*******************************************************



11.1 인텔 구조에서 키보드 

경고: 이 장의 부분은 전적으로 인텔 구조에 의존적이다. 인텔 구조에서 실행하는 것이 아니라면, 올바르게 수행되지 않을 것이다. 여기의 코드를 컴파일 하려고 시도하지 말라.  


이 장의 예제 코드는 문제점을 가지고 있다. 한편으로 이것은 모든 사람에게 의미 있는 결과를 가져다주는 예제이다. 다른 한편으로, 커널이 이미 보통의 장치들에 대한 드라이버를 모두 포함하고 있고, 이런 장치 드라이버들은 내가 작성한 것과 같이 존재할수 없다. 키보드 인터럽트를 위한 이 코드를 위한 해결책으로 내가 발견한 것은, 우선 원래의 키보드 인터럽트를 불능으로 하는 것이다. 커널의 소스 파일에 정적 심벌이 정의되어 있으므로(driver/char/keyboards.c), 이것을 재 적재할 방법은 없다. 이 코드를 insmod하기 전에 다른 터미널에서 sleep 120을 실행한다. 이 코드는, 인텔 구조에서 키보드의 제어를 하는 IRQ인, IRQ 1에 묶여있다. 키보드 인터럽트를 수신하면, 키보드의 상태를 읽고(inb(0x64)가 이용된다) 키보드에서 리턴된 값을 조사한다. 이제 커널은 이용된 키 코드(스캔 코드의 처음 7 비트들)의 값을, 이것은 눌려지거나(8번째 비트가 0이면) 또는 떨어졌을(만약 1이면), got_char에서 얻는다.


<intrpt.c>

/* intrpt.c - 인터럽트 처리기 */

/* Copyright (C) 1998 by Ori Pomerantz */

/* 필요한 헤더 파일들   */

/* 커널 모듈에서의 표준 헤더 */

#include <linux/kernel.h>     /* 커널 작업을 한다. */

#include <linux/module.h>    /* 특별히, 모듈 */


/* CONFIG_MODVERSIONS 다루기 */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

#include <linux/sched.h>

#include <linux/tqueue.h>


/* 인터럽트를 원한다. */

#include <linux/interrupt.h>

#include <asm/io.h>


/* 2.2.3에서 /usr/include/linux/version.h에 이를 위한 매크로를 포함한다. 그러나, 2.0.35에서는 포함하지 않으며 필요한 경우에 이곳에 추가한다.*/

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif


/* Bottom Half - 가능한 안전하게 모든 것을 수행하도록 보통은 커널 모듈에 의해 허가되고 커널에 의해 호출되어 얻게된다. */

static void got_char(void *scancode)

{

    printk("Scan Code %x %s.\n", (int) *((char *) scancode) & 0x7F,

              *((char *) scancode) & 0x80 ? "Released" : "Pressed");

}


/* 이 함수는 키보드 인터럽트를 서비스한다. 키보드로부터 관계된 정보를 읽고 커널이 안전하다고 여기는 때에 수행하기 위해 'bottom half'에서 스케줄 된다. */

void irq_handler(int irq, void *dev_id, struct pt_regs *regs)

{

    /* 'bottom half'에서 포인터를 통해 인식할 필요가 있기에 이 변수들은 정적이다. */

   static unsigned char scancode;

   static struct tq_struct task =

   {

       NULL,

       0,

       got_char,

       &scancode

   };

   unsigned char status;

      

   /* 키보드의 상태를 읽기 */

   status = inb(0x64);

   scancode = inb(0x60);

      

   /* 'bottom half'에서 수행되기 위해 스케줄 */

   #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);

}


/* 모듈의 초기화 - IRQ 처리기의 등록 */

int init_module()

{

    /* 키보드의 인터럽트 처리기가 다른 처리기와 함께 존재할 수 없으므로, 여기에서 처럼, 작업을 하기 전에 이것을

    불능(free irq) 으로 해야한다. 이것이 어디에 있는지 모르므로, 나중에 원상태로 복원할 수 없게된다 - 따라서

    컴퓨터는 종료된 시점에서 재시동 될 것이다. */

   free_irq(1, NULL);


   /* IRQ 1을 요청, 키보드의 IRQ, irq_handler로 가기 위해. */

   return request_irq(1,irq_handler,SA_IRQ,"test_keyboard_irq_handler", NULL");

      

   /*

   1               - 키보드의 IRQ 번호 

   irq_handler  - 처리기 

   SA_SHIRQ   - SA_SHIRQ는 이 IRQ상에서 다른 처리기를 가질 수 있다는 의미.

   SA_INTERRUPT는 빠른 인터럽트에서 처리기를 만들기 위해 이용.

   */

}


/* 제거 */

void cleanup_module()

{

   /* 이것은 단지 완벽성을 위해 여기 있는 것이다. 이것은 전적으로 부적절하며, 키보드 인터럽트를 재 적재할 어떤 방법도

   없으므로 컴퓨터는 완전히 쓸 수 없게 되고 재 시동되어야만 한다. */   

   free_irq(1, NULL);

}



제 12 장


대칭형 다중 프로세싱

하드웨어의 성능을 향상하는 가장 손쉬운 방법중의 하나는 보드 상에 많은 CPU를 장착하는 것이다. 이것은 다른 CPU에서 다음 작업을 수행하게 하거나(비대칭형 다중 프로세싱) 또는 동일한 작업을 병렬로 수행하게 만들 수도 있다(대칭형 다중 프로세싱, SMP). 비대칭형 다중 프로세싱을 효율적으로 동작하기 위해서는 컴퓨터가 해야 할 태스크들에 대한 특별한 지식이 요구되며, 리눅스와 같은 일반 목적을 위한 운영체제에는 적합하지 않다. 달리 말하면, 대칭형 다중 프로세싱은 상대적으로 쉽게 적용할 수 있다. 상대적으로 쉬운, 정확한 의미는 --- 실제로는 결코 쉽지 않다. 대칭형 다중 프로세싱 환경에서, CPU는 동일한 메모리를 공유하며, 하나의 CPU에서 수행된 결과가 다른 CPU에 이용되는 메모리에 영향을 미칠 수 있다. 더 이상 이전에 지정한 변수의 값이 동일할 지 확신할 수 없다 --- 수행되고 있지 않는 동안에 다른 CPU가 그것을 다룰지도 모른다. 명백하게, 이처럼 동작하는 프로그램은 불가능하다. 프로세스는 보통 동시에 하나의 CPU에서만 실행될 것이기에, 이런 종류의 프로세스 프로그래밍은 여기에서 다룰 주제는 아니다. 다른 한편, 커널은, 다른 CPU상에서 수행되는 다른 프로세스에 의해 호출되어질 것이다. 버전 2.0.x에서, 전체 커널이 하나의 거대한 스핀 락의 내부였기에 문제가 될게 없었다. 이는 하나의 CPU가 커널을 사용하고, 다른 CPU가 이것을 얻기 원하면, 예를 들어 시스템 호출 같은, 다른 CPU는 앞의 CPU가 완료되길 기다리게 된다. 이는 리눅스의 SMP를 안전하게 만들지만, 끔직할 정도로 비효율적이다(*1). 버전 2.2.x에서는, 여러 개의 CPU들이 동시에 하나의 커널을 사용하는 것이 가능하다. 그래서 모듈 작성자들은 이것을 잘 알 필요가 있다. SMP 기계를 하나 얻게되었기에, 이 책의 다음 버전에서는 좀더 자세한 내용을 포함할 수 있기를 바란다.


각주1 ***************************************************************************

이것의 예외는 여러개의 CPU상에 한번에 수행 가능한 쓰레드된 프로세스들이다,  이것은 SMP와 함께 이용해도 안전하다.

*******************************************************************************



제 13 장 


범하기 쉬운 실수 

이전에 나는 세상 속으로 나가는 그리고 커널 모듈을 작성하는 방법을 알려주었고, 여기에서 몇가지를 주의할 필요가 있음을 알려주겠다. 경고를 내지 못하거나 무언가 나쁜 일이 생기면, 문제를 나에게 알려 달라.


1. 표준 라이브러리의 이용은 불가능하다. 커널 모듈에서는 단지 커널 함수만이 이용 가능하며, 이들 함수는 /proc/ksyms에서 볼 수 있다.


2. 짧은 시간을 위한 일이 필요하면 인터럽트를 불능으로 해도 된다. 그러나, 나중에 인터럽트 가능으로 하지 않으면, 시스템은 악화되고 전원을 꺼야만 하게 된다.


3. 커다란 괴물 앞에서 기죽지 말라. 아마 이것에 대해 경고를 보내지 못하겠지만, 내가 이것을 알아낸 경우에는 그렇게 할 것이다.



부록 A

2.0과 2.2사이의 변화

문서의 모든 변경 내용이 전체 커널 버전에 대해서 적용가능한지는 모르겠다. 예제들의 변환 과정에서(실제로, Emmanual Papirakis의 변경을 적용) 아래의 차이점들을 알아내었다. 언급된 모든 내용은 모듈 프로그래머에게 도움을 주기 위해, 특별히 이 책의 이전 버전의 내용을 공부했던 프로그래머에게 유용할 것이고 새로운 버전으로의 변환에 이용한 기법은 대부분 유사하다. 2.2로의 변환을 원하는 사람들을 위한 추가적인 내용을 http:://www.atnf.csiro.au/~rgooch/linux/docs/porting-to-2.2.html에서 알 수 있다.


1. asm/uaccess.h - put_user와 get_user가 필요하면 이것을 #include해야 한다.


2. 버전 2.2에서 get_user -  get_user는 사용자 메모리의 포인터와 정보를 채우기 위한 커널 메모리의 변수를 인자로 갖는다. get_user는 이제 변수가 2 혹은 4바이트 길이면 동시에 2 혹은 4 바이트를 읽는 것이 가능하다.


3. file_operations - 이 구조체는 이제 open과 close사이의 flush함수를 가진다.


4. file_operations내의 close - 버전 2.2에서, close함수는 integer결과를 리턴하고, fail 결과가 허가되어 진다.


5. file_opearations내의 read와 write - 이들 함수를 위한 헤더들은 변경되었다. 이제 이들은 integer 대신에 ssize_t를 리턴하고, 이들의 인자 리스트는 다르다. inode는 더 이상 인자가 아니며, 파일내의 offset가 추가되었다.


6. proc_register_dynamic - 이 함수는 더 이상 존재하지 않는다. 대신에, proc_register을 호출할 수 있고 구조체의 indoe필드 안에 0을 지정한다.


7. Signals - 구조체의 시스널들은 더 이상 32비트의 integer가 아니며, NSIG_WORDS 배열의 interger들이다.


8. queue_task_irq - 인터럽트 처리기안에서 태스크를 스케줄하길 원하면 queue_task을 이용한다. queue_task_irq는 이용할 수 없다.


9. 모듈의 인자들 - 더 이상 모듈 인자를 전역 변수로 설정할 필요가 없다. 2.2에서 그들의 형선언을 위해 MODULE_PARM을 이용해야 한다. 이것은 기능이 많이 향상되었으며, 혼란이 없게 디지트와 함께 시작하는 string인자들의 수신을 허가한다.


10. 대칭형 다중 프로세싱 - 커널의 내부에 더 이상 거대한 스핀 락이 존재하지 않는다. 커널 모듈은 SMP임을 인식해야 한다. 


부록 B

어디에서 추가적인 내용을 얻을 수 있는가?

이 책의 많은 장을 좀더 쉽게 쓸 수 있었는지도 모르겠다. 새로운 파일 시스템 만들기, 새로운 프로토콜 스택에 대한 내용을 추가하고도 싶었다. 부트 스트래핑이나 디스크 인터페이스와 같은, 여기서 다루지 않은 커널의 메커니즘에 대한 설명을 추가하고도 싶었다. 그러나, 이렇게 하지는 않았다. 내가 이 책을 쓴 목적은 커널 모듈 프로그래밍의 숨겨진 비밀을 알리고 기본적인 기법을 가르치고자 함이었다. 커널 프로그래밍에 상당한 관심을 가지는 사람들을 위해, http://jungla.dit.upm.es/~jmseyas/linux/kernel/hackers-docs.htm의 리소스를 살펴보길 추천한다. 또한, 리누스가 말했듯이, 커널을 이해하는 가장 좋은 방법은 커널 소스 자체를 보는 것이다. 좀더 많은 커널 모듈의 예제들을 살펴보기 원하면, Phrack 잡지를 추천한다. 보안에 대해 관심이 없을지라도, 커널 모듈이 커널의 내부에서 어떤 일을 하는지에 대한

좋은 예제들을 많이 가지고 있으며, 이들을 이해하는데는 큰 노력이 요구되지 않는다. 더 나은 프로그래머가 되기 위한 여정에 내가 조금의 도움이라도 주었길 바라며, 적어도 여기의 기술을 통해 약간의 재미를 느끼길 바란다. 그리고, 당신이 유용한 커널 모듈을 작성하게 되면, GPL하에서 공개하길 바라며, 나 또한 그렇게 할 것이다.


부록 C

상품과 서비스 

I hope nobody minds the shameless promotions here. They are all things which are likely to be of use to beginning Linux Kernel Module programmers.


C.1 프린트된 출력물 얻기 

The Coriolis group is going to print this book sometimes in the summer of '99. If this is already summer, and you want this book in print, you can go easy on your printer and buy it in a nice, bound form.


부록 D

당신이 호의를 보이려면 

This is a free document. You have no obligations beyond those given in the GNU Public License (Appendix E). However, if you want to do something in return for getting this book, there are a few things you could do.


Send me a postcard to

Ori Pomerantz

Apt. #1032

2355 N Hwy 360

Grand Prairie

TX 75050

USA


If you want to receive a thank-you from me, include your e-mail address. Contribute money, or better yet, time, to the free software community. Write a program or a document and publish it under the GPL. Teach other people how to use free software, such as Linux or Perl. Explain to people how being selfish is not incompatible with living in a society or with helping other people. I enjoyed writing this document, and

I believe publishing it will contribute to me in the future. At the same time, I wrote a book which, if you got this far, helps you. Remember that happy people are usually more useful to oneself than unhappy people, and able people are way better than people of low ability.


Be happy. If I get to meet you, it will make the encounter better for me, it will make you more useful for me ;).


부록 E

GNU General Public License

This is an unofficial translation of the GNU General Public License into  Korean. It was

not published   by the  Free  Software  Foundation,  and does   not legally   state the

distribution terms for software that uses the  GNU GPL--only the original English text

of the GNU  GPL does  that. However,  I hope that  this translation  will help  Korean

speakers understand the GNU GPL better.


이 문서는 자유 소프트웨어 재단(Free Software Foundation)의 GNU General Public License를 한국어로 번역한 것이다. 이 문서는 GNU General Public License가 내포하고 있는 호혜적인 자유와 공유의 정신을 보다 많은 사람들에게 알리기 위한 희망에서 작성되었지만 자유 소프트웨어 재단의 공식 문서로 취급될 수는 없다. 이는 원래의 문서가 의도하고 있는 내용들이 왜곡되지 않고 법률적으로 유효하기 위해서 선행되어야 할 양국의 현행 법률과 언어의 적합성 여부에 대한 전문가들의 검토 작업에 많은 비용이 필요하기 때문이다. 따라서, 자유 소프트웨어 재단은 오역이나 해석상의 난점으로 인해서 발생될 지 모를 분쟁의 가능성을 미연에 방지하고 문서가 담고 있는 내용과 취지를 보다 많은 사람들에게 홍보하려는 상반된 목적을 한국어 번역문을 공식적으로 승인하지 않음으로써 양립시킬 수 있을 것이다. 자유 소프트웨어 재단은 GNU General Public License를 실무에 적용할 경우, 오직 영문판 GNU General Public License에 의해서 만이 그 법률적 효력이 올바르게 발생될 수 있음을 권고하고 있다. 이 번역문은 법률적 검토와 문서간의 동일성 여부에 대한 검증을 거치지 않은 것이며 이로 인해서 야기될 수 있을 지도 모를 법률적인 문제에 대해서 어떠한 형태의 보증도 하지 않는다.



출처: http://kldp.org/Translations/Kernel_Module_Programming_Guide