Linux驅動開發筆記總結(一)

基於Cortex A8的Linux驅動編程初級基礎知識總結
Linux操作系統中的驅動程序分3種:
1.字符設備 順序讀寫,以字節爲單位,不帶緩衝區的設備
2.塊設備 隨機讀寫,以數據塊爲單位,帶有緩衝區的設備
3.網絡設備 不是基於文件系統訪問的設備,而是通過一個特殊的網絡接口(struct net_device)實現的。
Linux模塊編程中需要用到的宏:
module_init:內核中定義的一個宏,用來修飾模塊加載函數,可以將模塊加載函數鏈接到.init.text段上。
module_exit:內核中定義的一個宏,用來修飾模塊卸載函數,可以將模塊卸載函數鏈接到.exit.text段
MODULE_LICENSE("GPL"):添加許可聲明,必須加,否則會出現內核污染
如何在內核中添加一個組件?
共有兩種方法,靜態編譯和動態加載模塊。在Linux驅動編程中,一般使用動態加載模塊來對內核進行添加組件的操作。如果使用靜態編譯一方面可能會導致內核過大,另一方面在添加和卸載組件時都需要重新編譯內核,這樣會十分麻煩。使用動態加載的方法,並不會將模塊編譯進內核,而是採用模塊化的方式,在需要的時候可以動態的安裝或卸載模塊
Linux下字符設備驅動編程基本流程:
1.通過datasheet和原理圖,瞭解設備的操作方法
2.定義模塊的加載和卸載函數,在加載和卸載函數中實現相應的初始化和卸載時所需要執行的任務(需使用module_init和module_exit修飾)
3.實現對設備的相應操作函數,如open(),close(),ioctl(),write(),read()函數
4.如果設備需要用到中斷的實現,則需編寫中斷處理函數
5.編譯成模塊,使用insmod命令進行模塊的安裝
在進行Linux下驅動模塊編程的時候,通常會使用Makefile文件來對模塊文件進行編譯
Makefile文件內容如下:
obj_m += xxx.o //xxx表示模塊文件名稱,表示將整個文件編譯成模塊
all:
make -C /home/driver/cw210_kernel_2.6.35.7 M=$(PWD) modules //其中-C表示進入後面的內核原碼路徑,-M表示當前模塊所在路徑,modueles目標指向obj-m變量中設定的模塊
clean:
rm -rf *.o *.ko *.order *.sy* *.mod*
Linux模塊編程常用命令:
insmod 安裝模塊 insmod xxx.ko
rmmod 刪除模塊 rmmod xxx.ko
lsmod 查看模塊信息
modinfo 查看單個模塊信息 modinfo xxx.ko
內核模塊編程的特點:
1、不能使用C庫函數 也不能訪問標準C頭文件
2、不能使用浮點數
3、沒有內存保護機制
4、必須使用GNU C進行編程
5、內核爲每個進程分配了一個8KB的定長堆棧
6、內核支持中斷,搶佔和SMP,時刻注意同步和併發
7、要考慮可移植的重要性
導出符號:
導出符號的本質就是在內核態a.c中的函數或者變量要被b.c使用,如何處理?
在a.c文件中用EXPORT_SYMBOL或EXPORT_SYMBOL_GPL宏對將要被使用的函數或變量進行修飾,並編寫一個頭文件,在其中聲明函數或變量是在外部定義的。然後直接在b.c文件中包含這個頭文件,並使用函數或變量即可
a.c : func
	EXPORT_SYMBOL(func);
	EXPORT_SYMBOL_GPL(func);

a.h:

	extern int func(void);
b.c:
	#include "a.h"
	func();
