一、字元裝置驅動簡介
字元裝置是 Linux 驅動中最基本的一類裝置驅動,字元裝置就是一個一個位元組,按照位元組 流進行讀寫操作的裝置,讀寫資料是分先後順序的。比如我們最常見的點燈、按鍵、IIC、SPI,LCD 等等都是字元裝置,這些裝置的驅動就叫做字元裝置驅動。
- Linux 應用程式對驅動程式的調用:

在 Linux 中,一切皆為檔案,驅動加載成功以後會在“/dev”目錄下生成一個相應的檔案,應 用程式通過對這個名為“/dev/xxx”(xxx 是具體的驅動檔案名字)的檔案進行相應的操作即可實作對硬體的操作。
- 基礎函數表:
open() | 打開檔案 /dev/xx |
close() | 關閉檔案/dev/xx |
read() | 從驅動擷取資料(狀态) |
write() | 向此驅動寫入資料(控制參數) |
- open函數調用流程:
- 應用程式運作在使用者空間,Linux 驅動驅動運作于核心空間。
- 因為使用者空間不能直接對核心進行操作,是以必須使用一個叫做“系統調用”的方法來實作從使用者空間陷入到核心空間,這樣才能實作對底層驅動的操作。
- 應用程式使用到的函數在具體驅動程式中都有與之對應的函數,比 如應用程式中調用了 open 這個函數,那麼在驅動程式中也得有一個名為 open 的函數。
- 每一個系統調用,在驅動中都有與之對應的一個驅動函數,在 Linux 核心檔案 include/linux/fs.h 中有個叫做 file_operations 的結構體,此結構體就是 Linux 核心驅動操作函數集合。
file_operation 結構體中比較重要的、常用的函數:
open | 打開裝置檔案 |
release | 釋放 ( 關閉 ) 裝置檔案,與應用程式中的 close 函數對應 |
read | 讀取裝置檔案 |
write | 向裝置檔案寫入 ( 發送 ) 資料 |
llseek | 修改檔案目前的讀寫位置 |
poll | 輪詢函數,用于查詢裝置是否可以進行非阻塞的讀寫 |
unlocked_ioctl | 提供對于裝置的控制功能,與應用程式中的 ioctl 函數對應 |
compat_ioctl | 與 unlocked_ioctl 函數功能一樣,差別在于在 64 位系統上,32 位的應用程式調用将會使用此函數 |
mmap | 将裝置的記憶體映射到程序空間中 ( 也就是使用者空間 ),一般幀緩沖裝置會使用此函數,比如 LCD 驅動的顯存 |
fasync | 重新整理待處理的資料,用于将緩沖區中的資料重新整理到磁盤中 |
aio_fsync | 與 fasync 函數的功能類似,隻是 aio_fsync 是異步重新整理待處理的資料 |
二、字元裝置驅動開發步驟
- 驅動子產品的加載和解除安裝:
Linux 驅動有兩種運作方式:
第一種就是将驅動編譯進 Linux 核心中,這樣當 Linux 核心啟動的時候就會自動運作驅動程式。
第二種就是将驅動編譯成子產品(Linux下子產品擴充名為.ko)。
加載和解除安裝函數:
insmod 子產品加載驅動。不能解決子產品的依賴關系。比如 drv.ko 依賴 first.ko 這個子產品,就必須先使用insmod 指令加載 first.ko 這個子產品,然後再加載 drv.ko 這個子產品 modprobe(推薦) 子產品加載驅動。modprobe 會分析子產品的依賴關系,然後會将所有的依賴子產品都加載到核心中。
(提供了子產品的依賴性分析、錯誤檢查、錯誤報告等功能)
rmmod(推薦) 子產品解除安裝驅動。僅解除安裝掉驅動子產品。 modprobe -r 子產品解除安裝驅動。可以解除安裝掉驅動子產品所依賴的其他子產品,前提是這些依賴子產品已經沒有被其他子產品所使用,否則就不能使用 modprobe 來解除安裝驅動子產品。
實際調用的函數:
module_init(xxx_init) 注冊子產品加載函數。參數 xxx_init 就是需要注冊的具體函數,當使用“insmod”指令加載驅動的時候,xxx_init 這個函數就會被調用 module_exit(xxx_exit) 注冊子產品解除安裝函數。參數 xxx_exit 就是需要注冊的具體函數,當使用“rmmod”指令解除安裝具體驅動的時候 xxx_exit 函數就會被調用
- 字元裝置注冊與登出
對于字元裝置驅動而言,當驅動子產品加載成功以後需要注冊字元裝置,同樣,解除安裝驅動子產品的時候也需要登出掉字元裝置。
一般字元裝置的注冊在驅動子產品的入口函數 xxx_init 中進行,字元裝置的登出在驅動子產品的出口函數 xxx_exit 中進行。
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
register_chrdev 函數用于注冊字元裝置,參數含義如下:
major:主裝置号,Linux下每個裝置都有一個裝置号,裝置号分為主裝置号和次裝置号兩部分。
name:裝置名字,指向一串字元串。
fops:結構體 file_operations 類型指針,指向裝置的操作函數集合變量。
static inline void unregister_chrdev(unsigned int major, const char *name)
unregister_chrdev 函數使用者登出字元裝置,參數含義如下:
major:要登出的裝置對應的主裝置号。
name:要登出的裝置對應的裝置名。
- 添加 LICENSE 和作者資訊
MODULE_LICENSE() | 添加子產品 LICENSE 資訊 |
MODULE_AUTHOR() | 添加子產品作者資訊 |
MODULE_LICENSE("GPL"); LICENSE 采用 GPL 協定。
MODULE_AUTHOR("xx"); 添加作者名字。
三、Linux裝置号
- 裝置号的組成
- Linux 中每個裝置都有一個裝置号,裝置号由主裝置号和次裝置号兩部分組成。
- 主裝置号表示某一個具體的驅動,次裝置号表示使用這個驅動的各個裝置。
- Linux 提供了 一個名為 dev_t 的資料類型表示裝置号,其實就是 unsigned int 類型,這 32 位的資料構成了主裝置号和次裝置号兩部分,其中高 12 位為主裝置号,低20 位為次裝置号。是以 Linux系統中主裝置号範圍為 0~4095。
- 在檔案 include/linux/kdev_t.h 中提供了幾個關于裝置号的操作函數(本質是宏):
#define MINORBITS 20 //次裝置号位數,一共是 20 位 #define MINORMASK ((1U << MINORBITS) - 1) //次裝置号掩碼 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //從 dev_t 中擷取主裝置号,将 dev_t 右移 20 位即可 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //從 dev_t 中擷取此裝置号,取 dev_t 的低 20 位的值即可 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //将給定的主裝置号和次裝置号的值組合成 dev_t 類型的裝置号
- 裝置号的配置設定
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
函數 alloc_chrdev_region 用于申請裝置号,參數含義如下:
dev:儲存申請到的裝置号。
baseminor:次裝置号起始位址,alloc_chrdev_region 可以申請一段連續的多個裝置号,這 些裝置号的主裝置号一樣,但是次裝置号不同,次裝置号以 baseminor 為起始位址位址開始遞增。一般 baseminor 為 0,也就是說次裝置号從 0 開始。
count:要申請的裝置号數量。
name:裝置名字。
void unregister_chrdev_region(dev_t from, unsigned count)
函數 unregister_chrdev_region 用于釋放裝置号,參數含義如下:
from:要釋放的裝置号。
count:表示從 from 開始,要釋放的裝置号數量。