天天看點

linux device drivers 讀書筆記(第二章)搭建測試系統hello world子產品核心子產品VS應用程式使用者空間和核心空間核心中的并行目前程序其他的一些細節編譯和裝載核心符号表預備初始化和關閉子產品參數在使用者空間做到

搭建測試系統

(告訴你應該自己搭一個測試系統)

hello world子產品

示例子產品

#include <linux/init.h>//init所需要
#include <linux/module.h>//module都應該包含的,包括一些symbol
MODULE_LICENCE("Dual BSD/GPL");//協定要求

static int hello_init(void)
{
    printk(KERN_ALERT "Hello, world!");
    return ;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, world!");
}

module_init(hello_init);//設定Init函數
module_exit(hello_exit);//設定退出函數
           

編譯(編譯方法見後)後通過在root下,insmod和rmmod可以在tty看到提示

核心子產品VS應用程式

應用程式存在連結過程,可以使用libc定義的函數,而核心子產品限制較多,隻與核心連結,不能使用libc中的函數,隻能使用核心當中所定義的,是以與應用程式程式設計差距較大.比如不能使用一般的頭檔案,比如stdarg.h等,另外一個比較大的差別是對于錯誤的解決辦法,對于應用程式,無非是出現段錯誤,然後等待解決,但是對于核心子產品來說可能就會有很嚴重的錯誤了.

使用者空間和核心空間

子產品是在核心空間運作的 ,應用程式在使用者空間運作,核心的運作權限更高,高于使用者程式,它們運作在不同的cpu模式下,我們将運作模式稱為核心空間和使用者空間,這兩種說法不僅指明了權限問題,也指明了它們個字有自己的記憶體映射,也就是位址空間.

核心中的并行

驅動代碼作為核心代碼,必須是可重入的,是以在寫驅動程式的時候就要考慮到關于并行時可能帶來的問題.

目前程序

核心也是通過特定的一些程序來運作的,核心代碼通過進入全局執行個體current,在 < asm/current.h>當中定義的來找到目前運作的程序.而current可以得到一個task_struct的結構體的指針,在< linux/sched.h>當中定義.

其實current并不是一個完全的全局變量,由于需要快速存取current,核心選擇将task_struct結構體放在核心棧上,然而具體實作對于其他子系統來說是屏蔽的,其他部分隻需要通過包括linux/sched.h然後擷取到current就可以了.

其他的一些細節

  1. 應用程式在虛拟記憶體中有很大的一片棧區域,核心隻有很小的一段棧區域,可以小到就隻有一頁,4096位元組.
  2. 核心的API有一些函數名用雙下劃線開頭,表示是底層元件的接口,使用的時候需要多加注意
  3. 核心代碼不能進行浮點運算

編譯和裝載

編譯

和使用者空間應用程式的建構有顯著的差別,而且和之前版本的核心也會有差別. 在核心源碼Documentation/kbuild目錄下有詳細的介紹.

核心源碼中的Documentation/Changes目錄列出了所需要的工具版本.需要注意所使用的工具版本符合要求.

準備好所有工作之後,你需要寫一個makefile. 我們以之前的helloworld子產品為例,一行就可以解決問題

這不是一個傳統的makefile,而是由核心建構系統來處理剩下的工作.最終會編譯出一個hello.ko

如果你有一個modole.ko由兩個源檔案(file1.c file2.c)創造,則makefile為

obj-m := module.o
module-objs := file1.o file2.o
           

如果你的核心源碼樹在你的~/kernel-2.6目錄下,那麼make指令就是

make -c ~/kernel- M=`pwd` modules
           

-C用來切換目錄,M=使得makefile在構造子產品之前移動回子產品源碼目錄.

使用上面的make可能會有些麻煩,是以核心開發者開發了一些方法使其變得更容易,以下是這種方法的makefile

#如果KERNELRELEASE被定義了,說明我們是從核心編譯系統調用,直接使用他的語言
ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
#否則我們是直接從指令行調用,是以需要調用核心編譯系統
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
           

裝載和移除子產品

通過insmod可以裝載子產品,它和ld的作用比較類似,将為解析的符号連結到核心的符号表中,但是核心不改變子產品的磁盤檔案,而是在它在記憶體中的映像中改變.

核心支援insmod是通過一個在kernel/module.c中定義的系統調用來實作的,sys_init_module配置設定了核心記憶體來儲存一個子產品,然後把子產品文本複制到這塊區域中,解析引用然後調用子產品初始化函數開始工作.

系統調用函數在核心源碼中為sys_字首.

modprobe和insmod一樣,也是裝載一個子產品進入核心,但是它還會看是否這個子產品引用了一些沒有在核心符号表中的函數,然後會去其他子產品中尋找,之後将其加載進入核心,但是insmod在這種情況下就會直接報告無法解析符号.

rmmod用來解除安裝子產品,但是如果核心認為這個子產品還在使用或者核心被配置為不允許子產品解除安裝,就會報錯,當然可以把核心配置為可強制移除子產品,但是可能會造成一些問題

版本依賴

