天天看點

Linux驅動開發庖丁解牛之二 ——子產品程式設計

Linux 驅動開發庖丁解牛之二 

—— 子產品程式設計 

dreamice 

e-mail:[email protected] 

本文是建立在前面的開發環境已經成功建立的基礎之上的。如果沒有建立好,請參照《 Linux 驅動開發庖丁解牛之一 —— 開發環境的建立》。 

已經有很多文檔講述子產品程式設計,個人覺得《 The Linux kernel module programming guide 》是最詳盡的。本文不再立足于從理論上去闡述子產品程式設計的相關知識,而着重從實踐的基礎上去掌握子產品程式設計,領悟子產品程式設計的實質。當然,具備足夠的理論知識才能從實踐出發,是以,本文檔盡量配合《 Linux Device Driver 》第三版的第二章,以及講述子產品程式設計最完善的文檔《 The Linux kernel module programming guide 》。下面,我們從實踐開始出發吧。 

1.  人之初( hello world ) 

#include <linux/module.h>  

#include <linux/kernel.h>  

#include <linux/init.h> 

MODULE_LICENCE( “ Dual BSD/GPL ” ); 

static int hello_init(void) 

printk(KERN_INFO "Hello world/n"); 

return 0; 

static void hello_exit(void) 

printk(KERN_INFO "Goodbye world/n"); 

module_init(hello_init); 

module_exit(hello_exit); 

MODULE_AUTHOR(“dreamice,  [email protected] ”); 

MODULE_DESCRIPTION(“The first module program”); 

MODULE_VERSION(“V1.0”); 

MODULE_ALIAS(“Chinese: ren zhi chu”); 

Makefile: 

obj-m := hello.o 

KERNELDIR ?= /lib/modules/$(shell uname -r)/build 

PWD := $(shell pwd) 

default: 

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 

clean: 

$(RM) *.o *.ko *.mod.c Module.symvers 

現在,我們一步一步來解析這個最簡單的hello world 子產品程式。 

1. /linux/module.h  這個是必須的。這個頭檔案包含了對子產品結構的定義以及相關資訊。 

2. module_init  和 module_exit 這兩個函數是必須的。 module_init 就好比應用程式的 main 函數,沒有 main 函數,應用程式将不知道從哪裡開始執行。 

3.關于 printk ,在 ldd3 的第四章有詳細的說明,這個可以說是核心調試的一個基本手段。 

4. MODULE_LICENCE , MODULE_AUTHOR , MODULE_DESCRIPTION , MODULE_VERSION , MODULE_ALIA ,分别是子產品許可證,子產品作者、描述、版本以及别名的描述,除了許可證這個比較正式以外(遵循 GPL ),其它幾個主要是用作開發者的一些控制和描述資訊,使用比較靈活。 

5. Makefile ,很特别,不同于一般的應用程式的 Makefile 。首先,子產品編譯的目标必須以 obj-m 這樣的形式指出;其次,子產品的編譯必須指定核心源代碼的路徑 —— 這也是子產品運作在核心空間的一個原因。子產品有多個源檔案生成的情況可如下編寫: 

obj-m := module.o 

module-objs := file1.o file2.o 

6. module_exit 為子產品推出執行清理的函數。如果子產品加載後不允許解除安裝,那麼這個函數就不用實作了。 

2. 子產品常見錯誤 

處理錯誤常常是程式員比較頭痛的事情,這裡我把比較常見的一些子產品編譯錯誤羅列一下,可能不是很全面。 

1.  Invalid module format 

這個通常是版本不一緻問題導緻的。比如說,你在2.4 核心上編譯的子產品,如果運作到 2.6 版的核心,有可能就會出現這樣的提示。 

另外可以嘗試一下,把一個普通檔案改名為hello.ko,  執行 insmod hello.ko ,也會報

這個錯誤。 

2.  Unresolved symbol…… 

這個錯誤常常是你引用的某個函數可能出了問題。如引用核心子產品并沒有導出的函數。在這裡,順便把子產品符号導出描述一下: 

EXPROT_SYMBOL(name) 

EXPROT_SYMBOL_GPL(name) 

如果一個子產品希望另一個子產品引用自己的函數,那麼必須使用以上兩個函數導出符号,否則,其它子產品是不能引用的。就會報這個錯誤了。 

3. 這個錯誤不是編譯問題的錯誤: 

子產品運作于核心空間,是以,不能引用标準庫函數,也不能處理浮點數。如果不加注意,可能導緻一些無法預知的錯誤。 

4. 由于核心版本更新,導緻一些結構體的改變,如果在低版本的核心上編寫子產品,到高版本編譯,就可能出現 “no such member……” 類似的錯誤。 

Linux驅動程式的開發者無非處于兩種情況: 

1. 對一個全新的硬體編寫驅動程式。這種情況需要對該版本核心的相關部分有充分了解。程式員的精力既要考慮子產品功能的設計實作,又要考慮核心的接口功能等等。 

2. 移植一個驅動程式。往往從低版本到高版本或者高版本到低版本的移植。這種情況下,需要對兩個版本的核心有一個充分的認識,就是可能有些結構體,或者函數接口變化的問題。程式員往往不需要花太多的精力在子產品功能的實作上,而更多的精力在版本差異上。 

3. 子產品層疊技術 

關于子產品層疊技術很多書上介紹的比較簡略。子產品層疊技術,主要指子產品的依賴關系,如子產品A 的實作依賴于 B ,如果 B 并沒有加載,那麼,當 insmod A 的時候,将無法成功。這個時候,就必須使用 modprobe A ,把相關的子產品統統加載到核心,即 B 也得到了加載。我們盡量不要使用 modprobe –r 來移出一個子產品,因為這将導緻相關聯的子產品都被移出。 

4.子產品實作的核心代碼分析 

1.資料結構 

子產品相關的資料結構存放在include/linux/module.h 

struct module 

enum module_state state; 

struct list_head list; 

char name[MODULE_NAME_LEN]; 

struct module_kobject mkobj; 

struct module_param_attrs *param_attrs; 

struct module_attribute *modinfo_attrs; 

const char *version; 

const char *srcversion; 

const struct kernel_symbol *syms; 

unsigned int num_syms; 

const unsigned long *crcs; 

const struct kernel_symbol *gpl_syms; 

unsigned int num_gpl_syms; 

const unsigned long *gpl_crcs; 

const struct kernel_symbol *unused_syms; 

unsigned int num_unused_syms; 

const unsigned long *unused_crcs; 

const struct kernel_symbol *unused_gpl_syms; 

unsigned int num_unused_gpl_syms; 

const unsigned long *unused_gpl_crcs; 

const struct kernel_symbol *gpl_future_syms; 

unsigned int num_gpl_future_syms; 

const unsigned long *gpl_future_crcs; 

unsigned int num_exentries; 

const struct exception_table_entry *extable; 

int (*init)(void); 

void *module_init; 

void *module_core; 

unsigned long init_size, core_size; 

unsigned long init_text_size, core_text_size; 

void *unwind_info; 

struct mod_arch_specific arch; 

int unsafe; 

int license_gplok; 

#ifdef CONFIG_MODULE_UNLOAD 

struct module_ref ref[NR_CPUS]; 

struct list_head modules_which_use_me; 

struct task_struct *waiter; 

void (*exit)(void); 

#endif 

#ifdef CONFIG_KALLSYMS 

Elf_Sym *symtab; 

unsigned long num_symtab; 

char *strtab; 

struct module_sect_attrs *sect_attrs; 

#endif 

void *percpu; 

char *args; 

}; 

在核心中,每一個核心子產品都由這樣一個module 對象來描述。所有的 module 對象由一個連結清單連結在一起,其中每個對象的 next 域都指向連結清單的下一個元素。 

State 表示 module 目前的狀态 , 主要包括一下幾種狀态: 

MODULE_STATE_LIVE 

MODULE_STATE_COMING 

MODULE_STATE_GOING 

其中,加載後的子產品的狀态為MODULE_STATE_LIVE 。 

name儲存 module 的名字; 

param_attrs指向 module 可傳遞的參數的名稱及屬性; 

init和 exit 這兩個函數指針,可以看作是 hello world 對應的 init 和 exit 函數,即子產品初始化和子產品退出所調用的函數; 

struct list_head modules_which_use_me這個成員是一個連結清單,訓示了所有依賴于該子產品的子產品。 

下面我們繼續看看子產品的加載和退出函數。 

作業系統在初始化時,調用static LIST_HEAD(modules) 建立了一個空連結清單,之後,每裝入一個核心子產品,即建立一個 struct module 結構,并把它鍊入 modules 這個全局的連結清單中。 

從作業系統核心的角度來說,它提供的使用者服務,都是通過系統調用來實作的。實際上,我們在調用module_init 和 module_exit 時,都是首先需要通過這兩個系統調用來實作的: sys_init_module(), sys_delete_module() 。我使用的是比較新的 2.6.25 的核心源碼。在核心源碼: arch/x86/kernel/ syscall_table_32.S, 我們看到這兩個系統調用的函數: 

………… 

.long sys_sigprocmask 

.long sys_ni_syscall  

.long sys_init_module 

.long sys_delete_module 

.long sys_ni_syscall  

asmlinkage long 

sys_init_module(void __user *umod, 

unsigned long len, 

const char __user *uargs) 

struct module *mod; 

int ret = 0; 

if (!capable(CAP_SYS_MODULE)) 

return -EPERM; 

if (mutex_lock_interruptible(&module_mutex) != 0) 

return -EINTR; 

mod = load_module(umod, len, uargs); 

if (IS_ERR(mod)) { 

mutex_unlock(&module_mutex); 

return PTR_ERR(mod); 

mutex_unlock(&module_mutex); 

blocking_notifier_call_chain(&module_notify_list, 

MODULE_STATE_COMING, mod); 

if (mod->init != NULL) 

ret = mod->init();  

if (ret < 0) { 

mod->state = MODULE_STATE_GOING; 

synchronize_sched(); 

module_put(mod); 

mutex_lock(&module_mutex); 

free_module(mod); 

mutex_unlock(&module_mutex); 

wake_up(&module_wq); 

return ret; 

if (ret > 0) { 

printk(KERN_WARNING "%s: '%s'->init suspiciously returned %d, " 

"it should follow 0/-E convention/n" 

KERN_WARNING "%s: loading module anyway.../n", 

__func__, mod->name, ret, 

__func__); 

dump_stack(); 

mod->state = MODULE_STATE_LIVE;  

wake_up(&module_wq); 

mutex_lock(&module_mutex); 

module_put(mod); 

unwind_remove_table(mod->unwind_info, 1); 

module_free(mod, mod->module_init); 

mod->module_init = NULL; 

mod->init_size = 0; 

mod->init_text_size = 0; 

mutex_unlock(&module_mutex); 

return 0; 

asmlinkage long 

sys_delete_module(const char __user *name_user, unsigned int flags) 

struct module *mod; 

char name[MODULE_NAME_LEN]; 

int ret, forced = 0; 

if (!capable(CAP_SYS_MODULE)) 

return -EPERM; 

if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0) 

return -EFAULT; 

name[MODULE_NAME_LEN-1] = '/0'; 

if (mutex_lock_interruptible(&module_mutex) != 0) 

return -EINTR; 

mod = find_module(name); 

if (!mod) { 

ret = -ENOENT; 

goto out; 

if (!list_empty(&mod->modules_which_use_me)) { 

ret = -EWOULDBLOCK; 

goto out; 

if (mod->state != MODULE_STATE_LIVE) { 

DEBUGP("%s already dying/n", mod->name); 

ret = -EBUSY; 

goto out; 

if (mod->init && !mod->exit) { 

forced = try_force_unload(flags); 

if (!forced) { 

ret = -EBUSY; 

goto out; 

mod->waiter = current; 

ret = try_stop_module(mod, flags, &forced); 

if (ret != 0) 

goto out; 

if (!forced && module_refcount(mod) != 0) 

wait_for_zero_refcount(mod); 

if (mod->exit != NULL) { 

mutex_unlock(&module_mutex); 

mod->exit();  

mutex_lock(&module_mutex); 

strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); 

free_module(mod); 

out: 

mutex_unlock(&module_mutex); 

return ret; 

以上就是子產品實作的核心源碼分析,雖然分析不夠詳盡,但對于編寫核心子產品來說,理清這麼一條線,将有助于更加深入的了解子產品的執行及在核心中的情況。 

5.後記 

雖然費了很大力氣寫完了這篇子產品程式設計,但總感覺欠完善,這也跟自己對核心以及驅動程式的經驗和整體把握能力有關。我希望它雖然存在很多缺點,但能起到一個引線的作用,作一個總結和分析,同時是對自己閱讀學習的總結,也希望對它人有所幫助,作為去深入細化研究的一個參考吧。 

敬請批評指正! 

繼續閱讀