Linux設備管理(二)_從cdev_add說起

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/12/19/6196198.html
-Advertisement-
Play Games

我在 "Linux字元設備驅動框架" 一文中已經簡單的介紹了字元設備驅動的基本的編程框架,這裡我們來探討一下Linux內核(以4.8.5內核為例)是怎麼管理字元設備的,即當我們獲得了設備號,分配了 cdev 結構,註冊了驅動的操作方法集,最後進行 cdev_add() 的時候,究竟是將哪些內容告訴了 ...


我在Linux字元設備驅動框架一文中已經簡單的介紹了字元設備驅動的基本的編程框架,這裡我們來探討一下Linux內核(以4.8.5內核為例)是怎麼管理字元設備的,即當我們獲得了設備號,分配了cdev結構,註冊了驅動的操作方法集,最後進行cdev_add()的時候,究竟是將哪些內容告訴了內核,內核又是怎麼管理我的cdev結構的,這就是本文要討論的內容。我們知道,Linux內核對設備的管理是基於kobject的(參見Linux設備管理(一)_kobject_kset_kobj_type),這點從我們的cdev結構中就可以看出,所以,接下來,你將看到"fs/char_dev.c"中實現的操作字元設備的函數都是基於"lib/kobject.c"以及"drivers/base/map.c"中對kobject操作的函數。好,現在我們從cdev_add()開始一層層的扒。

cdev_map對象

//fs/char_dev.c
 27 static struct kobj_map *cdev_map;

內核中關於字元設備的操作函數的實現放在"fs/char_dev.c"中,打開這個文件,首先註意到就是這個在內核中不常見的靜態全局變數cdev_map(27),我們知道,為了提高軟體的內聚性,Linux內核在設計的時候儘量避免使用全局變數作為函數間數據傳遞的方式,而建議多使用形參列表,而這個結構體變數在這個文件中到處被使用,所以它應該是描述了系統中所有字元設備的某種信息,帶著這樣的想法,我們可以在"drivers/base/map.c"中找到kobj_map結構的定義:

//drivers/base/map.c
 19 struct kobj_map {        
 20         struct probe {
 21                 struct probe *next;
 22                 dev_t dev;
 23                 unsigned long range;
 24                 struct module *owner;
 25                 kobj_probe_t *get;
 26                 int (*lock)(dev_t, void *);
 27                 void *data;
 28         } *probes[255];  
 29         struct mutex *lock;
 30 };

從中可以看出,kobj_map的核心就是一個struct probe指針類型、大小為255的數組,而在這個probe結構中,第一個成員next(21)顯然是將這些probe結構通過鏈表的形式連接起來,dev_t類型的成員dev顯然是設備號,get(25)和lock(26)分別是兩個函數介面,最後的重點來了,void*作為C語言中的萬金油類型,在這裡就是我們cdev結構(通過後面的分析可以看出),所以,這個cdev_map是一個struct kobj_map類型的指針,其中包含著一個struct probe指針類型、大小為255的數組,數組的每個元素指向的一個probe結構封裝了一個設備號和相應的設備對象(這裡就是cdev),可見,這個cdev_map封裝了系統中的所有的cdev結構和對應的設備號,最多為255個字元設備

cdev_add

瞭解了cdev_map的功能,我們就可以一探cdev_add()

//fs/char_dev.c
456 int cdev_add(struct cdev *p, dev_t dev, unsigned count)  
457 {
458         int error;
459 
460         p->dev = dev;
461         p->count = count;
462 
463         error = kobj_map(cdev_map, dev, count, NULL,
464                          exact_match, exact_lock, p);
465         if (error)
466                 return error;
467 
468         kobject_get(p->kobj.parent);
469 
470         return 0;
471 }

函數很短,(460-461)就是將我們之前獲得設備號和設備號長度填充到cdev結構中,kobject_get()(468)也沒做什麼事:

//lib/kobject.c 
 591 struct kobject *kobject_get(struct kobject *kobj)    
 592 {      
 593         if (kobj) {
             ...
 598                 kref_get(&kobj->kref);
 599         }
 600         return kobj;
 601 }

所以,核心工作顯然是交給了kobj_map()

kobj_map()

這個函數在內核的設備管理中占有重要的地位,這裡我們只從字元設備的角度分析它的功能,先上實現

//drivers/base/map.c
 32 int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
 33              struct module *module, kobj_probe_t *probe,
 34              int (*lock)(dev_t, void *), void *data)
 35 {
 36         unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
 37         unsigned index = MAJOR(dev);
 38         unsigned i;
 39         struct probe *p;
        ...
 44         p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
        ...
 48         for (i = 0; i < n; i++, p++) {  
 49                 p->owner = module;
 50                 p->get = probe;
 51                 p->lock = lock;
 52                 p->dev = dev;
 53                 p->range = range;
 54                 p->data = data;
 55         }
 56         mutex_lock(domain->lock);
 57         for (i = 0, p -= n; i < n; i++, p++, index++) {
 58                 struct probe **s = &domain->probes[index % 255];
 59                 while (*s && (*s)->range < range)
 60                         s = &(*s)->next;
 61                 p->next = *s;       
 62                 *s = p;
 63         }
 64         mutex_unlock(domain->lock);      
 65         return 0;
 66 }

這個函數的設計也很單純,就是封裝好一個probe結構並將它的地址放入probes數組進而封裝進cdev_map,(48-55)j就是根據傳入的設備號的個數,將設備號和cdev依次封裝到kmalloc_array分配的n個probe結構中,(57-63)就是遍歷probs數組,直到找到一個值為NULL的元素,再將probe的地址存入probes。至此,我們就將我們的cdev放入的內核的數據結構,當然,cdev中大量屬性都是由內核幫我們填充的。

chrdev_open()

將設備放入的內核,我們再來看看內核是怎麼找到一個特定的cdev的,對一個字元設備的訪問流程大概是:文件路徑=>inode=>chrdev_open=>cdev->fops->my_chr_open。所以只要通過VFS找到了inode,就可以找到chrdev_open(),這裡我們就來關註一個chrdev_open()是怎麼從內核的數據結構中找到我們的cdev並回調里滿的my_chr_open()的。首先,chrdev_open()嘗試將inode->i_cdev(一個cdev結構指針)保存在局部變數p中(359),如果p為空,即inode->i_cdev為空(360),我們就根據inode->i_rdev(設備號)通過kobj_lookup搜索cdev_map,並返回與之對應kobj(364),由於kobject是cdev的父類,我們根據container_of很容易找到相應的cdev結構並將其保存在inode->i_cdev中(367),找到了cdev,我們就可以將inode->devices掛接到inode->i_cdev的管理鏈表中,這樣下次就不用重新搜索,直接cdev_get()即可(378)。找到了我們的cdev結構,我們就可以將其中的操作方法集inode->i_cdev->ops傳遞給filp->f_ops(386-390),這樣,我們就可以回調我們的設備打開函數my_chr_open()(392);

//fs/char_dev.c
326 static struct kobject *cdev_get(struct cdev *p)     
327 {
328         struct module *owner = p->owner;
329         struct kobject *kobj;
330         
331         if (owner && !try_module_get(owner))
332                 return NULL;
333         kobj = kobject_get(&p->kobj);
        ...
336         return kobj;
337 }

351 static int chrdev_open(struct inode *inode, struct file *filp)
352 {
353         const struct file_operations *fops;
354         struct cdev *p;
355         struct cdev *new = NULL;
356         int ret = 0;
        ...
359         p = inode->i_cdev;
360         if (!p) {
361                 struct kobject *kobj;
362                 int idx;
            ...
364                 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
            ...
367                 new = container_of(kobj, struct cdev, kobj);
369                 /* Check i_cdev again in case somebody beat us to it while
370                    we dropped the lock. */
371                 p = inode->i_cdev;
372                 if (!p) {
373                         inode->i_cdev = p = new;
374                         list_add(&inode->i_devices, &p->list);
375                         new = NULL;
376                 } else if (!cdev_get(p))
377                         ret = -ENXIO;
378         } else if (!cdev_get(p))
379                 ret = -ENXIO;
            ...
386         fops = fops_get(p->ops);
        ...
390         replace_fops(filp, fops);
391         if (filp->f_op->open) {
392                 ret = filp->f_op->open(inode, filp);
            ...
395         }
396 
397         return 0;
398 
399  out_cdev_put:
400         cdev_put(p);
401         return ret;
402 }




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

-Advertisement-
Play Games
更多相關文章
  • 關於排序,其實不管是哪種語言,都有它內置的排序函數,我們要用的時候調用就行了,既然如此,我們為什麼還要講這個東西呢?我想,其實,我們講排序更多是在於排序中包含的思想演算法,因為,演算法對於電腦來說相當重要,一個好的演算法能夠讓電腦的效率達到事半功倍的效果,所以,演算法是電腦語言中一門相當熱門的課程,它 ...
  • var http = require( 'http' ) var handlePaths = [] /** * 初始化路由配置數組 */ function initRotute() { handlePaths.push( '/' ) handlePaths.push( '/login' ) hand... ...
  • 1、相關環境 centos7 hadoop2.6.5 zookeeper3.4.9 jdk1.8 hbase1.2.4 本篇文章僅涉及hbase集群的搭建,關於hadoop與zookeeper的相關部署參見上篇文章http://www.cnblogs.com/learn21cn/p/6184490. ...
  • $slice 如果希望數組的最大長度是固定的,那麼可以將 $slice 和 $push 組合在一起使用,就可以保證數組不會超出設定好的最大長度。$slice 的值必須是負整數。 假設$slice的值為10,如果$push 後的數組的元素個數小於10,那麼所有元素都會保留。反之,只有最後那10個元素會 ...
  • 本文是在Cat Qi的原貼的基礎之上,經本人逐題分別在MySql資料庫中實現的筆記,持續更新... 參考原貼:http://www.cnblogs.com/qixuejia/p/3637735.html 01 表結構 Student(Sno,Sname,Sage,Ssex) 學生表 Course(C ...
  • 本文是介紹MySQL資料庫InnoDB存儲引擎重做日誌漫游 00 – Undo LogUndo Log 是為了實現事務的原子性,在MySQL資料庫InnoDB存儲引擎中,還用Undo Log來實現多版本併發控制(簡稱:MVCC)。 - 事務的原子性(Atomicity) 事務中的所有操作,要麼全部完 ...
  • 1.環境準備 手動添加資料庫依賴: 在package.json的dependencies中新增, “mysql” : “latest”, 使用命令安裝mysql並添加依賴: 2.官方例子: 運行node ...
  • 一:在Dos里切換盤符 a:在電腦左下角右擊顯示圖片;(我用的是win10系統,其他系統類似) b:點擊運行,輸入cmd; c:點擊確定: d:輸入盤符:(如f:) 或F: 只寫字母,不寫分號是不行的!如下圖: cd f:(F:)是不行的如下圖: ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...