日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux块设备驱动简述(Linux驱动开发篇)

發布時間:2023/12/31 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux块设备驱动简述(Linux驱动开发篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、塊設備

  • 塊設備驅動是Linux 三大驅動類型之一。
  • 塊設備驅動要遠比字符設備驅動復雜得多,不同類型的存儲設備又對應不同的驅動子系統。
  • 塊設備是針對存儲設備的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、機械硬盤、固態硬盤等。

塊設備和字符設備的區別?

  • 塊設備以塊(是VFS基本數據傳輸單位)為單位進行讀寫,字符設備以字節的形式進行讀寫,
  • 塊設備在結構上是可以進行隨機訪問的,對于這些設備的讀寫都是按塊進行的塊設備使用緩沖區來暫時存放數據,等到條件成熟以后在一次性將緩沖區中的數據寫入塊設備中。(這樣就減少了對塊設備的擦除次數,提高了塊設備壽命。)
  • 字符設備是順序的數據流設備,字符設備是按照字節進行讀寫訪問的。字符設備不需要緩沖區,對于字符設備的訪問都是實時的,而且也不需要按照固定的塊大小進行訪問
  • 總結一下:
    字符設備:實時、按照字節訪問,無緩沖。
    塊設備:帶有緩沖區,按照塊大小進行訪問。(一種是隨機訪問。一種是按順序訪問)

塊設備的類別

  • 針對不同的存儲設備實現了不同的I/O調度算法。
  • 塊設備結構的不同其 I/O算法也會不同,比如對于EMMC、SD卡、NAND Flash這類沒有任何機械設備的存儲設備就可以任意讀寫任何的扇區(塊設備物理存儲單元)。
  • 但是對于機械硬盤這樣帶有磁頭的設備,讀取不同的盤面或者磁道里面的數據,磁頭都需要進行移動,因此對于機械硬盤而言,將那些雜亂的訪問按照一定的順序進行排列可以有效提高磁盤性能。
  • ===這個后面的框架你會發現是有所不同的。

二、塊設備驅動框架簡述

  • 在了解具體的塊設備驅動框架的大概,我們現需要了解一下其中的結構體和一些函數
/* @ 定義在include/linux/fs.h文件中 @ block_device 結構體 @ linux 內核使用 block_device 表示塊設備 */struct block_device { dev_t bd_dev; /* not a kdev_t - it's a search key */ int bd_openers; struct inode *bd_inode; /* will die */ struct super_block *bd_super; struct mutex bd_mutex; /* open/close mutex */ struct list_head bd_inodes; void * bd_claiming; void * bd_holder; int bd_holders; bool bd_write_holder; #ifdef CONFIG_SYSFS struct list_head bd_holder_disks; #endif struct block_device *bd_contains; unsigned bd_block_size; struct hd_struct *bd_part; /*number of times partitions within this device have been opened.*/unsigned bd_part_count; int bd_invalidated; struct gendisk *bd_disk; //比如一個硬盤或者分區,如果是硬盤的話 bd_disk就指向通用磁盤結構 gendiskstruct request_queue *bd_queue; struct list_head bd_list;unsigned long bd_private; /* The counter of freeze processes */ int bd_fsfreeze_count; /* Mutex for freeze */ struct mutex bd_fsfreeze_mutex; }; /* @ 向內核注冊新的塊設備、申請設備號 @ major:主設備號。 @ name:塊設備名字。 @ 返回值:如果參數 major 在 1~255 之間的話表示自定義主設備號,那么返回 0 表示注冊成功,如果返回負值的話表示注冊失敗。如果 major 為 0 的話表示由系統自動分配主設備號,那么返回值就是系統分配的主設備號(1~255),如果返回負值那就表示注冊失敗 */ int register_blkdev(unsigned int major, const char *name) /* @ 如果不使用某個塊設備了,那么就需要注銷掉 @ major:要注銷的塊設備主設備號 @ name:要注銷的塊設備名字。 @ 返回值:無 */ void unregister_blkdev(unsigned int major, const char *name)/* @ 定義在 include/linux/genhd.h中 @ gendisk 結構體 @ linux內核使用gendisk來描述一個磁盤設備 */ struct gendisk { /* major, first_minor and minors are input parameters only, * don't use directly. Use disk_devt() and disk_max_parts(). */ int major; /* major number of driver */ int first_minor; /*磁盤的第一個次設備號*/int minors; /*磁盤的次設備號數量,也就是磁盤的分區數量,這些分區的主設備號一樣,次設備號不同*/char disk_name[DISK_NAME_LEN]; /* name of major driver */ char *(*devnode)(struct gendisk *gd, umode_t *mode); unsigned int events; /* supported events */ unsigned int async_events; /* async events, subset of all */ /* Array of pointers to partitions indexed by partno. * Protected with matching bdev lock but stat and other * non-critical accesses use RCU. Always access through * helpers. */ struct disk_part_tbl __rcu *part_tbl; /*此數組每一項都對應一個分區信息*/struct hd_struct part0; const struct block_device_operations *fops; /*字符設備操作集 file_operations一樣*/struct request_queue *queue; /*,queue為磁盤對應的請求隊列,所以針對該磁盤設備的請求都放到此隊列中,驅動程序需要處理此隊列中的所有請求*/void *private_data; int flags; struct device *driverfs_dev; // FIXME: remove struct kobject *slave_dir; struct timer_rand_state *random; atomic_t sync_io; /* RAID */ struct disk_events *ev; #ifdef CONFIG_BLK_DEV_INTEGRITY struct blk_integrity *integrity; #endif int node_id; } /*************************************帶有機械磁頭的磁盤 @ 使用gendisk之前要先申請 @ minors:次設備號數量,也就是gendisk對應的分區數量。 @ 返回值:成功:返回申請到的 gendisk,失敗:NULL。 */ struct gendisk *alloc_disk(int minors) /* @ 使用 add_disk 函數將申請到的gendisk添加到內核中 @ disk:要添加到內核的 gendisk。 @ 返回值:無。 */ void add_disk(struct gendisk *disk) /* @ 在初始化 gendisk 的時候也需要設置其容量 @ disk:要設置容量的 gendisk。 @ size:磁盤容量大小,注意這里是扇區數量。塊設備中最小的可尋址單元是扇區,一個扇區一般是 512字節比如一個 2MB 的磁盤,其扇區數量就是(2*1024*1024)/512=4096。 @ 返回值:無。 */ void set_capacity(struct gendisk *disk, sector_t size) /* @ 內核會通過 get_disk 和 put_disk 這兩個函數來調整 gendisk 的引用計數 @ get_disk是增加 gendisk的引用計數 @ put_disk是減少gendisk的引用計數 */ truct kobject *get_disk(struct gendisk *disk) void put_disk(struct gendisk *disk) /* @ 刪除gendisk的話可以使用函數del_gendisk @ gp:要刪除的gendisk。 @ 返回值:無。 */ void del_gendisk(struct gendisk *gp) /* @ 定義在include/linux/blkdev.h中 @ block_device_operations 結構體 @ 和字符設備的file _operations一樣,塊設備也有操作集 */struct block_device_operations { int (*open) (struct block_device *, fmode_t); void (*release) (struct gendisk *, fmode_t); int (*rw_page)(struct block_device *, sector_t, struct page *, int rw); /*讀寫指定頁*/int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); /*32-32塊設備的IO控制*/int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); /*64-32塊設備IO控制*/long (*direct_access)(struct block_device *, sector_t, void **, unsigned long *pfn, long size); unsigned int (*check_events) (struct gendisk *disk, unsigned int clearing); /* ->media_changed() is DEPRECATED, use ->check_events() instead */int (*media_changed) (struct gendisk *); void (*unlock_native_capacity) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); /*磁頭、柱面、扇區*//* this callback is with swap_lock and sometimes page table lock held */ void (*swap_slot_free_notify) (struct block_device *, unsigned long); struct module *owner; /*表示此結構屬于哪個模塊*/};
  • 我們上面的介紹的fops操作集里面不像字符設備一樣里面有read和write函數,但是我們呢,是肯定要進行讀寫操作的。

  • 要想了解整個流程,首先就要引入塊設備驅動中非常重要的三個角色。
    request_queue、
    request
    bio

  • 內核將對塊設備的讀寫都發送到請求隊列 request_queue 中,request_queue 中是大量的request(請求結構體),而 request 又包含了 bio,bio 保存了讀寫相關數據。比如從塊設備的哪個地址開始讀取、讀取的數據長度,讀取到哪里,如果是寫的話還包括要寫入的數據等。

  • 下面我們繼續了解一下其中的結構體和一些函數目的就是可以了解requestqueue和quest。

/* @ 上面談到過block_device 里面含有requet_queue. @ 因為在編寫塊設備驅動的時候,每個磁盤(gendisk)都要分配一個 request_queue */ /*============================================================機械磁頭硬盤 @ 這個一般用于像機械硬盤這樣的存儲設備,需要 I/O 調度器來優化數據讀寫過程 @ 申請并初始化一個 request_queue @ rfn:請求處理函數指針,每個 request_queue 都要有一個請求處理函數, @ lock:自旋鎖指針,需要驅動編寫人員定義一個自旋鎖,然后傳遞進來。請求隊列會使用這個自旋鎖。 @ 返回值:如果為NULL的話表示失敗,成功的話就返回申請到的request_queue 地址。 */ request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) /* @ 請求處理函數需要驅動編寫人員自行實現 @ 與上面依賴 @ q:請求隊列 */ void (request_fn_proc) (struct request_queue *q) /*我們只傳入了request+_queue*/ /*===========================================================EMMC、SD卡非機械設備 @ 但是對于 EMMC、SD 卡這樣的非機械設備,可以進行完全隨機訪問,所以就不需要復雜的 I/O 調度器了(分配請求隊列+綁定制造請求函數) @ gfp_mask: 內存分配掩碼 @ 返回值:申請到的無 I/O調度的request_queue。 */ struct request_queue *blk_alloc_queue(gfp_t gfp_mask) /* @ 上面blk_alloc_queue函數申請到的請求隊列綁定一個“制造請求”函數 @ q:需要綁定的請求隊列,也就是 blk_alloc_queue申請到的請求隊列。 @ mfn:需要綁定的“制造”請求函數 @ 返回值:無 */ void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn) /* @ 與上面依賴 @ “制造請求”函數需要驅動編寫人員實現 @ 返回值:無。 */ void (make_request_fn) (struct request_queue *q, struct bio *bio) /* @ 當卸載塊設備驅動的時候我們還需要刪除掉前面申請到的 request_queue @ q:需要刪除的請求隊列 @ 返回值:無。 */ void blk_cleanup_queue(struct request_queue *q) /* @ 請求隊列(request_queue)里面包含的就是一系列的請求(request) */ /* @ 從request_queue中依次獲取每個request @ q:指定request_queue。 @ 返回值:request_queue 中下一個要處理的請求(request),如果沒有要處理的請求就返回NULL @ */ request *blk_peek_request(struct request_queue *q) /* @ 獲取到下一個要處理的請求以后就要開始處理這個請求 @ req:要開始處理的請求 @ 返回值:無。 */ void blk_start_request(struct request *req) /* @ 此函數功能等是上面的兩個函數的合體==一步到位處理請求 @ q:指定request_queue。 */ struct request *blk_fetch_request(struct request_queue *q) { struct request *rq; rq = blk_peek_request(q); if (rq) blk_start_request(rq); return rq; }
  • 每個request里面里面會有多個bio,bio保存著最終要讀寫的數據、地址等信息上層應用程序對于塊設備的讀寫會被構造成一個或多個bio結構,bio結構描述了要讀寫的起始扇區、要讀寫的扇區數量、是讀取還是寫入、頁偏移、數據長度等等信息。上層會將 bio提交給I/O調度器, I/O 調度器會將這些bio構造成request結構,而一個物理存儲設備對應一個 request_queue,request_queue里面順序存放著一系列的 request。新產生的 bio可能被合并到 request_queue里現有的 request 中,也可能產生新的 request,然后插入到 request_queue 中合適的位置,這一切都是由I/O 調度器來完成的

  • 下面我們繼續了解一下其中的結構體和一些函數目的就是可以了解quest中的bio。