模塊參數:
模塊參數本質就是用來解決安裝模塊時傳遞參數的問題。在實際開發中,可以用來更改在模塊中被使用的參數的值以便於調試。
對要做爲參數的變量採用module_param修飾,對於數組則使用module_param_array來修飾
module_param(name,type,perm)
name 變量名稱
type 數據類型(bool,int,short,long,charp)
perm 指定該變量的訪問權限 0664
module_param_array(name,type,nump,perm)
name 變量名稱
type 數據類型(bool,int,short,long,charp)
nmup 有效數組元素個數的指針
perm 指定該變量的訪問權限 0664
具體代碼示例如下:
#include <linux/init.h>
#include <linux/module.h>
//全局變量,模塊參數
static int irq;
static char *pstr;
static int fish[10];
static int nr_fish;//用來記錄有效的數組元素個數
//模塊參數的聲明
module_param(irq, int, 0664);
module_param(pstr, charp, 0);
module_param_array(fish, int, &nr_fish, 0640);
static int moduleparam_init(void)
{
    int i = 0;
	printk("%s: irq=%d, pstr=%s\n",__func__, irq, pstr);
	for(; i<nr_fish; i++){
		printk("%s: fish[%d] = %d\n",__func__, i, fish[i]);
	}
	return 0;	
}
static void moduleparam_exit(void)
{
    int i = 0;
	printk("%s: irq=%d, pstr=%s\n",__func__, irq, pstr);
	for(; i<nr_fish; i++){
		printk("%s: fish[%d] = %d\n",__func__, i, fish[i]);
	}	
}
module_init(moduleparam_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");
在安裝模塊時聲明參數的值即可,如:insmod moduleparam.ko irq=5 pstr=Phoenix fish=1,2,3,4,5
【注意】在模塊中聲明權限不爲0的模塊參數,會在/sys/module/xxx/parameters/目錄下產生一個與參數同名的文件,在實際應用中可以通過改變這個文件中的值來動態的改變模塊參數的值
printk優先級
printk是在在內核中運行的向控制檯輸出顯示的函數,一共有8個優先級。
    #define KERN_EMERG 0	/*緊急事件消息,系統崩潰之前提示,表示系統不可用*/
    #define KERN_ALERT 1	/*報告消息,表示必須立即採取措施*/
    #define KERN_CRIT 2		/*臨界條件,通常涉及嚴重的硬件或軟件操作失敗*/
    #define KERN_ERR 3		/*錯誤條件,驅動程序常用KERN_ERR來報告硬件的錯誤*/
    #define KERN_WARNING 4	/*警告條件,對可能出現問題的情況進行警告*/
    #define KERN_NOTICE 5	/*正常但又重要的條件,用於提醒*/
    #define KERN_INFO 6		/*提示信息,如驅動程序啓動時,打印硬件信息*/
    #define KERN_DEBUG 7	/*調試級別的消息*/

一般在/proc/sys/kernel/printk文件中可以查看printk優先級的相關信息

cat /proc/sys/kernel/printk

7      4      1 7
第一個值:可以打印到終端的最大值
printk中使用的優先級要小於該值才能顯示出來
第二個值:默認優先級
printk("hello\n"); //默認優先級是4

使用方法:

    printk(KERN_ERR "helloworld\n");
    printk(<3> "helloworld\n");
改變printk優先級輸出的方法
1、可以通過命令echo 8>/proc/sys/kernel/printk來改變printk的顯示級別
2、第一種方法對於內核啓動時的信息是無法改變printk的優先級輸出的,但是可以通過設置開發板參數來實現
setenv bootargs root=/dev/nfs ...... debug/quiet/loglevel=數字
在實際的開發中,當內核啓動時出現問題,可以通過修改bootargs變量來設置printk的輸出優先級,這樣可以大大的減少內核輸出中的一些不重要的信息,減少找出錯誤的難度。
小實驗:通過/proc/sys/kernel/printk文件修改printk輸出優先級的方式動態改變模塊的打印信息
代碼如下:
#include <linux/init.h>
#include <linux/module.h>
static int prink_init(void)
{
	printk(KERN_EMERG "0");
	printk(KERN_ALERT "1");
	printk(KERN_CRIT "2");
	printk(KERN_ERR "3");
	printk(KERN_WARNING "4");
	printk(KERN_NOTICE "5");
	printk(KERN_INFO "6");
	printk(KERN_DEBUG "7");
	return 0;
}
static void printk_exit(void)
{
	printk(<0> "0");
	printk(<1> "1");
	printk(<2> "2");
	printk(<3> "3");
	printk(<4> "4");
	printk(<5> "5");
	printk(<6> "6");
	printk(<7> "7");
}
module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");
實驗步驟:
insmod prinkall.ko
echo 5>/proc/sys/kernel/printk
rmmod printk.ko
系統調用
系統調用是用戶空間的程序調用內核空間程序的一種方式
實現機制:
1、進程先將系統調用號填充寄存器;
2、調用一個特殊的指令;
3、讓用戶進程跳轉到內核事先定義好的一個位置;
4、內核的位置是entry(vector_swi) //entry-common.s
5、檢查系統調用號,通過系統調用號告訴內核所請求的服務
6、檢查系統調用表(sys_call_table)找到所調用的內核函數入口地址
7、調用該函數,執行完畢後返回用戶進程
如何添加一個自己定義的系統調用
1)在內核中的sys_arm.c文件中添加一個新的函數

vi arch/arm/kernel/sys_arm.c

asmlinkage int sys_add(int x,int y)
{
    printk("enter %s\n",__func__);
    return x+y;
}

PS:內核編程時不能使用浮點數(float,double)
2)更新頭文件unistd.h

vi arch/arm/include/asm/unistd.h

    #define __NR_add  (__NR_SYSCALL_BASE+366)
3)更新系統調用表call.S

vi arch/arm/kernel/calls.S

    CALL(sys_add)
4)重新編譯內核,讓開發板重新加載新的內核
make
cp arch/arm/boot/zImage /tftpboot
重啓開發板
5)編寫測試程序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章