內核模塊

以下內容只是記錄的要點,詳細看參考資料:

一、內核模塊的編寫:

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文件。


  1. 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出錯。       

  2. 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)。

    刪除目錄和文件都是使用這一個函數。

  3. proc_mkdir:
    1)原型:
    struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)

    2)作用:該函數用於創建一個proc目錄。

    3)參數:

      name:指定要創建的目錄的名稱;
      parent:該proc目錄所在的目錄。

  4. 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目錄所在的目錄。

  5. 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文件名稱。
  6. 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字段中。

  7. 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.

  8. 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操作函數。

  9. 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文件,但是他同時指定了對該文件的文件操作函數。
  10. proc_net_remove:
    1)原型:void proc_net_remove(const char *name)
    2)作用:該函數用於刪除前面兩個函數在/proc/net目錄下創建的文件。
    3)參數:
      name:指定要刪除的proc文件;
      
  11. 回調函數:

         我們可以使用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 函數來維護用戶空間的數據。



  12. 讀回調函數:

        我們可以使用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。

  13. 其他有用的/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);

發佈了70 篇原創文章 · 獲贊 22 · 訪問量 42萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章