1. 程式人生 > >Linux核心模組程式設計-HelloWorld

Linux核心模組程式設計-HelloWorld

HelloWorld核心

開始斷斷續續學習核心,大概半年了,多少開始對核心有點感悟了,但是對於這個龐然大物我顯得很渺小,在枯燥的核心原始碼之中似乎沒有一點點成功的喜悅,因此我選擇學習核心模組程式設計,通過編寫一些核心模組來體驗那一點點小小的成就感吧!

什麼是核心模組

核心模組是具有獨立功能的程式。它可以被單獨編譯,但是不能單獨執行,它的執行必須被連結到核心作為核心的一部分在核心空間中執行

最簡單的核心模組

#include <linux/module.h>  //所有模組都必須包含的標頭檔案
#include <linux/kernel.h> //一些巨集定義,例如這裡的KERN_INFO
int init_module(void) { printk(KERN_INFO "Hello world 1.\n"); /* * 返回非0表示模組初始化失敗,無法載入 */ return 0; } void cleanup_module(void) { printk(KERN_INFO "Goodbye world 1.\n"); } //一個模組至少需要兩個函式,一個初始化函式這裡是init_module在載入核心的時候呼叫, //一個結束函式,這裡是cleannup_module在從核心中登出的時候呼叫

一個Makefile來編譯這個核心模組

obj-m += hello-1.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

注意:本文所有環節都是基於Centos6.5下測試OK,你可能在有的書上看見Makefile是這樣寫的

make -C /usr/src/linux-headers-$(shell uname -r) M=$(PWD) modules
其實/lib/modules/$(shell uname -r)/build 這個路徑就是上面路徑的一個軟連結
[root@localhost
2.6.32-431.el6.x86_64]# ls -al build lrwxrwxrwx. 1 root root 44 Mar 16 05:26 build -> /usr/src/kernels/2.6.32-504.12.2.el6.x86_64/

編寫好makefile檔案後,使用make進行編譯,編譯完就出現一個.ko的檔案,這個就是核心模組,需要載入執行

載入核心模組進行執行

載入核心模組的方法有很多比如: modprobe 和 insmod前者會分析模組的依賴關係,並且會去指定路徑查詢核心模組載入,而後者需要指定核心模組的絕對路徑進行載入並且不解決模組的依賴關係。這裡我們使用insmod來載入核心模組,使用rmmod解除安裝核心模組
[root@localhost kernel_module]# insmod hello-1.ko
使用dmes檢視核心模組的速出
Hello world 1.
解除安裝核心模組
[root@localhost kernel_module]# rmmod hello-1 
dmesg檢視輸出
Goodbye world 1.

核心模組程式設計和應用程式程式設計的異同

  • 核心模組程式設計是不能去使用標準庫(比如malloc free等)和一些第三方的庫
  • 核心模組程式設計是沒有記憶體保護的,如果記憶體訪問錯誤,就會出現oops錯誤
  • 核心模組程式設計是沒有main函式的,只有一個初始化函式,和一個提出函式
  • 核心模組程式設計需要使用核心提供的標頭檔案和API
  • 核心模組程式設計的標準輸出是輸出到檔案,而不是輸出到螢幕
  • 核心模組程式設計的debug是不能使用gdb來進行除錯的。

核心模組進階

核心模組的程式設計不僅僅是上面的一個HelloWorld,核心模組程式設計還有一些更高階的寫法,下面會一一介紹:

去掉init_module/cleanup_module

在上面的HelloWorld模組中,你會發現初始化函式和退出函式好像是固定的名稱,那麼有沒有辦法自己自定義名稱呢其實是可以的,你可以自己自定義名稱,然後進行註冊即可(註冊其實就是做了一個函式指標的賦值而已)
下面是自定義名稱的寫法:

//不需要固定核心模組的初始化函式的名字和結束的名字
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int hello_2_init(void)
{
    printk(KERN_INFO "Hello,world 2\n");
    return 0;
}

static void  hello_2_exit(void)
{
    printk(KERN_INFO "Goodbye,world 2\n");
}

//這兩個函式來註冊模組初始化和模組結束
module_init(hello_2_init);
module_exit(hello_2_exit);

__init/__initdata/__exit

在有的核心模組程式設計的書籍或者介紹核心模組程式設計的部落格中,你或許會發現有這樣的一些特殊關鍵字__init ,_initdata ,__exit等等,其實這些都是gcc的擴充套件屬性:__init 巨集最常用的地方是驅動模組初始化函式的定義處,其目的是將驅動模組的初始化函式放入名叫.init.text的輸入段。當核心啟動完畢後,這個段中的記憶體會被釋放掉供其他使用。__initdata巨集用於資料定義,目的是將資料放入名叫.init.data的輸入段。其它幾個巨集也類似。