/* @ 定義在 include/linux/blk_types.h中 @ bio 結構體 */struct bio { struct bio *bi_next; /* 請求隊列的下一個 bio */ struct block_device *bi_bdev; /* 指向塊設備 */ unsigned long bi_flags; /* bio 狀態等信息 */ unsigned long bi_rw; /* I/O 操作,讀或寫 */ struct bvec_iter bi_iter; /* I/O 操作,讀或寫===== */ unsigned int bi_phys_segments; unsigned int bi_seg_front_size; unsigned int bi_seg_back_size; atomic_t bi_remaining; bio_end_io_t *bi_end_io; void *bi_private; #ifdef CONFIG_BLK_CGROUP /* * Optional ioc and css associated with this bio. Put on bio * release. Read comment on top of bio_associate_current(). */ struct io_context *bi_ioc; struct cgroup_subsys_state *bi_css; #endif union { #if defined(CONFIG_BLK_DEV_INTEGRITY) struct bio_integrity_payload *bi_integrity; #endif }; unsigned short bi_vcnt; /* bio_vec 列表中元素數量 */ unsigned short bi_max_vecs; /* bio_vec 列表長度 */ atomic_t bi_cnt; /* pin count */ struct bio_vec *bi_io_vec; /* bio_vec 列表 */ struct bio_set *bi_pool; struct bio_vec bi_inline_vecs[0]; }; /* @ 屬于bio中 @ 定義在 include/linux/blk_types.h中 @ bio 結構體 */struct bvec_iter { sector_t bi_sector; /* I/O 請求的設備起始扇區(512 字節) */ unsigned int bi_size; /* 剩余的 I/O 數量 */ unsigned int bi_idx; /* blv_vec 中當前索引 */ unsigned int bi_bvec_done; /* 當前 bvec 中已經處理完成的字節數 */ }; /* @ 屬于bio中 @ 定義在 include/linux/blk_types.h中 @ bio 結構體 */struct bio_vec { struct page *bv_page; /* 頁 */ unsigned int bv_len; /* 長度 */ unsigned int bv_offset; /* 偏移 */ };
  • 上面的bio和bio_vec和bvec_iter的關系
  • 我們對于物理存儲設備的操作不外乎就是將 RAM 中的數據寫入到物理存儲設備中,或者、將物理設備中的數據讀取到 RAM 中去處理。數據傳輸三個要求:數據源、數據長度以及數據目的地,也就是你要從物理存儲設備的哪個地址開始讀取、讀取到 RAM 中的哪個地址處、讀取的數據長度是多少。
    bi_iter這個結構體成員變量就用于描述物理存儲設備地址信息,比如要操作的扇區地址
    bi_io_vec指向 bio_vec數組首地址,bio_vec數組就是RAM信息,比如頁地址、頁偏移以及長度

三、簡單實例

  • 接下來我們使用開發板上的 RAM 模擬一段塊設備,也就是ramdisk,然后編寫塊設備驅動
  • 我們上面也提到過我們在上面請求隊列的時候分為機械硬盤比如SD卡非機械硬盤這兩種不同的
  • 一般 blk_alloc_queue 和blk_queue_make_request 是搭配在一起使用的,用于那么非機械的存儲設備、無需 I/O 調度器,比如 EMMC、SD 卡等。blk_init_queue 函數會給請求隊列分配一個 I/O調度器,用于機械存儲設備,比如機械硬盤等。

1、傳統的使用請求隊列的時候,也就是針對機械硬盤的時候如何編寫驅動

#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <linux/genhd.h> #include <linux/blkdev.h> #include <linux/hdreg.h>#include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h>#define RAMDISK_SIZE (2 * 1024 * 1024) /* 容量大小為2MB */ #define RAMDISK_NAME "ramdisk" /* 名字 */ #define RADMISK_MINOR 3 /* 表示有三個磁盤分區!不是次設備號為3! *//* ramdisk設備結構體 */ struct ramdisk_dev{int major; /* 主設備號 */unsigned char *ramdiskbuf; /* ramdisk內存空間,用于模擬塊設備 */spinlock_t lock; /* 自旋鎖 */struct gendisk *gendisk; /* gendisk */struct request_queue *queue;/* 請求隊列 */};struct ramdisk_dev ramdisk; /* ramdisk設備 *//** @description : 打開塊設備* @param - dev : 塊設備* @param - mode : 打開模式* @return : 0 成功;其他 失敗*/ int ramdisk_open(struct block_device *dev, fmode_t mode) {printk("ramdisk open\r\n");return 0; }/** @description : 釋放塊設備* @param - disk : gendisk* @param - mode : 模式* @return : 0 成功;其他 失敗*/ void ramdisk_release(struct gendisk *disk, fmode_t mode) {printk("ramdisk release\r\n"); }/** @description : 獲取磁盤信息* @param - dev : 塊設備* @param - geo : 模式* @return : 0 成功;其他 失敗*/ int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo) {/* 這是相對于機械硬盤的概念 */geo->heads = 2; /* 磁頭 */geo->cylinders = 32; /* 柱面 */geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一個磁道上的扇區數量 */return 0; }/* * 塊設備操作函數 */ static struct block_device_operations ramdisk_fops = {.owner = THIS_MODULE,.open = ramdisk_open,.release = ramdisk_release,.getgeo = ramdisk_getgeo, };/** @description : 處理傳輸過程* @param-req : 請求* @return : 無*/ static void ramdisk_transfer(struct request *req) { unsigned long start = blk_rq_pos(req) << 9; /* blk_rq_pos獲取到的是扇區地址,左移9位轉換為字節地址 */unsigned long len = blk_rq_cur_bytes(req); /* 大小 *//* bio中的數據緩沖區* 讀:從磁盤讀取到的數據存放到buffer中* 寫:buffer保存這要寫入磁盤的數據*/void *buffer = bio_data(req->bio); if(rq_data_dir(req) == READ) /* 讀數據 */ memcpy(buffer, ramdisk.ramdiskbuf + start, len);else if(rq_data_dir(req) == WRITE) /* 寫數據 */memcpy(ramdisk.ramdiskbuf + start, buffer, len);}/** @description : 請求處理函數* @param-q : 請求隊列* @return : 無*/ void ramdisk_request_fn(struct request_queue *q) {int err = 0;struct request *req;/* 循環處理請求隊列中的每個請求 */req = blk_fetch_request(q);while(req != NULL) {/* 針對請求做具體的傳輸處理 */ramdisk_transfer(req);/* 判斷是否為最后一個請求,如果不是的話就獲取下一個請求* 循環處理完請求隊列中的所有請求。*/if (!__blk_end_request_cur(req, err))req = blk_fetch_request(q);} }/** @description : 驅動出口函數* @param : 無* @return : 無*/ static int __init ramdisk_init(void) {int ret = 0;printk("ramdisk init\r\n");/* 1、申請用于ramdisk內存 */ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);if(ramdisk.ramdiskbuf == NULL) {ret = -EINVAL;goto ram_fail;}/* 2、初始化自旋鎖 */spin_lock_init(&ramdisk.lock);/* 3、注冊塊設備 */ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系統自動分配主設備號 */if(ramdisk.major < 0) {goto register_blkdev_fail;} printk("ramdisk major = %d\r\n", ramdisk.major);/* 4、分配并初始化gendisk */ramdisk.gendisk = alloc_disk(RADMISK_MINOR);if(!ramdisk.gendisk) {ret = -EINVAL;goto gendisk_alloc_fail;}/* 5、分配并初始化請求隊列 */ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);if(!ramdisk.queue) {ret = EINVAL;goto blk_init_fail;}/* 6、添加(注冊)disk */ramdisk.gendisk->major = ramdisk.major; /* 主設備號 */ramdisk.gendisk->first_minor = 0; /* 第一個次設備號(起始次設備號) */ramdisk.gendisk->fops = &ramdisk_fops; /* 操作函數 */ramdisk.gendisk->private_data = &ramdisk; /* 私有數據 */ramdisk.gendisk->queue = ramdisk.queue; /* 請求隊列 */sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); /* 設備容量(單位為扇區) */add_disk(ramdisk.gendisk);return 0;blk_init_fail:put_disk(ramdisk.gendisk);//del_gendisk(ramdisk.gendisk); gendisk_alloc_fail:unregister_blkdev(ramdisk.major, RAMDISK_NAME); register_blkdev_fail:kfree(ramdisk.ramdiskbuf); /* 釋放內存 */ ram_fail:return ret; }/** @description : 驅動出口函數* @param : 無* @return : 無*/ static void __exit ramdisk_exit(void) {printk("ramdisk exit\r\n");/* 釋放gendisk */del_gendisk(ramdisk.gendisk);put_disk(ramdisk.gendisk);/* 清除請求隊列 */blk_cleanup_queue(ramdisk.queue);/* 注銷塊設備 */unregister_blkdev(ramdisk.major, RAMDISK_NAME);/* 釋放內存 */kfree(ramdisk.ramdiskbuf); }module_init(ramdisk_init); module_exit(ramdisk_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
  • 當我們把上面細心閱讀后,會發現塊設備驅動框架還是有些字符設備的影子的,只是在具體實現上要復雜一下。

2、對于EMMC、SD、ramdisk這樣沒有機械結構的存儲設備,我們可以直接訪問任意一個扇區,因此可以不需要 I/O 調度器,也就不需要請求隊列了

  • 這里主要貼出區別的地方
/* @ 驅動函數入口 */static int __init ramdisk_init(void) { .... /* 5、分配請求隊列 */ ramdisk.queue = blk_alloc_queue(GFP_KERNEL); if(!ramdisk.queue){ ret = -EINVAL; goto blk_allo_fail; } /* 6、設置“制造請求”函數 */ blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);/* 7、添加(注冊)disk */ ramdisk.gendisk->major = ramdisk.major; /* 主設備號 */ ramdisk.gendisk->first_minor = 0; /* 起始次設備號 */ ramdisk.gendisk->fops = &ramdisk_fops; /* 操作函數 */ ramdisk.gendisk->private_data = &ramdisk; /* 私有數據 */ ramdisk.gendisk->queue = ramdisk.queue; /* 請求隊列 */ sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */ set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); /* 設備容量*/ add_disk(ramdisk.gendisk); .... return ret; } /* * @description : “制造請求”函數 * @param-q : 請求隊列 * @return : 無 */ void ramdisk_make_request_fn(struct request_queue *q, struct bio *bio) { int offset; struct bio_vec bvec; struct bvec_iter iter; unsigned long len = 0; offset = (bio->bi_iter.bi_sector) << 9; /* 獲取設備的偏移地址 */ /* 處理 bio 中的每個段 */ bio_for_each_segment(bvec, bio, iter){ char *ptr = page_address(bvec.bv_page) + bvec.bv_offset; len = bvec.bv_len; if(bio_data_dir(bio) == READ) /* 讀數據 */ memcpy(ptr, ramdisk.ramdiskbuf + offset, len); else if(bio_data_dir(bio) == WRITE) /* 寫數據 */ memcpy(ramdisk.ramdiskbuf + offset, ptr, len); offset += len; } set_bit(BIO_UPTODATE, &bio->bi_flags); bio_endio(bio, 0); }
  • (分配請求隊列+綁定制造請求函數) 這部分代替了原有的初始化請求隊列

