〖想觀看更多課程筆記,至[課程筆記]課程筆記系列總覽可以看到目前已發布的所有文章!〗
Course 3 - 我的第一個Linux驅動
字符設備驅動框架
上次有說到字符驅動提供給外部的api都被定義在file_operations structs內,這個結構被定義在/include/linux/fs.h下
0 | /* /include/linux/fs.h */ |
簡單介紹一下這個struct內的member:
- owner: 擁有該結構的module pointer,一般設置為THIS_MODULE
llseek()
: 用於修改當前文件的讀寫位置open()
: 打開文件read()
&write()
:用來存取文件poll()
: 用來輪詢device是否可以進行non-blocking的讀寫unlocked_ioctl()
&compat_ioctl()
: 提供對設備控制的街口mmap()
: 將device memory mapping到process memory,如此就不用做繁瑣的memory copy
function pointer不用全部實現,會用到哪個就實作哪個就好。
驅動模塊的加載與卸載
Linux驅動可以編譯到kernel裡面(zImage)。也可以編譯成module(.ko),需要的時候在載入就好,既然可以加載那就必須提供加載和卸載的func。
一個基礎的模板如下:0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* LICENSE */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("john");
static int __init chrdevbase_init(void)
{
printk(KERN_INFO "module init\n");
return 0;
}
static void __exit chrdevbase_exit(void)
{
printk(KERN_INFO "module exit\n");
return;
}
module_init(chrdevbase_init); /* probe entry */
module_exit(chrdevbase_exit); /* exit entry */
kernel內沒有printf()
,取而代之的是printk()
,可以顯示不同level的log,log level的定義位於/include/linux/kern_levels.h
0 | /* /include/linux/kern_levels.h */ |
而在/include/linux/printk.h中定義了CONSOLE_LOGLEVEL_DEFAULT的預設值是7,也就是說優先級高於7的log才會被印出在console上0
1
2
3
4
5
6/* /include/linux/printk.h */
/*
* Default used to be hard-coded at 7, quiet used to be hardcoded at 4,
* we're now allowing both to be set from kernel config.
*/
module_init()
是driver被載入時會執行的function,對於不同driver之間有時候是有著載入順序的相依性的,此時就可以透過呼叫不同的init function來控制,更深入的探討可以參考[Linux Kernel慢慢學]Linux modules載入及載入順序
而關於build code使用到了kbuild,一個Makefile的模板如下
KERNELDIR := /lib/modules/$(shell uname -r)/build |
- C表示切換到指定的目錄中
- M表示模塊源碼目錄
編好後,可以透過ismod
, modprobe
, rmmod
來加載和卸載,透過lsmod
, modinfo
來進行查看
ismod
並不能解決module的依賴關係modprobe
會去/lib/modules/查看modules進行查找 - 如果沒有該目錄,就自己創一個,然後將driver(.ko)放進去
modprobe <driver_name>.ko
- 如果出現”can’t open ‘modules.dep’: no such file or directory”,可以透過
depmod
自動生成
字符設備的註冊與註銷
基於模塊加載&卸載的code繼續修改,註冊&註銷char device或使用到下列兩個function,被定義在/include/linux/fs.h中
0 | /* /include/linux/fs.h */ |
- marjor是主設備號,可以透過
cat /proc/devices
查看當前已註冊的的主設備號 - name是device name
- fops就是之前提到過的file_operations struct,也就是這個driver能提供哪些操作
設備號
為了方便管理,Linux下每個設備都有一個設備號,設備號由主設備號和次設備號構成
- 前12bit為主設備號,表示某個具體的驅動
- 後20bit為次設備號,表示使用該驅動的各個設備
Linux使用dev_t的type來表示設備號,可以看到它是一個unsigned int0
1
2
3/* /include/linux/types.h */
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
關於主設備號、次設備號的定義位於include/linux/kdev_t.h
0 | /* /include/linux/kdev_t.h */ |
透過這幾個macro來產生設備號
但register_chrdev()
並沒有要填寫次設備號?他會佔用當前主設備號下的所有次設備號,比較不方便,所以有其他比較好用的register func,後面會提到
file_operations的具體實現
在字符設備驅動框架已經提過,我們必須要實作fops對應的function,然後將fops帶入註冊函數的參數中。
先寫個簡單的殼出來:
0 |
|
關於struct中採用了”.(dot)”的成員變數宣告方式,可以參考[Linux Kernel慢慢學]探討Designated Initializers
應用程序編寫
Linux下一切皆文件,所以要操作driver也需要將該driver的字元文件打開,下面編寫一個簡易的應用程序來介紹如何開啟字元驅動文件
0 | /** |
測試:
- 加載驅動:
modbrpobe <driver_name>.ko
- 加載完可透過
cat /proc/devices
確認
- 加載完可透過
- 創建device node:
mknod /dev/<driver_name> c 200 0
- c代表字元驅動、200是主設備號、0是次設備號
- 創建完後會在/dev下面看到驅動文件
- 透過應用開啟字元驅動文件:
./chrdevbaseAPP /dev/chrdevbase
chrdevbase虛擬設備驅動的完善
在file_operations的具體實現和應用程序編寫中只完成了驅動以及app的殼,這邊會將驅動和app剩下的code繼續完成。
驅動
- 驅動和數據的傳遞: data在kernel space和 user space不能直接調用,要透過
copy_to_user()
,copy_from_user()
來進行轉換#include <linux/uaccess.h>
0 |
|
app
App要可以透過驅動對device做讀寫,open char device file後要可以做read/write
0 | /** |
能夠透過app & driver之間做讀寫,代表未來就可以透過 app 去操控 driver 對 devices 做不同的操作(例如開關燈)