跟著內核學框架-從misc子系統到3+2+1設備識別驅動框架

来源:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/18/6413017.html
-Advertisement-
Play Games

misc子系統在Linux中是一個非常簡單的子系統,但是其清晰的框架結構非常適合用來研究設備識別模型。本文從misc子系統的使用出發,通過瞭解其機制來總結一套的設備識別的驅動框架,即使用 使用同一個驅動,向上提供多個設備文件介面,向下控制多個(相應的)設備 ,這就需要該驅動可以根據不同的設備文件來控 ...


misc子系統在Linux中是一個非常簡單的子系統,但是其清晰的框架結構非常適合用來研究設備識別模型。本文從misc子系統的使用出發,通過瞭解其機制來總結一套的設備識別的驅動框架,即使用使用同一個驅動,向上提供多個設備文件介面,向下控制多個(相應的)設備,這就需要該驅動可以根據不同的設備文件來控制與之相應的設備。

misc的使用

Linux 中有三大類設備:字元,網路,塊設備,每一種設備又細分為很多類,比如字元設備就被預先分為很多種類,併在文件中標記了這些種類都使用了哪個主設備號,但即便如此,硬體千千萬,總還是有漏網之魚,對於這些難以劃分類別的字元設備,Linux中使用"混雜",設備來統一描述,並分配給他們一個共同的主設備號10,只用此設備號進行區分設備,,這些設備主要包括隨機數發生器,LCD,時鐘發生器等。此外,和很多同樣是對cdev進行再次封裝的子系統一樣,misc也會自動創建設備文件,免得每次寫cdev介面都要使用class_create()和device_create()等。

內核中提供的misc對象:

//include/linux/miscdevice.h
 55 struct miscdevice  {    
 56         int minor;
 57         const char *name;
 58         const struct file_operations *fops;
 59         struct list_head list;
 60         struct device *parent;
 61         struct device *this_device;
 62         const char *nodename;
 63         umode_t mode;
 64 };

我們只要像字元設備一樣實現fops介面再給一個minor即可,如果minor使用巨集MISC_DYNAMIC_MINOR(其實就是255),內核會自動分配一個次設備號,其他的內核已經實現約定好的次設備號可以參考"include/linux/miscdevice.h"。萬事具備之後只需使用下麵的API註冊/註銷到內核

178 int misc_register(struct miscdevice * misc)
238 int misc_deregister(struct miscdevice *misc)

misc的分析

misc的使用是不是很簡單?但麻雀雖小五臟俱全,正是因為misc精簡的結構,我們可以很容易的抓到其中體現的分層思想,misc的設計方法體現在很多使用cdev作為介面的子系統,而其中的清晰的分層思想更是Linux驅動的兩大支柱之一(另外一個是分離)。我們可以借鑒其中的設計思路,提升我們的驅動程式的質量。下麵,我們簡單的分析一下misc的內部機制。

misc_init

作為Linux的一個子系統,misc子系統在Linux啟動過程中就會完成準備工作,主要包括初始化數據結構,創建相應的class,創建、初始化並註冊cdev對象到內核等。有了這些基礎,我們就可以使用misc的眾多好處進行編程。

//drivers/char/misc.c
56 static const struct file_operations misc_fops = {
157         .owner          = THIS_MODULE,
158         .open           = misc_open,
159         .llseek         = noop_llseek,
160 };
268 static int __init misc_init(void)
269 {
272 #ifdef CONFIG_PROC_FS
273         proc_create("misc", 0, NULL, &misc_proc_fops);
274 #endif
275         misc_class = class_create(THIS_MODULE, "misc");
281         if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
282                 goto fail_printk;
283         misc_class->devnode = misc_devnode;
284         return 0;
292 }
293 subsys_initcall(misc_init);   

misc_init()
--293-->系統啟動的過程中就會初始化misc子系統
--273-->根據系統配置,可能需要提供/proc介面
--275-->在/sysfs中創建一個類,名為misc
--281-->使用靜態主設備號(10)、封裝好的方法集misc_fops,register_chrdev()內部會創建一個cdev對象並使用這兩個參數將其初始化並註冊到內核,這個cdev對象將負責所有的混雜設備的設備號。關於cdev對象和設備號之間的關係參見cdev_map
--158-->misc的cdev對象使用的fops,顯然,至此和普通字元設備的調用過程一樣,chrdev_open()->misc_open()。

misc_register

