相信許多朋友和我一樣都在Linux環境下使用過C語言編過程序,其大多數都屬於用戶應用程序,也稱為普通用戶程序。寫了這麼多應用程序後,就有點兒想寫一點系統級的程序了,於是就參考了一些關於Linux內核編程原理的資料,並付之了實踐,現在就讓我將編寫內核模塊的方法給大家介紹一下吧......
一個Linux內核模塊至少需要包括以下兩個函數:
1.模塊初始化函數——當模塊被插入到Linux內核中時被調用;
2.模塊卸載函數——當模塊從Linux內核中被卸載時被調用。
一般來說,模塊初始化函數給新模塊在內核中注冊,並且得到一個調用句柄;或者它使新模塊的代碼覆蓋原有的代碼(通常情況下新模塊的代碼增加了一些新功能,然後調用原有的代碼)。
而模塊卸載函數正好做了模塊初始化函數相反的工作,它使新模塊安全地被卸載。
下面我們來看看如何在Linux內核中插入一個模塊,讓其在屏幕上輸出“Hello , this is module speaking!”的字樣。
程序文件:hello.c
#include $#@60;linux/kernel.h$#@62; /* 我們正在干一些關於內核的事情 */ #include $#@60;linux/module.h$#@62; /* 具體來說,是在寫一個模塊 */ #if CONFIG_MODVERSIONS==1 /* 如果需要指明模塊的版本的話 */ #define MODVERSIONS #include $#@60;linux/modversions.h$#@62; /* 那就將linux/modversions.h文件包含*/ #endif int init_module() /* 模塊初始化函數 */ { printk("Hello, this is the kernel speaking!\ n"); /* 如果我們將返回值置為非零,這說明初始化模塊失敗 */ return 0; } void cleanup_module() /* 模塊卸載函數 */ { printk(“ This kernel module has been removed.\ n"); }
為了編譯hello.c,我們還得編寫一個Makefile文件。
內核模塊不是一個單獨的可執行體,它只能作為一個二進制目標文件(相當於DOS的obj文件)被內核調用。經常在Linux下些程序的用戶一定熟悉cc或gcc的用法,在此我們使用GNU gcc來編譯hello.c文件,使用-c標志表示只將源文件編譯成二進制目標文件。
同時,所有的內核模塊在編譯時都要使用__KERNEL__(注意,每一邊都是兩個半角的下劃線)標示,只有這樣才能告訴編譯器這個程序將在內核模式下運行而不是一個 ǖ撓沒Ы程?
此外,在編譯時我們還得用到MODULE、LINUX兩個標示:
MODULE — 告訴編譯器給內核模塊一個合適的定義;
LINUX —為了提高代碼的可移植性,這裡說明我們的代碼是在Linux操作系統下編譯。
所以,Makefile如下:
文件:Makefile
CC=gcc MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX hello.o: hello.c /usr/include/linux/version.h $(CC) $(MODCFLAGS) -c hello.c
好了,接下來要做的事情就是以root的身份登錄系統,或使用su來使自己成為root用戶。然後,使用insmod hello和rmmod hello 命令來插入或卸載hello模塊。執行insmod hello命令後,hello模塊被初始化,於是console輸出“Hello, this is the kernel speaking!”的字樣;同樣,執行rmsmod hello命令後,hello模塊被卸載,於是系統要調用模塊卸載函數來做清理工作,這時,console輸出“This kernel module has been removed.”的字樣。另外,一旦使用了insmod hello插入hello模塊後,在/proc/modules目錄下將會有hello模塊的紀錄。
順便說一下,插入或卸載hello模塊的動作不要在X-Windows下做,這是因為在程序中我們使用了printk函數,它能從內核直接向console控制台輸出消息,如果您使用的Linux的內核是不穩定的版本的話,使用X-Windows下的虛擬終端輸出可能會導致系統崩潰或重起。
上面所說的是單個文件的程序,如果程序比較大,則需要分解成多個源文件。下面就來看看多個源文件組成的內核模塊程序是如何編寫、編譯的。
我們需要做以下三個工作:
1.在所有的源文件中除了一個沒有,其他均要加上 #define __NO_VERSION__的宏定義。這一點非常重要,因為在module.h中已經包含了系統內核版本的定義,所有的內核模塊都依照者這個已定義的包含了內核版本的全局變量來編譯的。現在使用了#define __NO_VERSION__語句,可以在編譯時避免使用上述的全局變量。但,此時您必須手工地包含version.h文件,因為有了__NO_VERSION__的定義,module.h就不會自動地包含version.h文件了;
2.和平時一樣編譯所有的源文件得到多個二進制目標文件;
3.連接(link)所有的二進制目標文件,在X86硬件環境下使用這樣的命令: ld ?m elf_i386 ?r ?o $#@60;模塊的名字$#@62;.o $#@60;源文件1的名字$#@62;.o $#@60;源文件2的名字$#@62;.o
好,下面就舉一個例子。
文件:start.c
/* 此文件不包含__NO_VERSION__宏的定義 */ #include $#@60;linux/kernel.h$#@62; /* 說明我們在做一些關於內核的工作 */ #include $#@60;linux/module.h$#@62; /* 具體來說,是在寫一個模塊 */ #if CONFIG_MODVERSIONS==1 /* 如果需要指明模塊的版本的話 */ #define MODVERSIONS #include $#@60;linux/modversions.h$#@62; #endif int init_module() /* 模塊初始化函數 */ { printk("Hello, this is the kernel speaking\ n"); return 0; }
文件:stop.c
#include $#@60;linux/kernel.h$#@62; /*說明我們在做一些關於內核的工作*/ #define __NO_VERSION__ /* 禁止module.h使用含有內核版本號的全局變量 */ /* 故,此宏的定義必須在包含module.h文件之前 */ #include $#@60;linux/module.h$#@62; /* 包含module.h文件 */ #include $#@60;linux/version.h$#@62; /* 由於__NO_VERSION__宏的定義,只能手工加入*/ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include $#@60;linux/modversions.h$#@62; #endif void cleanup_module() /* 模塊卸載函數 */ { printk("This kernel module has been removed.\ 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.
下面的操作和使用單個源文件的一樣,使用insmod hello和rmmod hello來裝卸hello模塊了。
關於Linux內核的編程有很多方面,涉及的知識點很多,需要程序員對C/C++甚至匯編語言很熟悉,同時對操作系統原理也有一定的了解。如果能讀懂Linux的源程序那是更好了。雖然很辛苦,但是你一定會有收獲的。