一、Linux驅動開發與裸機開發的區別
1、開發思維區別
裸機驅動:
(1)底層,跟寄存器打交道,有些MCU提供了庫
Linux驅動:
(1)Linux下驅動開發直接操作寄存器不現實
(2)根據Linux下的各種驅動框架進行開發。一定要滿足框架,也就是Linux下各種驅動框架的掌握。
(3)驅動最終表現就是 /dev/xxx 文件。打開、關閉、讀寫等的文件操作(Linux下一切皆文件)。
(4)現在新的內核支持設備樹。這是一個 .dts 文件,此文件,描述了板子的設備信息。(Linux驅動開發思維,如果有設備樹的話,第一件是就是要去修改設備樹,添加你板子的信息)
2、Linux驅動開發分類
在 /dev 目錄下設備的區分:
文件類型:
(1)c:字符設備
(2)b:塊設備
(3)d:目錄
(4)l:符號鏈接
????????在 /dev 目錄主要集中了 字符設備 和 塊設備,字符設備以 c 作為開頭,塊設備以 b 作為開頭。
Linux驅動分為三大類:
1、字符設備驅動(最多的):
? ? ? ?
設備:
? ? ? ? LED、KEY、BEEP、聲卡、顯卡、攝像頭、鼠標、鍵盤、觸摸屏、手寫板、USB、......
使用 ls /dev -l 命令查看字符設備:
特點:
? ? ? ? (1)設備類型是c。應用程序和驅動程序之間交互數據的時候,數據是以字節為單位;不同的設備類型,交互的數據格式是不一樣的。
? ? ? ? (2)字符設備驅動數據是實時傳遞的,按照固定的格式傳遞。
應用程序訪問方法:
? ? ? ? 通過Linux系統IO函數訪問:open()、read()、write()、ioctl()、close()、mmap()等。
? ? ? ?
????????如觸摸屏的訪問:
? ? ? ? (1)struct input_event ts_ev;
? ? ? ? (2)int fd = open("/dev/input/event0", O_RDONLY);
? ? ? ? (3)read(fd, &ts_ev, sizeof(struct input_event));
? ? ? ? (4)close(fd);
2、塊設備驅動(存儲相關、大容量的存儲設備):
? ? ? ??
設備:
? ? ? ? eMMC(nand flash:8GB)、SD卡、U盤、移動硬盤、......
使用 ls /dev -l 命令查看:
特點:
? ? ? ? (1)塊設備是帶有緩存的,當緩存滿了(刷新緩存)這些數據才會寫到設備上去。
? ? ? ? (2)塊設備是有文件系統的(如開發板的跟文件系統的格式是ext4)。
? ? ? ? (3)數據是以塊(block)為單位。
? ? ? ? (4)塊設備文件類型是b。
應用程序訪問方法:
? ? ? ? 例如:
? ? ? ? U盤中有一個文件test.txt,編寫一個程序,讀取test.txt文件中的內容,并將該內容通過串口2發送出去。
? ? ? ? 如何訪問U盤?
? ? ? ? (1)掛載(mount):將塊設備以某一種文件系統的格式掛載到根文件系統的某個目錄上,再根據該目錄訪問塊設備。有些嵌入式平臺可以自動掛載U盤(實際上是做了相關的配置文件)。沒有自動掛載,就采用手動掛載。
? ? ? ? (2)掛載好后,則可以訪問:fd = open("/mnt/udisk/test.txt", O_RDONLY);
? ? ? ? (3)read函數
? ? ? ? (4)close(fd);
? ? ? ?
????????如何訪問串口?
? ? ? ? (1)fd = open("/dev/ttySAC2", O_RDWR);
? ? ? ? (2)初始化串口2
? ? ? ? (3)write(fd, buf, sizeof(buf));
? ? ? ? (4)close(fd);
3、網絡設備驅動
設備:
? ? ? ? 網卡(有線網卡、無線網卡).......
特點:
? ? ? ? 網卡? -------------------------? 物理層
? ? ? ? 網絡驅動層 -----------------? 數據鏈路層
如何查看網絡設備?
? ? ? ? 使用命令 ifconfig -a
應用程序如何訪問網絡設備?
? ? ? ? socket套接字:
? ? ? ? ? ? ? ? (1)字節流? -----------------? TCP;
? ? ? ? ? ? ? ? (2)數據報? -----------------? UDP;
? ? ? ? ? ? ? ? (3)原始套接字? -----------? 開發網絡協議;
? ? ? ? ? ? ? ? (4)地址信息? --------------? IP地址和端口號。
? ? ? ?
????????TCP的服務器:
? ? ? ? ????????socket() / bind() / listen() / accept() / read() / recv() / write() / send() / close()
? ? ? ? TCP的客戶端:
? ? ? ? ? ? ? ? socket() / connect()?/ read() / recv() / write() / send() / close()
驅動程序的特點:
1、驅動程序是運行在Linux內核中的,而應用程序是運行在Linux的用戶空間的。
2、每個硬件都需要有一個驅動程序,驅動程序是獨立的。在一個應用程序中可以訪問多個驅動程序。
? ? ? ? 如:視頻播放器 ------>> 顯卡? +? 聲卡? + 鼠標
3、應用程序是有入口函數的,main 函數是入口。而應用程序是沒有出口的。
? ? ?而驅動程序是有入口、有出口的:
? ? ?(1)入口函數:module_init(watchdog_init);
? ? ?(2)出口函數:module_exit(watchdog_exit);
? ? ?(3)安裝驅動:
? ? ? ? ? ? ? ? insmod?watchdog_drv.ko ---->> 自動調用入口函數module_init ----->> 進入?watchdog_init 函數 ---->> 從內核申請資源、向內核注冊驅動、建立驅動模型......
? ? ? (4)卸載驅動:
? ? ? ? ? ? ? ? rmmod?watchdog_drv.ko ---->> 自動調用出口函數module_exit----->> 進入?watchdog_exit 函數 ---->> 釋放申請的資源、注銷驅動......
4、編寫應用程序的時候,我們可以使用 庫函數 和 系統調用函數。
? ? ? ? 那么應用程序使用的頭文件是哪里來的(stdio.h、stdlib.h、string.h):
? ? ? ? (1)gcc:/usr/include/stdio.h
? ? ? ? (2)arm-linux-gcc:/usr/local/arm/5.4.0/usr/arm-none-linux-gnueabi/sysroot/usr/include/stdio.h
? ? ? ? 編寫驅動程序的時候,使用的頭文件是哪里來的(linux/kernel.h、/linux/module.h):
? ? ? ? 來自于Linux內核源碼:/kernel/linux/module.h
5、函數的區別
????????應用程序:printf()、malloc()、sleep()
????????驅動程序:printk()、kmalloc()、ssleep()
? ? ? ? 注意:
? ? ? ? 雖然 printk 和 printf 函數非常相似,但是通常 printk 函數不支持浮點數,雖然能夠編譯成功,但是最終運行卻得不到想要的結果。
? ? ? ? 整個內核空間的調用鏈上只有 4KB 或 8KB 的棧,相對于應用程序來說是非常小的,如果需要大內存的空間,需要使用專門的函數進行動態分配——kmalloc、zmalloc、vmalloc。
6、__init 和 __exit 關鍵字
????????__init 用來修飾 初始化函數,一般情況下初始化函數只運行一次,運行結束以后,就會將該函數占用的內存釋放掉。
7、驅動程序安裝好以后,驅動程序不是一直運行的;而是安裝到內核中的一個程序,只有應用程序去調用驅動程序,這個驅動程序才開始工作。
注意:驅動是安裝在內存中正在運行的內核上。
????????一個設備不是說一定只屬于某一個類型。比如USB WIFI,SDIO WIFI,屬于網絡設備驅動,因為它們又有USB和SDIO,因此也屬于字符設備驅動。
? ? ? ? 驅動的本質就是 獲取外設,或者傳感器數據,控制外設(比如說燈、蜂鳴器等)。驅動就只管獲取這些數據,數據會提交給應用程序。
? ? ? ? 那么,Linux驅動編譯既要編寫一個驅動,還要編寫一個簡單的測試應用程序(APP)。
二、內核態和用戶態
????????內核態與用戶態是操作系統的兩種運行級別。CPU提供了 Ring0-Ring3 這四種特權級別。Ring0級別最高,而Ring3級別最低。
? ? ? ? 內核態(Kernel Mode),在內核模式下(運行內核和驅動程序),具有Ring0保護級別,代碼具有對硬件所有控制權限。可以執行所有CPU指令,可以訪問任意地址的內存。
? ? ? ? 用戶態(User Mode),在用戶模式下(執行應用程序),具有Ring3保護級別,帶么沒有對硬件的直接控制權,也不能直接訪問地址的內存。其程序是通過系統調用接口(System Call APIs)來達到訪問硬件和內存。
三、printk 函數
? ? ? ? 在驅動開發當中,我們不能使用 printf 函數,智能使用 printk 函數,使用方法跟 printf 函數類似,但是也有點不同。
????????用 dmesg 命令查看日志文件,它能夠將開機到現在的所有調試信息打印出來。
1、參考內核的一些驅動源碼:
#inclde <linux/kernel.h>
使用方法如下所示:
printk(KERN_INFO "%s%s", tpk_tag, tmp);printk(KERN_WARNING "warning: 'lp=0x%x' is deprecated, ignored\n", x);printk(KERN_ERR "dtlk_read times out\n");
2、printk 函數打印優先級的宏定義,在 printk.h 文件中可以找到:
中文版:
#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>" /* 調試級:調試級別的信息 */
????????數值越小,優先級越高!!!
3、查看 printk 的打印優先級:
輸入命令:
cat /proc/sys/kernel/printk
如下所示:
可見,該 printk 文件總共有 4 個值,這四個數值分別對應:
7:控制臺打印級別,默認的消息日志級別優先級高于該值,消息就能夠打印到控制臺。
4:默認的消息日志級別,例如 printk("Led driver init...\n");
1:寫到日志文件當中的最高優先級,能夠通過 dmesg 命令查看。
7:寫到日志文件當中的最低優先級,能夠通過 dmesg 命令查看。
4、修改 printk 的打印優先級
(1)第一種方法:直接修改 printk 文件
輸入以下命令:
echo 3 4 1 3 > /proc/sys/kernel/printk^C
如下所示:
(2)第二種方法:在 printk 函數添加優先級:
如:
printk("<3>" "Led driver init...\n");
也可以寫為:
printk(KERN_ERR "Led driver init...\n");
四、設備號
? ? ? ? 設備號的文檔介紹可以去查看對應的內核源碼下的 Documentation/devices.txt 。
????????其實設備號等同于設備的身份證號碼,方便系統進行管理。
? ? ? ? 設備文件跟普通文件的區別在于:
? ? ? ? 設備文件比普通文件多出了兩個數字,這兩個數字分別是 主設備號 和 次設備號。
(1)普通設備:
以 hello.c 文件講解:
????????其中,第一個字段(-rw-rw-r--)的第一個字符是 文件類型。這上面的 “-”,表示為 普通文件;如果是 “d”,就表示為 “目錄”。然后第一個字段剩下的 9個字符 是 模式,其實就是 權限位(access permission bits)。它是 3個 為一組,每一組用 rwx 表示 “讀(read)” “寫(write)” “執行(execute)”。如果是字母,就說明有這個權限;如果是橫線“-”,就說明沒有這個權限。
? ? ? ? 而這 3組 分別表示:文件所屬的用戶權限、文件所屬的組權限、其他用戶權限。如對于上述hello.c 文件中的 -rw-rw-r-- 就可以翻譯為:這是一個普通文件,對于所屬用戶,可讀可寫不能執行;對于所屬的組,可讀可寫不能執行;對于其他用戶,僅僅可讀。
? ? ? ? 第二個字段(1)是 硬鏈接(hard link)數目。
? ? ? ? 第三個字段(wsm)是 所屬用戶。
? ? ? ? 第四個字段(wsm)是 所屬組。
? ? ? ? 第五個字段(365)是 文件的大小。
? ? ? ? 第六個字段(Nov 6 08:08)是 文件被修改的日期。
? ? ? ? 最后一個字段(hello.c)是 文件名。
(2)設備文件:
觀察 fb0 設備文件,多出的數字為 “29,0”,這兩數字含義如下:
????????29:主設備號
????????0:次設備號
主設備號:區分某一類的設備。
? ? ? ? ? ? ? ? ? 如:ttySAC 這是串口設備,主設備號為204;
? ? ? ? ? ? ? ? ? ? ? ? ?mmcblk0 這是電子硬盤屬于塊設備,主設備號為179。
次設備號:用于區分同一類設備的不同個體或不同分區。
? ? ? ? ? ? ? ? ? 串口設備:串口0~串口3,則使用次設備號64~67進行區分。
? ? ? ? ? ? ? ? ? 電子硬盤設備:分區1~分區7,則使用次設備號1~7進行區分。
1、設備號的組成
????????Linux 中每個設備都有一個設備號,設備號由主設備號和次設備號兩部分組成,主設備號表示某一個具體的驅動(它就像是一個品牌,比如華為品牌的手機),次設備號表示使用這個驅動的各個設備(它就像是華為手機的各個系列,如 mate 60、P60等等)。 Linux 提供了一個名為 dev_t 的數據類型表示設備號, dev_t 定義在文件 include/linux/types.h 里面。dev_t 其實是unsigned int 類型,是一個 32 位的數據類型。其中高 12 位為主設備號, 低 20 位為次設備號。因此 Linux系統中主設備號范圍為 0~4095。
?
設備號相關的操作函數:
MAJOR :從 dev_t 中獲取 主設備號,將 dev_t 右移 20 位即可。
MINOR :從 dev_t 中獲取 次設備號,取 dev_t 的低 20 位的值即可。
MKDEV :將 給定的主設備號 和 次設備號的值 組合成 dev_t 類型的設備號。
部分代碼如下:
static dev_t devno; //設備號
static int major =239; //主設備號
static int minor =0; //次設備號int __init pin4_drv_init(void) //真實入口
{devno = MKDEV(major,minor); //創建設備號return 0;
}
系統中已經被驅動所使用的的主設備號可以在 /proc/devices?文件中查詢。
2、設備號的分配
申請設備號,有靜態注冊和動態注冊兩種方式。
(1)靜態申請設備號:
? ? ? ? 在注冊字符設備時,需要給設備指定一個設備號,這個設備號可以是驅動開發者靜態指定的設備號。但要注意該設備號得是沒有被內核開發者分配掉的設備號。
? ? ? ? 我們可以使用以下命令進行查看當前系統中所有已經使用了的設備號:
cat /proc/devices
如下所示:
使用 register_chrdev 函數 注冊字符設備 時,只需要給定一個主設備號即可:
/* 注冊字符設備驅動 */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
(2)動態分配設備號:
????????使用設備號時,向 Linux 內核申請,需要幾個就申請幾個,由 Linux 內核分配設備可以使用的設備號。
????????如果沒有指定設備號的話就使用如下函數來申請設備號:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
????????如果給定了設備的主設備號和次設備號就使用如下所示函數來注冊設備號即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
參數講解:
from :注冊的設備號,如果要一次注冊多個設備,from就是注冊設備號的起始值
count :次設備的數量
name :設備名稱,但是該名稱不是 /dev 目錄下設備文件的名字,而是在 /proc/devices 目錄當中的名字。
返回值:
成功,返回0;
失敗,返回負數的錯誤碼。
動態分配設備號代碼:
/* 注冊字符設備驅動 */
/* 1、創建設備號 */
if (newchrled.major)
{ /* 定義了設備號 */newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
}
else
{ /* 沒有定義設備號 */alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申請設備號 */newchrled.major = MAJOR(newchrled.devid); /* 獲取分配號的主設備號 */newchrled.minor = MINOR(newchrled.devid); /* 獲取分配號的次設備號 */
}
printk("newchrled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
? ? ? ? 注銷設備號:
void unregister_chrdev_region(dev_t from, unsigned count)
五、字符設備驅動
? ? ? ? 字符設備 就是 一個一個字節,按照字節流進行讀寫操作的設備,讀寫順序是分先后的。
Linux下的應用程序調用驅動程序流程如下所示:
????????注意:應用程序不會直接調用系統調用,而是通過API函數來間接調用系統調用,比如:C庫、POSIX、API等。UNIX操作系統中最常用的編程接口是POSIX。
? ? ? ? 應用程序運行在用戶空間,而Linux驅動屬于內核的一部分,因此驅動運行于內核空間。
? ? ? ? 當我們在用戶空間想要實現對內核的操作時,由于用戶空間不能直接對內核進行操作,因此必須使用一個叫做“系統調用”的方法來實現從用戶空間“陷入”到內核空間,這樣才能實現對底層驅動的操作。
? ? ? ? 應用程序使用到的函數在具體驅動程序中都有與之對應的函數。比如:應用程序調用open函數,那么在驅動程序中也得有一個open函數。
? ? ? ? 每一個系統調用,在驅動中都有與之對應的一個驅動函數,在Linux內核文件 include/linux/fs.h 中有一個叫做 file_operations 的結構體,這個結構體是字符設備驅動的操作函數的集合。
? ? ? ? 對上述結構體中的比較重要的、常用的函數介紹如下(了解即可):
? ? ? ? (1)owner:擁有該結構體的模塊的指針,一般設置為 THIS_MODULE 。
? ? ? ? (2)llseek 函數:用于修改文件當前的讀寫位置。
? ? ? ? (3)read 函數:用于讀取設備文件。
? ? ? ? (4)write 函數:用于向設備文件寫入(發送)數據。
? ? ? ? (5)poll 函數:輪詢函數,用于查詢設備是否可以進行非阻塞的讀寫。
? ? ? ? (6)unlocked_ioctl 函數:提供對于設備的控制功能,與應用程序中的 ioctl 函數對應。
? ? ? ? (7)compat_ioctl 函數:與?unlocked_ioctl 函數功能一樣。
? ? ? ? ? ? ? ? 區別在于:
? ? ? ? ? ? ? ? 1)在64位系統上,運行32位應用程序調用將會使用此函數。
? ? ? ? ? ? ? ? 2)在32位系統上,運行32位應用程序調用將會使用?unlocked_ioctl 函數。
? ? ? ? (8)mmap 函數:將設備的內存映射到進程空間中(也就是用戶空間)。一般幀緩沖設備會使用此函數,比如LCD驅動的顯存,將幀緩沖(LCD顯存)映射到用戶空間中,然后應用程序就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制了。
? ? ? ? (9)open 函數:用于打開設備文件。
? ? ? ? (10)release 函數:用于釋放(關閉)設備文件,與應用程序的 close 函數對應。
? ? ? ? (11)fasync 函數:用于刷新等待處理的數據,用于將緩沖區中的數據刷新到磁盤中。
? ? ? ? (12)aio_fsync 函數:與 fasync 函數 的功能類似,只是該函數是異步刷新待處理的數據。
1、字符設備驅動開發流程
(1)在Linux下一切皆文件,驅動在加載成功后,會在 /dev/ 目錄下生成一個相應的文件,應用程序通過對 /dev/xxx(xxx是具體的驅動文件名) 文件進行相應的操作即可實現對硬件的操作。
(2)比如說 /dev/led 的驅動文件,這文件是led燈的驅動文件。
????????對于文件而言,應用程序可以通過open函數打開?/dev/led 文件,使用完設備后,如果要關閉設備,用close函數關閉即可。
????????如果要點亮(比如寫1)或者關閉(比如寫0)led,那么就是用write函數來進行操作;
????????如果要獲取led燈的狀態,就用read函數從驅動中讀取相應的狀態即可。
(3)編寫驅動的時候,也需要編寫驅動對應的 open、close、write、read函數。字符設備驅動:file_operations。
? ? ? ? 驅動是分驅動框架的,要按照驅動框架來編寫,對于字符設備驅動來說,重點編寫應用程序對應的open、close、write、read等函數。
2、字符設備驅動框架
? ? ? ? 字符設備驅動的編寫主要就是驅動對應的open、close、read、write等函數的編寫。其實就是file_operations 結構體的成員變量的實現。
3、驅動模塊的加載與卸載
? ? ? ? Linux驅動程序可以編譯到 kernel 里面(也就是 zlmage),當Linux內核啟動的時候就會自動運行驅動程序;也可以編譯為模塊(即生成 .ko 文件)。編譯成模塊后,測試的時候,只需要加載 .ko 模塊即可。
? ? ? ? 驅動模塊的加載與卸載函數如下所示:
????????module_init(xxx_init); //注冊模塊加載函數
????????module_exit(xxx_exit); //注冊模塊卸載函數
????????module_init 函數用來向 Linux 內核注冊一個模塊加載函數,參數 xxx_init 就是需要注冊的具體函數,當使用“insmod”命令加載驅動的時, xxx_init 這個函數就會被調用。
????????module_exit函數用來向 Linux 內核注冊一個模塊卸載函數,參數 xxx_exit 就是需要注冊的具體函數,當使用“rmmod”命令卸載具體驅動的時候 xxx_exit 函數就會被調用。
? ? ? ? 編寫驅動:
#include <linux/module.h>static int __int chrdevbase_init(void)
{//chrdevbase_init 函數名自己可自行更改return 0;
}static void __exit chrdevbase_exit(void)
{}//模塊入口
module_init(chrdevbase_init); //加載模塊//模塊出口
module_exit(chrdevbase_exit); //卸載模塊
編寫驅動的時候需要注意的事項:
????????編譯驅動的時候需要用到 Linux 內核源碼!!因此要解壓縮Linux內核源碼,編譯Linux內核源碼。編譯完Linux內核源碼后,會得到 zlmage 和 .dtb 。需要使用使用編譯后得到的 zlmage 和 .dtb 去啟動系統。
4、編譯驅動程序,創建 Makefile 文件
KERNELDIR := /home/wsm/SYSTEM/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
Makefile寫好后,輸入make命令即可編譯驅動模塊。?
上述Makefile文件講解如下:
(1)KERNELDIR :表示開發板所使用的 Linux 內核源碼目錄,使用絕對路徑,大家根據自己的實際情況填寫即可。
(2)CURRENT_PATH :表示當前路徑,直接通過運行“pwd”命令來獲取當前所處路徑。
(3)obj-m:表示將?chrdevbase.c 這個文件編譯為模塊。
(4)$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules:
????????modules :表示編譯模塊。
? ? ? ? -C :表示將當前的工作目錄切換到指定目錄中,也就是KERNELDIR目錄。
? ? ? ? M:表示模塊源碼目錄,“make modules”命令中加入 M=dir 以后程序會自動到指定的 dir 目錄中讀取模塊的源碼并將其編譯為 .ko 文件。
5、編譯驅動的內核源碼
1、內核源碼的版本要與目標平臺上運行的Linux內核的版本一致:
(1)開發板:用如下命令查看
uname -r
(2)Ubuntu:到所在的內核源碼中,進Makefile查看
2、內核源碼要正對目標平臺正確的配置過:
可以用如下命令,以菜單的形式配置內核源碼:
make menuconfig
3、內核源碼要正確的編譯過,才可以用這個內核源碼來去編譯驅動。
6、驅動的調試
(1)查看ko文件的信息(modinfo):
(2)查看ko文件的格式(file):
7、加載或者卸載編譯好的模塊
? ? ? ?(1) 將編譯出來的 .ko 文件放到根文件系統里面。加載驅動會用到加載命令:insmod、modprobe。移除驅動使用 rmmod 命令。
????????對于一個新的模塊使用 modprobe 加載的時候需要先調用一下 depmod 命令。
? ? ? ? (2)驅動模塊加載以后可以使用 lsmod 命令查看一下:
? ? ? ?加載安裝驅動后,會生成以一個設備文件,而這個設備文件是我們應用程序去訪問驅動程序的入口。
????????(3)卸載模塊使用 rmmod 命令:
8、內核模塊參數
? ? ? ? 比如我們編寫一個串口驅動,想要在串口驅動加載的時候,波特率能夠由命令行參數設定,就像運行普通的應用程序的時候,通過命令行來傳遞信息一樣,應用程序示例如下(假設是一個UDP連接的程序):
int main(int argc,char **argv)
{argv[0];argv[1];......
}
在終端通過命令行來傳遞參數:
./udp 192.168.1.100
? ? ? ? 模塊參數允許用戶在加載模塊的時候,通過命令行獲得參數值,內核支持的參數:bool、反轉bool值、charp(字符串指針)、short、int、long、ushort、uint、ulong類型,這些類型可以對應于整型、數組、字符串。
insmod led_drv.ko 各種參數
(1)module_param 與 module_param_array 宏定義
module_param(name, type, perm)module_param_array(name, type, nump, perm)/* 參數釋義 */
name:變量的名字
type:變量或數組元素的類型
nump:數組元素個數的指針,可選。默認寫NULL。
perm:在 sysfs 文件系統中對應的文件的權限屬性,決定哪些用戶能夠傳遞哪些參數,如果該用戶權限過低,則無法通過命令行傳遞參數給該內核模塊。
(2)示例源碼
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>static int baud = 9600; //默認波特率
static int port[4] = {0,1,2,3};
static char *name = "vcom";//通過以下宏定義來接收命令行的參數
module_param(baud, int, 0644); //0644 對應的權限為:rw- r-- r--
module_param_array(port, int, NULL, 0644); //0644 對應的權限為:rw- r-- r--
module_param(name, charp, 0644); //0644 對應的權限為:rw- r-- r--//入口函數
static int __init led_init(void)
{printk("led init...\n");printk("baud = %d\n",baud);printk("port = %d %d %d %d\n",port[0],port[1],port[2],port[3]);printk("name = %s\n",name);return 0;
}//出口函數
static void __exit led_exit(void)
{printk("led exit...\n");
}//驅動程序的入口
module_init(led_init);//驅動程序的出口
module_exit(led_exit);//模塊描述
MODULE_AUTHOR("1971363937@qq.com"); //作者信息
MODULE_DESCRIPTION("Led driver"); //模塊功能說明
MODULE_LICENSE("GPL v2"); //許可證,驅動遵循的協議
(3)編譯運行
將編譯好的驅動下載到板子上:
通過U盤的方式,將 .ko 驅動 復制 到 板子上:
加載驅動:
a、直接按照默認的加載驅動(不使用參數):
insomd leddriver.ko
b、加載時更改默認波特率、端口、名字等內容(動態添加參數方法):
insmod leddriver.ko baud=115200 port=1,2,3,4 name="tcom"
如下所示: