天天看點

【Linux驅動開發】字元裝置驅動開發

一、字元裝置驅動簡介

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

  • Linux 應用程式對驅動程式的調用:
【Linux驅動開發】字元裝置驅動開發
在 Linux 中,一切皆為檔案,驅動加載成功以後會在“/dev”目錄下生成一個相應的檔案,應 用程式通過對這個名為“/dev/xxx”(xxx 是具體的驅動檔案名字)的檔案進行相應的操作即可實作對硬體的操作。
  • 基礎函數表:
open() 打開檔案 /dev/xx
close() 關閉檔案/dev/xx
read() 從驅動擷取資料(狀态)
write() 向此驅動寫入資料(控制參數)
  • open函數調用流程:
【Linux驅動開發】字元裝置驅動開發
  • 應用程式運作在使用者空間,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 開始,要釋放的裝置号數量。

繼續閱讀