接下來,老規矩,我們從"XXX_register"開始分析,在Linux內核中,這些"XXX_register"往往就是一個設備對象註冊到內核的介面,是研究當相應對象註冊進去之後內核動作的最佳入口。

178 int misc_register(struct miscdevice * misc)
179 {  
180         dev_t dev;
187         if (misc->minor == MISC_DYNAMIC_MINOR) {
188                 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
193                 misc->minor = DYNAMIC_MINORS - i - 1;
194                 set_bit(i, misc_minors);
195         } 
206         dev = MKDEV(MISC_MAJOR, misc->minor);
208         misc->this_device = device_create(misc_class, misc->parent, dev,
209                                           misc, "%s", misc->name);
210         if (IS_ERR(misc->this_device)) {
211                 int i = DYNAMIC_MINORS - misc->minor - 1;
212                 if (i < DYNAMIC_MINORS && i >= 0)
213                         clear_bit(i, misc_minors);
214                 err = PTR_ERR(misc->this_device);
216         }
222         list_add(&misc->list, &misc_list);
226 }

misc_register()
--187--> 如果指定的minor是動態分配,那麼進入相關語句塊。
--188--> 使dev用點陣圖遍歷API-find_first_zero_bit找到最小未用的設備號。
--193--> 得到分配好的次設備號。
--208--> (根據設備號)創建設備文件,使用的是misc_init中創建的misc_class,至此就可以實現misc設備文件的自動創建。就相當與我們在純粹的cdev驅動中使用class_create()+device_create()創建設備文件。一個設備文件和一個設備號相聯繫,而misc的所有的設備號都和misc_init創建的cdev對象相聯繫,所以打開的任何一個misc設備文件首先回調的就是(chrdev_open()->)misc_open()。
--222--> 關鍵,將這個新分配的misc加入到misc鏈表中,用於管理所有的misc設備,便於misc_open()提取具體設備的fops。

misc_open

構建的misc子系統,將設備添加到了該子系統中,接下來我們來看一下應用層程式是如何打開一個misc設備的。由於misc也是一種字元設備,所以其提供的介面也是位於/dev中。但是正如misc的定義,其中的設備五花八門卻共用同一個主設備號,這就意味著最終被chrdev_open回調的misc_open一定要具備根據被打開的不同文件為file結構準備不同的操作方法這一能力,即在驅動中實現對子設備的識別,或者稱之為"多態"。

112 static int misc_open(struct inode * inode, struct file * file)
113 {
114         int minor = iminor(inode);
115         struct miscdevice *c;
116         int err = -ENODEV;
117         const struct file_operations *new_fops = NULL;
121         list_for_each_entry(c, &misc_list, list) {
122                 if (c->minor == minor) {
123                         new_fops = fops_get(c->fops);           
124                         break;
125                 }
126         }
144         replace_fops(file, new_fops);
145         if (file->f_op->open) {
146                 file->private_data = c;
147                 err = file->f_op->open(inode,file);
148         }
152 }

misc_open()
--121-->遍歷misc設備鏈表,根據被打開的設備的次設備號找到設備對象。
--123-->存儲這個設備對象的操作方法集unique_fops。
--144-->將misc設備具體的操作方法集unique_fops替換到filp中的f_op中,這個位置原來是misc的cdev對象的fops,filp帶著這個unique_fops從open()返回,就實現了不同的設備對應不同的操作方法,即面向對象的"多態"

3+2+1多設備識別驅動模型

通過上述對misc機制的分析,我們不難總結出一個支持設備識別的3+2+1驅動模型(3個函數+2個數據結構+1個封裝):

  1. 初始化整個驅動組的xxx_init(),通常用模塊載入函數或匯流排的probe函數實現;
  2. 用於註冊一個子驅動的xxx_register(),需要EXPORT到符號表;
  3. 能夠根據傳入的inode識別具體的設備並將其操作方法集放到filp中的xxx_open()

+

  1. 用於存儲每一個驅動對象的通用鏈表或數組+priv_data
  2. 用於存儲子設備號的點陣圖。

+

  1. 將所有的不同的設備用一個統一的結構進行封裝

至此,我們就可以寫一寫這個3+2+1驅動模型的模板。

1個封裝

struct multidevice{
    struct list_head head;
    int minor;
    struct file_operations* fops;
    void *priv;     //私有數據,供read/write等介面識別的信息,以及其他數據都放這裡
};

2個數據結構

struct list_head multi_dev_list;
unsigned int minors_map;   //根據設備號數目的不同選數據類型or數組

3個函數

