以下內容只是記錄的要點,詳細看參考資料:
一、內核模塊的編寫:
1、內核模塊的代碼編寫沒有外部的函數庫可以用,只能使用內核導出的函數;這點於應用程序是有區別的,應用程序習慣於使用外部的庫函數,在編譯的時候將程序與庫函數鏈接在一起。比如說:內核模塊中不能使用printf(),而只能使用printk()函數。
2、內核模塊至少包含兩個函數:模塊加載函數、卸載函數;
內核版本2.3.13以前,使用init_module()和cleanup_module作爲模塊的初始和結束函數; 在內核Linux 2.4中,你可以爲你的模塊的“開始”和“結束”函數起任意的名字了。它們不再必須使用init_module()和cleanup_module()的名字。這可以通過宏 module_init()和module_exit()實現。這些宏在頭文件kernel../include/linux/init.h定義。唯一需要注意的地方是函數必須在宏的使用前定義,否則會有編譯 錯誤。
3、內核模塊包含頭文件及宏說明:
#include<linux/module.h>:它定義了模塊的 API、類型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的內核模塊都必須包含這個頭文件。
#include<linux/kernel.h>:使用內核信息優先級時要包含這個文件,一般在使用printk函數時使用到優先級信息。
#include<linux/init.h>頭文件:module_init、module_exit等宏定義。
#include<linux/proc_fs.h>: 在對/proc文件系統的文件目錄進行操作時使用到的函數等在這個文件中定義,比如:create_proc_entry()在/proc下創建文件。
接下來解釋一下常用宏定義:
MODULE_LICENSE
定義了這個模塊使用的許可證。此處,我們定義的是 GPL,從而防止會污染到內核。
二、內核模塊的編譯(2.6系列內核):
編譯模塊的時候,你可以將模塊放在代碼樹中,用make modules來編譯你的模塊;你也可以將模塊相關文件目錄放在代碼樹以外的位置,用如下命令來編譯模塊:
make -C path/to/kernel/source M=$PWD modules
參數說明: -C 指定代碼樹的位置;M=$PWD或者M=`PWD`告訴kbuild回到當前目錄執行build操作。
參考資料: 2.6內核Makefile簡單語法與應用
http://blog.chinaunix.net/u/24474/showart_237820.html
在我的機子上運行的內核版本是2.6.22-14-generic,編譯內核模塊mymodule的步驟如下
內核模塊的編譯:
1)寫內核模塊源文件(任意目錄), 如mymodule.c
2)在模塊源文件目錄編寫Makefile,內容:obj-m+=mymodule.o
3) 在終端執行: make -C /usr/src/linux版本目錄 SUBDIRS=$PWD modules
網上搜到的命令是:
make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
其中:linux-`uname -r`應該根據自己的實際情況進行調整,比如我的就是linux-headers-2.6.22-14-generic,也就是linux-headers-'uname -r'。
說明:SUBDIRS=..和
M=..是一樣的,
疑點:對kbuild的理解?
2.6內核引入了kbuild,將外部的內核模塊的編譯和內核源碼樹的編譯統一起來了。
三、插入模塊:
$insmod ./mymodule.ko
查看信息:demsg | tail -5 , 並且 cat /proc/modules 可看到插入的模塊:mymodule。
卸載模塊:
$rmmod ./mymodule.ko
參考資料:2.6內核模塊的編寫框架和編譯方法
http://hi.baidu.com/skie/blog/item/87ab59a9b45b46fc1f17a208.html
四、 模塊的安裝
安裝模塊。模塊的默認安裝目錄是 /lib/modules/<內核-版本>
當你需要將模塊安裝到非默認位置的時候,你可以用INSTALL_MOD_PATH 指定一個前綴,如:
make INSTALL_MOD_PATH=/foo modules_install
模塊將被安裝到 /foo/lib/modules目錄下。
四、內核模塊的運行:
內核模塊運行在內核空間,而應用程序在用戶空間。應用程序的運行會形成新的進程,而內核模塊一般不會。每當應用程序執行系統調用時,linux執行模式從用戶空間切換到內核空間。
五、幾個可以用來開發有用LKM(可加載內核模塊)的內核API:
要使用/proc文件系統,首先一定要包含procfs的頭文件:include<proc_fs.h>(這些函數是在此文件中聲明的),其次利用procfs提供的如下API函數。
首先值得一提的是:結構體proc_dir_entry,他的部分成員如下:
struct proc_dir_entry {
const char *name; // virtual file name
mode_t mode; // mode permissions
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // Inode operations functions
struct file_operations *proc_fops; // File operations functions
struct proc_dir_entry *parent; // Parent directory
...
read_proc_t *read_proc; // /proc read function
write_proc_t *write_proc; // /proc write function
void *data; // Pointer to private data
atomic_t count; // use count
...
};
爲了創建可讀可寫的proc文件並指定該proc文件的寫操作函數,必須設置下面那些創建proc文件的函數返回指針指向的struct proc_dir_entry結構的write_proc字段,並指定該proc文件的訪問權限有寫權限。
我們會使用 read_proc
和 write_proc
命令來插入對這個虛擬文件進行讀寫的函數。
讀者可以通過cat和echo等文件操作函數來查看和設置這些proc文件。
- create_proc_entry:
1)原型:
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,struct proc_dir_entry *parent );2)作用:該函數用於在/proc文件系統中創建一個虛擬文件(proc條目)。
3)參數:
name: 給出要創建的文件名稱;
mode:給出建立的該proc文件的訪問權限;
parent:指定建立的proc文件所在的目錄;
如果要在/proc根目錄下建立proc文件,parent應當爲NULL,否則他應當爲proc_mkdir,也就是說我們也可以在自己創建的目錄下面再創建虛擬文件.(詳細解釋見remove_proc_entry).
4)返回struct proc_dir_entry結構的指針,我們可以使用這個返回的指針來配置這個虛擬文件的其他參數,比如對該文件執行讀操作時應調用的函數。
若返回NULL,說明create出錯。 - remove_proc_entry:
1)原型:
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );
2)作用:用於刪除上面函數創建的proc文件。
3)參數:
name:給出要刪除的proc文件的名稱;
parent:指定創建的proc文件所在的目錄。
parent參數可以是NULL(指/proc根目錄),也可以取其他值,這取決於我們創建時將這個文件放到了什麼地方。可以使用的其他一些值 proc_dir_entry:proc_root_fs、proc_net、proc_bus、proc_root_driver等。這些取值在linux/proc_fs.h中引入,如下:
extern struct proc_dir_entry proc_root; (/proc目錄)
extern struct proc_dir_entry *proc_root_fs; (/proc/fs目錄)
extern struct proc_dir_entry *proc_net; (/proc/net)
extern struct proc_dir_entry *proc_bus; (/proc/bus)
extern struct proc_dir_entry *proc_root_driver; (/proc/driver)
extern struct proc_dir_entry *proc_root_kcore; (/proc/kcore)
注意:上面的定義中,proc_root和其他變量的類型不同,其他的都是指針,在使用時要注意。利用這些位置值,我們可在/proc文件系統的根目錄(/proc)及其內部的目錄下(如/proc/net、/proc/bus等)建立文件。除此之外,我們還可以用自己創建的目錄(proc_mkdir)。
刪除目錄和文件都是使用這一個函數。 -
proc_mkdir:
1)原型:
struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)
2)作用:該函數用於創建一個proc目錄。
3)參數:
name:指定要創建的目錄的名稱;
parent:該proc目錄所在的目錄。 - proc_mkdir_mode:
1)原型:
extern struct proc_dir_entry *proc_mkdir_mode(const char *name,
mode_t mode, struct proc_dir_entry *parent)
2)作用:該函數用於以一定的模式創建proc目錄。
3)參數:
name:指定要創建的目錄的名稱;
mode:給出了建立該proc目錄的訪問權限;
parent:該proc目錄所在的目錄。 - proc_symlink:
1)原型:
truct proc_dir_entry *proc_symlink(const char * name,struct proc_ dir_entry * parent, const char * dest)
2)作用:該函數用於建立一個proc文件的符號連接。
3)參數:
name:給出要建立鏈接的proc文件的名稱;
parent:指定符號鏈接所在的目錄;
dest:指定鏈接到的proc文件名稱。 - creat_proc_read_entry:
1)原型:
struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void * data)
2)作用:該函數用於建立一個規則的只讀proc文件
3)參數:
name: 給出要創建的文件名稱;
mode:給出了建立該proc文件的訪問權限;
base:指定建立proc文件所在的目錄(指定方式同上);
read_proc:給出讀取該文件的操作函數;
data:爲該proc文件的專用數據,它將保存在該proc文件對應的struct file結構中的
private_data字段中。 - creat_proc_info_entry:
1)原型:
struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)
2)作用:該函數用於創建一個info型的proc文件。
3)參數:
name: 給出要創建的proc文件名稱;
mode:給出了建立該proc文件的訪問權限;
base:指定建立proc文件所在的目錄(指定方式同上);
get_info:指定該proc文件的get_info操作函數。實際上get_info等同於read_proc,如果proc文件沒有定義read_proc,對該文件的read操作將由
get_info取代,因此它在功能上非常類似於函數creat_proc_read_entry. - proc_net_creat:
1)原型:
struct proc_dir_entry *proc_net_create(const char *name, mode_t mode, get_info_t *get_info)
2)作用:該函數用於在/proc/net目錄下創建一個proc文件。
3)參數:
name:給出了要創建的proc文件名稱;
mode:給出了建立該proc文件的訪問權限;
get_info:指定該proc文件的get_info操作函數。 - proc_net_fops_create:
1)原型:
struct proc_dir_entry *proc_net_fops_create(const char *name, mode_t mode, struct file_operations *fops)
2)作用:該函數也用於在/proc/net目錄下創建一個proc文件,但是他同時指定了對該文件的文件操作函數。 - proc_net_remove:
1)原型:void proc_net_remove(const char *name)
2)作用:該函數用於刪除前面兩個函數在/proc/net目錄下創建的文件。
3)參數:
name:指定要刪除的proc文件;
-
回調函數:
我們可以使用write_proc函數向/proc中寫入一項。此函數原型:
int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data );
參數:
filp
參數實際上是一個打開文件結構(我們可以忽略這個參數);buff
參數是存放在緩衝區的要寫入的字符串數據;len
參數定義了在buff
中有多少數據要被寫入;data
參數是一個指向私有數據的指針(proc_dir_entry中)。注意:緩衝區buff地址實際上是一個用戶空間的緩衝區,因此我們不能直接讀取它。Linux 提供了一組 API 來在用戶空間和內核空間之間移動數據,我們使用了
copy_from_user
函數來維護用戶空間的數據。 -
讀回調函數:
我們可以使用read_proc函數從一個/proc項中讀取數據(從內核空間到用戶空間)。此函數原型:
int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data );
參數:
page
參數是這些數據寫入到的位置;count
定義了可以寫入的最大字符數;在返回多頁數據(通常一頁是 4KB)時,我們需要使用
start
和off
參數;當所有數據全部寫入之後,就需要設置
eof
(文件結束參數);與
write
類似,data
表示的也是私有數據。注意:此處提供的page緩衝區在內核空間中,因此我們可以直接讀出,而不用調用copy_to_user。
- 其他有用的/proc函數:
對於只需要一個read函數的簡單/proc項來說,可以使用create_proc_read_entry,這會創建一個/proc項,並在一個調用中對read_proc函數進行初始化。
其他有用的/proc函數:
copy_to_user/* Copy buffer to user-space from kernel-space */
copy_from_user/* Copy buffer to kernel-space from user-space */
vmalloc/* Allocate a 'virtually' contiguous block of memory */
vfree/* Free a vmalloc'd block of memory */
EXPORT_SYMBOL( symbol )/* Export a symbol to the kernel (make it visible to the kernel)*/
EXPORT_SYMTAB* Export all symbols in a file to the kernel (declare before module.h) */
5、參考文獻:The Linux Kernel Module Programming Guide
* 每一個內核模塊都需要包含linux/module.h;當需要使用printk()記錄級別的宏擴展KERN_ALERT等時,需要包含linux/kernel.h。
*printk()函數中,當指定的優先級低於consle_loglevel時,信息會直接打印到終端上(不是在Xwindows下的虛擬終端下,而是真正的字符界面下);如果同時syslogd和klogd都在運行,信息也同時添加在文件/var/log/messages中,不管是否顯示在控制檯上。
說明:printk()函數不是設計用來同用戶交互的,而是爲內核提供日誌功能,記錄內核信息或給出警告。
注意,內核的輸出進到了內核迴環緩衝區中,而不是打印到 stdout
上,這是因爲 stdout
是進程特有的環境。要查看內核迴環緩衝區中的消息,可以使用 dmesg
工具(或者通過
/proc 本身使用 cat /proc/kmsg
命令)。下面指令 給出了 dmesg
顯示的最後幾條消息:dmesg | tail -5.
* 一種稱爲kbuild的新方法被引入,現在外部的可加載內核模塊的編譯的方法已經同內核編譯統一起來。 想了解更多的編 譯非內核代碼樹中的模塊,請參考幫助文件linux/Documentation/kbuild/modules.txt。
* 2.6的內核現在引入一種新的內核模塊命名規範:內核模塊現在使用.ko的文件後綴(代替 以往的.o後綴),這樣內核模塊就可以同普通的目標文件區別開。更詳細的文檔請參考linux/Documentation/kbuild/makefiles.txt。
* 內核模塊證書 :
在2.4或更新的內核中,一種識別代碼是否在GPL許可下發布的機制被引入, 因此人們可以在使用非公開的源代碼產品時得到警告。 利用宏 MODULE_LICENSE(),即當你設置在GPL證書下發布你的代碼時, 你可以取消這些警告。這種證書機制在頭文件linux/module.h 實現 。
類似的,宏MODULE_DESCRIPTION()用來描述模塊的用途。
宏MODULE_AUTHOR()用來聲明模塊的作者。
宏MODULE_SUPPORTED_DEVICE() 聲明模塊支持的設備。
這些宏都在頭文件linux/module.h定義, 並且內核本身並不使用這些宏。它們只是用來提供識別信息,可用工具程序像objdump查看。
* 版本印戳作爲一個靜態的字符串存在於內核模塊中,以 vermagic:。 版本信息是在連接階段從文件init/vermagic.o中獲得的。 查看版本印戳和其它在模塊中的一些字符信息,可以使用下面的命令modinfo module.ko(模塊名).
*
模塊是在insmod加 載時才連接的目標文件。那些要用到的函數(如:printk)的符號鏈接是內核自己提供的。 也就是說, 你可以在內核模塊中使用的函數只能來自內核本身。如果你對內核提供了哪些函數符號 鏈接感興趣,看一看文件/proc/kallsyms。 文件/proc/kallsyms保存着內核知道的所有的符號,你可以訪問它們, 因爲它們是內核代碼空間的一部分。
*
庫函數和系統調用的區別: 庫函數是高層的,完全運行在用戶空間, 爲程序員提供調用真正的在幕後 完成實際事務的系統調用的更方便的接口。系統調用在內核 態運行並且由內核自己提供。
一般庫函數在用戶態執行。 庫函數調用一個或幾個系統調用,而這些系統調用爲庫函數完成工作,但是在超級狀態。 一旦系統調用完成工作後系統調用就返回同時程序也返回用戶態。
* 宏 __init 和 __exit 可以使函數在運行完成後自動回收內存(限模塊中),__initdata用於變量,
舉例:
#include //需要包含的頭文件
static int ntest __initdata = 3;
static int __init test_init(void) {...}
static void __exit test_exit(void) {...}
module_init(test_init); //申明放在實現函數後
module_exit(test_exit);