国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

linux中block驅動的編寫詳解

454398 ? 來源: AI加速 ? 作者: AI加速 ? 2020-11-21 10:56 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

引言

像IIC、LED、KEY等都屬于字符設備,這些設備的驅動是所有驅動類型中最為簡單的。塊設備是另外一種不同于字符設備的類型,這兩類設備在linux的驅動結構中有很大差異。總體來說,塊設備驅動比字符設備驅動復雜的多,在IO操作上也表現出很大的不同。緩沖、IO的調度、請求隊列等都是和塊設備驅動相關的概念。

本章從驅動小白(指本人)的切身實際出發,先不去了解那些深奧的XXX,只從一個最簡單的例子開始,對塊設備驅動的結構有一個大體的了解。路漫漫其修遠兮,驅動是一個大坑,夠你用10年來填。慢慢學吧。

1. 塊設備結構

塊設備就是指磁盤、CD-ROM等硬件存儲介質,塊設備驅動連接了塊設備和用戶空間,實現用戶空間對磁盤的大塊數據訪問。整個子系統如下圖所示,包含虛擬文件系統,塊IO調度層,塊設備驅動以及具體的塊設備。塊設備不同于字符設備,它是以塊為單位接收輸入和返回輸出,而字符設備是以字節為單位。塊設備支持隨機訪問,而且其讀寫速度都快于字符設備,因此驅動的表現也至關重要。這也是為什么塊設備驅動的結構和字符設備的驅動結構被分開來寫。塊是最小的讀寫單位,不同的文件系統有不同大小的塊尺寸,但是它必須是2的指數,同時不能超過頁大小。通常使用的大小有512字節,1K字節,4K字節等。

虛擬文件系統(VFS):隱藏了各種硬件的具體細節,為用戶操作不同的硬件提供了一個統一的接口。其基于不同的文件系統格式,比如EXT,FAT等。用戶程序對設備的操作都通過VFS來完成,在VFS上面就是諸如open、close、write和read的函數API

映射層(mapping layer):這一層主要用于確定文件系統的block size,然后計算所請求的數據包含多少個block。同時調用具體文件系統函數來訪問文件的inode,確定所請求的數據在磁盤上面的邏輯地址。

IO調度器:這部分是linux塊系統中非常關鍵的部分,其涉及到如何接收用戶請求并能最高效去訪問硬件磁盤中的數據。

Block driver:完成和塊設備的具體交互。


2. 驅動程序詳解

通過編寫一個vmem_disk驅動來了解block驅動的結構,vmem_disk是一種模擬磁盤,其數據實際上存儲在RAM中。它通過vmalloc()分配出來的內存空間來模擬出一個磁盤,以塊設備方式來訪問這片內存。現在來看其主要結構。

2.1 block_device_operations

Block_device_operations類似于字符設備驅動中的file_operations結構,它是對塊設備各種操作的集合,定義代碼如下:

struct block_device_operations {
    int (*open) (struct block_device *, fmode_t);
    int (*release) (struct gendisk *, fmode_t);
    int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*direct_access) (struct block_device *, sector_t,void **, unsigned long *);
    int (*media_changed) (struct gendisk *);
    int (*revalidate_disk) (struct gendisk *);
    int (*getgeo)(struct block_device *, struct hd_geometry *);
    struct module *owner;
};

1) 打開和釋放

int (*open)(struct inode *inode ,struct file *filp);

int (*release)(struct inode *inode ,struct file *filp);

這個和字符設備驅動類似,當設備被打開和關閉時將調用它們。

2) IO控制

int (*ioctl)(struct inode *inode,struct file *filp uusignwd intcmd,unsigned long arg)

這個和字符設備驅動中的ioctrl類似,也是用于系統調用。塊設備包含大量的標準請求,這些標準請求由linux通用塊設備層處理,因此大部分ioctrl函數相當短。

3) 介質改變

int (*check_media_change) (kdev_t);

int (*revalidate) (kdev_t);

