linux進階:設備號

来源:https://www.cnblogs.com/couvrir/archive/2023/08/13/17626647.html
-Advertisement-
Play Games

設計字元設備 文件系統調用系統IO的內核處理過程 在Linux文件系統管理中,當應用程式調用open函數時,內核會根據文件路徑找到文件的索引結點(inode),為文件分配文件描述符和文件對象,並根據打開模式和許可權等參數進行相應的操作和設置。 硬體層原理 思路:把底層寄存器配置操作放在文件操作介面里, ...


設計字元設備

文件系統調用系統IO的內核處理過程

inode索引節點是文件系統中的一種數據結構,用於存儲文件的元數據信息,包括文件的大小、訪問許可權、創建時間、修改時間等。每個文件在文件系統中都對應著一個唯一的inode節點,通過inode節點可以查找到文件的實際數據塊的位置。inode節點通常存儲在磁碟的inode表中,文件系統通過inode號來訪問和管理文件。

file_operation結構體是函數指針表,用於定義文件的操作方法。當應用程式通過文件描述符打開文件時,內核會根據文件描述符找到對應的inode節點,並獲取與inode節點關聯的file_operation表。通過file_operation表中的函數指針,內核可以調用相應的函數來執行文件操作,如open、read、write、close等。不同內核可以有不同的file_operation表,因為不同的內核可能有不同的文件操作方法和特性。

task_struct結構體用於描述和管理進程,內容很多很複雜。裡面有個成員變數是struct files_struct *files,用於存儲與進程相關的文件描述符表的信息(文件描述符表記錄了進程打開的文件以及相應的操作許可權等信息)。要想獲取進程的文件描述符相關信息,需要通過訪問task_struct結構體的files指針來獲取files_struct結構體,進而訪問文件描述符表的信息。

files_struct結構體用於跟蹤和管理進程打開的文件。fd_array[]為指針數組,用於存儲進程打開的文件描述符的信息,即每個文件描述符都對應一個files_struct。通過fd_array數組可以快速訪問和操作這些文件描述符,數組索引值對應著文件描述符的值。

 

硬體層原理

思路:把底層寄存器配置操作放在文件操作介面里,新建一個文件綁定該文件操作介面,應用程式通過操作指定文件來配置底層寄存器。

基本介面實現:查原理圖,數據手冊,確定底層需要配置的寄存器。類似於裸機開發。實現一個文件的底層操作介面,這是文件的基本特征。

struct file_operations存放在ebf-buster-linux/include/linux/fs.h

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        int (*iterate_shared) (struct file *, struct dir_context *);
        __poll_t (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        unsigned long mmap_supported_flags;
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
        ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
        int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
        int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
        int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

幾乎所有成員都是函數指針,用來實現文件的具體操作。 

 

驅動層原理

設備號(dev_t)是uint32_t類型,主設備號(高12位)+次設備號(低20位)。主設備號用於標識該驅動所管理的設備類型,次設備號用於標識同一類型設備的具體設備實例。

 

把file_operations文件操作介面註冊到內核,內核通過主次設備號來記錄它(cdev_init存放在ebf-buster-linux/fs/char_dev.c)

cdev_init()  //把用戶構建的file_operations結構體記錄在內核驅動的基本對象cdev

cdev_init()用於初始化字元設備驅動中的struct cdev結構體。struct cdev結構體是字元設備驅動的核心數據結構,描述了字元設備驅動的屬性和操作(file_operations)。

當開發者編寫字元設備驅動時,需要先調用cdev_init()函數來初始化struct cdev結構體。這個函數會將struct cdev的各個成員初始化為合適的值,並建立與字元設備驅動相關的關聯。

cdev_init()函數通常在字元設備驅動載入時init函數中調用,確保驅動模塊的正確初始化。

 

cdev_add(存放在ebf-buster-linux/fs/char_dev.c),作用:根據哈希函數保存cdev到probes哈希表中,方便內核查找file_operation使用。

int cdev_add(struct cdev *p, dev_t dev, unsigned int count);
參數:
    p:指定的字元設備對象
    dev:設備號
    count:設備號數量

cdev_add()函數用於向字元設備驅動註冊字元設備,即將一個已初始化的字元設備對象(struct cdev)和一個設備號(dev_t)進行關聯,並將其註冊到內核中,使其能夠被用戶程式訪問和使用。

cdev_add()函數通常在字元設備驅動初始化階段調用來註冊字元設備,在此之前,需要先通過cdev_init()初始化。註意:需要正確初始化之後才調用,否則可能導致註冊失敗或出現意外的行為。

 

兩個Hash表(幫助找到cdev結構體)

chrdevs:登記設備號。

cdev_map->probe:保存驅動基本對象struct cdev。

 

文件系統層原理

mknod + 主次設備號

構建一個新的設備文件,通過主次設備號在cdev_map中找到cdev->file_operations,把cdev->file_operations綁定到新的設備文件中。

到這一步,應用程式就可以使用open()、write()、read()等函數來控制設備文件了。

 

 

設備號的組成與哈希表

ebf-buster-linux/include/linux/kdev_t.h描述了設備號的具體構成。

/* 截取部分代碼,關於設備號的描述 */
#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) -1)
 
#define MAJOR(dev)   ((unsigned int)((dev) >> MINORBITS))
#define MINOR(dev)   ((unsigned int)((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

理論取值範圍:

主設備號:2^12=4K

次設備號:2^20=1M

cat /proc/devices查看已註冊的設備號

內核是希望一個設備驅動(file_operation)可以獨自占有一個主設備號和多個次設備號,而通常一個設備文件綁定一個主設備號和一個次設備號,所以設備驅動與設備文件是一對一或者一對多的關係。

 

Hash Table(哈希表、散列表,數組和鏈表的混合使用)

以主設備號為編號,使用哈希函數f(major)= major % 255 來計算主設備號的對應數組下標。

主設備號衝突(如0、255,都掛載在數組0下標),則以次設備號為比較值來排序鏈表節點。

哈希函數的設計目標:鏈表節點儘量平均分佈在各個數組元素中,提高查詢效率。

 

 

設備號管理

關鍵的數據結構:char_device_struct(存放在ebf-buster-linux/fs/char_dev.c)

static struct char_device_struct {
        struct char_device_struct *next;    //指向下一個鏈表節點
        unsigned int major;                 //主設備號  
        unsigned int baseminor;             //次設備號 
        int minorct;                        //次設備號的數量
        char name[64];                      //設備名稱
        struct cdev *cdev;                  //內核字元對象(已丟棄)
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

 

關鍵的函數:__register_chrdev_region(存放在ebf-buster-linux/fs/char_dev.c)

static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
{
        struct char_device_struct *cd, **cp;
        int ret = 0;
        int i;

     /* 動態申請記憶體 */ cd
= kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL)  return ERR_PTR(-ENOMEM);      /* 加互斥鎖保護資源 */ mutex_lock(&chrdevs_lock); if (major == 0) {  
          /* 主設備號為0,從chadevs哈希表中查找一個空閑位置 */ ret
= find_dynamic_major(); if (ret < 0) { pr_err("CHRDEV \"%s\" dynamic allocation region is full\n", name); goto out; }
          /* 返回主設備號 */ major
= ret;        } if (major >= CHRDEV_MAJOR_MAX) { pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", name, major, CHRDEV_MAJOR_MAX-1); ret = -EINVAL; goto out; }
     /* 保存參數 */ cd
->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name));
    /* 哈希函數,計算哈希表的位置 */ i
= major_to_index(major);
     /* 鏈表排序,按主設備號從小到大排序。如果主設備號相等,按次設備號從小到大排序,要考慮次設備號的最大值 */
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ( (*cp)->major > major || ( (*cp)->major == major && ( ( (*cp)->baseminor >= baseminor ) || ( (*cp)->baseminor + (*cp)->minorct > baseminor) ) ) )  break; /* 如果主設備號相等,檢查次設備號是否存在衝突 */ if (*cp && (*cp)->major == major) {
          /* 獲取鏈表節點的次設備號範圍 */
int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
          /* 獲取新設備的次設備號範圍 */
int new_min = baseminor; int new_max = baseminor + minorct - 1; /* 判斷新設備的次設備號最大值是否位於鏈表節點的次設備號範圍 */ if (new_max >= old_min && new_max <= old_max) {
               /* 確定衝突,返回錯誤 */ ret
= -EBUSY; goto out; } /* 判斷新設備的次設備號最小值是否位於鏈表節點的次設備號範圍 */ if (new_min <= old_max && new_min >= old_min) {
               /* 確定衝突,返回錯誤 */ ret
= -EBUSY; goto out; }
          /* 判斷新設備的次設備號是否跨越鏈表節點的次設備號範圍 */
if (new_min < old_min && new_max > old_max) {
               /* 確定衝突,返回錯誤 */ ret
= -EBUSY; goto out; } }
     /* 插入新設備的鏈表節點 */ cd
->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }

上訴函數主設備號相等,判斷新舊次設備號三種錯誤圖如下。

該函數用於註冊字元設備驅動,保存新註冊的設備號到chrdevs哈希表中,防止設備號衝突。

主設備為0時,需要動態分配設備號(優先使用255~234,其次使用511~384),函數會從字元設備哈希表中找到一個空閑的位置,分配主設備號,並將該主設備號保存到字元設備的數據結構中。主設備號最大為512。

然後函數將傳入的參數保存到字元設備結構體cd中,並計算出在字元設備哈希表中的位置。如果遍歷字元設備哈希表的鏈表,按主設備號從小到大排序。

如果遍歷過程中,主設備號和傳入的主設備號衝突或次設備號範圍與傳入的次設備號範圍有重疊,函數會返回錯誤。否則函數會將新字元設備的節點插入到鏈表中,並返回字元設備結構體cd。

該函數一般在字元設備驅動載入時調用,用於註冊字元設備。通過註冊字元設備,內核可以識別和管理相應的設備,並提供相應的介面供用戶空間程式進行讀寫操作。

 

 

保存file_operation結構體

關鍵數據結構:cdev(存放在ebf-buster-linux/include/linux/cdev.h)

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
} __randomize_layout;

 

關鍵數據結構:kobj_map(與哈希表有關,存放在ebf-buster-linux/drivers/base/map.c)

struct kobj_map {
        struct probe {
                struct probe *next;
                dev_t dev;
                unsigned long range;
                struct module *owner;
                kobj_probe_t *get;
                int (*lock)(dev_t, void *);
                void *data;
        } *probes[255];
        struct mutex *lock;
};

 

 

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 基本環境準備(第一節)2023年8月9日16:37 1.安裝Node.js;Windows 上安裝 Node.js你可以採用以下兩種方式來安裝。1、Windows 安裝包(.msi)本文實例以 v0.10.26 版本為例,其他版本類似, 安裝步驟: 步驟 1 : 雙擊下載後的安裝包 v0.10.26 ...
  • 在一個需要用到flag作為信號控制代碼中一些代碼片段是否運行的,比如"--flag True"或者"--flag False"。 但是古怪的是無法傳入False,無論傳入True還是False,程式裡面都是True的參數,所以這個flag並沒有生效,也就失去了意義。 參考代碼: ```python ...
  • 在本篇文章中,會先介紹 Python 中對象的基礎概念,之後會提到對象的深淺拷貝以及區別。在閱讀後,應該掌握如下的內容: - 理解變數、引用和對象的關係 - 理解 Python 對象中 identity,type 和 value 的概念 - 什麼是 mutable 和 immutable 對象?以及 ...
  • # 《Rust編程之道》學習筆記一 ## 序 ### Rust語言的主要特點 - 系統級語言 - 無GC - 基於LLVM - 記憶體安全 - 強類型+靜態類型 - 混合編程範式 - 零成本抽象 - 線程安全 ### 程式員的快樂 何謂快樂?真正的快樂不僅僅是寫代碼時的“酸爽”,更應該是代碼部署到生產 ...
  • 項目工程中,集成資料庫實現對數據的增曬改查管理,是最基礎的能力,通常涉及三個基礎組件:連接池,持久層框架,數據源。 ...
  • 當我們需要處理一個大量的數據集合時,一次性將其全部讀入記憶體並處理可能會導致記憶體溢出。此時,我們可以採用迭代器`Iterator`和生成器`Generator`的方法,逐個地處理數據,從而避免記憶體溢出的問題。迭代器是一個可以逐個訪問元素的對象,它實現了`python`的迭代協議,即實現了`__iter... ...
  • 通過編碼實戰瞭解quarkus攔截器的另一個高級特性:禁用類級別攔截器,這樣可以避免類級別和方法級別攔截器的疊加衝突 ...
  • [toc] ### 1.晶元手冊中的LED電路圖 ![圖1](https://img-blog.csdnimg.cn/34c2a95aa89c4cbe8a7904429d889564.png) ### 2.官網手冊 ![圖2](https://img-blog.csdnimg.cn/e9b1131d ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...