在子產品build過程中,由一步是将子產品連結到一個叫做vermagic.o的檔案,這個檔案位于目前核心樹中,包含了一些關于核心的資訊,比如目标核心版本,編譯器版本等.在裝載的時候,這些資訊會被用來作相容性檢驗,如果不符合,則不會被裝載

比如出現以下錯誤

# insmod hello.ko
Error inserting './hello.ko': - Invalid module format
           

在系統日志中,比如/var/log/messages或者其他地方會指明錯誤原因.

在linux/module.h預設包含了linux/version.h,定義了以下宏可以用來在#ifdef中進行版本校驗,來使得子產品可以用于多版本.

UTS_RELEASE
為一個版本的字元串,如"2.6.10"
LINUX_VERSION_CODE
用二進制表示的核心版本,比如表示為
KERNEL_VERSION(major,minor,release)
将major.minor.release轉換為二進制表示的核心版本,如KERNEL_VERSION(,,)即
           

平台依賴

(似乎沒有太多需要記住的東西)

核心符号表

insmod通過核心符号表解析符号,這個表包含了核心中的全局符号的位址,比如函數和變量.而子產品被載入之後,子產品中導出的符号也就成為了核心符号表的一部分.

新的子產品可以使用被你的子產品導出的符号,是以可以将其他子產品堆疊上去,而在這種情況下,modprobe功能就十分有用了.

核心源碼頭檔案提供了一些很友善的方法用來管理符号,比如需要導出的時候

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
           

帶gpl的版本保證在GPL協定下可用.這些宏必須在全局的位置使用,不能在函數内使用,因為這些宏會展開為特殊用途的全局變量,然後存儲在檔案的ELF區.linux/module.h定義了一些細節.

預備

幾乎所有的子產品包含了以下兩個頭檔案

#include <linux/module.h>//包含了很多符号的定義
#include <linux/init.h>//需要其用來指明初始化和清除函數
           

很多子產品還包含了moduleparam.h,用來保證裝載時的參數傳遞.

協定可以使用以下宏來定義

可用的協定如下描述(以下來自linux/module.h)

/*
 * The following license idents are currently accepted as indicating free
 * software modules
 *
 *  "GPL"               [GNU Public License v2 or later]
 *  "GPL v2"            [GNU Public License v2]
 *  "GPL and additional rights" [GNU Public License v2 rights and more]
 *  "Dual BSD/GPL"          [GNU Public License v2
 *                   or BSD license choice]
 *  "Dual MIT/GPL"          [GNU Public License v2
 *                   or MIT license choice]
 *  "Dual MPL/GPL"          [GNU Public License v2
 *                   or Mozilla license choice]
 *
 * The following other idents are available
 *
 *  "Proprietary"           [Non free products]
 *
 * There are dual licensed components, but when running with Linux it is the
 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
 * is a GPL combined work.
 *
 * This exists for several reasons
 *    So modinfo can show license info for users wanting to vet their setup
 *  is free
 *    So the community can ignore bug reports including proprietary modules
 *    So vendors can do likewise based on their own policies
 */
           

另外一些描述性的定義包括MODULE_AUTHOR指明作者,MODULE_DESCRIPTION指明描述,MODULE_VERSION指明版本.MODULE_ALIAS指明别名,MODULE_DEVICE_TABLE指明子產品支援.

MODULE_開頭的定義在源檔案函數外的任何地方都可以出現,不過大多會放在檔案最後.

初始化和關閉

static int __init func(void)
{
//初始化
}
module_init(func);
           

這樣的方式用來指明初始化函數,__init表明該函數僅在初始化時使用,之後會被處理掉.相應的還有__initdata,用來說明資料隻在初始化時使用.

還有__devinit和__devinitdata,這些僅在核心被配置為熱插拔時翻譯為__init和__initdata.

module_init用來指明一個初始化函數,為子產品的目标檔案添加一個特殊的區域用來找到初始化函數.

清除函數

static void __exit func(void)
{
//清除
}
module_exit(func);
           

__exit和__init對應,用處也對應,module_exit和module_init對應,不過它們都是在子產品移除時調用.注意這個函數不需要傳回.如果沒有定義清除函數,核心将不允許移除這個子產品.

初始化時的錯誤處理

注意在初始化步驟中出現錯誤需要将已經進行的步驟逆轉,使用方法可以使用goto來使得工作更加簡化.

子產品裝載時的競争

注意競争條件,其他章會詳細說明

子產品參數

insmod可以指明子產品參數,

howmany和whom即子產品參數,這些子產品參數用module_param宏來定義,module_param在moduleparam.h中定義.

module_param的原型為

name表示變量名,type表示類型,permission是一個權限掩碼,是為檔案系統準備的.

比如用法

static char *whom = "world";
static int howmany = ;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
           

支援的類型包括

bool 和 invbool :内部使用int類型表示,invbool是反過來的true和false
charp :char指針
int ,long, short, uint, ulong, ushort : 基本的整形值,u表示無符号.
           

數組參數是用逗号隔開的清單,也是被支援的,數組參數的定義是

type是其中元素的類型,num是整型變量

permission值在

在使用者空間做到

(一些在使用者空間寫程式代替子產品的方法)

繼續閱讀