5.設備驅動程序

5. 設備驅動程序

  • Linux 內核是一個比較龐大的系統,深入理解內核可以減少在系統移植中的障礙。在系統移植中設備驅動開發是一項很復雜的工作,由于 Linux 內核提供了一部分源代碼,同時還提供了對某些公共部分的支持,例如, USB 驅動對讀寫 U 盤、鍵盤、鼠標等設備提供了通用驅動程序,一般情況可以直接使用內核提供的驅動。但是對于復雜的 USB 設備沒有現成的驅動,就需要讀者對驅動開發過程有一定的認識,必要時參考 Linux 源碼重新開發驅動程序。

  • 設備驅動,實際上是硬件功能的一個抽象。針對同一個硬件不同的驅動可以將硬件封裝成不同的功能。設備驅動是硬件層和應用程序(或者操作系統)的媒介,能夠讓應用程序或者操作系統使用硬件。

  • 在 Linux 操作系統下有 3 類主要的設備文件類型:塊設備、字符設備和網絡設備。設備驅動程序是指管理某個外圍設備的一段代碼,它負責傳送數據控制特定類型的物理設備的操作,包括開始和完成 I/O 操作,檢測和處理設備出現的錯誤。

1. 字符設備驅動程序

  • 字符設備是一種能像字節流一樣進行串行訪問的設備,對設備的存取只能按順序、按字節存取,不能隨機訪問。字符設備沒有請求緩沖區,必須按順序執行所有的訪問請求。常見的字符設備有鼠標、鍵盤、串口、控制臺等。

  • 應用程序對字符設備的訪問是通過字符設備結點來完成的。字符設備是 Linux 中最簡單的設備,可以像文件一樣訪問。應用程序使用標準系統調用打開、讀、寫和關閉字符設備,完全可以把它們當做普通文件一樣進行操作,甚至被 PPP 守護進程使用,用于將一個 Linux系統連接到網上的 modem,也被看做一個普通文件。

  • 當字符設備初始化時,它的設備驅動程序向 Linux 內核注冊,向 chrdevs 向量表中增加一個 device_struct 數據結構項。

  • 通常一種類型設備的主設備標識符是固定的,例如 tty 設備是 4。設備的主設備標識符,用作chrdevs 向量表的索引。

  • 向量表中的每一項(即 device_struct 數據結構)包括兩個元素:

    • 一個是指向登記的設備驅動程序名字的指針
    • 另一個是指向一組文件操作的指針。這組文件操作本身位于這個設備的字符設備驅動程序中,每一個都處理一個特定的文件操作(如打開、讀、寫和關閉)。
  • 用戶進程通過設備文件對硬件進行訪問,對設備文件的操作方式通過一些系統調用來實現,如 openreadwriteclose 等。下面通過一個關鍵的數據結構 file_operations將系統調用和驅動程序關聯起來:

    struct file_operations {int (*seek) (struct inode *struct file *off_tint);int (*read) (struct inode *struct file *charint);int (*write) (struct inode *struct file *off_tint);int (*readdir) (struct inode *struct file *struct dirent *int);int (*select) (struct inode *struct file *int , select_table *);int (*ioctl) (struct inode *struct file *, unsined intunsigned long);int (*mmap) (struct inode *struct file *struct vm_area_struct *);int (*open) (struct inode *struct file *);int (*release) (struct inode *struct file *);int (*fsync) (struct inode *struct file *);int (*fasync) (struct inode *struct file *int);int (*check_media_change) (struct inode *struct file *);int (*revalidate) (dev_t dev);
    };
    
    • 該結構中每一個成員的名字都對應著一個系統調用。用戶進程利用系統調用在對設備文件進行諸如 read/write 操作時,系統調用根據設備文件的主設備號找到對應的設備驅動程序,然后讀取這個數據結構相應的函數指針,接著把控制權交給該函數。
  • 編寫驅動程序就是針對上面相應的函數編寫具體的實現,然后將它們對應上。編寫完驅動后,把驅動程序嵌入內核。驅動程序可以采用兩種方式進行編譯:一種是編譯進內核,驅動被靜態加載;另一種是編譯成模塊(modules),驅動模塊需要動態加載。

  • 在模塊被調入內存時, init()函數向系統的字符設備表登記了一個字符設備:

    int __init chr_dev_init(void)
    {if (devfs_register_chrdev(CHR_MAJOR,"chr_name",&chr_fops))printk("unable to get major %d for chr devs\n", MEM_MAJOR);...return 0;
    }
    
  • cleanup_chr_dev()函數被調用時,它釋放字符設備 chr_name 在系統字符設備表中占有的表項:

    void cleanup_chr_dev(void)
    {unregister_chrdev(CHR_MAJOR, "chr_name");
    }
    