模組描述資訊

可以使用modinfo去檢視一個模組的模組資訊,下面是自己編寫的模組和系統自帶的模組的兩個模組資訊的對比

[root@localhost kernel_module]# modinfo hello-1.ko
filename:       hello-1.ko
srcversion:     0D3956C127A907CC9E7114F
depends:        
vermagic:       2.6.32-504.12.2.el6.x86_64 SMP mod_unload modversions 

[root@localhost kernel_module]# modinfo  /lib/modules/2.6.32-431.el6.x86_64/kernel/fs/ext4/ext4.ko 
filename:       /lib/modules/2.6.32-431.el6.x86_64/kernel/fs/ext4/ext4.ko
license:        GPL
description:    Fourth Extended Filesystem
author:         Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
srcversion:     345EBDA2AEFF60FFED78864
depends:        mbcache,jbd2
vermagic:       2.6.32-431.el6.x86_64 SMP mod_unload modversions 

從上面的對比可知,自己編寫的模組的模組資訊很少,沒有作者資訊,沒有許可證資訊等等,其實這些都可以設定

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#define DRIVER_AUTHOR "zyf"
#define DRIVER_DESC   "A sample driver"

static int __init init_hello_4(void)
{
    printk(KERN_INFO "Hello, world 4\n");
    return 0;
}

static void __exit cleanup_hello_4(void)
{
    printk(KERN_INFO "Goodbye, world 4\n");
}

module_init(init_hello_4);
module_exit(cleanup_hello_4);

//模組的許可證 
MODULE_LICENSE("GPL");
//模組的作者
MODULE_AUTHOR(DRIVER_AUTHOR);
//模組的描述
MODULE_DESCRIPTION(DRIVER_DESC);

模組引數

在使用者態編寫程式的時候我們都應該清楚,是可以給程式傳遞引數的,那麼同樣核心模組同樣也有這樣的需求,下面的例子展示瞭如何去給核心模組傳遞引數:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYF");
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintArray[2] = {-1,-1};
static int arr_argc = 0;
//需要使用module_param來對引數進行說明,指明這個引數的型別,許可權等charp是字元指標
//定義陣列引數需要使用module_param_array
module_param(myshort,short,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
MODULE_PARM_DESC(myshort,"A short integer");
module_param(myint,int,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
MODULE_PARM_DESC(myint,"A integer");
module_param(mylong,long,S_IRUSR);
MODULE_PARM_DESC(mylong,"A long integer");
module_param(mystring,charp,0000);
MODULE_PARM_DESC(mystring,"A character string");
module_param_array(myintArray,int,&arr_argc,0000);
MODULE_PARM_DESC(myintArray,"An array of integer");
static int __init hello_5_init(void)
{
    int i;
    printk(KERN_INFO "Hello, world 5\n=============\n");
    printk(KERN_INFO "myshort is a short integer: %hd\n", myshort);
    printk(KERN_INFO "myint is an integer: %d\n", myint);
    printk(KERN_INFO "mylong is a long integer: %ld\n", mylong);
    printk(KERN_INFO "mystring is a string: %s\n", mystring);
    for (i = 0; i < (sizeof myintArray / sizeof (int)); i++)
    {
        printk(KERN_INFO "myintArray[%d] = %d\n", i, myintArray[i]);
    }
    printk(KERN_INFO "got %d arguments for myintArray.\n", arr_argc);
    return 0;
}

static void __exit hello_5_exit(void)
{
    printk(KERN_INFO "Goodbye,world 5\n");
}

module_init(hello_5_init);
module_exit(hello_5_exit);
/*

載入模組的時候,如果不指定引數就是上面的預設值,如果要指定引數的話
可以像下面這樣來指定引數。
insmod hello-5.ko mystring="superc" myint=444 

*/

模組檔案分割

在使用者態寫程式的時候,你會將一個大的程式分割成好幾個檔案,這樣程式脈絡就顯的很清晰。在這裡我們將初始化函式和退出函式分開在兩個檔案中編寫。

start.c中
#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module */

int init_module(void)
{
    printk(KERN_INFO "Hello, world - this is the kernel speaking\n");
    return 0;
}

stop.c中
#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module  */

void cleanup_module()
{
    printk(KERN_INFO "Short is the life of a kernel module\n");
}

那麼Makefile編譯的時候需要設定成這樣:
obj-m += startstop.o
startstop-objs := start.o stop.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean