원저자 : Ori Pomerantz
역자 : 채병철(dataeng@chollian.net)
번역시작 : 1999년 8월 16일.
이 책의 모든 번역 내용은 아래에 언급한, 그리고 원문의 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
리눅스 커널 모듈 프로그래밍 안내서는 무료이다. 당신은 자유 소프트웨어 재단의 G-
NU 공개 라이선스하(Ver2)에서 이를 수정, 재 배포할 수 있다. 버전 2는 이 책의 부록
E에 기술되어 있다. 이 책이 쓸모 있게 여러 사람에 배포되기를 바란다, 그러나 어떤
사후 보장도 없을 것이다; 상품성 있는 제품의 사후 보장은 없을 것이며, 특별한 목적
에 적합하게 바뀌지도 않을 것이다. 저자는 이 책이 위에 언급한 카피라이트에 준하여
사람들에게, 혹은 상업적으로 널리 퍼지는 것에 용기를 얻을 것이다. 다시 말하면, 당
신은 이 책을 무료로 복사하고 재 배포할 수 있다. 물리적, 전자적, 또는 그 중간의
매체로서 재 생산되는 것에 저자의 어떤 명시적 허가도 필요 없다. 주의할 것은, 파생
된 결과 그리고 이 문서의 변환은 반드시 GNU의 공개 라이센스하에서만 가능하다는 것
이며, 원 카피라이트 조건은 유지되어져야만 한다. 당신이 이 책에 새로운 내용을 첨
부하기 원한다면, 새로운 교정을 위해 유용한 원문의 소스 코드와 함께(L A T E X와
같은) 이 문서의 유지 보수자인, 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(고맙게도, 이제 나쁜 운영체제를 견뎌야 한다)하에서 중
요한 디렉토리를 지울 수 있는 와일드 포인터를 가지고 있고, 리눅스 하에서도 안전하
게 살수 있을지 모른다. 주의: 여기의 코드는 인텔 펜티엄에서 실행되는 버전 2.0.35
에서 2.2.3에서 작성되고 검사되었다. 대부분의 경우에, 버전이 2.0.x에서 2.2.x이라
면 다른 CPU의 다른 커널에 있어서도 제대로 동작할 것이나 그 어떤 약속할 수는 없다
. 한 가지 예외는 11장이며, 이것은 x86을 제외한 구조에서는 적절하게 동작하지 않을
것이다.
0.1 누가 이것을 읽어야 하는가?
이 문서는 커널 모듈에 대해 알기 원하는 사람을 위한 것이다. 여러 부분에서 커널의
내부에 대한 내용을 약간씩 다룰 것이나, 이것은 이 문서의 목적이 아니다. 내가 여기
서 설명한 것 보다 많은 훌륭한 내용을 커널에서 알 수 있다. 이 문서는 또한 커널 모
듈을 작성하기 원하는 사람을 위한 것이나, 버전 2.2의 커널에서 아직 적합하지 않다.
당신이 이러한 경우라면, 부록 A의 예제들을 살펴보길 권한다. 이 목록의 어떤 것도
이해하기 쉽지는 않지만, 대부분의 기본적인 기능을 포함하고 있으며 당신이 시작하기
에 충분한 내용을 제공할 것이다.
커널은 많은 조각 프로그램의 집합이고, 나는 프로그래머들이 적어도 몇 가지 커널 소
스를 읽어보고 이해한다고 믿는다. 이미 말했지만, 시스템의 처음과 함께 하는 가치와
나중에 물어볼 질문들을 확신한다. 내가 새로운 프로그램언어를 배우기 시작했을 때,
라이브러리 코드를 함께 읽지는 않았지만, 작은 "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에 감사드리며, 출간 전에 많은 실수들
을 교정해준 많은 사람들에게 또한 감사한다. 물론 나의 실수로 드물지 않게 실수가
남아 있지만...
이 책의 T E X초고는 '리눅스 설치, 시작'에서 부끄러움을 느끼면서 훔쳐(?)왔으며,
Matt Welsh에 의해 T E X의 작업이 완료되었다.
리누스 토발즈에게 경의를 표하며, 리차드 스톨만과 나의 컴퓨터에 뛰어난 운영체제를
갖추도록 해준 다른 많은 사람들과 아무런 조건 없이 소스코드를 얻도록 해준(맞아 --
내가 왜 이유를 말해야 하지: 역주-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 -MODULE -D__KERNEL__ -D_LINUX
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를 실행시켜야 하는 걸까? 이는 커널이 메시지를 표시할 때 p-
rintk를 이용하기 때문이다. X를 이용하지 않을 때, 이 메시지는 당신이 이용하는 가
상 터미널(ALT-F?에 의해 선택된)에 표시되며, 볼 수 있게 된다. 반면에, 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가 필요하다면 이것을 포함시킨다.
3. 오브젝트 코드를 합한다. 파일들은 하나로 된다.
x86하에서, 이는 ld -m elf i386 -r -o <name of module>.o <1st source file>.o <sec
ond 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를 호출하여 커널의 장치 테이블
에 장치 드라이버를 등록한다. 이것은 또한 장치를 위한 주 번호를 리턴한다. claenu-
p_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?999 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의 커널을 둘 다 포함한다. 이 둘 사이의 차이
점으로 인해, 커널 버전에 의존적인 컴파일 조건을 요구된다. 이러한 일에 매크로 LI-
NUX_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_en-
try가 무엇에 이용되었는지 살펴봄으로(주로 필자는 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파일을 만드
는 것도 가능하다. 이것은 입력에서도 이와 유사함을 보여준다. 문제라면, 응답을 얻
을 어떤 방법도 없다는 것이다. 커널 모듈에 입력을 보내기 위한 첫 번째 방법은 /pr-
oc 파일에서 쓰여진 것을 통해서이다. 때문에 /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의 작업 말고도 다른 일
에 함수들을 지정할 수 있다.(이런 일은 그다지 까다롭지 않다, 필요치 않는 경우 NU-
LL을 지정한다)
*/
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 o-
utput control의 줄임 말인). 모든 장치는 자신의 ioctl 명령을 가지며, ioctl의 읽기
(프로세스에서 커널로 정보를 보내기 위한),ioctl의 쓰기(*1)(프로세스에서 정보를 리
턴하기 위한)가 둘 다 쓰일 수도 혹은 아닐 수도 있다.
각주 1 ************************************************************************
주의할 점은 읽기와 쓰기의 규칙이 다시 바뀐다는 것, ioctl의 읽기는 커널에 정보를
보내는 것이고 쓰기는 커널에서 정보를 수신한다는 것이다.
*******************************************************************************
ioctl 함수는 3개의 인자와 함께 호출되어진다: 접근하려는 장치 파일의 파일 기술자,
ioctl 번호, 그리고 하나의 인자, 이것을 통해 어떤 것이라도 전달하기 위해 캐스트(
cast:형변화 연산자)를 이용하는 것이 가능하다.(*2)
각주 2 ************************************************************************
이것은 엄밀하게 따지자면 정확하지는 않다. 예를 들면 구조체등은 전달할 수는 없다.
--- 그러나, 구조체의 포인터를 전달 할 수는 있다.
*******************************************************************************
ioctl 번호는 주 장치 번호, ioctl의 형태, 명령, 그리고 인자의 형태 등을 변환한다.
ioctl 번호는 헤더 파일에 정의된 보통 하나의 매크로 호출(_IO, _IOR, _IOW, _IOWR
-- 형태에 의존적인)에 의해 만들어진다. 이 헤더 파일은 이용되는 ioctl(이렇게 해야
접근할 수 있는 ioctl 함수를 만들 수 있다)과 커널 모듈(이것의 이해를 가능하게 한
다) 모두에 의해 #inlcude되어 포함되어져야 한다. 아래의 예에서, 헤더 파일은 char-
dev,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
/* 시스템 호출 테이블(함수들의 테이블), 이것은 외부실행으로 정의하며 커널은 ins-
mod를 할 때 이것을 채울 것이다. */
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_n-
oblock 프로그램이, 이장의 소스 디렉토리에 있는, 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_ta-
sk에 넣게되며, 이 목록의 태스크는 다음 타이머 인터럽트에서 실행된다. 함수가 계속
실행되도록 유지해야 하기 때문에, 다음 타이머 인터럽트에서 실행되도록 호출될 때마
다 tq_timer상에 이것을 다시 넣는 일이 필요하다.
여기서 기억해야 할 중요한 점이 하나 있다. 모듈이 rmmod에 의해 제거될 때, 첫 번째로
참조 카운터를 검사한다. 이것이 0이면, module_cleanup이 정상적으로 호출된 것이다.
이때, 모듈은 메모리에서 관계된 모든 함수를 제거한다. 만약 타이머의 태스크 목록
상에 더 이상 유용하지 않은 이들 함수들 중 하나의 포인터를 포함하게되면, 누구도
이를 알지 못한다. 나중에 나이가 든 후(컴퓨터의 관점에서, 인간의 관점이 아닌,
1/100초 보다 적을 것이다), 커널은 타이머를 가지고 태스크 목록상의 태스크를 호출
하려 할 것이다. 불행히도, 이 함수는 더 이상 존재하지 않는다. 대부분의 경우, 이것
은 사용되지 않게 된 메모리 페이지이며, 오류 메시지를 보게 될 것이다. 그러나, 만
약 다른 코드가 동일한 메모리의 위치에 존재하면, 매우 잘못된 결과를 얻게된다. 불
행히도, 태스크의 목록으로부터 태스크를 제거할 쉬운 방법을 갖고 있지 못하다. cle-
anup_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_IMME-
DATE)가 '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에서 다음 작업을 수행하게 하거나(비대칭형 다중 프로
세싱) 또는 동일한 작업을 병렬로 수행하게 만들 수도 있다(대칭형 다중 프로세싱, S-
MP). 비대칭형 다중 프로세싱을 효율적으로 동작하기 위해서는 컴퓨터가 해야 할 태스
크들에 대한 특별한 지식이 요구되며, 리눅스와 같은 일반 목적을 위한 운영체제에는
적합하지 않다. 달리 말하면, 대칭형 다중 프로세싱은 상대적으로 쉽게 적용할 수 있
다. 상대적으로 쉬운, 정확한 의미는 --- 실제로는 결코 쉽지 않다. 대칭형 다중 프로
세싱 환경에서, 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를 리턴하고, 이들의 인자 리스트는 다르다. in-
ode는 더 이상 인자가 아니며, 파일내의 offset가 추가되었다.
6. proc_register_dynamic - 이 함수는 더 이상 존재하지 않는다. 대신에, proc_regi-
ster을 호출할 수 있고 구조체의 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에 의해서 만이 그 법률적 효력이 올바르게 발생될
수 있음을 권고하고 있다. 이 번역문은 법률적 검토와 문서간의 동일성 여부에 대한
검증을 거치지 않은 것이며 이로 인해서 야기될 수 있을 지도 모를 법률적인 문제에
대해서 어떠한 형태의 보증도 하지 않는다.
Original Copy: GNU General Public License
Korean Translator: 1998 Song Changhun 송창훈 mailto:chsong@cyber.co.kr
목 차
* GNU GENERAL PUBLIC LICENSE
* 전 문(前 文)
* 복제와 개작, 배포에 관한 조건과 규정
* GPL을 실무에 적용하는 방법
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
저작권과 사용 허가에 대한 본 사항이 명시되는 한,
어떠한 정보 매체에 의한 본문의 전재나 발췌도 무상으로 허용된다.
단, 원문에 대한 수정과 첨삭은 허용되지 않는다.
전 문 (前 文)
소프트웨어에 대한 대부분의 라이센스는 해당 소프트웨어에 대한 수정과 공유의 자유
를 제한하려는 것을 그 목적으로 한다. 그러나, GNU General Public License는 자유
소프트웨어에 대한 수정과 공유의 자유를 호혜적으로 보장하기 위해서 성립되었다. 자
유 소프트웨어 재단이 제공하는 대부분의 소프트웨어들은 GNU General Public License
의 규정에 의해서 관리되고 있으며 별도의 관리 방법이 보다 효과적이라고 판단되는
라이브러리 서브루틴(library subroutine)의 경우, 독립된 문서인 GNU Library Gener-
al Public License를 선택적으로 적용하고 있다. 자유 소프트웨어란 이를 사용하려는 모
든사람에 대해서 동일한 자유와 귄리가 함께 양도되는 소프트웨어를 의미하며 프로그
램저작자의 의지에 따라 어떠한 프로그램에도 이 규정들이 적용될 수 있다.
자유 소프트웨어를 언급할 때 사용되는 '자유'라는 단어의 의미는 금전적인 측면의 자
유가 아닌 구속되지 않는다는 관점에서의 자유를 의미하며 GNU General Public Licen-
se는 자유 소프트웨어를 이용한 복제와 개작, 배포와 수익 사업 등의 가능한 모든 형
태의 자유를 실제적으로 보장한다. 여기에는 소스 코드의 전부 또는 일부를 원용해서
개선된 프로그램으로 변형시키거나 새로운 프로그램을 창작할 수 있는 자유가 포함되
며 자신에게 양도된 이러한 자유와 권리들을 보다 명확하게 인식할 수 있도록 하기 위
한 규정 또한 포함되어 있다.
GNU General Public License는 이 문서에 소프트웨어 피양도자의 권리를 제한하는 특
정 조항과 단서들을 별항으로 첨가시키지 못하게 함으로써 사용자들의 실질적인 자유
와 권리를 보장하고 있다. 자유 소프트웨어의 개작과 배포에 관계하고 있는 사람들은
이러한 무조건적인 권리 양도 규정을 준수해야만 한다.
예를 들면, 특정 프로그램을 배포할 경우 양도자는 피양도자에게 자신이 양도받았던
모든 권리를 수익 여부에 관계없이 그대로 이전해야만 한다. 소스 코드에 대한 사용
권리 또한 여기에 포함되어야 하며 이와 같은 사항들을 명시함으로써 피양도자들에게
그들이 양도받은 권리를 알 수 있도록 해야 한다.
자유 소프트웨어 재단은 다음과 같은 두 가지 방법에 의해서 반복되는 양도에 따른 사
용자 모두의 권리를 보호한다: (1) 저작권을 인정함으로써 프로그램 저작자들의 권리
를 보호한다. (2) 저작권의 양도에 관한 실정법에 의해서 유효한 법률적 효력을 갖는
GNU General Public License를 통해서 소프트웨어의 복제와 개작, 배포 등에 대한 소
프트웨어 피양도자의 권리를 실질적으로 보장한다.
자유 소프트웨어의 사용자들은 지속적인 양도 과정을 통해서 소프트웨어 자체에 수정
과 변형에 의한 문제가 발생될 수 있으며 이는 최초의 저작자에 의한 소프트웨어가 갖
는 문제가 아닐 수 있다는 개연성에 대해서 인식하고 있어야 한다. GNU General Publ-
ic License에 자유 소프트웨어에 대한 어떠한 형태의 보증도 규정하지 않은 이유는 이
러한 점들이 고려되었기 때문이며 이는 프로그램 원저작자와 자유 소프트웨어 재단의
자유로운 활동을 보장하는 현실적인 수단이기도 하다.
특허 제도는 자유 소프트웨어의 발전을 위협하는 요소일 수밖에 없다. 따라서, 자유
소프트웨어를 배포할 경우 개별적인 배포 과정에 특허를 취득한 저작물을 함께 포함시키
지 않음으로써 이용 상의 자유가 제한되지 않도록 하는 것이 최선의 방법이다. GNU G-
eneral Public License는 이러한 문제에 대처하기 위해서 특허가 취득된 저작물은 그
라이센스를 불특정 다수(이하, "공중"이라 한다)에게 공개적으로 허용하는 경우에 한
해서 자유 소프트웨어와 함께 사용할 수 있도록 규정하고 있다.
복제(copying)와 개작(modification), 배포(distribution) 등에 관련된 구체적인 조건
과 규정은 다음과 같다.
복제와 개작, 배포에 관한 조건과 규정
제 0 항. 본 라이센스는 GNU General Public License의 규정에 따라서 배포될 수 있다
는 사항이 저작권자에 의해서 명시된 모든 컴퓨터 프로그램 저작물에 대해서 동일하게
적용된다. 컴퓨터 프로그램 저작물(이하, "프로그램"이라 한다)이란 특정 프로그램이
나 이와 관련된 기타 저작물을 의미하고 "2차적 프로그램"이란 저작권법의 규정에 따
라 프로그램의 전부 또는 상당 부분을 원용하거나 다른 프로그래밍 언어로의 번역을
포함할 수 있는 개작 과정을 통해서 창작된 새로운 프로그램과 이와 관련된 저작물을
의미한다(이후로 다른 프로그래밍 언어로의 번역은 별다른 제한 없이 개작의 범위에
포함되는 것으로 간주한다) "피양도자"란 GNU General Public License의 규정에 의해
서 프로그램을 양도받은 사람을 의미한다.
본 라이센스는 프로그램에 대한 복제와 개작, 배포 행위에 대해서만 적용된다. 따라서
, 프로그램을 실행시키는 행위는 제한되지 않으며 프로그램의 실행에 따른 결과물은
실행 자체에 의한 결과물의 생성 여부에 상관없이 결과물이 2차적 프로그램을 구성했
을때에 한해서 본 라이센스의 규정을 적용할 수 있다. 2차적 프로그램의 구성 여부는
2차적 프로그램 안에서의 프로그램의 역할을 토대로 판단한다.
제 1 항.피양도자는 프로그램에 대한 보증을 제공하지 않는다는 사실과 저작권을 함께
명시하는 한, 양도받은 소스 코드의 전부 또는 일부를 어떠한 정보 매체를 통해서도
복제해서 배포할 수 있다. 피양도자가 프로그램의 소스 코드를 재배포할 때는 프로그
램에 대한 보증이 결여되어 있다는 사실과 본 라이센스에 대해서 언급한 사항들을 양
도받은 그대로 유지시켜야 하며 GNU General Public License 원문을 함께 제공해야 한
다. 복제물을 배포할 경우, 복제물을 제작하기 위해서 소요된 경비를 충당하기 위해서
배포본을 유료로 판매할 수 있으며 유료 판매에 따른 배포본의 환불을 보장하는 별도
의 보증을 설정할 수 있다.
제 2 항. 피양도자는 자신이 양도받은 프로그램의 전부 또는 일부를 개작할 수 있으며
이를 통해서 2차적 프로그램을 창작할 수 있다. 개작된 프로그램이나 창작된 2차적 프
로그램의 소스 코드는 제 1 항의 규정에 의해서 다음의 사항들을 만족시키는 조건에
한해서 복제해서 배포될 수 있다.
* a) 개작된 파일은 파일이 개작된 사실과 개작된 날짜가 명시적으로 확인될 수 있도
록 작성되어야 한다.
* b) 배포하거나 출판하려는 저작물의 전부 또는 일부가 양도받은 프로그램으 로부터
파생된 것이라면 개작된 프로그램에 대한 배포본이나 출판물 전 체에 대한 사용 권리
를 공중에게 무상으로 허용해야 한다.
* c) 개작된 프로그램의 일반적인 실행 형태가 명령어 입력 방식에 의한 대화 형 구조
일 경우, 개작된 프로그램은 이러한 대화형 구조로 평이하게 실 행되었을 때 저작권에
대한 사항과 프로그램에 대한 보증이 결여되어 있 다는 사실이 개작된 프로그램을 본
라이센스의 규정에 의해서 다시 개작 해서 배포할 수 있다는 사항과 GNU General Pub-
lic License를 열람할 수 있는 방법과 함께 실행 직후에 지면 또는 화면을 통해서 출
력될 수 있도 록 작성되어야 한다(예외 규정: 양도받은 프로그램이 대화형 구조를 갖
추고 있다 하더라도 통상적인 실행 환경에서 전술한 사항들이 출력되지 않는 형태였을
경우, 이를 개작한 프로그램 역시 관련 사항들을 출력시 키지 않아도 된다)
본 조항들은 개작된 부분이 포함된 2차적 프로그램 전체에 적용된다. 만약, 어떠한 저
작물이 2차적 프로그램에 포함되어 있는 부분과 동일한 것이라 하더라도 그것이 양도
받은 프로그램으로부터 파생된 것이 아니라 별도의 독립 저작물로 인정될 만한 상당한
이유가 있을 경우, 이 저작물의 개별적인 배포 과정에는 본 라이센스와 규정들이 적용
되지 않는다. 그러나, 이러한 저작물이 2차적 프로그램에 포함되어 함께 배포된다면
개별적인 저작권과 배포 기준에 상관없이 배포본의 전체 저작물 모두가 본 라이센스에
의해서 관리되어야 하며 전체 저작물의 일부 또는 전부에 대한 사용상의 모든 권리가
공중에게 무상으로 양도되어야 한다.
이러한 규정은 개별적인 저작물에 대한 저작권자의 권리를 말소시키려는 것이 아니라
2차적 프로그램으로부터 반복적으로 파생되거나 이러한 프로그램들을 모아 놓은 배포
본에 대해서 본 라이센스의 규정들을 동일하게 적용하기 위한 것이다. 프로그램(또는
2차적 프로그램)들을 단순히 저장하거나 배포할 목적으로 함께 구성해 놓은 경우는 이
들이 파생적 저작물을 생성하지 않는 한 본 라이센스에 의해서 관리된다.
제 3 항. 피양도자는 다음의 조항 중 하나를 만족시키는 조건에 한해서 제 1 항과 제
2 항의 규정에 따라 프로그램(또는 제 2 항의 규정에 의한 2 차적 프로그램)을 목적
코드나 실행 형태로 복제해서 배포할 수 있다.
* a) 목적 코드나 실행 형태에 해당하는 소스 코드의 전부를 제 1 항과 제 2 항의 규
정에 따라서 컴퓨터가 입력받거나 번역할 수 있는 형태로 소프트 웨어의 배포를 위해
서 일반적으로 사용되는 정보 매체를 통해서 함께 제 공해야 한다.
* b) 목적 코드나 실행 형태에 해당하는 소스 코드의 전부를 최소한 3년 이상 유지될
수 있는 인쇄물의 형태로 제 1 항과 제 2 항의 규정에 따라서 소프트웨어의 배포를 위
해서 일반적으로 사용되는 정보 매체를 통해서 제작 실비에 준하는 비용만을 부과해서
공중에게 양도될 수 있도록 함께 제공해야 한다.
* c) 목적 코드나 실행 형태에 해당하는 소스 코드의 전부를 취득할 수 있는 방법에
대한 정보를 함께 제공해야 한다(이 항목은 비영리적인 배포와 항목 b)에 의해서 목적
코드나 실행 형태의 배포본을 제공할 때에 한해 서 적용될 수 있다)
저작물에 대한 소스 코드란 해당 저작물을 개작하기 위해서 일반적으로 선호되는 표현
형식을 의미하고 실행물에 대한 소스 코드란 올바르게 실행되기 위해서 필요한 모듈과
인터페이스 정의 파일, 컴파일과 설치를 위해서 필요한 스크립트 등을 모두 포함한다.
그러나, 컴파일러나 커널과 같은 운영체제의 주요 부분들에 대한 소스 코드나 바이너
리형태는 프로그램이 이러한 부분들과 직접 관계되지 않는 한 함께 제공하지 않아도
무관하다. 목적 코드나 실행 형태를 특정 장소로부터 복제할 수 있도록 허용하는 방식
으로 배포할 경우, 동일한 장소로부터 소스 코드를 복제할 수 있도록 허용하는 것은
피양도자에게 소스 코드를 목적 코드나 실행 형태와 함께 복제해 갈 것을 규정하지 않
았다 하더라도 소스 코드를 함께 배포하는 것으로 간주한다.
제 4 항. 본 라이센스에 의해서 명시적으로 프로그램을 양도받지 않았다면 양도받은
프로그램에 대한 복제와 개작, 별도의 라이센스 설정과 배포 행위 둥을 할 수 없다.
이와 관련된 어떠한 행위도 법률적으로 무효이며 본 라이센스에서 규정하고 있는 사용
상의 모든 권리는 자동적으로 소멸된다. 단, 본 라이센스의 규정에 의하지 않고 양도
받은 프로그램이라 하더라도 이를 명시적인 라이센스 양도 규정에 따라 다시 배포했을
경우, 프로그램을 다시 양도받은 제 3의 피양도자는 본 라이센스를 준수하는 조건하에
서 사용상의 권리를 유지할 수 있다.
제 5 항. 피양도자는 프로그램의 양도에 관한 본 라이센스에 서명하지 않음으로써 본
라이센스의 규정들을 받아들이지 않을 수 있다. 이 경우, 피양도자에게는 프로그램에
대한 단순한 사용만이 허용되며 프로그램과 2차적 프로그램에 대한 개작과 배포 행위
는 허용되지 않는다. 이는 피양도자가 라이센스에 서명하지 않음으로써 발생된 법률적
금지 사항이다. 따라서, 프로그램(또는 2차적 프로그램)을 개작하거나 배포하는 행위
는 복제와 개작, 배포에 관한 본 라이센스의 규정과 조건들을 모두 받아들이겠다는 묵
시적인 동의로 간주한다.
제 6 항. 피양도자에 의해서 프로그램(또는 2차적 프로그램)이 반복적으로 배포될 경
우, 각 단계에서의 피양도자는 본 라이센스의 규정에 의한 프로그램의 복제와 개작,
배포에 대한 권한을 최초의 프로그램 양도자로부터 양도받은 것으로 자동적으로 간주
된다. 프로그램(또는 2차적 프로그램)을 양도할 때는 피양도자의 권리를 제한할 수 있
는 어떠한 사항도 별항으로 첨가할 수 없으며 그 누구도 본 라이센스의 규정들을 준수
하도록 강제할 수 없다.
제 7 항. 법원의 판결이나 특허권 침해에 대한 주장 또는 특허 문제에 국한되지 않는
그 밖의 다른 이유들로 인해서 본 라이센스의 규정에 배치되는 사안이 발생한다 하더
라도 본 라이센스에 배치되는 규정들이 본 라이센스에 대한 실행 상의 우선권을 갖게
되지는 않는다. 따라서, 법원의 명령이나 합의 등에 의해서 본 라이센스에 위배되는
사항들이 부과된다 하더라도 본 라이센스의 규정들을 함께 충족시키면서 해당 프로그
램을 배포할 수 없다면 이 프로그램의 배포는 금지된다. 예를 들면, 특정 특허 관련
라이센스가 직접 또는 간접적인 양도 방법에 의해서 프로그램을 무상으로 배포하는 것
을 허용하지 않는다면 이 프로그램은 본 라이센스의 규정에 의해서 관리되는 프로그램
들과 함께 배포될 수 없다. 특정 상황에서 본 조항의 일부분이 적용될 수 없는 경우에
는 해당 부분을 제외한 나머지 부분들을 적용시키며 본 조항의 전부를 적용시키기 위
해서는 다른 상황과 조건들이 필요하다. 본 조항의 목적은 특허나 재산권 침해 등의
행위를 조장하거나 해당 권리를 인정하지 않으려는 것이 아니라 GNU General Public
License의 실제적인 적용을 통해서 자유 소프트웨어의 배포 체계를 통합적으로 보호하
기 위한 것이다. 많은 사람들이 배포 체계에 대한 신뢰있는 지원을 계속해 줌으로써
소프트웨어의 다양한 분야에 많은 공헌을 해 주었다. 소프트웨어를 어떠한 배포 체계
를 통해서 배포할 것인가를 결정하는 것은 전적으로 저작자와 기증가들의 의지에 달려
있지 일반 사용자들이 강요할 수 있는 문제는 아닌 것이다. 본 조항은 계속되는 본 라
이센스의 내용들을 통해서 중요하게 취급되고 있는 점들을 보다 명확하게 설명하는 데
도움이 될 것이다.
제 8 항. 특허권과 저작권의 법적 처리 방식에 의해서 특정 국가에서 프로그램의 배포
와 사용이 함께 또는 개별적으로 금지될 경우, 본 라이센스에 의해서 프로그램을 공개
한 원저작자는 문제가 발생되지 않는 국가에 한해서 이를 배포한다는 배포상의 지역적
제한 조건을 설정할 수 있으며 이러한 사항은 본 라이센스의 일부로 간주된다.
제 9 항. 자유 소프트웨어 재단은 GNU General Public License를 개정하거나 갱신할
수 있다. 개정되거나 변동되는 사항은 새로운 문제와 관심에 따라서 세부적으로 조정
되겠지만 그 근본 정신은 바뀌지 않을 것이다.
GNU General Public License의 모든 버전은 다른 버전 번호로 구별될 것이다. 양도받
은 프로그램이 특정 버전의 라이센스를 명시하고 있다면 해당 버전 또는 그 이후의
라이센스가 적용되며 버전을 명시하지 않은 경우는 어떠한 버전의 라이센스를 적용해
도 무방하다.
제 10 항. 프로그램의 일부를 본 라이센스와 배포 기준이 다른 자유 프로그램과 함께
배포할 경우, 해당 프로그램의 저작자로부터 서면을 통한 승인을 받아야 한다. 자유
소프트웨어 재단이 저작권을 갖고 있는 소프트웨어를 사용하기 위해서는 자유 소프트
웨어 재단의 승인을 얻어야 한다. 자유 소프트웨어 재단은 승인 요건에 대해서 예외
규정을 둘 수 있다. 자유 소프트웨어 재단은 자유 소프트웨어의 2차적 저작물들을 모
두 자유로운 상태로 유지시키려는 목적과 소프트웨어의 일반적인 공유와 재활용을 증
진시키려는 기준에 근거해서 승인 여부를 결정할 것이다.
보증의 결여
제 11 항. 본 라이센스에 의한 프로그램은 무상으로 양도되므로 관련 법이 허용하는
한도 내에서 어떠한 형태의 보증도 제공하지 않는다. 단, 프로그램의 저작권자와 제
3의 배포자에 의해서 공동 또는 개별적으로 특정 목적에 대한 프로그램의 적합성 여
부를 검증하기 위한 경우나 상업적 판매에 따른 별도의 보증이 제공된다는 사항이 서
면으로 명시되어 있는 경우는 예외로 한다. 이 경우도 해당 프로그램 자체가 갖고 있
는 근원적인 보증의 결여를 제한할 수는 없다. 프로그램과 프로그램의 실행에 따라 발
생할 수 있는 위험은 모두 피양도자에게 인수되며 이에 따른 보수 및 복구를 위한 제
반 경비 또한 모두 피양도자가 부담한다.
제 12 항. 저작권자나 제 3의 배포자가 프로그램의 손상 가능성을 사전에 알고 있었다
하더라도 발생된 손실이 관련 법규에 의해서 보호되고 있거나 저작권자나 프로그램 자
체에 대한 보증을 제공하지 않는다는 전제로 프로그램과 개작된 프로그램을 함께 또는
개별적으로 공급한 배포자가 서면으로 별도의 보증을 설정한 경우가 아니라면 프로그
램의 사용이나 사용상의 미숙으로 인해서 발생된 손실은 모두 피양도자의 책임이다.
발생된 손실의 일반성이나 특수성 뿐만 아니라 원인의 우발성 및 필연성도 고려되지
않는다.
복제와 개작, 배포에 관한 조건과 규정의 끝.
규정들을 실무에 적용하는 방법
개발한 프로그램이 보다 많은 사람들에게 유용하게 사용되기를 원한다면 그 프로그램
이 본 라이센스의 규정에 따라서 자유롭게 수정되고 배포될 수 있도록 자유 소프트웨
어로 만드는 것이 최선의 방법이다.
다음과 같은 사항들을 프로그램에 첨가함으로써 해당 프로그램을 자유 소프트웨어로
만들 수 있다. 프로그램에 대한 보증을 제공하지 않는다는 사실을 가장 효과적으로 전
달할 수 있는 방법은 소스 코드가 포함되어 있는 모든 파일의 시작 부분에 이러한 사
항들을 명시하는 것이다. 각각의 파일들은 최소한 저작권과 GPL을 취득할 수 있는 방
법을 명시해야만 한다.
프로그램의 이름과 용도에 대한 설명을 위해서 한 줄을 사용한다.
Copyright (C) 19yy 프로그램 저작자의 이름
이 프로그램은 자유 소프트웨어이다. 소프트웨어의 피양도자는 자유 소프트웨어 재단
의 GNU General Public License의 규정에 의해서 이 프로그램을 개작된 2차적 프로그
램과 함께 또는 개별적으로 배포할 수 있다.
이 프로그램은 보다 유용하게 사용될 수 있으라는 희망에서 배포되고 있지만 제품에
대한 어떠한 형태의 보증도 하지 않는다. 보다 자세한 사항에 대해서는 GNU General
Public License를 참고하기 바란다.
GNU General Public License는 이 프로그램과 함께 제공된다.
만약, 이 문서가 누락되어 있다면 자유 소프트웨어 재단에 문의하기 바란다(자유 소프
트웨어 재단: Free Software Foundation, Inc., 59 Temple Place - Suite 330, Bost-
on, MA 02111-1307, USA)
또한, 프로그램 저작자와 서면 또는 전자 메일을 통해서 연락할 수 있는 정보를 기재
해야 한다.
만약, 이 프로그램이 명령어 입력 방식에 의한 대화형 구조를 택하고 있다면 프로그램
이 대화형 방식으로 실행되는 초기 상태에서 다음과 같은 주의 사항을 출력시켜야 한
다.
Gnomovision version 69, Copyright (C) 19yy프로그램 저작자의 이름
Gnomovision은 제품에 대한 어떠한 형태의 보증도 제공되지 않는다.
보다 자세한 사항은 'show w' 명령어를 이용해서 관련된 사항을 출력해 보기 바란다.
본 프로그램은 자유 소프트웨어이며 특정 규정들을 만족시키는 조건하에서 재배포될
수 있다. 배포에 대한 해당 규정은 'show c' 명령어를 통해서 참조할 수 있다.
'show w'와 'show c'는 General Public License의 해당 부분을 참조하기 위한 가상의
명령어이다. 따라서, 이 명령어들은 마우스를 이용하거나 메뉴 방식을 구성하는 등의
프로그램에 적합한 여러 가지 형태으로 변형될 수 있을 것이다. 만약, 프로그램 저작
자가 학교나 기업과 같은 단체나 기관에 고용되어 있다면 프로그램의 자유로운 배포를
위해서 고용주나 해당 기관장으로 부터 프로그램에 대한 저작권을 포기한다는 동의를
얻어야 한다. 예를 들면, 다음과 같은 형식이 될 수 있다.
본사는 James Hacker에 의해서 작성된
'Gnomovision' 프로그램에 관계된 모든 저작권을 포기한다.
1989년 4월 1일
Yoyodye, Inc., 부사장: Ty Coon
서명: Ty Coon의 서명
본 라이센스는 자유 소프트웨어로 설정된 프로그램을 독점 소프트웨어와 함께 사용하
는 것을 허용하지 않는다. 만약, 작성된 프로그램이 라이브러리 서브루틴과 같은 프로
그램일 경우에는 이를 독점 소프트웨어 형태의 응용 프로그램과 함께 사용함으로써 보
다 효과적으로 활용될 수 있다고 생각할 수도 있을 것이다. 이러한 경우는 본 라이센
스 대신에 GNU Library General Public License를 사용함으로써 소기의 목적을 충족시
킬 수 있을 것이다.