2. 塊設備驅動程序

  • 塊設備具有請求緩沖區,從塊設備讀取數據時,可以從任意位置讀取任意長度,即塊設備支持隨機訪問而不必按照順序存取數據。例如,可以先存取后面的數據,然后再存取前面的數據,字符設備則不能采用該方式存取數據。 常見的塊設備有各種硬盤、 flash 磁盤、 RAM 磁盤等。Linux 下的磁盤設備均為塊設備,應用程序訪問 Linux 下的塊設備結點是通過文件系統及其高速緩存來訪問塊設備的,并非直接通過設備結點讀寫塊設備上的數據。

  • 塊設備既可以用做普通的裸設備存放任意數據,也可以將塊設備按某種文件系統類型的格式進行格式化,然后根據該文件系統類型的格式進行讀取。無論使用哪種方式,訪問設備上的數據都必須通過調用設備本身的方法實現。兩者的區別在于前者直接調用塊設備的操作方法,而后者則間接(通過文件系統)調用塊設備的操作方法。

  • 塊設備用與字符設備類似的方法進行設備的注冊與釋放。塊設備使用 register_blkdev()函數和 block_device_operations結構的指針,其中定義的 openreleaseioctl 方法和字符設備的對應方法相同,但沒有對 read 和 write 操作定義,因為所有涉及塊設備的 I/O 通常由系統進行緩沖處理。

  • 塊驅動程序最終必須提供完成實際塊 I/O 操作的機制,在 Linux 中,用于這些 I/O 操作的方法稱為 request(請求)。

  • 注冊塊設備時,通過 blk_init_queue 來完成對 request 隊列的初始化, blk_init_queue 函數創建隊列,并將該驅動程序的 request 函數關聯到隊列。在模塊的清除階段,調用 blk_cleanup_queue 函數。

  • 初始化塊設備的時候,將塊設備注冊到內核中,下面為塊設備的注冊函數 mtdblock_release()的實現:

    int register_blkdev(unsigned int major, const char *name)
    {struct blk_major_name **n, *p;int index, ret = 0;mutex_lock(&block_class_lock);/*為塊設備指定主設備號,如果指定為 0 則表示由系統來分配*/if (major == 0) {for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {if (major_names[index] == NULL)break;}if (index == 0) {printk("register_blkdev: failed to get major for %s\n", name);ret = -EBUSY;goto out;}major = index;ret = major;}/*為塊設備名字分配空間*/p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);if (p == NULL) {ret = -ENOMEM;goto out;}p->major = major;strlcpy(p->name, name, sizeof(p->name));p->next = NULL;index = major_to_index(major);for (n = &major_names[index]; *n; n = &(*n)->next) {if ((*n)->major == major)break;}if (!*n)*n = p;elseret = -EBUSY;if (ret < 0) {printk("register_blkdev: cannot get major %d for %s\n",major, name);kfree(p);}
    out:mutex_unlock(&block_class_lock);return ret;
    }
    
  • 塊設備被注冊到系統后,訪問硬件的操作 open 和 release 等就能夠被對應的系統調用指針所綁定,應用程序使用系統調用就可以對硬件進行訪問了。下面是塊設備主要的操作函數 open()和 release():

    • 塊設備 open()操作函數:

      static int mtdblock_open(struct mtd_blktrans_dev *mbd)
      {struct mtdblk_dev *mtdblk;struct mtd_info *mtd = mbd->mtd;int dev = mbd->devnum;DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n");if (mtdblks[dev]) {/*如果設備已經打開,則只需要增加其引用計數*/mtdblks[dev]->count++;return 0;}/*為設備創建 mtdblk_dev 對象保存 mtd 設備的信息*/mtdblk = kzalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);if (!mtdblk)return -ENOMEM;mtdblk->count = 1;mtdblk->mtd = mtd;mutex_init(&mtdblk->cache_mutex);mtdblk->cache_state = STATE_EMPTY;if ( !(mtdblk->mtd->flags & MTD_NO_ERASE) && mtdblk->mtd->erasesize){mtdblk->cache_size = mtdblk->mtd->erasesize;mtdblk->cache_data = NULL;}mtdblks[dev] = mtdblk;DEBUG(MTD_DEBUG_LEVEL1, "ok\n");return 0;
      }
      
    • 塊設備 release()操作函數:

      static int mtdblock_release(struct mtd_blktrans_dev *mbd)
      {int dev = mbd->devnum;struct mtdblk_dev *mtdblk = mtdblks[dev];DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n");mutex_lock(&mtdblk->cache_mutex);write_cached_data(mtdblk);mutex_unlock(&mtdblk->cache_mutex);if (!--mtdblk->count) { mtdblks[dev] = NULL;	/*用戶計數遞減為 0 時釋放設備*/if (mtdblk->mtd->sync)mtdblk->mtd->sync(mtdblk->mtd);vfree(mtdblk->cache_data);kfree(mtdblk);}DEBUG(MTD_DEBUG_LEVEL1, "ok\n");return 0;
      }
      

3. 網絡設備驅動程序

  • 網絡設備是面向數據報文的、不支持隨機訪問, 也沒有請求緩沖區。在 Linux里網絡設備也可以被稱為網絡接口,如 eth0,應用程序是通過 Socket(套接字),而不是設備結點來訪問網絡設備,在系統中不存在網絡設備結點。

  • 網絡設備用來與其他設備交換數據,它可以是硬件設備,也可以是純軟件設備,如loopback 接口。網絡設備由內核中的網絡子系統驅動,負責發送和接收數據包,但它不需要了解每項事務如何映射到實際傳送的數據包。 許多網絡連接(如TCP連接)是面向流的,但網絡設備圍繞數據包的傳輸和接收設計。

  • 網絡驅動程序不需要知道各個連接的相關信息,它只需處理數據包。字符設備和塊設備都有設備號,而網絡設備沒有設備號,只有一個獨一無二的名字,例如 eth0、 eth1 等,這個名字也無須與設備文件結點對應。

  • 內核利用一組數據包傳輸函數與網絡設備驅動程序進行通信,它們不同于字符設備和塊設備的 read()和 write()方法。

  • Linux 網絡設備驅動程序從下到上分為 4 層,依次為”網絡設備與媒介層、設備驅動功能層、網絡設備接口層和網絡協議接口層“。

  • 在設計具體的網絡設備驅動程序時,需要完成的主要工作是編寫設備驅動功能層的相關函數以填充 net_device 數據結構的內容,并將net_device 注冊入內核。

  • 下面以 DM9000 代碼為例說明網絡設備驅動的注冊、注銷等主要過程。

    • 驅動的注冊(在設備初始化時被調用)

      static int __init dm9000_init(void)
      {printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME,DRV_VERSION);return platform_driver_register(&dm9000_driver);
      }int platform_driver_register(struct platform_driver *drv)
      {drv->driver.bus = &platform_bus_type;if (drv->probe)drv->driver.probe = platform_drv_probe;if (drv->remove)drv->driver.remove = platform_drv_remove;if (drv->shutdown)drv->driver.shutdown = platform_drv_shutdown;if (drv->suspend)drv->driver.suspend = platform_drv_suspend;if (drv->resume)drv->driver.resume = platform_drv_resume;return driver_register(&drv->driver);
      }
      
    • 驅動的注銷(在設備被清除時被調用,其中包括將設備從系統中移除和將驅動從總線上移除。 )

      static void __exit dm9000_cleanup(void)
      {platform_driver_unregister(&dm9000_driver);
      }void platform_driver_unregister(struct platform_driver *drv)
      {driver_unregister(&drv->driver);
      }void driver_unregister(struct device_driver *drv)
      {driver_remove_groups(drv, drv->groups);bus_remove_driver(drv);
      }static void driver_remove_groups(struct device_driver *drv, struct attribute_group **groups)
      {int i;if (groups)for (i = 0; groups[i]; i++)sysfs_remove_group(&drv->p->kobj, groups[i]);
      }void bus_remove_driver(struct device_driver *drv)
      {if (!drv->bus)return;remove_bind_files(drv);driver_remove_attrs(drv->bus, drv);driver_remove_file(drv, &driver_attr_uevent);klist_remove(&drv->p->knode_bus);pr_debug("bus: '%s': remove driver %s\n", drv->bus->name,drv->name);driver_detach(drv);module_remove_driver(drv);kobject_put(&drv->p->kobj);bus_put(drv->bus);
      }
      
      • 有關網絡設備驅動的詳細接口函數解析和驅動移植將在后面的章節中敘述。

4. 內存與I/O操作

  • 一般來說,在系統運行時,外設的 I/O 內存資源的物理地址是已知的,由硬件的設計決定。但是 CPU 通常并沒有為這些已知的外設 I/O 內存資源的物理地址,預定義虛擬地址范圍,驅動程序并不能直接通過物理地址訪問 I/O 內存資源,只能先將它們映射到內核的虛擬地址空間內(通過頁表),然后才能根據映射的內核虛擬地址范圍訪問這些 I/O 內存資源。

  • Linux 在 io.h 頭文件中聲明了函數 ioremap()iounmap(),分別用來將 I/O 內存資源的物理地址映射和解映射到核心虛擬地址空間(3GB~4GB)中,原型如下:

    void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
    void iounmap(void * addr);
    
  • 在將 I/O 內存資源的物理地址映射成內核的虛擬地址后,就可以像讀寫 RAM 那樣直接讀寫 I/O 內存資源了。但為了保證驅動程序跨平臺的可移植性,應該使用 Linux 中特定的函數訪問 I/O 內存資源,而不是通過指向內核虛擬地址的指針直接訪問。如在 ARM平臺上,讀寫 I/O 的函數如下:

    #define __raw_base_writeb(val,base,off) __arch_base_putb(val,base,off)
    #define __raw_base_writew(val,base,off) __arch_base_putw(val,base,off)
    #define __raw_base_writel(val,base,off) __arch_base_putl(val,base,off)#define __raw_base_readb(base,off) __arch_base_getb(base,off)
    #define __raw_base_readw(base,off) __arch_base_getw(base,off)
    #define __raw_base_readl(base,off) __arch_base_getl(base,off)
    
  • 驅動程序中 mmap()函數的實現原理是,用 mmap 映射一個設備,表示將用戶空間的一段地址關聯到設備內存上,這樣當程序在分配的地址范圍內進行讀取或者寫入時,實際上就是對設備的訪問。這一映射原理類似于 Linux 下 mount 命令,將一種類型的文件系統或設備掛載到另外一個文件系統或者目錄下時,掛載成功后,對掛載點的任何操作實際上是對被掛載的文件系統和設備的操作。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/715198.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/715198.shtml
英文地址,請注明出處:http://en.pswp.cn/news/715198.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構與算法:堆

朋友們大家好啊&#xff0c;本篇文章來到堆的內容&#xff0c;堆是一種完全二叉樹&#xff0c;再介紹堆之前&#xff0c;我們首先對樹進行講解 樹與堆 1.樹的介紹1.1節點的分類 2.樹的存儲結構3.二叉樹的概念和結構3.1 二叉樹的特點3.2 特殊的二叉樹3.3二叉樹的存儲結構 4.堆的…

Acwing---1460. 我在哪?

我在哪&#xff1f; 1.題目2.基本思想3.代碼實現 1.題目 農夫約翰出門沿著馬路散步&#xff0c;但是他現在發現自己可能迷路了&#xff01; 沿路有一排共 N N N 個農場。 不幸的是農場并沒有編號&#xff0c;這使得約翰難以分辨他在這條路上所處的位置。 然而&#xff0c;…

Mybatis | 動態SQL

目錄: 動態SQL中的 “元素” :\<if>元素\<choose>、\<when>、\<otherwise>元素\<where>、\<trim>元素\<set>元素\<foreach>元素\<bind>元素 作者簡介 &#xff1a;一只大皮卡丘&#xff0c;計算機專業學生&#xff0c;正…

單細胞Seurat - 降維與細胞標記(4)

本系列持續更新Seurat單細胞分析教程&#xff0c;歡迎關注&#xff01; 非線形降維 Seurat 提供了幾種非線性降維技術&#xff0c;例如 tSNE 和 UMAP&#xff0c;來可視化和探索這些數據集。這些算法的目標是學習數據集中的底層結構&#xff0c;以便將相似的細胞放在低維空間中…

__vueParentComponent和__vue__獲取dom元素上的vue實例

vue2: 使用__vue__ const el document.querySelector(.xxx); const vueInstance el.__vue__;vue3: 使用 __vueParentComponent const el document.querySelector(.xxx); const vueInstance el.__vueParentComponent;

Python錯題集-4:NameError:(變量名錯誤)

1問題描述 Traceback (most recent call last): File "D:\pycharm\projects\1-可視化學習\8.3更改小提琴圖的中位數、均值、顏色等.py", line 8, in <module> violin_parts plt.violinplot(data, showmediansTrue, showmeansTrue) …

代碼隨想錄算法訓練營第四十四天 完全背包 、零錢兌換 II 、組合總和 Ⅳ

代碼隨想錄算法訓練營第四十四天 | 完全背包 、零錢兌換 II 、組合總和 Ⅳ 完全背包 題目鏈接&#xff1a;題目頁面 (kamacoder.com) 解釋一、01背包 一維 &#xff1a;為什么要倒序遍歷背包&#xff1f; 首先要明白二維數組的遞推過程&#xff0c;然后才能看懂二維變一維的…

【MATLAB源碼-第150期】基于matlab的開普勒優化算法(KOA)機器人柵格路徑規劃,輸出做短路徑圖和適應度曲線。

操作環境&#xff1a; MATLAB 2022a 1、算法描述 開普勒優化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一個虛構的、靈感來自天文學的優化算法&#xff0c;它借鑒了開普勒行星運動定律的概念來設計。在這個構想中&#xff0c;算法模仿行星圍繞太陽的…

項目風險:測試大佬結合實例告訴你如何應對!

項目有風險 今天下午15點&#xff0c;團隊成員D向他的主管Z反饋他測試的項目有風險&#xff1a;項目在測試周期內&#xff0c;但在用例評審時發現有一處功能邏輯有爭議&#xff0c;需要產品經理跟業務方確認&#xff0c;可能出現的情況有&#xff1a; 1 不變更需求&#xff0…

【技巧】SpringCloud Gateway實現多子域(單個應用開放多個端口)

0. 目錄 1. 需求背景2. 實現3. 額外 - 其它Servlet容器實現3.1 Undertow3.2 Tomcat 4. 相關 1. 需求背景 瀏覽器針對單個網站地址(ipport)存在“6個請求”限制&#xff1b;通過多子域配置可以突破這個限制&#xff0c;增加網站的響應效率&#xff0c;尤其是針對三維服務這類大…

【深入了解設計模式】組合設計模式

