基於4.14內核, beagleBone green平臺html
有過電腦使用經驗的人都知道,當咱們將外部硬件設備好比鼠標鍵盤插入到電腦端口(一般是USB口)時,在windows系統右下角會彈出"安裝設備驅動程序"的顯示框,那麼,爲何每一個硬件都須要安裝設備驅動程序才能使用呢?linux
首先,每一個硬件都有相應的功能,鼠標的功能就是將鼠標的位移與點擊狀態轉換成相應的數據,而後將數據傳輸給電腦,而後電腦根據收到的數據移動屏幕上的光標。算法
若是沒有相應的鼠標驅動程序,電腦並不知道鼠標的接口以什麼協議將數據傳輸過來,也不知道怎麼解析相應的數據,因此固然電腦上的光標不會跟隨鼠標的移動而移動,歸根結底,鼠標的移動和電腦上光標的移動是二者間數據同步的結果。shell
同理,打印機也是同樣,電腦將文件數據以某種格式傳遞給打印機,而後經過控制數據控制打印機的運行,打印機驅動程序基本上也是識別接收數據以及對數據的處理,這就是爲何通常外部設備都須要使用一根數據線與主機進行鏈接。編程
在基於MCU的普通嵌入式驅動程序開發中,並不會常常接觸到鼠標、鍵盤、硬盤這一類的設備,多數是一些較爲簡單的傳感器設備、小容量的存儲設備等等,一般數據的傳輸使用的是spi、i2c、串口這一類的串行通訊協議,一般一個設備驅動程序的開發就是這樣的流程:windows
在linux系統中,一個硬件設備想要運行一樣須要提供設備驅動程序,底層的原理和MCU中的設備驅動程序同樣:收發數據以及處理數據,只是因爲桌面操做系統的特殊性,設備驅動程序的流程會複雜不少。框架
在這一系列的文章中,我將介紹怎麼去編寫linux設備驅動程序,linux內核支持將設備驅動程序編譯進內核和運行時加載進內核兩種方式,在這一系列文章中,主要討論編譯可加載模塊,通常開發者會將設備驅動程序編譯成可加載模塊,在linux運行時動態加載進內核,以實現對應設備的驅動。函數
linux將內核與用戶分離,驅動模塊運行在內核空間中,而應用程序運行在用戶空間,內核主要對公共且有限的資源進行管理、調度,好比硬件外設資源、內存資源等。工具
當用戶須要使用系統資源時,經過系統調用進入內核,由內核基於某種調度算法對這部分資源進行調度。ui
在用戶空間看來,每一個用戶進程都請求到了相應的資源得以運行。
在內核空間看來,避免用戶程序與有限的硬件資源直接交互,保護了相應資源,且用戶程序之間相互隔離,互不影響,用戶程序的崩潰並不會影響內核空間,保障了系統的穩定運行。
可是,linux設備驅動程序的開發目的是將模塊加載到內核空間中,內核空間的操做向來都是危險的,一旦不當心就極可能致使內核的崩潰,同時咱們須要掌握一些內核調試的技巧。
話很少說,先來個hello_world吧。
hello_world.c:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> //指定license版本 MODULE_LICENSE("GPL"); //設置初始化入口函數 static int __init hello_world_init(void) { printk(KERN_DEBUG "hello world!!!\n"); return 0; } //設置出口函數 static void __exit hello_world_exit(void) { printk(KERN_DEBUG "goodbye world!!!\n"); } //將上述定義的init()和exit()函數定義爲模塊入口/出口函數 module_init(hello_world_init); module_exit(hello_world_exit);
上述代碼就是一個設備驅動程序代碼框架,這套框架主要的任務就是將內核模塊中的init函數動態地註冊到系統中並運行,由module_init()和module_exit()來實現,分別對應驅動的加載和卸載。
只是它並不作什麼事,僅僅是打印兩條語句而已,若是要實現某些驅動,咱們就能夠在init函數中進行相應的編程。
編譯這個程序,咱們都知道,linux下編譯程序通常使用make工具(簡單的程序能夠直接命令行來操做),以及一個Makefile文件,在內核開發中,Makefile並不像應用程序那樣,而是通過了一些封裝,咱們只須要往其中添加須要編譯的目標文件便可:
obj-m+=hello_world.o all: make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
其中hello_world.o就是目標文件,make工具會根據目標文件自動推導來編譯hello_world.c文件。
編譯:
make
編譯結果會在當前目錄生成hello_world.ko文件,這個文件就是咱們須要的內核模塊文件了。
針對編譯模塊makefile的介紹能夠看看我介紹makefile的博客:
linux可加載模塊makefile
linux 內核makefile總覽
編譯生成了內核文件,接下來就要將其加載到內核中,linux支持動態地添加和刪除模塊,因此咱們能夠直接在系統中進行加載:
sudo insmod hello_world.ko
咱們能夠經過lsmod命令來檢查模塊是否被成功加載:
lsmod | grep "hello_world"
lsmod顯示當前被加載的模塊。
同時,咱們也能夠卸載這個模塊:
sudo rmmod hello_world.ko
一樣咱們也能夠經過lsmod指令來查看模塊是否卸載成功。
可是,在這裏咱們並無看到有任何打印信息的輸出,在程序中咱們使用printk函數來打印信息。
事實上,printk屬於內核函數,它與printf在實現上惟一的區別就是printk能夠經過指定消息等級來區分消息輸出,在在這裏,printk輸出的消息被輸出到/var/log/kern.log文件中,咱們能夠經過另開一個終端來查看內核日誌消息:
tail -f /var/log/kern.log
tail -f表示循環讀取/var/log/kern.log文件中的消息並顯示在當前終端中,這樣咱們就能夠在終端查看內核中printk輸出的消息。
若是你不想從新開一個終端來顯示內核日誌,但願直接顯示在當前終端,你能夠這樣作:
tail -f /var/log/kern.log &
僅僅是將這條指令放在當前進程後臺執行,當前終端關閉時,這個後臺進程也會被關閉。
咱們使用開第二種方式來顯示內核消息的方式來從新加載hello_world.ko模塊:
sudo insmod hello_world.ko
而後查看消息輸出,果真,在終端輸出日誌:
Dec 16 10:01:15 beaglebone kernel: [98355.403532] hello world!!!
而後卸載,一樣,終端中顯示相應的輸出:
Dec 16 10:01:50 beaglebone kernel: [98390.181631] goodbye world!!!
從結果能夠看出,在模塊加載時,執行了hello_world_init()函數,在卸載時執行了hello_world_exit()函數。
在上面實現了一個linux內核驅動程序(雖然什麼也沒幹),接下來咱們再來添加一些小功能來豐富這個驅動程序:
廢話很少說,直接上代碼:
hello_world_PLUS.c:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> MODULE_AUTHOR("Downey"); //做者信息 MODULE_DESCRIPTION("Linux kernel driver - hello_world PLUS!"); //模塊的描述,可使用modinfo xxx.ko指令來查看 MODULE_VERSION("0.1"); //模塊版本號 //指定license版本 MODULE_LICENSE("GPL"); static char *name = "world"; module_param(name,charp,S_IRUGO); //設置加載時可傳入的參數 MODULE_PARM_DESC(name,"name,type: char *,permission: S_IRUGO"); //參數描述信息 //設置初始化入口函數 static int __init hello_world_init(void) { printk(KERN_DEBUG "hello %s!!!\n",name); return 0; } //設置出口函數 static void __exit hello_world_exit(void) { printk(KERN_DEBUG "goodbye %s!!!\n",name); } //將上述定義的init()和exit()函數定義爲模塊入口/出口函數 module_init(hello_world_init); module_exit(hello_world_exit);
添加了MODULE_AUTHOR(),MODULE_DESCRIPTION(),MODULE_VERSION()等模塊信息
添加了module_param()傳入參數功能
編譯以前須要修改Makefile,將hello_world.o修改成hello_world_PLUS.o。
在上述程序中咱們添加了module_param這一選項,module_param支持三個參數:變量名,類型,以及訪問權限,咱們能夠先試一試傳入參數:
sudo insmod hello_world_PLUS.ko Downey
查看日誌輸出,顯示:
Dec 16 10:07:38 beaglebone kernel: [98738.153909] hello Downey!!!
看到模塊中name變量被賦值爲Downey,代表參數傳入成功。
而後卸載:
sudo insmod hello_world_PLUS
日誌輸出:
Dec 16 10:08:12 beaglebone kernel: [98772.191127] goodbye Downey!!!
上面講解了傳入一個參數時的示例,若是傳入多個參數呢?該怎麼修改,這個就留給你們去嘗試了。
在hello_world_PLUS中,咱們添加了一些模塊信息,可使用modinfo來查看:
modinfo hello_world_PLUS.ko
輸出:
filename: /home/debian/linux_driver_repo/hello_world_PLUS/hello_world_PLUS.ko license: GPL version: 0.1 description: Linux kernel driver - hello_world PLUS! author: Downey srcversion: 549C47CB670506CE16F56D8 depends: name: hello_world_PLUS vermagic: 4.14.71-ti-r80 SMP preempt mod_unload modversions ARMv7 p2v8 parm: name:type: char *,permission: S_IRUGO (charp)
sysfs是一個文件系統,可是它並不存在於非易失性存儲器上(也就是咱們常說的硬盤、flash等掉電不丟失數據的存儲器),而是由linux系統構建在內存中,簡單來講這個文件系統將內核驅動信息展示給用戶。
當咱們裝載hello_world_PLUS.ko時,會在/sys/module/目錄下生成一個與模塊同名的目錄即hello_world_PLUS,目錄裏囊括了驅動程序的大部分信息,查看目錄:
ls /sys/module/hello_world_PLUS
輸出:
coresize initsize notes refcnt srcversion uevent holders initstate parameters sections taint version
這一部分的知識僅僅是在這裏引出提一下,創建個映象,在這裏就再也不贅述,若是想進一步瞭解能夠參考博主的另外一篇博客linux設備驅動程序--sysfs。
好了,關於linux驅動程序-hello_world就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身.