【轉】初探linux內核編程,參數傳遞以及模塊間函數調用


http://www.cnblogs.com/yuuyuu/p/5119891.html

ZC: 疑問,最后的 模塊kernel_mod 調用 模塊kernel_fun的函數fun,是成功的OK的。但是 模塊kernel_mod 怎么就知道 它調用的就是 模塊kernel_fun的fun函數?如果 又有一個 模塊kernel_fun01它也導出了fun函數,此時 模塊kernel_mod調用fun的話調用的是哪一個模塊的fun函數?

  (ZC: 測試了一下,兩個模塊 有相同的導出函數的話,在 加載模塊時[insmod] 會報錯:"insmod: error inserting 'kernel_fun01.ko': -1 Invalid module format",然后 用命令"dmesg |tail -n 10" 可以看到更明確的信息:"kernel_fun01: exports duplicate symbol fun (owned by kernel_fun)"。可見 內核模塊不能導出相同名字的函數...)

  ZC: 模塊kernel_mod 調用 函數fun,是通過 模塊所在的路徑來找 fun函數的嗎?模塊所在的路徑 找不到的話,就繼續找PATH里面的路徑??還是所有模塊都是在同一個特殊的路徑下??

ZC: 命令"insmod ???" 安裝的內核模塊,都是位於相同的地方?就是說 相同的路徑?內核模塊有沒有 相對路徑之說?

ZC: 不同路徑的模塊 能導出相同函數名的函數嗎?

  ZC: 答:這樣測試的:kernel_fun.ko 放置於"/home/DriverZ/" 並且已經安裝,將 kernel_fun01.ko 放於"/home/"下 安裝時 報錯"insmod: error inserting 'kernel_fun01.ko': -1 Invalid module format","dmesg |tail -n 10" 顯示"kernel_fun01: exports duplicate symbol fun (owned by kernel_fun)"

    ZC: 這樣子的模塊,加載之后,在OS重啟之后 需要再次手動加載。∵上面加載 kernel_fun01.ko時 報錯,而我重啟OS之后 加載kernel_fun01.ko很順利,說明 kernel_fun.ko在OS重啟之后 沒有被自動加載。於是 有如下疑問:

      (1)、如何使得內核模塊開機自動加載?

      (2)、如何查看 當前已經加載了哪些內核模塊 以及它們的詳細信息?    --> 貌似是 lsmod

 

ZC: 本文章的一些命令:

  insmod ???.ko 參數名=參數值

  rmmod ???.ko

  modinfo ???.ko

  lsmod

  dmesg |tail -n 10

  cat /proc/kallsyms | grep kernel_fun      --> 顯示模塊的導出函數(kernel all symbols)

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

 

一.前言                                 

我們一起從3個小例子來體驗一下linux內核編程。如下:

1.內核編程之hello world

2.模塊參數傳遞

3.模塊間函數調用

二.准備工作                          

首先,在你的linux系統上面安裝linux頭文件,debian系列:

1 $:sudo apt-get install linux-headers-`uname -r`

安裝后,在你的/lib/modules/目錄下有你剛剛安裝的頭文件版本號對應的目錄。頭文件夾下面還有個build文件夾,里面的Makefile文件是等會要編譯內核模塊用的。如圖,這是我機器上面的:

注意:安裝的頭文件版本一定要和你的系統版本一樣,不然你自己編寫的模塊不能插入到本機的內核。如果你apt-get安裝頭文件時,沒有對應的頭文件,或者你的源里面放不穩定版本的源后,依然沒有對應的頭文件,你可以到這里搜索需要的deb包來安裝。再或者下載跟本機對應的內核源碼來構建環境。

三.內核編程之hello world    

我們先來了解下內核模塊文件的入口和出口。它由2個宏來注冊入口和出口,分別是:

1 module_init(x);
2 module_exit(x);

這2個宏在頭文件目錄的include/linux/module.h。宏里面的x代表注冊到內核的入口和出口函數。通俗一點講就是模塊初始化和模塊卸載時調用的函數。

初始化函數的形式:int my_init(void);

退出函數的形式:void my_exit(void);

另外還有一些宏:

MODULE_LICENSE(_license):模塊的許可證。

MODULE_AUTHOR(_author):模塊的作者。

MODULE_VERSION(_version):模塊版本

MODULE_DESCRIPTION(_description):模塊的描述。

還有一些就不一一舉例了。

現在,我來看最簡單的hello world例子:

文件名:kernel_hello.c

復制代碼
 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 
 5 /* 以下4個宏分別是許可證,作者,模塊描述,模塊版本 */
 6 MODULE_LICENSE("Dual BSD/GPL");
 7 MODULE_AUTHOR("yuuyuu");
 8 MODULE_DESCRIPTION("kernel module hello");
 9 MODULE_VERSION("1.0");
10 
11 /* 入口函數 */
12 static int hello_init(void)
13 {
14     printk(KERN_ALERT "hello_init() start\n");
15 
16     return 0;
17 }
18 
19 /* 退出函數 */
20 static void hello_exit(void)
21 {
22     printk(KERN_ALERT "hello_exit() start\n");
23 }
24 
25 /* 注冊到內核 */
26 module_init(hello_init);
27 module_exit(hello_exit);
復制代碼

上面的printk()函數時內核自己實現的輸出函數,KERN_ALERT時輸出信息的級別!

然后再寫一個很簡單的Makefile文件:

復制代碼
1 KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
2 PWD := $(shell pwd)
3 
4 obj-m := kernel_hello.o
5 
6 default:
7     $(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
復制代碼

KERNAL_DIR:你剛剛安裝頭文件的目錄

PWD:代表當前你編寫模塊文件的目錄。

obj-m:模塊的依賴目標文件。另外,內核的Makefile文件:obj-m代表將目標文件編譯成模塊,obj-y代表編譯到內核,ojb-n代表不編譯。

-C:執行make的時候,把工作目錄切換到-C后面指定的參數目錄,這里即頭文件目錄下的build目錄。

M:這個M時內核Makefile里面的一個變量。作用時回到當前目錄繼續讀取Makefile,這里的就是讀完build目錄下的Makefile之后再回到我們的這個目錄,讀取我們剛剛編寫的那個Makefile。

最后的modules是代表編譯模塊。

現在我們來編譯下,在我們編寫Makefile的目錄下執行make,會看到生成了模塊文件kernel_hello.ko

 

查看模塊信息:sudo modinfo kernel_hello.ko

可以看到剛剛那幾個宏插入的模塊信息。

 現在我們一口氣執行4個動作:插入模塊,查看內核已插入的模塊,卸載模塊,查看dmesg信息:

可以看到,模塊在初始化和退出時都打印了函數里面的信息。

四.模塊參數傳遞                    

模塊的參數傳遞也是一個宏,在頭文件目錄的include/linux/moduleparam.h:

1 module_param(name, type, perm)

name:模塊中的變量名,也是用戶可指定參數名。

type:byte,short,ushot,int,uint,long,ulong,charp,bool這些

perm:模塊的權限控制。跟linux文件權限控制一樣的。

文件名:kernel_hello_param.c

復制代碼
 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 
 5 /* 以下4個宏分別是許可證,作者,模塊描述,模塊版本 */
 6 MODULE_LICENSE("Dual BSD/GPL");
 7 MODULE_AUTHOR("yuuyuu");
 8 MODULE_DESCRIPTION("kernel module hello");
 9 MODULE_VERSION("1.0");
10 
11 static char *msg;
12 module_param(msg, charp, 0644);
13 
14 /* 入口函數 */
15 static int hello_init(void)
16 {
17     printk(KERN_ALERT "hello_init() start\n");
18     printk(KERN_ALERT "%s\n", msg);
19 
20     return 0;
21 }
22 
23 /* 退出函數 */
24 static void hello_exit(void)
25 {
26     printk(KERN_ALERT "hello_exit() start\n");
27 }
28 
29 /* 注冊到內核 */
30 module_init(hello_init);
31 module_exit(hello_exit);
復制代碼

比上一個文件,就增加了11,12,18行。注意第12行的charp,是內核的字符指針。

編譯后,傳參插入,dmesg查看信息:

插入的參數msg跟在模塊后面即可。

五.模塊間函數調用                

模塊的函數導出到符號表才可以供其他函數使用,需要用到宏:

1 EXPORT_SYMBOL(sym)

該宏在include/linux/export.h里面。

既然模塊間函數調用,我們要編寫2個模塊。

文件一:kernel_fun.h

復制代碼
1 #ifndef KERNEL_FUN_H
2 #define KERNEL_FUN_H
3 
4 void fun(void);
5 
6 #endif
復制代碼

文件二,要導出的模塊文件:kernel_fun.c

復制代碼
 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/export.h>
 5 
 6 #include "kernel_fun.h"
 7 
 8 /* 以下4個宏分別是許可證,作者,模塊描述,模塊版本 */
 9 MODULE_LICENSE("Dual BSD/GPL");
10 MODULE_AUTHOR("yuuyuu");
11 MODULE_DESCRIPTION("kernel module hello");
12 MODULE_VERSION("1.0");
13 
14 /* 入口函數 */
15 static int fun_init(void)
16 {
17     printk(KERN_ALERT "fun_init() start\n");
18 
19     return 0;
20 }
21 
22 void fun()
23 {
24     printk(KERN_ALERT "fun() is called\n");
25 }
26 
27 /* 退出函數 */
28 static void fun_exit(void)
29 {
30     printk(KERN_ALERT "fun_exit() start\n");
31 }
32 
33 /* 注冊到內核 */
34 module_init(fun_init);
35 module_exit(fun_exit);
36 
37 /* 導出符號表 */
38 EXPORT_SYMBOL(fun);
復制代碼

最后一行就是導出到符號表。

 

文件三,要調用模塊文件二的函數:kernel_mod.c

復制代碼
 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/export.h>
 5 
 6 #include "kernel_fun.h"
 7 
 8 /* 以下4個宏分別是許可證,作者,模塊描述,模塊版本 */
 9 MODULE_LICENSE("Dual BSD/GPL");
10 MODULE_AUTHOR("yuuyuu");
11 MODULE_DESCRIPTION("kernel module hello");
12 MODULE_VERSION("1.0");
13 
14 /* 入口函數 */
15 static int mod_init(void)
16 {
17     printk(KERN_ALERT "mod_init() start\n");
18 
19     /* 調用fun */
20     fun();
21     return 0;
22 }
23 
24 /* 退出函數 */
25 static void mod_exit(void)
26 {
27     printk(KERN_ALERT "mod_exit() start\n");
28 }
29 
30 /* 注冊到內核 */
31 module_init(mod_init);
32 module_exit(mod_exit);
復制代碼

第20行即是調用其他模塊的函數。

 

這里要編譯2個模塊,對應的Makefile文件:

復制代碼
1 KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
2 PWD := $(shell pwd)
3 
4 obj-m := kernel_mod.o kernel_fun.o
5 
6 default:
7     $(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
復制代碼

編譯好這2個模塊后,我們現在來驗證。注意,因為kernel_mod依賴kernel_fun,所以我要先插入kernel_fun模塊。

卸載模塊的時候,我們要先卸載kernel_mod,原因同上。

依次插入kernel_fun,查看它的符號表,然后插入kernel_mod,查看dmesg:

可以看到kernel_fun的fun()被kernle_mod調用了。

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM