1.1 감사말 ¶
1.2 Nota Bene ¶
2.1 커널 모듈이란 ¶
2.2 어떻게 모듈을 커널에 넣을 것인가? ¶
- softdog 혹은 ppp 같은 모듈이름
- char-major-10-30 같은 일반적인 아이덴티파이어
modprobe가 일반적인 아이덴티파이어를 전달 받는다면, 그것은 /etc/modules.conf 파일에서 그 문자열을 찾는다. 다음과 같은 알리어스 행을 찾는다면
alias char-major-10-30 softdog
일반적인 아이덴티파이어는 softdog.o라는 모듈을 참조한다는 사실을 알게 된다.
insmod /lib/modules/2.5.1/kernel/fs/fat/fat.o insmod /lib/modules/2.5.1/kernel/fs/msdos/msdos.o
혹은 “modprobe -a msdos”를 실행하자.
#This file is automatically generated by update-modules path[misc]=/lib/modules/2.4.?/local keep path[net]=~p/mymodules options mydriver irq=10 alias eth0 eepro
#으로 시작하는 행은 주석이며, 빈 행은 무시된다.
2.2.1 시작하기 전에 ¶
2.2.1.1 모듈 버전 ¶
2.2.1.2 X 사용하기 ¶
2.2.1.3 컴파일 문제와 커널 버전 ¶
3.1 Hello, World (part 1): The Simplest Module ¶
/* hello-1.c - The simplest kernel module. */ #include <linux/module.h> /* 모든 모듈에 필요 */ #include <linux/kernel.h> /* KERN_ALERT에 필요 */
int init_module(void)
{
printk(“<1>Hello world 1.\n“);
// 0이 아닌 값을 리턴하는 것은 init_module이 실패한 것을 의미한다. 고로 모듈은 로드되지 못한다.
return 0;
}
void cleanup_module(void)
{
printk(KERN_ALERT “Goodbye world 1.\n“);
}
3.1.1 printk()함수 소개 ¶
3.2 커널 모듈 컴파일 ¶
- -c: 커널 모듈은 독립적으로 실행 가능한 파일이 아니며, insmod를 사용하여 실행 시간 중에 커널에 링크되는 오브젝트 파일 이다. 결론적으로 모듈은 -c옵션을 주고 컴파일 해야 한다.
- -O2: 커널은 인라인 함수를 주로 사용하기 때문에 모듈은 이 옵션 플래그를 사용해야 한다. 이 옵션을 사용하지 않은 경우 어떤 어셈블러 매크로는 함수 호출 시 정상적으로 작동하지 않을 것이다. insmod는 커널에서 원하는 함수를 찾지 못하고 결국 모듈의 적재는 실패할 것이다.
- -W -Wall:프로그램에서의 실수는 당신의 시스템을 다운 시킬 수도 있다. 컴파일러 경고 기능은 항상 켜둬라, 이것은 모듈 컴파일 뿐 아니라 당신의 모든 컴파일 행위에 적용된다.
- -isystem /lib/modules/uname -r/build/include: 컴파일 대상이 되는 커널의 헤더를 사용해야만 한다. 기본적인 /usr/include/linux를 사용하는 것은 작동하지 않을 것이다.
- -DKERNEL:이 심볼을 정의 하는 것은 헤더 파일에 이 코드가 유저 프로세스로 동작하지 않고 커널 모드에서 작동한다는 사실을 알린다.
- -DMODULE:이 심볼은 헤더 파일에 커널 모듈을 위한 올바른 정의를 하게 한다.
module.h를 포함 시킬 때 –W –Wall이 유발시키는 사용하지 않는 변수에 대한 경고를 GCC로 하여금 막기 위해, -I 옵션 대신 GCC의 -isystem옵션을 사용해야 한다. GCC-3.0하에서 –isystem을 사용함으로써 커널 헤더를 주의 깊게 다루어지고 경고는 절재 된다. -I옵션을 대신 사용한다면 (gcc 2.9x하에서 -isystem옵션을 사용하는 것도 ) 사용하지 않는 변수에 대한 경고가 출력될 것이다. 그렇다면 무시하면 된다.
TARGET := hello-1 WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes INCLUDE := -isystem /lib/modules/`uname -r`/build/include CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE} CC := gcc-3.0
${TARGET}.o: ${TARGET}.c
.PHONY: clean
clean:
rm -rf {TARGET}.o
3.3 Hello World (part 2) ¶
/* hello-2.c - Demonstrating the module_init() and module_exit() macros. This is the preferred over using init_module() and cleanup_module(). hello-2.c – module_init()와 module_exit()매크로의 예. 이것은 init_module()과 cleanup_moduel() 보다 낫다. */ #include <linux/module.h> // Needed by all modules #include <linux/kernel.h> // Needed for KERN_ALERT#include <linux/init.h> // Needed for the macrosstatic
int hello_2_init(void)
{
printk(KERN_ALERT “Hello, world 2\n“);
return 0;
}
static void hello_2_exit(void)
{
printk(KERN_ALERT “Goodbye, world 2\n“);
}
module_init(hello_2_init);
module_exit(hello_2_exit);
WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes INCLUDE := -isystem /lib/modules/`uname -r`/build/include CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE} CC := gcc-3.0 OBJS := ${patsubst %.c, %.o, ${wildcard *.c}}
all: ${OBJS}
.PHONY: clean
clean:
rm -rf *.o
3.4 Hello World (part 3): The __init and __exit Macros ¶
/* hello-3.c - Illustrating the __init, __initdata and __exit macros. */ #include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_ALERT */ #include <linux/init.h> /* Needed for the macros */ static int hello3_data __initdata = 3;
static int __init hello_3_init(void)
{
printk(KERN_ALERT “Hello, world %d\n“, hello3_data);
return 0;
}
static void __exit hello_3_exit(void)
{
printk(KERN_ALERT “Goodbye, world 3\n“);
}
module_init(hello_3_init);
module_exit(hello_3_exit);
__initfunction(int init_module(void)) { printk(KERN_ALERT "Hi there.\n"); return 0; }
이 메크로는 __init와 같은 동작을 한다. 그러나 __init에 비해 매우 적게 사용된다. 그저, 커널에서 당신이 보았을 수도 있기에 언급한 것이다. 2.4.18 버전의 커널에 __initfunction()은 38군데에서 보이고, 2.4.20에서는 37군데에서만 보인다. 그러나 당신의 코드에서는 사용하지 말 것을 권한다.
3.5 Hello World (prt 4): 저작권과 모듈 문서 ¶
# insmod hello-3.o Warning: loading hello-3.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modules Hello, world 3 Module hello-3 loaded, with warnings
/* hello-4.c - Demonstrates module documentation. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #define DRIVER_AUTHOR "Peiter Jay Salzman <p@dirac.org>" #define DRIVER_DESC "A sample driver"
int init_hello_3(void);
void cleanup_hello_3(void);
static int init_hello_4(void){
printk(KERN_ALERT “Hello, world 4\n“);
return 0;
}
static void cleanup_hello_4(void)
{
printk(KERN_ALERT “Goodbye, world 4\n“);
}
module_init(init_hello_4);
module_exit(cleanup_hello_4);
/* You can use strings, like this: */
MODULE_LICENSE(“GPL”); // Get rid of taint message by declaring code as GPL..
/* Or with defines, like this: */
MODULE_AUTHOR(DRIVER_AUTHOR); // Who wrote this module?
MODULE_DESCRIPTION(DRIVER_DESC); // What does this module do?
/* This module uses /dev/testdevice.
The MODULE_SUPPORTED_DEVICE macro might be used in the future to help automatic configuration of modules,
but is currently unused other than for documentation purposes.
*/
MODULE_SUPPORTED_DEVICE(“testdevice”);
3.6 커맨드 라인 인자 모듈에 넘기기 ¶
int myint = 3; char *mystr;
MODULE_PARM (myint, “i”);
MODULE_PARM (mystr, “s”);
배열 역시 지원된다. MODULE_PARM에 선행되는 integer 값은 배열의 최대 길이를 알려준다. ‘-‘에 의해 분리된 두 번호는 최대 최소 값을 알려 준다. 예를 들어 최소값 2과 최대값 4를 갖는 short형의 배열은 다음과 같이 선언될 수 있다.
int myshortArray[4]; MODULE_PARM (myintArray, "2-4i");
어떤 IO포트 혹은 IO 메모리가 사용되는지와 같이 모듈의 변수 값은 디폴트 값으로 세팅 되는 것이 좋다. 만약 변수가 기본값을 갖는다면 자동인식(차후에 설명이 나옴)를 수행 할 수 있다. 그렇지 않다면 현재의 값을 유지한다. 이것은 앞으로도 명확하다. 지금은 모듈로 인자를 넘기는 것만을 예로 들겠다.
/* hello-5.c - Demonstrates command line argument passing to a module. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h>
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Peiter Jay Salzman”);
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = “blah”;
MODULE_PARM (myshort, “h”);
MODULE_PARM (myint, “i”);
MODULE_PARM (mylong, “l”);
MODULE_PARM (mystring, “s”);
static int __init hello_5_init(void)
{
printk(KERN_ALERT “Hello, world 5\n=============\n“);
printk(KERN_ALERT “myshort is a short integer: %hd\n“, myshort);
printk(KERN_ALERT “myint is an integer: %d\n“, myint);
printk(KERN_ALERT “mylong is a long integer: %ld\n“, mylong);
printk(KERN_ALERT “mystring is a string: %s\n“, mystring);
return 0;
}
static void __exit hello_5_exit(void)
{
printk(KERN_ALERT “Goodbye, world 5\n“);
}
module_init(hello_5_init);
module_exit(hello_5_exit);
3.7 다중 파일 모듈 ¶
- 하나의 파일을 제외하고 모든 소스 파일에 #define NO_VERSION 이 필요하다. module.h는 일반적으로 kernel_version, 모듈이 컴파일되는 커널의 전역 변수를 포함하기 때문에 이것은 중요하다. 만약 version.h가 필요하다면 그것을 포함시켜야 한다. 왜냐하면 NO_VERSION를 가진 module.h 은 이것을 해주지 않는다.
- 일반적으로 모든 소스 파일을 컴파일 해야 한다.
- 모든 오브젝트 파일을 하나로 컴파일 해야 한다. X86환경에서는 ld -m elf_i386 -r -o <module name.o> <1st src file.o> <2nd src file.o>를 사용한다.
다음은 앞서 언급한 것에 대한 예제다.
/* start.c - Illustration of multi filed modules */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ int init_module(void) { printk("Hello, world - this is the kernel speaking\n"); return 0; }
다음 파일
/* stop.c - Illustration of multi filed modules */ #if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) #include <linux/modversions.h> /* Will be explained later */ #define MODVERSIONS #endif #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ #define __NO_VERSION__ /* It's not THE file of the kernel module */ #include <linux/version.h> /* Not included by module.h because of __NO_VERSION__ */
void cleanup_module()
{
printk(“<1>Short is the life of a kernel module\n“);
}
CC=gcc MODCFLAGS := -O -Wall –DMODULE -D__KERNEL__
hello.o: hello2_start.o hello2_stop.o
ld -m elf_i386 -r -o hello2.o hello2_start.o hello2_stop.o
start.o: hello2_start.c
${CC} ${MODCFLAGS} -c hello2_start.c
stop.o: hello2_stop.c
${CC} ${MODCFLAGS} -c hello2_stop.c
4.1.1 모듈은 어떻게 시작하고 끝나는가 ¶
4.1.2 모듈에 사용 가능한 함수들 ¶
#include <stdio.h> int main(void) { printf("hello"); return 0; }
gcc -Wall -o hello hello.c. strace hello을 실행해보자. 놀랐는가? 당신이 보는 각각의 행이 시스템 콜에 해당된다. strace[1]는 어떤 시스템콜로 프로그램이 구성됬는가, 어떤 아규먼트가 넘겨지는가, 무엇을 리턴하는가를 보여주는 간단한 프로그램이다. 이 프로그램은 프로그램이 어떤 파일에 접근하는가와 같은 것들을 설명하는데 매우 중요한 도구다. 끝 부분에 write(1, “hello”, 5hello)라는 부분을 볼수 있을 것이다. 이것이 printf()의 정체다. 대부분의 사람들은 파일 입출력에 fopen(), fputs(), fclose()와 같은 라이브러리 함수를 사용하기 때문에, write()에 익숙하지 않을 것이다. 만약 당신도 그렇다면 , man 2 write을 보라. 매뉴얼 페이지의 2번째 섹션은 kill(), read()와 같은 시스템 콜에 할당되있다. 3번째 섹션은 독자들이 익숙한 cosh(), random() 등의 라이브러리 함수에 할당 되어 있다.
4.1.3 사용자 공간 vs 커널 공간 ¶
4.1.4 이름 공간 ¶
4.1.5 코드 영역 ¶
4.1.6 장치 드라이버 ¶
4.1.6.1 장치 주 번호와 장치 부 번호 ¶
# ls -l /dev/hda[1-3] brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1 brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3
콤마에 의해 구분된 번호의 열을 주시하자 첫 번째 번호를 장치 주 번호라 부르며 두 번째 번호는 부 번호라 부른다. 주 번호는 드라이버가 어떤 하드웨어에 엑세스하는지 알려준다. 각 드라이버는 유일한 주 번호를 할당 받으며, 동일한 주 번호를 갖는 모든 디바이스 파일은 같은 드라이버에 의해 컨트롤 된다. 위의 주 번호가 모두 3인 것은, 그들이 같은 드라이버에 의해 콘트롤 되기 때문이다.
crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0 crw-r----- 1 root dial 4, 65 Nov 17 10:26 /dev/ttyS1 crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2 crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3
만일 어떤 주 번호가 할당됐는가 알기를 원한다면, /usr/src/linux/Documentation/devices.txt 파일을 참고하기 바란다.
% ls -l /dev/fd0 /dev/fd0u1680 brwxrwxrwx 1 root floppy 2, 0 Jul 5 2000 /dev/fd0 brw-rw---- 1 root floppy 2, 44 Jul 5 2000 /dev/fd0u1680
이제 두 개의 디바이스 파일을 보고 그들이 블록 디바이스라는 것과 동일한 드라이버에 의해 처리된다는 것을 즉시 알 수 있을 것이다. 두 개의 디바이스 파일은 당신의 플로피 드라이브를 나타낸다는 것을 알 것이다. 그런데 당신은 하나의 플로피 드라이버만을 가지고 있지 않은가. 왜 두 개인가? 하난 1.44MB의 플로피 드라이브를 나타낸다. 다른 하나는 흔히 말하는 ‘superformatted’ 드라이브에 해당되는 1.68MB의 동일한 저장 장치다. 표준 포맷 플로피 보다 많은 양의 데이터를 저장하는 것이다. 이것이 동일한 실제 하드웨어에 서로 다른 장치 부 번호를 갖는 경우다. 우리의 논의에서 ‘하드웨어’라고 하는 단어가 상당히 추상적이라는 것을 알기 바란다.
5.1.1 file_operations 구조체 ¶
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); };
struct file_operations fops = { read: device_read, write: device_write, open: device_open, release: device_release; }
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
5.1.2 file 구조체 ¶
5.1.3 Registering A Device ¶
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
5.1.4 장치의 등록해제 ¶
- MOD_INC_USE_COUNT:카운터를 증가시킨다.
- MOD_DEC_USE_COUNT:카운터를 감소시킨다.
- MOD_IN_USE:카운터를 보여준다.
카운터를 정확하기 유지시키는 것이 중요하다. 만약 정확한 카운터의 수를 잃어 버린다면, 모듈을 해제(unload)하는 것은 불가능하다. 부팅을 다시 하자. 모듈 개발을 하는 동안 언젠가는 닥칠 일이다.
5.1.5 chardev.c ¶
/* chardev.c: 얼마나 많이 디바이스 파일에 접근했는지 알려주는 모듈 */ #if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) #include <linux/modversions.h> #define MODVERSIONS #endif #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <asm/uaccess.h> /* for put_user */
/* Prototypes – this would normally go in a .h file */
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME “chardev” /* /proc/devices에 나타나는 장치 이름 */
#define BUF_LEN 80 /* 장치로부터 메시지의 최대 길이 */
/* 정적 변수로 전역변수 선언 */
tatic int Major; /* 주장치 번호 */
static int Device_Open = 0; /* 장치가 열렸는가? 중복사용 방지 */
/* access to the device */
static char msg[BUF_LEN]; /* 요청이 있을 때 장치가 보내는 메시지 */
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
/* Functions */
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk (“Registering the character device failed with %d\n“, Major);
return Major;
}
printk(“<1>I was assigned major number %d. To talk to\n“, Major);
printk(“<1>the driver, create a dev file with\n“);
printk(“‘mknod /dev/hello c %d 0′.\n“, Major);
printk(“<1>Try various minor numbers. Try to cat and echo to\n“);
printk(“the device file.\n“);
printk(“<1>Remove the device file and module when done.\n“);
return 0;
}
void cleanup_module(void)
{
/* Unregister the device */
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0) printk(“Error in unregister_chrdev: %d\n“, ret);
}
/* Methods */
/* “cat /dev/mycharfile” 처럼 프로세스가 디바이스 파일을 열려고 할 때 호출됨 */
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open) return –EBUSY;
Device_Open++;
sprintf(msg,“I already told you %d times Hello world!\n“, counter++);
msg_Ptr = msg;
MOD_INC_USE_COUNT;
return SUCCESS;
}
/* 디바이스 파일이 닫힐 때 호출됨. */
static int device_release(struct inode *inode, struct file *file)
{
Device_Open –; /* We’re now ready for our next caller */
/* 사용 카운트 감소 혹은 한번 열어본 파일이 아니라면 모듈을 제거할 수 없음 */
MOD_DEC_USE_COUNT;
return 0;
}
/* 이미 열린 장치 파일에서 무엇인가 읽으려 할 때 호출 */
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
/* 버퍼에 실제 쓰여진 바이트 수 */
int bytes_read = 0;
/* 메시지의 끝에 메시지의 끝임을 알리기 위해 0리턴 */
if (*msg_Ptr == 0) return 0;
/* 버퍼에 실제 데이터를 입력 */
while (length && *msg_Ptr) {
/* 버퍼는 커널 세그먼트가 아니고 사용자 세그먼트다
* 할당은 일어 나지 않는다. 커널 데이터영역에서 사용자 영역으로
* 데이터를 복사하는 put_user() 사용
*/
put_user(*(msg_Ptr++), buffer++);
length–;
bytes_read++;
}
/* 읽혀진 데이터의 바이트 수를 버퍼에 기록 */
return bytes_read;
}
/* Called when a process writes to dev file: echo “hi” > /dev/hello */
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t *off)
{
printk (“<1>Sorry, this operation isn’t supported.\n“);
return –EINVAL;
}
5.1.6 여러 커널 버전을 위한 모듈 작성 ¶
#if LINUX_KERNEL_VERSION >= KERNEL_VERSION(2,2,0) #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif
6.1 /proc File System ¶
/* procfs.c - create a "file" in /proc */ #include <linux/kernel.h> /* 커널 작업 */ #include <linux/module.h> /* 모듈 작업 */ /* Deal with CONFIG_MODVERSIONS */ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include <linux/modversions.h> #endif /* proc fs를 다룬다 */ #include <linux/proc_fs.h> /* 2.2.3 버전 이전에는 다음의 매크로가 없다. 필요하다면 여기서 정의 한다.*/ #ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif
/* proc fs에 데이터 전달.
Argument =========
1. 데이터가 삽입되는 버퍼, 만일 사용하기 원한다면
2. 문자열에 대한 포인터, 만일 커널이 버퍼를 할당하기 원하지 않는 경우 사용
3. 파일에서 현재 위치
4. 첫 인자 버퍼의 크기
5. 0(미래의 사용을 위해?).
Usage and Return Value ======================
만일 자신만의 버퍼를 원한다면 내가 한 것처럼 두번 째 인자에 넣고 버퍼에서 사용한 바이트 수를 리턴하라
리턴 값이 NULL인것은 더 이상의 정보가 없음을 의미한다(end of file). 또한 음수인 경우 에러 상황이다.
For More Information ====================
The way I discovered what to do with this function
내가 이 함수에서 무엇인가를 발견한 방법은 문서를 읽는 것이 아니고, 사용되는 코드를 읽는 것이었다.
proc_dir_entry구조체의 get_info필드를 어디다 쓰는가를 살펴봤다.
또한 /fs/proc/array.c 파일이 사용되는 것을 살펴 본 것이다.
If something is unknown about the kernel, this is
무언가 커널에서 잘 모르는 부분이 있다면 이런 방식이 일반적으로 행해진다.
리눅스에서 우리는 커널 코드를 얻을 수 있다는 것은 대단한 장점이다. 활용하라. */
int procfile_read(char *buffer,
char **buffer_location, off_t offset,
int buffer_length,
int zero)
{
int len; /* 실제 사용되는 바이트 수 */
/* 우리가 이 함수를 나갈 때도 이 스테틱 변수이기 때문에 메모리에 남겨 진다. */
static char my_buffer[80];
static int count = 1;
/* 우리(직역)는 한번에 모든 정보를 주기 때문에 유저(?직역)가 추가적인 정보를 요구할 때 우리의 대답은 NO다.
* 표준 라이브러리 함수는 커널이 더 이상 줄 정보가 없다고 말할 때까지, 혹은 표준 라이브러리 함수의 버퍼가 다 찰 때까지
* read 시스템 콜을 하기 때 문에 중요하다. */
if (offset > 0) return 0; /* Fill the buffer and get its length */
len = sprintf(my_buffer, “For the %d%s time, go away!\n“, count,
(count % 100 > 10 && count % 100 < 14) ? “th” : (count % 10 == 1) ? “st” : (count % 10 == 2) ? “nd” :
(count % 10 == 3) ? “rd” : “th” );
count++; /* 함수에 버퍼의 위치를 알린다. */
*buffer_location = my_buffer; /* Return the length */
return len;
}
struct proc_dir_entry Our_Proc_File =
{0, /* Inode 번호 –여기서는 무시한다. 이것은 proc_register[_dynamic]에 의해 채워진다. */
4, /* 파일의 이름 길이 */
“test”, /* 파일이름 */
S_IFREG | S_IRUGO, /* File mode – 일반 파일, 소유자, 그룹, 모든 사람들이 있을 수 있다.*/
1, /* 파일이 참고하는 디렉토리의 링크 개수*/
0, 0, /* uid, gid 루트에게 권한을 준다.*/
80, /* ls에 의해 보고되는 파일의 크기. */
NULL, /* link, remove등 inode상에 행해지는 함수- 우리는 아무것도 지원하지 않는다. */
procfile_read, /* 읽기 함수, 무엇인가를 읽으려 할 때 호출되는 함수 */
NULL /* 퍼미션, 소유권 등을 다루는 inode를 채우는 함수의 포인터 */ };
/* Initialize the module – register the proc file */
int init_module()
{
/* proc_register[_dynamic]이 성공하면 성공, 아니면 실패 */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
/* 구조체에서 그 값이 0이면 2.2버전에서는 proc_register()함수가 inode번호를 동적으로 할당한다.
* proc_register_dynamic()함수가 필요없게 된다. */
return proc_register(&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif
/* proc_root는 proc파일 시스템의 루트(/proc)
* 우리가 파일을 위치시키기 원하는 위치다.
*/
}
/* Cleanup – unregister our file from /proc */
void cleanup_module()
{
proc_unregister(&proc_root, Our_Proc_File.low_ino);
}
7.1 입력을 위한 /proc의 사용 ¶
/* procfs.c - create a "file" in /proc, which allows both input and output. */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ /* Necessary because we use proc fs */ #include <linux/proc_fs.h> /* In 2.2.3 /usr/include/linux/version.h includes a * macro for this, but 2.0.35 doesn't - so I add it * here if necessary. */ #ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) #include <asm/uaccess.h> /* for get_user and put_user */ #endif
/* The module’s file functions ********************** */
/* Here we keep the last message received, to prove
* that we can process our input */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];
/* Since we use the file operations struct, we can’t
* use the special proc output provisions – we have to
* use a standard read function, which is this function */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t module_output(
struct file *file, /* The file read */
char *buf, /* The buffer to put data to (in the
* user segment) */
size_t len, /* The length of the buffer */
loff_t *offset)
/* Offset in the file – ignore */
#else
static int module_output(
struct inode *inode, /* The inode read */
struct file *file, /* The file read */
char *buf, /* The buffer to put data to (in the
* user segment) */
int len)
/* The length of the buffer */
#endif
{
static int finished = 0;
int i;
char message[MESSAGE_LENGTH+30];
/* We return 0 to indicate end of file, that we have
* no more information. Otherwise, processes will
* continue to read from us in an endless loop. */
if (finished)
{
finished = 0;
return 0;
}
/* We use put_user to copy the string from the kernel’s
* memory segment to the memory segment of the process
* that called us. get_user, BTW, is
* used for the reverse. */
sprintf(message, “Last input:%s“, Message);
for(i=0; i<len && message[i]; i++)
put_user(message[i], buf+i);
/* Notice, we assume here that the size of the message
* is below len, or it will be received cut. In a real
* life situation, if the size of the message is less
* than len then we’d return len and on the second call
* start filling the buffer with the len+1’th byte of
* the message. */
finished = 1;
return i;
/* Return the number of bytes “read” */
}
/* This function receives input from the user when the
* user writes to the /proc file. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t module_input(
struct file *file, /* The file itself */
const char *buf, /* The buffer with input */
size_t length, /* The buffer’s length */
loff_t *offset) /* offset to file – ignore */
#else
static int module_input(
struct inode *inode, /* The file’s inode */
struct file *file, /* The file itself */
const char *buf, /* The buffer with the input */
int length) /* The buffer’s length */
#endif
{
int i;
/* Put the input into Message, where module_output
* will later be able to use it */
for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
get_user(Message[i], buf+i);
/* In version 2.2 the semantics of get_user changed,
* it not longer returns a character, but expects a
* variable to fill up as its first argument and a
* user segment pointer to fill it from as the its
* second.
*
* The reason for this change is that the version 2.2
* get_user can also read an short or an int. The way
* it knows the type of the variable it should read
* is by using sizeof, and for that it needs the
* variable itself. */
#else
Message[i] = get_user(buf+i);#endif Message[i] = ‘\0’;
/* we want a standard, zero
* terminated string */
/* We need to return the number of input characters
* used */
return i;
}
/* This function decides whether to allow an operation
* (return zero) or not allow it (return a non-zero
* which indicates why it is not allowed).
*
* The operation can be one of the following values:
* 0 – Execute (run the “file” – meaningless in our case)
* 2 – Write (input to the kernel module)
* 4 – Read (output from the kernel module)
*
* This is the real function that checks file
* permissions. The permissions returned by ls -l are
* for referece only, and can be overridden here.
*/
static int module_permission(struct inode *inode, int op)
{
/* We allow everybody to read from our module, but
* only root (uid 0) may write to it */
if (op == 4 || (op == 2 && current->euid == 0))
return 0;
/* If it’s anything else, access is denied */
return –EACCES;
}
/* The file is opened – we don’t really care about
* that, but it does mean we need to increment the
* module’s reference count. */
int module_open(struct inode *inode, struct file *file)
{
MOD_INC_USE_COUNT;
return 0;
}
/* The file is closed – again, interesting only because
* of the reference count. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
int module_close(struct inode *inode, struct file *file)
#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;
/* success */
#endif
}
/* Structures to register as the /proc file, with
* pointers to all the relevant functions.
*/
/* File operations for our proc file. This is where we
* place pointers to all the functions called when
* somebody tries to do something to our file. NULL
* means we don’t want to deal with something. */
static struct file_operations File_Ops_4_Our_Proc_File =
{ NULL, /* lseek */
module_output, /* “read” from the file */
module_input, /* “write” to the file */
NULL, /* readdir */
NULL, /* select */
NULL, /* ioctl */
NULL, /* mmap */
module_open, /* Somebody opened the file */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
NULL, /* flush, added here in version 2.2 */
#endif
module_close, /* Somebody closed the file */
/* etc. etc. etc. (they are all given in
* /usr/include/linux/fs.h). Since we don’t put
* anything here, the system will keep the default
* data, which in Unix is zeros (NULLs when taken as
* pointers). */
};
/* Inode operations for our proc file. We need it so
* we’ll have some place to specify the file operations
* structure we want to use, and the function we use for
* permissions. It’s also possible to specify functions
* to be called for anything else which could be done to
* an inode (although we don’t bother, we just put * NULL). */
static struct inode_operations Inode_Ops_4_Our_Proc_File =
{ &File_Ops_4_Our_Proc_File,
NULL, /* create */
NULL, /* lookup */
NULL, /* link */
NULL, /* unlink */
NULL, /* symlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* mknod */
NULL, /* rename */
NULL, /* readlink */
NULL, /* follow_link */
NULL, /* readpage */
NULL, /* writepage */
NULL, /* bmap */
NULL, /* truncate */
module_permission /* check for permissions */
};/* Directory entry */
static struct proc_dir_entry Our_Proc_File =
{ 0, /* Inode number – ignore, it will be filled by
* proc_register[_dynamic] */
7, /* Length of the file name */
“rw_test”, /* The file name */
S_IFREG | S_IRUGO | S_IWUSR,
/* File mode – this is a regular file which
* can be read by its owner, its group, and everybody
* else. Also, its owner can write to it.
*
* Actually, this field is just for reference, it’s
* module_permission that does the actual check. It
* could use this field, but in our implementation it
* doesn’t, for simplicity. */
1, /* Number of links (directories where the
* file is referenced) */
0, 0, /* The uid and gid for the file –
* we give it to root */
80, /* The size of the file reported by ls. */
&Inode_Ops_4_Our_Proc_File,
/* A pointer to the inode structure for
* the file, if we need it. In our case we
* do, because we need a write function. */
NULL
/* The read function for the file. Irrelevant,
* because we put it in the inode structure above */
};
/* Module initialization and cleanup ******************* */
/* Initialize the module – register the proc file */
int init_module()
{
/* Success if proc_register[_dynamic] is a success,
* failure otherwise */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
/* In version 2.2, proc_register assign a dynamic
* inode number automatically if it is zero in the
* structure , so there’s no more need for
* proc_register_dynamic */
return proc_register(&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif
}
/* Cleanup – unregister our file from /proc */
void cleanup_module()
{
proc_unregister(&proc_root, Our_Proc_File.low_ino);
}
8.1 Talking to Device Files (writes and IOCTLs) ¶
/* chardev.c - Create an input/output character device */
#include <linux/kernel.h> /* We’re doing kernel work */
#include <linux/module.h> /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
/* For character devices */
/* The character device definitions are here */
#include <linux/fs.h>
/* A wrapper which does next to nothing at
* at present, but may help for compatibility
* with future versions of Linux */
#include <linux/wrapper.h>
/* Our own ioctl numbers */
#include “chardev.h”
/* In 2.2.3 /usr/include/linux/version.h includes a
* macro for this, but 2.0.35 doesn’t – so I add it
* here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h> /* for get_user and put_user */
#endif
#define SUCCESS 0
/* Device Declarations ******************************** */
/* The name for our device, as it will appear in
* /proc/devices */
#define DEVICE_NAME “char_dev”
/* The maximum length of the message for the device */
#define BUF_LEN 80
/* Is the device open right now? Used to prevent
* concurent access into the same device */
static int Device_Open = 0;
/* The message the device will give when asked */
static char Message[BUF_LEN];
/* How far did the process reading the message get?
* Useful if the message is larger than the size of the
* buffer we get to fill in device_read. */
static char *Message_Ptr;
/* This function is called whenever a process attempts
* to open the device file */
static int device_open(struct inode *inode,
struct file *file)
{
#ifdef DEBUG
printk (“device_open(%p)\n“, file);
#endif
/* We don’t want to talk to two processes at the
* same time */
if (Device_Open)
return –EBUSY;
/* If this was a process, we would have had to be
* more careful here, because one process might have
* checked Device_Open right before the other one
* tried to increment it. However, we’re in the
* kernel, so we’re protected against context switches.
*
* This is NOT the right attitude to take, because we
* might be running on an SMP box, but we’ll deal with
* SMP in a later chapter.
*/
Device_Open++;
/* Initialize the message */
Message_Ptr = Message;
MOD_INC_USE_COUNT;
return SUCCESS;
}
/* This function is called when a process closes the
* device file. It doesn’t have a return value because
* it cannot fail. Regardless of what else happens, you
* should always be able to close a device (in 2.0, a 2.2
* device file could be impossible to close).
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int device_release(struct inode *inode,
struct file *file)
#else
static void device_release(struct inode *inode,
struct file *file)
#endif
{
#ifdef DEBUG
printk (“device_release(%p,%p)\n“, inode, file);
#endif
/* We’re now ready for our next caller */
Device_Open –;
MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
return 0;
#endif
}
/* This function is called whenever a process which
* has already opened the device file attempts to
* read from it. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_read(
struct file *file,
char *buffer, /* The buffer to fill with the data */
size_t length, /* The length of the buffer */
loff_t *offset) /* offset to the file */
#else
static int device_read(
struct inode *inode,
struct file *file,
char *buffer, /* The buffer to fill with the data */
int length) /* The length of the buffer
* (mustn’t write beyond that!) */
#endif
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;
#ifdef DEBUG
printk(“device_read(%p,%p,%d)\n“, file, buffer, length);
#endif
/* If we’re at the end of the message, return 0
* (which signifies end of file) */
if (*Message_Ptr == 0)
return 0;
/* Actually put the data into the buffer */
while (length && *Message_Ptr) {
/* Because the buffer is in the user data segment,
* not the kernel data segment, assignment wouldn’t
* work. Instead, we have to use put_user which
* copies data from the kernel data segment to the
* user data segment. */
put_user(*(Message_Ptr++), buffer++);
length –;
bytes_read ++;
}
#ifdef DEBUG
printk (“Read %d bytes, %d left\n“, bytes_read, length);
#endif
/* Read functions are supposed to return the number
* of bytes actually inserted into the buffer */
return bytes_read;
}
/* This function is called when somebody tries to
* write into our device file. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_write(struct file *file,
const char *buffer,
size_t length,
loff_t *offset)
#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;
/* Again, return the number of input characters used */
return i;
}
/* This function is called whenever a process tries to
* do an ioctl on our device file. We get two extra
* parameters (additional to the inode and file
* structures, which all device functions get): the number
* of the ioctl called and the parameter given to the
* ioctl function.
*
* If the ioctl is write or read/write (meaning output
* is returned to the calling process), the ioctl call
* returns the output of this function.
*/
int device_ioctl(
struct inode *inode,
struct file *file,
unsigned int ioctl_num,/* The number of the ioctl */
unsigned long ioctl_param) /* The parameter to it */
{
int i;
char *temp;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
char ch;
#endif
/* Switch according to the ioctl called */
switch (ioctl_num) {
case IOCTL_SET_MSG:
/* Receive a pointer to a message (in user space)
* and set that to be the device’s message. */
/* Get the parameter given to ioctl by the process */
temp = (char *) ioctl_param;
/* Find the length of the message */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
get_user(ch, temp);
for (i=0; ch && i<BUF_LEN; i++, temp++)
get_user(ch, temp);
#else
for (i=0; get_user(temp) && i<BUF_LEN; i++, temp++)
;
#endif
/* Don’t reinvent the wheel – call device_write */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
device_write(file, (char *) ioctl_param, i, 0);
#else
device_write(inode, file, (char *) ioctl_param, i);
#endif
break;
case IOCTL_GET_MSG:
/* Give the current message to the calling
* process – the parameter we got is a pointer,
* fill it. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
i = device_read(file, (char *) ioctl_param, 99, 0);
#else
i = device_read(inode, file, (char *) ioctl_param, 99);
#endif
/* Warning – we assume here the buffer length is
* 100. If it’s less than that we might overflow
* the buffer, causing the process to core dump.
*
* The reason we only allow up to 99 characters is
* that the NULL which terminates the string also
* needs room. */
/* Put a zero at the end of the buffer, so it
* will be properly terminated */
put_user(‘\0’, (char *) ioctl_param+i);
break;
case IOCTL_GET_NTH_BYTE:
/* This ioctl is both input (ioctl_param) and
* output (the return value of this function) */
return Message[ioctl_param];
break;
}
return SUCCESS;
}
/* Module Declarations *************************** */
/* This structure will hold the functions to be called
* when a process does something to the device we
* created. Since a pointer to this structure is kept in
* the devices table, it can’t be local to
* init_module. NULL is for unimplemented functions. */
struct file_operations Fops = {
NULL, /* seek */
device_read,
device_write,
NULL, /* readdir */
NULL, /* select */
device_ioctl, /* ioctl */
NULL, /* mmap */
device_open,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
NULL, /* flush */
#endif
device_release /* a.k.a. close */
};
/* Initialize the module – Register the character device */
int init_module()
{
int ret_val;
/* Register the character device (atleast try) */
ret_val = module_register_chrdev(MAJOR_NUM,
DEVICE_NAME,
&Fops);
/* Negative values signify an error */
if (ret_val < 0) {
printk (“%s failed with %d\n“,
“Sorry, registering the character device “,
ret_val);
return ret_val;
}
printk (“%s The major device number is %d.\n“,
“Registeration is a success”,
MAJOR_NUM);
printk (“If you want to talk to the device driver,\n“);
printk (“you’ll have to create a device file. \n“);
printk (“We suggest you use:\n“);
printk (“mknod %s c %d 0\n“, DEVICE_FILE_NAME,
MAJOR_NUM);
printk (“The device file name is important, because\n“);
printk (“the ioctl program assumes that’s the\n“);
printk (“file you’ll use.\n“);
return 0;
}
/* Cleanup – unregister the appropriate file from /proc */
void cleanup_module()
{
int ret;
/* Unregister the device */
ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
/* If there’s an error, report it */
if (ret < 0)
printk(“Error in module_unregister_chrdev: %d\n“, ret);
}
/* chardev.h - the header file with the ioctl definitions. * * The declarations here have to be in a header file, because * they need to be known both to the kernel module * (in chardev.c) and the process calling ioctl (ioctl.c) */
#ifndef CHARDEV_H
#define CHARDEV_H
#include <linux/ioctl.h>
/* The major device number. We can’t rely on dynamic
* registration any more, because ioctls need to know
* it. */
#define MAJOR_NUM 100
/* Set the message of the device driver */
#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)
/* _IOR means that we’re creating an ioctl command
* number for passing information from a user process
* to the kernel module.
*
* The first arguments, MAJOR_NUM, is the major device
* number we’re using.
*
* The second argument is the number of the command
* (there could be several with different meanings).
*
* The third argument is the type we want to get from
* the process to the kernel.
*/
/* Get the message of the device driver */
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
/* This IOCTL is used for output, to get the message
* of the device driver. However, we still need the
* buffer to place the message in to be input,
* as it is allocated by the process.
*/
/* Get the n’th byte of the message */
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
/* The IOCTL is used for both input and output. It
* receives from the user a number, n, and returns
* Message[n]. */
/* The name of the device file */
#define DEVICE_FILE_NAME “char_dev”
#endif
/* ioctl.c - the process to use ioctl's to control the kernel module * * Until now we could have used cat for input and output. But now * we need to do ioctl's, which require writing our own process. */
/* device specifics, such as ioctl numbers and the
* major device file. */
#include “chardev.h”
#include <fcntl.h> /* open */
#include <unistd.h> /* exit */
#include <sys/ioctl.h> /* ioctl */
/* Functions for the ioctl calls */
ioctl_set_msg(int file_desc, char *message)
{
int ret_val;
ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);
if (ret_val < 0) {
printf (“ioctl_set_msg failed:%d\n“, ret_val);
exit(-1);
}
}
ioctl_get_msg(int file_desc)
{
int ret_val;
char message[100];
/* Warning – this is dangerous because we don’t tell
* the kernel how far it’s allowed to write, so it
* might overflow the buffer. In a real production
* program, we would have used two ioctls – one to tell
* the kernel the buffer length and another to give
* it the buffer to fill
*/
ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);
if (ret_val < 0) {
printf (“ioctl_get_msg failed:%d\n“, ret_val);
exit(-1);
}
printf(“get_msg message:%s\n“, message);
}
ioctl_get_nth_byte(int file_desc)
{
int i;
char c;
printf(“get_nth_byte message:”);
i = 0;
while (c != 0) {
c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);
if (c < 0) {
printf(
“ioctl_get_nth_byte failed at the %d‘th byte:\n“, i);
exit(-1);
}
putchar(c);
}
putchar(‘\n’);
}
/* Main – Call the ioctl functions */
main()
{
int file_desc, ret_val;
char *msg = “Message passed by ioctl\n“;
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);
}
9.1 시스템 콜 ¶
/* syscall.c * * System call "stealing" sample. */
/* Copyright (C) 2001 by Peter Jay Salzman */
/* The necessary header files */
/* Standard in kernel modules */
#include <linux/kernel.h> /* We’re doing kernel work */
#include <linux/module.h> /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
#include <sys/syscall.h> /* The list of system calls */
/* For the current (process) structure, we need
* this to know who the current user is. */
#include <linux/sched.h>
/* In 2.2.3 /usr/include/linux/version.h includes a
* macro for this, but 2.0.35 doesn’t – so I add it
* here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h>
#endif
/* The system call table (a table of functions). We
* just define this as external, and the kernel will
* fill it up for us when we are insmod’ed
*/
extern void *sys_call_table[];
/* UID we want to spy on – will be filled from the
* command line */
int uid;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
MODULE_PARM(uid, “i”);
#endif
/* A pointer to the original system call. The reason
* we keep this, rather than call the original function
* (sys_open), is because somebody else might have
* replaced the system call before us. Note that this
* is not 100% safe, because if another module
* replaced sys_open before us, then when we’re inserted
* we’ll call the function in that module – and it
* might be removed before we are.
*
* Another reason for this is that we can’t get sys_open.
* It’s a static variable, so it is not exported. */
asmlinkage int (*original_call)(const char *, int, int);
/* For some reason, in 2.2.3 current->uid gave me
* zero, not the real user ID. I tried to find what went
* wrong, but I couldn’t do it in a short time, and
* I’m lazy – so I’ll just use the system call to get the
* uid, the way a process would.
*
* For some reason, after I recompiled the kernel this
* problem went away.
*/
asmlinkage int (*getuid_call)();
/* The function we’ll replace sys_open (the function
* called when you call the open system call) with. To
* find the exact prototype, with the number and type
* of arguments, we find the original function first
* (it’s at fs/open.c).
*
* In theory, this means that we’re tied to the
* current version of the kernel. In practice, the
* system calls almost never change (it would wreck havoc
* and require programs to be recompiled, since the system
* calls are the interface between the kernel and the
* processes).
*/
asmlinkage int our_sys_open(const char *filename,
int flags,
int mode)
{
int i = 0;
char ch;
/* Check if this is the user we’re spying on */
if (uid == getuid_call()) {
/* getuid_call is the getuid system call,
* which gives the uid of the user who
* ran the process which called the system
* call we got */
/* Report the file, if relevant */
printk(“Opened file by %d: “, uid);
do {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
get_user(ch, filename+i);
#else
ch = get_user(filename+i);
#endif
i++;
printk(“%c“, ch);
} while (ch != 0);
printk(“\n“);
}
/* Call the original sys_open – otherwise, we lose
* the ability to open files */
return original_call(filename, flags, mode);
}
/* Initialize the module – replace the system call */
int init_module()
{
/* Warning – too late for it now, but maybe for
* next time… */
printk(“I’m dangerous. I hope you did a “);
printk(“sync before you insmod’ed me.\n“);
printk(“My counterpart, cleanup_module(), is even”);
printk(“more dangerous. If\n“);
printk(“you value your file system, it will “);
printk(“be \”sync; rmmod\” \n“);
printk(“when you remove this module.\n“);
/* Keep a pointer to the original function in
* original_call, and then replace the system call
* in the system call table with our_sys_open */
original_call = sys_call_table[__NR_open];
sys_call_table[__NR_open] = our_sys_open;
/* To get the address of the function for system
* call foo, go to sys_call_table[__NR_foo]. */
printk(“Spying on UID:%d\n“, uid);
/* Get the system call for getuid */
getuid_call = sys_call_table[__NR_getuid];
return 0;
}
/* Cleanup – unregister the appropriate file from /proc */
void cleanup_module()
{
/* Return the system call back to normal */
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;
}
10.1.1 printk() 대체하기 ¶
11.1 printK()대체하기 ¶
12.1 작업 스케줄링하기 ¶
13.1.1 인터럽트 핸들러 ¶
13.1.2 인텔 아키텍쳐에서의 키보드 ¶
14.1 대칭적 다중 처리(프로세싱) ¶
15.1 흔한 함정 ¶
인터럽트를 불가능하게 하는 것
머리를 대형 육식동물의 입 안에 찔러 넣는 일
16.1.1 2.0에서 2.2로의 변화 ¶
get_user
file_operations
close in file_operations
file_operations에서의 read, write
proc_register_dynamic
Signals
queue_task_irq
Module Parameters
Symmetrical Multi-Processing