1. 程式人生 > >14 Linux裝置驅動基礎程式設計

14 Linux裝置驅動基礎程式設計

Linux裝置驅動基礎程式設計


核心功能模組:程序排程,記憶體管理(mmu,分配程序記憶體),檔案系統管理(如:支援的檔案系統格式),裝置驅動(硬體驅動由核心來統一管理),網路協議棧。

模組機制:
靜態載入:把驅動模組編進核心,在核心啟動時自動載入。
動態載入:把驅動模組編為.ko檔案,在核心啟動後,需要用時手動載入。


核心驅動標頭檔案中所定義有關的巨集:

#define __init      __section(.init.text)
#define __initdata  __section(.init.data)
#define __section(S) __attribute__ ((__section__(#S)))
//段".init*"其實就是表示只要初始化後不會再使用,核心可以把這段裡的空間回收使用 #define __exitdata __section(.exit.data) #define __exit __section(.exit.text) //段".exit*"應是用於集中管理只有驅動模組解除安裝時才會觸發呼叫的資源,防止被誤呼叫 char __initdata buf[] = "hello world";//表示此字元陣列在驅動初始化後可以回收空間 #define module_init(initfn) \
static inline initcall_t __inittest(void) \ { return initfn; } \ int init_module(void) __attribute__((alias(#initfn))); //module_init這個巨集其實就是把我們的初始化函式名多加個別名("init_module") //module_exit巨集用於把解除安裝函式多加個別名("cleanup_module") //module_exit具體的巨集定義沒列出來
//linux核心2.4版本時,裝置驅動模組的初始化函式名必須是"init_module",解除安裝函式名必須是"cleanup_module"。 //現在我們寫驅動模組,初始化函式和解除安裝函式名可以隨便命名,但其實核心裡還是沒變。

簡單的事例程式碼(xxx.c)(xxx為具體的.c檔名):

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

//__init為了把test_init的函式程式碼放入統一的初始化段裡,當核心把驅動初始化完後,自動釋放此函式的程式碼指令空間
static int __init test_init(void)
{
    printk("This is the test init function\n");//printk為核心預設的列印函式
    return 0;//返回0表示成功,返回負數退出載入模組,返回正數,會有警告但還是會載入
}

//__exit為了指定此函式只在驅動解除安裝時使用,用完後自動釋放
static void __exit test_exit(void)
{
    printk("This is the test exit function\n");
}

module_init(test_init);//指定test_init為模組初始化函式
module_exit(test_exit);//指定test_exit為模組退出時執行的解除安裝函式

MODULE_LICENSE("GPL");//指定所支援的協議
MODULE_AUTHOR("作者");
MODULE_DESCRIPTION("描述");
MODULE_VERSION("版本");

編譯驅動模組,需要呼叫核心原始碼目錄裡的Makefile。
在程式碼目錄下建立一個Makefile檔案來指定編譯目標。

Makefile檔案內容:

obj-m += xxx.o

KSRC := /目錄路徑/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-

all:
    make -C $(KSRC) modules M=`pwd` 

.PHONY : clean
clean:
    make -C $(KSRC) modules clean M=`pwd`

編譯完成後會生成xxx.ko檔案,可以將其載入到驅動中(相當於執行該檔案):

insmod xxx.ko   //載入驅動模組
rmmod  xxx     //解除安裝驅動模組

檢視驅動模組資訊:

modinfo xxx.ko  //檢視模組的資訊
cat /proc/modules   //檢視當前系統的動態載入模組(相當於lsmod)
    如:xxx 1768 0 - Live 0xbf03c000
    (模組名 使用的記憶體大小 正在被呼叫次數 - 有效 模組所在的記憶體地址) 
ls /sys/module  //檢視系統裡現有的驅動模組(包括動靜態驅動模組)

因為printk的輸出級別問題,如果不進行設定,我們是看不到輸出的。
我們可以通過以下命令來檢視驅動輸出的訊息:

cat /var/log/messages
tail /var/log/messages
dmesg | tail

printk的輸出級別控制:

#include <linux/kernel.h>
#define KERN_EMERG  "<0>"   /* system is unusable */
#define KERN_ALERT  "<1>"   /* action must be taken immediately */
#define KERN_CRIT   "<2>"   /* critical conditions */
#define KERN_ERR    "<3>"   /* error conditions */
#define KERN_WARNING    "<4>"   /* warning conditions */
#define KERN_NOTICE "<5>"   /* normal but significant condition */
#define KERN_INFO   "<6>"   /* informational */
#define KERN_DEBUG  "<7>"   /* debug-level messages */

//預設的級別為13: "<d>"        
#define KERN_DEFAULT    "<d>"

使用:printk(KERN_INFO”內容”);//相當於:printk(“<6>kskdlfj”);//指定輸出級別為6

cat /proc/sys/kernel/printk //檢視當前核心的輸出級別
    7       7       1       7
    7: console_loglevel 
    7: default_message_loglevel 
    1: minimum_console_loglevel
    7: default_console_loglevel
    當printk函式使用的級別小於當前console_loglevel級別時, 則可以輸出, 否則不輸出

echo "8 4" > /proc/sys/kernel/printk  //修改級別輸出  
//輸出級別只要小於8就可以輸出(否則會看不到輸出,要通過檢視驅動資訊才能看到),預設級別為4(即不指定級別時使用此級別)

程式碼裡用於除錯輸出的巨集:

#ifdef DEBUG
    #define TS_DEBUG(fmt,args...)   do { printk(fmt, ##args); } while (0)
#else
    #define TS_DEBUG(fmt,args...)   do { } while (0)
#endif

用法:

TS_DEBUG("hello\n");
TS_DEBUG("%d, %d\n", num , num2);