linux块设备驱动简述(Linux驱动开发篇)
一、塊設備
- 塊設備驅動是Linux 三大驅動類型之一。
- 塊設備驅動要遠比字符設備驅動復雜得多,不同類型的存儲設備又對應不同的驅動子系統。
- 塊設備是針對存儲設備的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、機械硬盤、固態硬盤等。
塊設備和字符設備的區別?
- 塊設備以塊(是VFS基本數據傳輸單位)為單位進行讀寫,字符設備以字節的形式進行讀寫,
- 塊設備在結構上是可以進行隨機訪問的,對于這些設備的讀寫都是按塊進行的塊設備使用緩沖區來暫時存放數據,等到條件成熟以后在一次性將緩沖區中的數據寫入塊設備中。(這樣就減少了對塊設備的擦除次數,提高了塊設備壽命。)
- 字符設備是順序的數據流設備,字符設備是按照字節進行讀寫訪問的。字符設備不需要緩沖區,對于字符設備的訪問都是實時的,而且也不需要按照固定的塊大小進行訪問
- 總結一下:
字符設備:實時、按照字節訪問,無緩沖。
塊設備:帶有緩沖區,按照塊大小進行訪問。(一種是隨機訪問。一種是按順序訪問)
塊設備的類別
- 針對不同的存儲設備實現了不同的I/O調度算法。
- 塊設備結構的不同其 I/O算法也會不同,比如對于EMMC、SD卡、NAND Flash這類沒有任何機械設備的存儲設備就可以任意讀寫任何的扇區(塊設備物理存儲單元)。
- 但是對于機械硬盤這樣帶有磁頭的設備,讀取不同的盤面或者磁道里面的數據,磁頭都需要進行移動,因此對于機械硬盤而言,將那些雜亂的訪問按照一定的順序進行排列可以有效提高磁盤性能。
- ===這個后面的框架你會發現是有所不同的。
二、塊設備驅動框架簡述
- 在了解具體的塊設備驅動框架的大概,我們現需要了解一下其中的結構體和一些函數
-
我們上面的介紹的fops操作集里面不像字符設備一樣里面有read和write函數,但是我們呢,是肯定要進行讀寫操作的。
-
要想了解整個流程,首先就要引入塊設備驅動中非常重要的三個角色。
request_queue、
request
bio
-
內核將對塊設備的讀寫都發送到請求隊列 request_queue 中,request_queue 中是大量的request(請求結構體),而 request 又包含了 bio,bio 保存了讀寫相關數據。比如從塊設備的哪個地址開始讀取、讀取的數據長度,讀取到哪里,如果是寫的話還包括要寫入的數據等。
-
下面我們繼續了解一下其中的結構體和一些函數目的就是可以了解requestqueue和quest。
-
每個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。
- 上面的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 調度器,也就不需要請求隊列了
- 這里主要貼出區別的地方
- (分配請求隊列+綁定制造請求函數) 這部分代替了原有的初始化請求隊列
整體例子代碼
#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驱动开发篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何搭建一个好的测试环境
- 下一篇: Linux搭建测试环境详细步骤