像磁盤、CD-ROM等塊設備是可插拔的,因此需要有個函數來檢測設備是否存在。當介質發生改變,使用revalidate_disk來響應,給驅動一個機會進行必要的工作來使介質準備好。

4) 獲得驅動信息

int (*getgeo)(struct block_device   *,struct hd_geometry *);

該函數根據驅動器的幾何信息填充一個hd_geometry結構體,hd_geometry包含磁頭、扇區、柱面等信息。

所以我們要填充這個結構體信息,并定義其對應函數。填充如下:

static struct block_device_operations vmem_fops={
         .owner=THIS_MODULE,
         .getgeo=vmem_getgeo,
         .ioctl=vmem_ioctl,
         .open=vmem_open,
         .release=vmem_release,
};

我們只定義了open、release、ioctrl、getgeo函數。為了簡化這個驅動,我們把open、release、ioctrl函數的具體內容也都省略了,只是給出一個定義,沒有任何有效內容。但是hd_geometry的信息需要填充,所以getgeo函數定義如下:

static int vmem_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
         geo->cylinders=1;
         geo->heads=1;
         geo->sectors=BLK_SIZE/SECTOR_SIZE;
         return 0;
}

定義了使用的塊設備的柱面、磁頭和扇區個數。

2.2 gendisk結構體

在linux內核中,用gendisk結構體來表示一個獨立的磁盤設備。就像字符設備驅動中使用cdev結構體一樣,它也包含主次設備號,需要分配內存,釋放結構體和初始化操作。

1) 分配gendisk

分配函數為:

struct gendisk *alloc_disk(int minors);

2) 增加gendisk

這個是用于注冊磁盤設備,函數為:

void add_disk(struct gendisk *gd);

3) 釋放gendisk

當不再需要使用磁盤時候,需要釋放這個結構體,也即釋放其分配的內存。

void del_gendisk(struct gendisk *gd);

以上這些函數在快設備初始化和關閉驅動中調用。

2.3 請求處理

每個塊設備驅動的核心是它的請求函數,實際的工作,至少如設備的啟動,都是在這個函數里完成的。塊設備驅動程序的request函數有以下原型:

void request(request_queue_t *queue);

當內核需要驅動程序處理讀取、寫入以及其它對設備的操作時,就會調用該函數。在其返回前,request函數不必完成所有隊列中的請求。事實上,對大多數真實設備而言,它可能沒有完成任何請求。

每個設備都有一個請求隊列,這是因為對磁盤數據實際傳入和傳出發生的時間,與內核請求的時間相差很大,因此內核需要有一定靈活性,以安排在適當時刻(比如把影響相鄰磁盤扇區的請求分成一組)進行傳輸。

我們用一個簡單的request函數:

static void vmem_request(struct request_queue *q){
         struct request *req;
         uint64_t pos=0;
         ssize_t size=0;
         struct bio_vec bvec;
         int rv=0;
         struct req_iterator iter;
         void *kaddr=NULL;
         while((req=blk_fetch_request(q)) != NULL){
                   spin_unlock_irq(q->queue_lock);
                   pos=blk_rq_pos(req)*SECTOR_SIZE;
                   size=blk_rq_bytes(req);
                   if(pos+size>vdev->size){
                            printk(KERN_WARNING "beyond addr/n");
                            rv=-EIO;
                            goto skip;
                   }
                   rq_for_each_segment(bvec, req, iter){
                            kaddr=kmap(bvec.bv_page);
                            rv=vmem_transfer(vdev, pos, bvec.bv_len, kaddr+bvec.bv_offset, rq_data_dir(req));
                            if(rv<0)
                                     goto skip;
                   pos+=bvec.bv_len;
                   kunmap(bvec.bv_page);
                   }
         skip:
                   blk_end_request_all(req, rv);
                   spin_lock_irq(q->queue_lock);
         }
}