int major,baseminor = 0,max_dev = sizeof(minors_map)*8;
#define DEV_NAME "multi_device"
struct class *cls;
xxx_open(struct inode *inode,struct file *file){
    int minor = iminor(inode);
         struct multidevice *dp;
         const struct file_operations *new_fops = NULL;
                  list_for_each_entry(dp, &multi_dev_list, head) {
                     if (dp->minor == minor) {
                         new_fops = fops_get(dp->fops);           
                         break;
                 }
         }
         replace_fops(file, new_fops);
         if (file->f_op->open) {
                 file->private_data = dp
                 file->f_op->open(inode,file);
         }
}

xxx_init(void){
    dev_t devno,
    INIT_LIST_HEAD(&multi_dev_list);
    init_map(&minors_map);
    struct cdev *multi_cdev = cdev_alloc();
    cdev_init(multi_cdev, multi_fops);
    alloc_chrdev_region(&devno, baseminor, count,DEV_NAME);
    major = MAJOR(devno);
    cdev_add(multi_cdev , devno, count);
    cls = class_create(THIS_MODULE, DEV_NAME);
}
/*---------------下麵是給待加驅動用的----------------------*/
xxx_register(struct *multidevice dev){
            dev_t dev;
         if (dev->minor == MISC_DYNAMIC_MINOR) {
                 int i = find_first_zero_bit(minors_map, DYNAMIC_MINORS);
                 dev->minor = DYNAMIC_MINORS - i - 1;
                 set_bit(i, minors_map);
         } 
         dev_t pri_devno = MKDEV(major, dev->minor);
         device_create(multi_class, NULL, pri_devno, "%s", misc->name);
         list_add(dev->head, &multi_dev_list);
}
EXPORT_SYMBOL(xxx_register)




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、 " Oracle 資料庫管理工具概述 " 2、 " SQL Plus 實用命令參考 " 2.1、 "連接/斷開命令" 2.2、 "執行 SQL 語句" 2.3、 "執行 PL/SQL 語句" 2.4、 "文件操作命令" 2.5、 "修改用戶密碼" 2.6、 "執行存儲過程" 2.7、 "其它命 ...
  • 本文屬於一篇內部規範文檔,整理的初衷是為了規範、統一集團的Windows伺服器(僅僅SQL Server資料庫伺服器)防火牆設置,僅僅供內部其它同事設置Windows防火牆時作為參考的文檔資料。如有不足,敬請指正。後續將不斷完善、整理該文檔。文檔裡面部分內容直接摘抄自MSDN,敬請知曉! 文檔類型 ... ...
  • 終於開始看Spark源碼了,先從最常用的spark shell腳本開始吧。不要覺得一個啟動腳本有什麼東東,其實裡面還是有很多知識點的。另外,從啟動腳本入手,是尋找代碼入口最簡單的方法,很多開源框架,其實都可以通過這種方式來尋找源碼入口。 先來介紹一下Spark shell是什麼? Spark she ...
  • 輸入設備都有共性:中斷驅動+字元IO ,基於分層的思想,Linux內核將這些設備的公有的部分提取出來,基於cdev提供介面,設計了輸入子系統,所有使用輸入子系統構建的設備都使用 主設備號13 ,同時輸入子系統也 支持自動創建設備文件 ,這些文件採用阻塞的IO讀寫方式,被創建在 "/dev/input ...
  • 這隻是單單安裝了jre而已,不要安裝這個 安裝下麵這個就行了 配置環境 添加 執行生效 檢查 輸入 ...
  • 第十四節 用戶管理初級(上) 標簽(空格分隔): Linux實戰教學筆記 陳思齊 [更多資料點我查看][1] 1,賬號管理 1.1 管理用戶命令彙總 |命令|註釋說明(特殊顏色的必須掌握)| | | | |useradd增| | |userdel刪| | |passwd| | |chage| | | ...
  • 假設在win(192.168.0.101)上安裝了xmanager,想接收來自linux(192.168.100.16)的圖形界面。 1.在win端打開Xmanager - Passive 2.在linux上設置DISPLAY環境變數 export DISPLAY=192.168.0.101:0.0... ...
  • 1、AWK內部代碼編寫(為了更好的理解) 2、去掉回車,加上其他部分 目的:要查看當前目錄中大於4kB的文件的,並將其大小相加求出總大小。 3、查看當前目錄大於4Kb的文件 4、AWK常用參數 -F(表示已什麼為分隔符) 目的:查看nologin用戶 grep查看以nologin結尾的行,AWK查詢 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...