組合設計模式 組合模式是一種結構型設計模式&#xff0c;它允許你將對象組合成樹狀結構來表現“整體-部分”關系。組合模式使得客戶端可以統一對待單個對象和組合對象&#xff0c;從而使得代碼更加靈活和易于擴展。 概述 ? 對于這個圖片肯定會非常熟悉&#xff0c;上圖我們可…

Carla自動駕駛仿真九:車輛變道路徑規劃

文章目錄 前言一、關鍵函數二、完整代碼效果 前言 本文介紹一種在carla中比較簡單的變道路徑規劃方法&#xff0c;主要核心是調用carla的GlobalRoutePlanner模塊和PID控制模塊實現變道&#xff0c;大體的框架如下圖所示。 一、關鍵函數 1、get_spawn_point(),該函數根據指定r…

c語言字符串函數之strcpy函數,strnpy函數

strcpy函數 語法格式 strcpy(字符數組1,字符串2&#xff09; 它的作用是把字符串2復制到字符數組1里面 #include<stdio.h> #include<string.h> int main() {char c[]"河南";char d[]"安徽";char d[];printf("%s\n",strcpy(c,d));…

力扣hot100題解(python版41-43題)

41、二叉樹的層序遍歷 給你二叉樹的根節點 root &#xff0c;返回其節點值的 層序遍歷 。 &#xff08;即逐層地&#xff0c;從左到右訪問所有節點&#xff09;。 示例 1&#xff1a; 輸入&#xff1a;root [3,9,20,null,null,15,7] 輸出&#xff1a;[[3],[9,20],[15,7]]示例…

【C語言結構體】用戶自定義類型--結構體,結構體傳參,位段,聯合體和枚舉【圖文詳解】

歡迎來CILMY23的博客喔&#xff0c;本篇為【C語言結構體】用戶自定義類型--結構體&#xff0c;結構體傳參&#xff0c;位段&#xff0c;聯合體和枚舉【圖文詳解】&#xff0c;感謝觀看&#xff0c;支持的可以給個一鍵三連&#xff0c;點贊關注收藏。 前言 上一篇&#xff08;ht…

GO—函數

Go 語言支持普通函數、匿名函數和閉包&#xff0c;從設計上對函數進行了優化和改進&#xff0c;讓函數使用起來更加方便。 Go 語言的函數屬于“一等公民”&#xff08;first-class&#xff09;&#xff0c;也就是說&#xff1a; 函數本身可以作為值進行傳遞。支持匿名函數和閉…

Leetcode.2369 檢查數組是否存在有效劃分

題目鏈接 Leetcode.2369 檢查數組是否存在有效劃分 rating : 1780 題目描述 給你一個下標從 0 0 0 開始的整數數組 n u m s nums nums &#xff0c;你必須將數組劃分為一個或多個 連續 子數組。 如果獲得的這些子數組中每個都能滿足下述條件 之一 &#xff0c;則可以稱其為…

推薦6款SSH遠程連接工具

1、Xshell 介紹&#xff1a; xshell是一個非常強大的安全終端模擬軟件&#xff0c;它支持SSH1, SSH2, 以及Windows平臺的TELNET 協議。Xshell可以在Windows界面下用來訪問遠端不同系統下的服務器&#xff0c;從而比較好的達到遠程控制終端的目的。 業界最強大的SSH客戶機 官…

數據分析-Pandas數據的直方圖探查

數據分析-Pandas數據的直方圖探查 數據分析和處理中&#xff0c;難免會遇到各種數據&#xff0c;那么數據呈現怎樣的規律呢&#xff1f;不管金融數據&#xff0c;風控數據&#xff0c;營銷數據等等&#xff0c;莫不如此。如何通過圖示展示數據的規律&#xff1f; 數據表&…

農產品質量追溯系統—功能介紹(2)

儲藏管理 儲藏信息管理對需要儲藏的農產品,記錄儲藏的相關信息,如儲藏開始時間、存放倉庫、操作人員、儲藏原因等; 倉庫信息管理物流管理 物流公司管理對相關的物流公司信息進行登記,以便于管理和追溯; 車輛管理