整體例子代碼

#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <linux/genhd.h> #include <linux/blkdev.h> #include <linux/hdreg.h>#include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h>#define RAMDISK_SIZE (2 * 1024 * 1024) /* 容量大小為2MB */ #define RAMDISK_NAME "ramdisk" /* 名字 */ #define RADMISK_MINOR 3 /* 表示有三個磁盤分區!不是次設備號為3! *//* ramdisk設備結構體 */ struct ramdisk_dev{int major; /* 主設備號 */unsigned char *ramdiskbuf; /* ramdisk內存空間,用于模擬塊設備 */spinlock_t lock; /* 自旋鎖 */struct gendisk *gendisk; /* gendisk */struct request_queue *queue;/* 請求隊列 */};struct ramdisk_dev ramdisk; /* ramdisk設備 *//** @description : 打開塊設備* @param - dev : 塊設備* @param - mode : 打開模式* @return : 0 成功;其他 失敗*/ int ramdisk_open(struct block_device *dev, fmode_t mode) {printk("ramdisk open\r\n");return 0; }/** @description : 釋放塊設備* @param - disk : gendisk* @param - mode : 模式* @return : 0 成功;其他 失敗*/ void ramdisk_release(struct gendisk *disk, fmode_t mode) {printk("ramdisk release\r\n"); }/** @description : 獲取磁盤信息* @param - dev : 塊設備* @param - geo : 模式* @return : 0 成功;其他 失敗*/ int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo) {/* 這是相對于機械硬盤的概念 */geo->heads = 2; /* 磁頭 */geo->cylinders = 32; /* 柱面 */geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一個磁道上的扇區數量 */return 0; }/* * 塊設備操作函數 */ static struct block_device_operations ramdisk_fops = {.owner = THIS_MODULE,.open = ramdisk_open,.release = ramdisk_release,.getgeo = ramdisk_getgeo, };/** @description : “制造請求”函數* @param-q : 請求隊列* @return : 無*/ void ramdisk_make_request_fn(struct request_queue *q, struct bio *bio) {int offset;struct bio_vec bvec;struct bvec_iter iter;unsigned long len = 0;offset = (bio->bi_iter.bi_sector) << 9; /* 獲取要操作的設備的偏移地址 *//* 處理bio中的每個段 */bio_for_each_segment(bvec, bio, iter){char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;len = bvec.bv_len;if(bio_data_dir(bio) == READ) /* 讀數據 */memcpy(ptr, ramdisk.ramdiskbuf + offset, len);else if(bio_data_dir(bio) == WRITE) /* 寫數據 */memcpy(ramdisk.ramdiskbuf + offset, ptr, len);offset += len;}set_bit(BIO_UPTODATE, &bio->bi_flags);bio_endio(bio, 0); }/** @description : 驅動出口函數* @param : 無* @return : 無*/ static int __init ramdisk_init(void) {int ret = 0;printk("ramdisk init\r\n");/* 1、申請用于ramdisk內存 */ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);if(ramdisk.ramdiskbuf == NULL) {ret = -EINVAL;goto ram_fail;}/* 2、初始化自旋鎖 */spin_lock_init(&ramdisk.lock);/* 3、注冊塊設備 */ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系統自動分配主設備號 */if(ramdisk.major < 0) {goto register_blkdev_fail;} printk("ramdisk major = %d\r\n", ramdisk.major);/* 4、分配并初始化gendisk */ramdisk.gendisk = alloc_disk(RADMISK_MINOR);if(!ramdisk.gendisk) {ret = -EINVAL;goto gendisk_alloc_fail;}/* 5、分配請求隊列 */ramdisk.queue = blk_alloc_queue(GFP_KERNEL);if(!ramdisk.queue){ret = -EINVAL;goto blk_allo_fail;}/* 6、設置“制造請求”函數 */blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);/* 7、添加(注冊)disk */ramdisk.gendisk->major = ramdisk.major; /* 主設備號 */ramdisk.gendisk->first_minor = 0; /* 第一個次設備號(起始次設備號) */ramdisk.gendisk->fops = &ramdisk_fops; /* 操作函數 */ramdisk.gendisk->private_data = &ramdisk; /* 私有數據 */ramdisk.gendisk->queue = ramdisk.queue; /* 請求隊列 */sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); /* 設備容量(單位為扇區) */add_disk(ramdisk.gendisk);return 0;blk_allo_fail:put_disk(ramdisk.gendisk);//del_gendisk(ramdisk.gendisk); gendisk_alloc_fail:unregister_blkdev(ramdisk.major, RAMDISK_NAME); register_blkdev_fail:kfree(ramdisk.ramdiskbuf); /* 釋放內存 */ ram_fail:return ret; }/** @description : 驅動出口函數* @param : 無* @return : 無*/ static void __exit ramdisk_exit(void) {printk("ramdisk exit\r\n");/* 釋放gendisk */del_gendisk(ramdisk.gendisk);put_disk(ramdisk.gendisk);/* 清除請求隊列 */blk_cleanup_queue(ramdisk.queue);/* 注銷塊設備 */unregister_blkdev(ramdisk.major, RAMDISK_NAME);/* 釋放內存 */kfree(ramdisk.ramdiskbuf); }module_init(ramdisk_init); module_exit(ramdisk_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");

總結

以上是生活随笔為你收集整理的linux块设备驱动简述(Linux驱动开发篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。