不管是Windows還是Linux,驅動程序都扮演着重要的角色。應用程序只能通過驅動程序才能同硬件設備或系統內核通訊。Linux內核對不同的系統定義了標準的接口(API),應用程序就是通過這些標準的接口來操作內核和硬件。驅動可以被編譯的內核中(build-in),也可以做爲內核模塊(Module)存在於內核的外面,需要的時候動態插入到內核中運行。
就像你學習操作系統概念時所瞭解的那樣,Linux內核也分爲幾個大的部分:進程管理、內存管理、文件系統、設備控制、網絡系統等,參考圖1-1。
這裏就不對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來看到這些信息。
驅動基礎
我們通過分析上面的代碼來了解一個驅動程序的基本概念。
- 頭文件
#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); |
- 版權信息
MODULE_LICENSE("GPL"); |
後續
這裏你瞭解了一個驅動程序的基本框架,所有的驅動都會包含這些內容。這裏我們沒有對Linux 驅動程序的編譯系統做詳細的介紹,因爲它相對C應用程序的編譯有些複雜。Linux2.6內核採用Kbuild系統做編譯,下一章你會瞭解到Kbuild的詳細內容。