Blk_fetch_request從請求隊列中獲取一個請求,當沒有請求需要時,返回NULL。然后while中的程序開始處理這個請求。當請求隊列創建的時候,request函數綁定了它,并且提供了一個自旋鎖。當調用request函數時,該鎖由內核控制。因此request函數是一個原子上下文中運行的。因此在獲得request時,需要通過spin_unlock_irq函數來解鎖。

然后通過blk_rq_pos和blk_rq_bytes來獲得請求中的位置和大小。rq_for_each_segment是一個宏定義,其遍歷一個請求中的所有bio。這里插入一下對bio的介紹:

從本質上講,一個request結構是作為一個bio結構的鏈表實現的。Bio結構是在底層對部分塊設備IO請求的描述。Bio結構體定義如下:

struct bio { 
    sector_t bi_sector;//該bio結構所要傳輸的第一個(512字節)扇區:磁盤的位置 
    struct bio *bi_next;    //請求鏈表 
    struct block_device *bi_bdev;//相關的塊設備 
    unsigned long bi_flags//狀態和命令標志 
    unsigned long bi_rw; //讀寫 
    unsigned short bi_vcnt;//bio_vesc偏移的個數 
    unsigned short bi_idx;    //bi_io_vec的當前索引 
    unsigned short bi_phys_segments;//結合后的片段數目 
    unsigned short bi_hw_segments;//重映射后的片段數目 
    unsigned int bi_size;    //I/O計數 
    unsigned int bi_hw_front_size;//第一個可合并的段大小; 
    unsigned int bi_hw_back_size;//最后一個可合并的段大小 
    unsigned int bi_max_vecs;    //bio_vecs數目上限 
    struct bio_vec *bi_io_vec;    //bio_vec鏈表:內存的位置 
    bio_end_io_t *bi_end_io;//I/O完成方法 
    atomic_t bi_cnt; //使用計數 
    void *bi_private; //擁有者的私有方法 
    bio_destructor_t *bi_destructor;    //銷毀方法 
}; 

與bio對應的數據每次存放的內存不一定是連續的,bio_vec結構體用于描述與這個bio對應的所有內存,它并不總是在一個頁面里,因此需要一個向量。IO調度算法將連續的bio合并成一個request,然后可以改善讀寫磁盤的性能。


遍歷bio的時候,就可以定義一個transfer函數來完成bio的數據轉移了。Rq_data_dir獲得從request中得到數據傳輸方向,返回值0表示從設備讀數據,非0表示寫數據。Transfer中就可以通過簡單的memcpy來完成數據拷貝:

static int vmem_transfer(struct vmem_device *vdev, uint64_t pos, ssize_t size, void *buffer, int write)
{
         if(write)
                   memcpy(vdev->buf+pos, buffer, size);
         else
                   memcpy(buffer, vdev->buf+pos, size);
         return 0;
}

如果一個請求不是文件系統請求,就將請求傳遞給end_request。當處理非文件系統請求時,傳遞0表示不能成功完成該請求。

2.4 設備初始化

在塊設備初始化階段,與字符設備類似。基本過程如下:

1) 注冊塊設備

vmem_major=register_blkdev(0, "VMEM");

第一個參數0表示由內核自動分配主設備號,如果成功注冊就返回這個主設備號,如果注冊失敗就返回負值。

2) 定義設備結構體

這個設備結構體是自己定義的,一般包含gendisk、設備號、請求隊列等。

struct vmem_device {
         struct gendisk *disk;
         struct request_queue *que;
         void *buf;
         spinlock_t lock;
         ssize_t size;
};

3) vmem_dev結構體分配和buf分配

         vdev=kzalloc(sizeof(struct vmem_device), GFP_KERNEL);
         if(!vdev){
                   printk(KERN_WARNING "vmem_device: unable to allocate mem/n");
                   goto out;
         }
         vdev->size=BLK_SIZE;
         vdev->buf=vmalloc(vdev->size);
         if(vdev->buf==NULL){
                   printk(KERN_WARNING "failed to vmalloc vdev->buf/n");
                   goto out_dev;
         }

Buf就是一個虛擬的磁盤。

4) 初始化請求隊列

vdev->que=blk_init_queue(vmem_request, &vdev->lock);

5) 分配磁盤

disk=alloc_disk(1);

6) 填充vmem_dev結構體中的信息。

         vdev->disk=disk;
         disk->major=vmem_major;
         disk->first_minor=1;
         disk->fops=&vmem_fops;
         disk->queue=vdev->que;
         disk->private_data=vdev;
         sprintf(disk->disk_name, "VMEM");

7)注冊磁盤

         set_capacity(disk, BLK_SIZE/SECTOR_SIZE);

         add_disk(disk);

3.實驗

我們注冊驅動,并看到在dev下面有VMEM設備,這個就是我們的虛擬磁盤設備文件。


然后將其格式化為ext2文件系統:


接下來我們就可以將其掛載并創建文件了。


總結

最后總結一下linux中block驅動的編寫過程:

1) 填充request函數,這個函數在請求隊列初始化中將喝隊列綁定;

2) 定義vdev結構體,其中包含gendisk、request_queue等結構;

3) 定義設備初始化函數,并完成對disk的分配,注冊,請求隊列初始化工作;

4) 填充block_device_operations結構體;

5) 定義設備退出函數,主要是釋放結構體;

編輯:hfy


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    88

    文章

    11758

    瀏覽量

    219009
  • 驅動程序
    +關注

    關注

    19

    文章

    869

    瀏覽量

    50457
  • Block
    +關注

    關注

    0

    文章

    26

    瀏覽量

    15154
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    Rockchip CIF驅動深度解析:從架構設計到電源計數補丁修復

    在嵌入式 Linux 系統,Rockchip CIF(Camera Interface)驅動是攝像頭硬件與上層應用的“橋梁”—— 它不僅要實現設備初始化、格式協商、數據捕獲等核心功能,還需保障運行
    的頭像 發表于 02-06 16:49 ?3516次閱讀
    Rockchip CIF<b class='flag-5'>驅動</b>深度解析:從架構設計到電源計數補丁修復

    深度解析ES8389/ES8390/音頻芯片Linux驅動Linux6.1內核)

    基于 Linux6.1 內核,從驅動架構、寄存器配置、核心函數、數據流走向四個維度,完整拆解 ES8389 的 Linux 驅動實現,幫你吃透這款芯片的
    的頭像 發表于 02-02 11:37 ?1331次閱讀
    深度解析ES8389/ES8390/音頻芯片<b class='flag-5'>Linux</b><b class='flag-5'>驅動</b>(<b class='flag-5'>Linux</b>6.1內核)

    Linux驅動開發的必備知識

    、內核模塊編程: 掌握內核模塊的編寫、編譯、加載和卸載方法。 了解內核模塊的初始化和清理函數的編寫。 5、設備驅動框架: 熟悉字符設備、塊設備、網絡設備等驅動框架。 能夠根據
    發表于 12-04 07:58

    【迅為工業RK3568穩定可靠】itop-3568開發板Linux驅動開發實戰:RK3568內核模塊符號導出詳解

    【迅為工業RK3568穩定可靠】itop-3568開發板Linux驅動開發實戰:RK3568內核模塊符號導出詳解
    的頭像 發表于 11-21 13:25 ?1187次閱讀
    【迅為工業RK3568穩定可靠】itop-3568開發板<b class='flag-5'>Linux</b><b class='flag-5'>驅動</b>開發實戰:RK3568內核模塊符號導出<b class='flag-5'>詳解</b>

    【免費送書】成為硬核Linux開發者:《Linux 設備驅動開發(第 2 版)》

    Linux系統的設備驅動開發,一直給人門檻較高的印象,主要因內核機制抽象、需深度理解硬件原理、開發調試難度大所致。2021年,一本講解驅動開發的專著問世即獲市場青睞,暢銷近萬冊——這便是《Li
    的頭像 發表于 11-18 08:06 ?1397次閱讀
    【免費送書】成為硬核<b class='flag-5'>Linux</b>開發者:《<b class='flag-5'>Linux</b> 設備<b class='flag-5'>驅動</b>開發(第 2 版)》

    【書籍評測活動NO.67】成為硬核Linux開發者:《Linux 設備驅動開發(第 2 版)》

    數據采集與緩沖區功能。GPIO是嵌入式基礎硬件接口,本篇章講解 如何編寫GPIO控制器驅動程序,以及如何在GPIO控制器啟用IRQ芯片 。文中強調棄用舊的整數接口,改用基于描述符的GPIO接口;同時說明
    發表于 11-17 17:52

    Linux基礎命令which詳解

    Linux系統,which命令用于查找并顯示指定命令的可執行文件路徑。這對于系統管理員和開發人員來說是一個非常有用的工具,可以幫助定位命令所在的位置,確認命令是否已正確安裝,并且能夠用于配置環境變量等任務。下面是華納云對which命令的詳細解釋。
    的頭像 發表于 07-29 17:58 ?851次閱讀

    Linux系統中網絡配置詳解

    網絡配置是Linux系統運維的核心技能之一。正確理解和配置子網掩碼、網關等網絡參數,直接影響系統的網絡連通性和性能。本文將深入探討Linux系統中網絡配置的方方面面,為運維工程師提供全面的技術指導。
    的頭像 發表于 07-17 11:01 ?1196次閱讀

    詳解Linux系統的服務管理

    Linux,無論何時當你安裝任何帶有服務和守護進程的包,系統默認會把這些服務的初始化及 systemd腳本添加進去,不過此時它們并沒有被啟用。
    的頭像 發表于 05-23 15:10 ?833次閱讀
    <b class='flag-5'>詳解</b><b class='flag-5'>Linux</b>系統<b class='flag-5'>中</b>的服務管理

    itop-3568開發板驅動開發指南-實驗程序的編寫

    驅動例程\\\\02。 本章實驗將編寫 Linux 下的驅動傳參實例代碼,通過“insmod”命令進行參數的傳遞,并將相應的參數打印到串口終端上。
    發表于 05-19 10:26

    Linux環境再升級:PLIN驅動程序正式發布

    PLIN驅動程序現已正式發布,本文將展示如何安裝PLIN驅動程序,以及如何在Linux環境下進行基本的PLIN通信操作,確保您能夠快速掌握并應用這一新工具。
    的頭像 發表于 04-21 15:29 ?1047次閱讀
    <b class='flag-5'>Linux</b>環境再升級:PLIN<b class='flag-5'>驅動</b>程序正式發布

    迅為RK3568開發板helloworld 驅動實驗-驅動編寫

    在學習 C 語言或者其他語言的時候,我們通常是打印一句“helloworld”來開啟編程世界的大門。學習驅動程序編程亦可以如此,使用 helloworld 作為我們的第一個驅動程序。接下來開始編寫
    發表于 04-01 15:20

    嵌入式學習-飛凌嵌入式ElfBoard ELF 1板卡-Linux設備驅動的分類

    內核模塊嵌入到Linux內核,位于內核空間。它們直接與內核進行交互,通過內核提供的接口與硬件設備進行通信和控制。用戶空間的應用程序通過系統調用和設備文件接口與設備驅動進行交互。設備驅動
    發表于 03-12 10:20

    飛凌嵌入式ElfBoard ELF 1板卡-Linux驅動模塊之helloworld驅動

    驅動程序的基本結構和加載過程。源碼編寫(一)首先包含頭文件#include // 包含模塊相關函數的頭文件#include // 包含內核相關函數的頭文件#include// 包含初始化和清理函數
    發表于 03-12 10:15

    飛凌嵌入式ElfBoard ELF 1板卡-Linux設備驅動的分類

    內核模塊嵌入到Linux內核,位于內核空間。它們直接與內核進行交互,通過內核提供的接口與硬件設備進行通信和控制。用戶空間的應用程序通過系統調用和設備文件接口與設備驅動進行交互。設備驅動
    發表于 03-10 17:00