Linux驅動程序開發001 - 驅動程序基本框架

前言
不管是Windows還是Linux,驅動程序都扮演着重要的角色。應用程序只能通過驅動程序才能同硬件設備或系統核通訊。Linux內核對不同的系統定義了標準的接口(API),應用程序就是通過這些標準的接口來操作內核和硬件。驅動可以被編譯的內核中(build-in),也可以做爲內核模塊(Module)存在於內核的外面,需要的時候動態插入到內核中運行。
就像你學習操作系統概念時所瞭解的那樣,Linux內核也分爲幾個大的部分:進程管理、內存管理、文件系統、設備控制、網絡系統等,參考圖1-1。
Linux驅動程序開發 <wbr>- <wbr>驅動程序基本框架
圖1-1 Linux系統(來源:O’Reilly Media, LDD3)
這裏就不對Linux系統內核的各個部分做過多的介紹了,在後面的學習中你就會逐漸地對這些概念有個更深入的瞭解 。其實Linux內核的精髓遠不止這些,對於一個Linux內核的愛好者或開發者來說,最好詳細的瀏覽內核代碼,訂閱Linux內核相關的郵件列表,或是登陸Linux開發社區。更多的信息,請登陸Linux內核官方網站:http://www.kernel.org

一個簡單的驅動
下面我們來編寫第一個驅動程序,它很簡單,在運行時會輸出‘Hello World’消息。

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

static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello World!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Goodbye World!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

這就是一個簡單的驅動程序,它什麼也沒做,僅僅是輸出一些信息,不過對於我們來說這已經足夠了。保存這個程序,命名爲hello.c。在寫一個Makefile文件用來編譯它,Makefile和hello.c文件保存在同一個目錄下。
##Makefile
ifneq ($(KERNELRELEASE),)
MODULE_NAME = helloworld
$(MODULE_NAME)-objs := hello.o
obj-m   := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
        make -C $(KERNEL_DIR)  M=$(MODULEDIR) modules

clean distclean:
        rm -f *.o *.mod.c .*.*.cmd *.ko
        rm -rf .tmp_versions
endif

編譯並運行這個模塊:
//需要root權限來運行
make

insmod helloworld.ko
rmmod helloworld.ko

儘管我們對它的一些細節還不夠了解,它確實神奇的工作了,這個Hello World信息輸出到了屏幕終端上(不是VT),或者系統的Kenrel log裏(/var/log/messages),你可以通過運行dmesg來看到這些信息。

驅動基礎
我們通過分析上面的代碼來了解一個驅動程序的基本概念。
  • 頭文件
就像你寫C程序需要包含C庫的頭文件那樣,Linux內核編程也需要包含Kernel頭文件,大多的Linux驅動程序需要包含下面三個頭文件:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
    • init.h 定義了驅動的初始化和退出相關的函數,
    • kernel.h 定義了經常用到的函數原型及宏定義
    • module.h 定義了內核模塊相關的函數、變量及宏。
  • 初始化
任何一個驅動都去需要提供一個初始化函數,當驅動加載到內核中時,這個初始化函數就會被自動執行,初始化的函數原型定義如下:
 typedef int (*initcall_t)(void);

驅動程序是通過module_init宏來聲明初始化函數的:
static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello World!\n");
    return 0;
}
module_init(hello_init);

__init 宏告訴編譯器如果這個模塊被編譯到內核則把這個函數放到(.init.text)段,這樣當函數初始化完成後這個區域可以被清除掉以節約系統內存。Kenrel啓動時看到的消息“Freeing unused kernel memory: xxxk freed”同它有關。
初始化函數是有返回值的,只有在初始化成功是才返回0,否則返回錯誤碼(errno)。
  • 卸載
如果驅動程序編譯成模塊(動態加載)模式,那麼它需要一個清理函數。當移除一個內核模塊時這個函數被調用執行清理工作。清理函數的函數原型定義爲:
 typedef void (*exitcall_t)(void);

驅動程序是通過module_exit宏來聲明清理函數的:
static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Goodbye World!\n");
}
module_exit(hello_exit);
同__init類似,如果驅動被編譯進內核,則__exit宏會忽略清理函數,因爲編譯進內核的模塊不需要做清理工作。顯然,__init和__exit對動態加載的模塊是無效的。
  • 版權信息
Linux內核是按照GPL發佈的,同樣Linux的驅動程序也要提供版權信息,否則當加載到內核中是系統會給出警告信息。Hello World例子中的版權信息是GPL。
  MODULE_LICENSE("GPL");

後續
這裏你瞭解了一個驅動程序的基本框架,所有的驅動都會包含這些內容。這裏我們沒有對Linux 驅動程序的編譯系統做詳細的介紹,因爲它相對C應用程序的編譯有些複雜。Linux2.6內核採用Kbuild系統做編譯,下一章你會瞭解到Kbuild的詳細內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章