块设备驱动、bio理解

article/2025/11/8 9:03:51

别人写过的内容,我就不写了。贴一下大佬的博客,写的非常好:

  1. 块设备驱动实战基础篇一 (170行代码构建一个逻辑块设备驱动)

  2. 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行)

  3. 块设备驱动实战基础篇三 (BIO请求回调机制)

  4. 块设备驱动实战基础篇四 (逐渐成型,加入ioctl通信机制)

较遗憾的是,该博主的 块设备驱动实战高级篇 自2013年后就未更新了,可能有更重要的事忙。

复制进去的demo代码,直接编译会报错,做了轻微改动。我的实验环境如下:

系统:ubuntu 16.04

内核:4.15.0-122-generic

架构:x86-64

1. 编译生成demo

创建头文件:

vim fbd_device.h

fbd_device.h 的内容如下:

#ifndef  _FBD_DRIVER_H
#define  _FBD_DRIVER_H#include <linux/init.h>
#include <linux/module.h>		/* 写内核模块都需要包含该头文件 */
#include <linux/blkdev.h>		/* 写内核块设备驱动必须要包含的三个头文件:blkdev.h, bio.h, genhd.h */
#include <linux/bio.h>
#include <linux/genhd.h>#define SECTOR_BITS             (9)		/* 用来表示扇区的比特数,对于块设备,扇区是其最小的传输和存储单元,默认扇区大小是512字节,这里的9代表将512换算为二进制需要多少位描述,很快可以算出来:2^9 = 512 */
#define DEV_NAME_LEN            32		/* 过滤块设备的名字最长为32个字节 */
#define DEV_SIZE                (512UL<< 20)   /* 过滤块设备大小是512M,1左移20位是1M,再乘以扇区大小即为512M */#define DRIVER_NAME            "filter driver"	/* 给驱动程序注册的名字"fbd_driver" */#define DEVICE1_NAME           "fbd1_dev"		/* 过滤块设备驱动程序创建的过滤块设备名字"fbd1_dev" */
#define DEVICE1_MINOR           0
#define DEVICE2_NAME           "fbd2_dev"		/* 过滤块设备驱动程序创建的过滤块设备名字"fbd2_dev" */
#define DEVICE2_MINOR           1struct fbd_dev {		/* 结构体fbd_dev,三个成员:queue指针成员,disk指针,设备大小,该结构体描述我们创建的过滤块设备 */struct request_queue *queue;struct gendisk *disk;sector_t size;          /* device size in Bytes */
};#endif

创建主要代码文件:

vim fbd_device.c

fbd_device.c 的内容如下:

/***  fbd-driver - filter block device driver*  Author: Talk@studio*  Modified by abin**/#include "fbd_driver.h"static int fbd_driver_major = 0;static struct fbd_dev fbd_dev1 = {NULL};
static struct fbd_dev fbd_dev2 = {NULL};static int fbddev_open(struct inode *inode, struct file *file);
static int fbddev_close(struct inode *inode, struct file *file);static struct block_device_operations disk_fops = {.open = (void *)fbddev_open,.release = (void *)fbddev_close,.owner = THIS_MODULE,
};/* 块设备被打开时调用该函数 */
static int fbddev_open(struct inode *inode, struct file *file)
{printk("device is opened by:[%s]\n", current->comm);return 0;
}/* 块设备被关闭时调用该函数 */
static int fbddev_close(struct inode *inode, struct file *file)
{printk("device is closed by:[%s]\n", current->comm);return 0;
}/* 仓库的加工函数,在dev_create中被调用 */
static int make_request(struct request_queue *q, struct bio *bio)		//参数1是我们的关卡请求队列,参数2是上层准备好的盒子bio请求描述结构体指针
{struct fbd_dev *dev = (struct fbd_dev *)q->queuedata;printk("device [%s] recevied [%s] io request, ""access on dev sector[%llu], length is [%u] sectors.\n",dev->disk->disk_name,bio_data_dir(bio) == READ ?"read" : "write",(long long)bio->bi_iter.bi_sector,bio_sectors(bio));bio_endio(bio);		//结束一个bio请求return 0;
}/* 创建过滤设备的函数,在init函数中被调用 */
static int dev_create(struct fbd_dev *dev, char *dev_name, int major, int minor)
{int ret = 0;/* init fbd_dev */dev->size = DEV_SIZE;dev->disk = alloc_disk(1);		/* 申请仓库gendisk,返回值为gendisk结构体 */if (!dev->disk) {printk("alloc diskerror");ret = -ENOMEM;goto err_out1;}dev->queue = blk_alloc_queue(GFP_KERNEL);		/* 建立关卡,关卡申请后,可以用也可以不用,但必须申请 */if (!dev->queue) {printk("alloc queueerror");ret = -ENOMEM;goto err_out2;}/* init queue */blk_queue_make_request(dev->queue, (void *)make_request);		/* 仓库加工函数,即:请求处理函数make_request,第一参数是刚申请到的请求队列,第二个参数是我们写好的make_request函数名 */dev->queue->queuedata = dev;/* init gendisk */strncpy(dev->disk->disk_name, dev_name, DEV_NAME_LEN);	/* 给gendisk的disk_name成员赋值,也就是给仓库取名字 */dev->disk->major = major;		/* 把申请到的门牌号赋值给disk的成员major */dev->disk->first_minor = minor;	/* 赋值了一个次设备号 */dev->disk->fops = &disk_fops;		/* 为gendisk的文件操作函数赋值了一个函数指针集结构体 */set_capacity(dev->disk, (dev->size >> SECTOR_BITS));	/* 设置设备的容量大小为512M *//* bind queue to disk */dev->disk->queue =dev->queue;		/* 把申请的queue地址保存在disk中,这样仓库和关卡就绑定在一起了 *//* add disk to kernel */add_disk(dev->disk);	/*告诉内核我们的仓库需要审核一下,如果通过,那仓库就建好了 */return 0;err_out2:put_disk(dev->disk);
err_out1:return ret;
}static void dev_delete(struct fbd_dev *dev, char *name)
{printk("delete the device [%s]!\n", name);blk_cleanup_queue(dev->queue);del_gendisk(dev->disk);put_disk(dev->disk);
}/* 内核模块入口,也是构建块设备驱动的核心部分 */
static int __init fbd_driver_init(void)
{int ret;/* register fbd driver, get the driver major number */fbd_driver_major =register_blkdev(fbd_driver_major, DRIVER_NAME);		/* 第一个参数是初始化的major号,第二参数是块设备驱动的名字。第一参数0时,系统会从它自己管理的情况表上查找是否有可用的号码,如果有就分配,作为regiser_blkdev的返回值 */if (fbd_driver_major < 0) {printk("get majorfail");ret = -EIO;goto err_out1;}/* create the first device */ret = dev_create(&fbd_dev1, DEVICE1_NAME, fbd_driver_major,DEVICE1_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE1_NAME);goto err_out2;}/* create the second device */ret = dev_create(&fbd_dev2, DEVICE2_NAME, fbd_driver_major,DEVICE2_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE2_NAME);goto err_out3;}return ret;err_out3:dev_delete(&fbd_dev1, DEVICE1_NAME);
err_out2:unregister_blkdev(fbd_driver_major, DRIVER_NAME);
err_out1:return ret;
}static void __exit fbd_driver_exit(void)
{/* delete the two devices */dev_delete(&fbd_dev2, DEVICE2_NAME);dev_delete(&fbd_dev1, DEVICE1_NAME);/* unregister fbd driver */unregister_blkdev(fbd_driver_major,DRIVER_NAME);printk("block device driver exit successfuly!\n");
}module_init(fbd_driver_init);
module_exit(fbd_driver_exit);
MODULE_LICENSE("GPL");

创建Makefile文件:

vim Makefile

Makefile 文件的内容:

obj-m := fbd_driver.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)default:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) cleanrm -rf Module.markers modules.order Module.symvers

编译块设备,生成内核模块:

make

make -C /lib/modules/4.15.0-122-generic/build M=/home/abin/Desktop/share/abin_files/bio modules

make[1]: Entering directory ‘/usr/src/linux-headers-4.15.0-122-generic’

CC [M] /home/abin/Desktop/share/abin_files/bio/fbd_driver.o

Building modules, stage 2.

MODPOST 1 modules

CC /home/abin/Desktop/share/abin_files/bio/fbd_driver.mod.o

LD [M] /home/abin/Desktop/share/abin_files/bio/fbd_driver.ko

make[1]: Leaving directory ‘/usr/src/linux-headers-4.15.0-122-generic’

ls -l

-rw-rw-r-- 1 abin abin 4255 Nov 12 20:22 fbd_driver.c

-rw-rw-r-- 1 abin abin 667 Nov 12 16:59 fbd_driver.h

-rw-rw-r-- 1 abin abin 8144 Nov 12 20:30 fbd_driver.ko

-rw-rw-r-- 1 abin abin 603 Nov 12 20:30 fbd_driver.mod.c

-rw-rw-r-- 1 abin abin 2584 Nov 12 20:30 fbd_driver.mod.o

-rw-rw-r-- 1 abin abin 7856 Nov 12 20:30 fbd_driver.o

-rw-rw-r-- 1 abin abin 229 Nov 12 20:09 Makefile

-rw-rw-r-- 1 abin abin 61 Nov 12 20:30 modules.order

-rw-rw-r-- 1 abin abin 0 Nov 12 20:30 Module.symvers

其中,fbd_driver.ko 是编译好的内核模块,也就是块设备。

2. 运行demo

加载内核模块:

sudo insmod fbd_driver.ko
dmesg

[337420.127568] device is opened by:[systemd-udevd]

[337420.127695] device is opened by:[systemd-udevd]

[337420.166190] device is closed by:[systemd-udevd]

[337420.166208] device is closed by:[systemd-udevd]

ls -l /dev/fbd*

brw-rw---- 1 root disk 252, 0 Nov 13 09:08 /dev/fbd1_dev

brw-rw---- 1 root disk 252, 1 Nov 13 09:08 /dev/fbd2_dev

/dev/fbd1_dev/dev/fbd2_dev 是创建好的过滤块设备,下面使用dd命令来使用其中一个设备。

sudo dd if=/dev/zero of=/dev/fbd1_dev bs=1M oflag=direct count=1
dmesg

[337577.539057] device is opened by:[dd]

[337577.539329] device [fbd1_dev] recevied [write] io request, access on dev sector[0], length is [2048] sectors.

[337577.539335] device is closed by:[dd]

第二行是在make_request函数中输出的,第一行是fbddev_open函数中输出的,第三行是fbddev_close函数中输出的。

3. 关键信息

块设备驱动程序做的四件事情:

序号函数功能
1register_blk_device注册并申请门牌号
2alloc_disk申请仓库
3alloc_queue申请仓库的关卡
4blk_queue_make_request注册仓库的加工处理函数

块设备核心数据结构:

结构体名称结构体作用
gendisk块设备仓库
hd_struct块设备分区
block_device文件系统层使用的块设备描述符
request_queue仓库的关卡(请求队列)
request包含多个bio的大请求
bio单个请求

块设备核心API接口:

API名称API作用
register_blkdev注册并申请门牌号
alloc_disk申请仓库
blk_alloc_queue申请仓库的关卡
blk_queue_make_request注册仓库的加工处理函数
add_disk将申请的仓库注册到内核中,成为合法仓库

bio关键成员:

类型字段说明
dev_tbd_dev块设备的主设备号和次设备号
struct inode*bd_inode指向bdev文件系统中块设备对应的文件索引节点的指针
intbd_openers计数器,统计块设备已经被打开了多少次
struct mutexbd_mutex打开或关闭的互斥量
struct list_headbd_inodes已打开的块设备文件的索引节点链表的首部
void*bd_holders块设备描述符的当前所有者
struct block_device*bd_contains如果块设备是一个分区,则指向整个磁盘的块设备描述符;否则,指向该块设备描述符
unsignedbd_block_size块大小
struct hd_struct*bd_part指向分区描述符的指针(如果该块设备不是一个分区,则为NULL)
unsignedbd_part_count计数器,统计包含在块设备中的分区已经被打开了多少次
struct gendisk*bd_disk指向块设备中基本磁盘的gendisk结构的指针
struct list_headbd_list用于块设备描述符链表的指针
unsigned longbd_private指向块设备持有者的私有数据的指针

hd_struct关键成员:

类型字段说明
sector_tstart_sect磁盘中分区的起始扇区
sector_tnr_sects分区的长度(总共的扇区数)
intpolicy如果分区是只读的,则置为1;否则为0
intpartno磁盘中分区的相对索引

gendisk关键成员:

类型字段说明
intmajor磁盘主设备号, 每个块设备都有唯一的主设备号,在这个块设备上建立的分区都使用这个相同的主设备号。具有相同主设备号的设备,使用相同的驱动程序。
intfirst_minor与磁盘关联的第一个次设备号。在某一个设备上首先创建的设备的初始次设备号为0,在名称中不显示,如sda;在这个设备上依次建立的其他设备,此设备号在0基础上依次加1,并在名称中显示,如sda1,sda2。
intminors与磁盘关联的次设备号范围。规定了可以在这个设备上创建多少个分设备(分区)。当次设备号数量是1时,表示这个设备不能被分区。
chardisk_name磁盘的标准命名(通常是相应设备文件的规范名称)
struct hd_structpart0磁盘的分区信息
const struct block_device_operations *fops指向块设备操作函数集的指针
struct request_queue *queue指向磁盘请求队列的指针
void *private_data块设备驱动程序的私有数据
intflags描述磁盘类型的标志

块设备gendisk fops函数指针集:

类型方法参数触发操作
int(*open)struct block_device*, fmode_t打开块设备文件,增加引用计数
int(*release)struct gendisk*, fmode_t关闭对块设备文件的最后一个引用,减少引用计数
int(*ioctl)struct block_device*, fmode_t, unsigned,unsigned long在块设备文件上发出ioctl()系统调用

request_queue请求队列描述符中的关键字段:

类型字段说明
struct list_headqueue_head待处理请求的链表
make_request_fn*make_request_fn设备驱动程序的请求处理函数

request描述符的关键字段:

类型字段说明
struct list_headqueuelist请求队列链表的指针
struct bio*bio请求中第一个没有完成传送操作的bio,不能直接对该成员进行访问;而要使用rq_for_each_bio访问
struct bio*biotail请求链表中末尾的bio

bio结构中的关键字段:

类型字段说明
sector_tbi_sector块I/O操作的第一个磁盘扇区
struct bio*bi_next链接到请求队列中的下一个bio
struct block_device *bi_bdev指向块设备描述符的指针
unsigned longbi_flagsbio的状态标志
unsigned longbi_rwI/O操作标志
unsigned shortbi_vcntbio的bio_vec数组中段的数目
unsigned shortbi_idxbio的bio_vec数组中段的当前索引值
unsigned intbi_phys_segments合并之后bio中物理段的数目
unsigned intbi_size需要传送的字节数
unsigned intbi_seg_front_size第一个可合并的段大小
unsigned intbi_seg_back_size最后一个可合并的段大小
unsigned intbi_max_vecsbio的bio_vec数组中允许的最大段数
struct bio_vec*bi_io_vec指向bio的bio_vec数组中的段的指针
atomic_tbi_cntbio的引用计数
bio_end_io_t*bi_end_iobio的I/O操作结束时调用的方法
void*bi_private通用块层和块设备驱动程序的I/O完成方法使用的指针

bio_vec结构中的字段:

类型字段说明
struct page*bv_page指向段的页框中页描述符的指针
unsigned intbv_len段的字节长度
unsigned intbv_offset页框中中段数据的偏移量

核心API函数:

函数名输入参数返回值说明
intregister_blkdevunsigned int major, const char* name成功返回主设备号,失败返回一个负数。
struct gendisk*alloc_diskint minors成功返回一个指向gendisk描述符的指针,失败返回NULL
struct request_queue*blk_alloc_queuegfp_t gfp_mask成功返回一个指向request_queue的指针,失败时返回NULL
voidblk_queue_make_requeststruct request_queue* q, make_request_fn* mfn无返回
voidadd_diskstruct gendisk *disk无返回
voiddel_gendiskstruct gendisk* disk无返回
voidput_diskstruct gendisk* disk无返回
voidunregister_blkdevunsigned int major, const char *name无返回
voidblk_cleanup_queuestruct request_queue *q无返回
voidbio_endiostruct bio *bio, int error无返回

4. 完善代码(加入bio过滤功能)

Makefile文件不变,fbd_driver.h 内容如下:

#ifndef  _FBD_DRIVER_H
#define  _FBD_DRIVER_H#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/genhd.h>#define SECTOR_BITS             (9)
#define DEV_NAME_LEN            32#define DRIVER_NAME            "filter driver"#define DEVICE1_NAME           "fbd1_dev"
#define DEVICE1_MINOR           0
#define DEVICE2_NAME           "fbd2_dev"
#define DEVICE2_MINOR           1struct fbd_dev {struct request_queue *queue;struct gendisk *disk;sector_t size;          /* devicesize in Bytes *//* new code */char lower_dev_name[DEV_NAME_LEN];struct block_device *lower_bdev;
};#endif

size记录将来要创建的fbd_dev设备的容量大小,该容量需要保持与fbd_dev底层设备大小一致,lower_dev_name记录了底层设备的文件名字,lower_bdev保存着底层设备的block_device描述符。

fbd_driver.c内容如下:

/***  fbd-driver - filter block device driver*  Author: Talk@studio*  Modified by abin**/#include "fbd_driver.h"static int fbd_driver_major = 0;/* new code */
static struct fbd_dev fbd_dev1 = {.queue = NULL,.disk = NULL,.lower_dev_name = "/dev/sdb",.lower_bdev = NULL,.size = 0
};
/* new code */
static struct fbd_dev fbd_dev2 = {.queue = NULL,.disk = NULL,.lower_dev_name = "/dev/sdc",.lower_bdev = NULL,.size = 0
};static int fbddev_open(struct inode *inode, struct file *file);
static int fbddev_close(struct inode *inode, struct file *file);static struct block_device_operations disk_fops = {.open = (void *)fbddev_open,.release = (void *)fbddev_close,.owner = THIS_MODULE,
};/* 块设备被打开时调用该函数 */
static int fbddev_open(struct inode *inode, struct file *file)
{printk("device is opened by:[%s]\n", current->comm);return 0;
}/* 块设备被关闭时调用该函数 */
static int fbddev_close(struct inode *inode, struct file *file)
{printk("device is closed by:[%s]\n", current->comm);return 0;
}/* 仓库的加工函数,在dev_create中被调用 */
static int make_request(struct request_queue *q, struct bio *bio)		/* 参数1是我们的关卡请求队列,参数2是上层准备好的盒子bio请求描述结构体指针 */
{struct fbd_dev *dev = (struct fbd_dev *)q->queuedata;printk("device [%s] recevied [%s] io request, ""access on dev sector[%llu], length is [%u] sectors.\n",dev->disk->disk_name,bio_data_dir(bio) == READ ?"read" : "write",(long long)bio->bi_iter.bi_sector,bio_sectors(bio));/* new code *//* bio->bi_bdev = dev->lower_bdev; */bio_set_dev(bio, dev->lower_bdev);	/* 告诉bio请求的下一站是下层设备,即: dev->lower_bdev */submit_bio(bio);		/* 提交bio请求 */return 0;
}/* 创建过滤设备的函数,在init函数中被调用 */
static int dev_create(struct fbd_dev *dev, char *dev_name, int major, int minor)
{int ret = 0;/* init fbd_dev */dev->size = DEV_SIZE;dev->disk = alloc_disk(1);		/* 申请仓库gendisk,返回值为gendisk结构体 */if (!dev->disk) {printk("alloc diskerror");ret = -ENOMEM;goto err_out1;}dev->queue = blk_alloc_queue(GFP_KERNEL);		/* 建立关卡,关卡申请后,可以用也可以不用,但必须申请 */if (!dev->queue) {printk("alloc queueerror");ret = -ENOMEM;goto err_out2;}/* init queue */blk_queue_make_request(dev->queue, (void *)make_request);		/* 仓库加工函数,即:请求处理函数make_request,第一参数是刚申请到的请求队列,第二个参数是我们写好的make_request函数名 */dev->queue->queuedata = dev;/* init gendisk */strncpy(dev->disk->disk_name, dev_name, DEV_NAME_LEN);	/* 给gendisk的disk_name成员赋值,也就是给仓库取名字 */dev->disk->major = major;		/* 把申请到的门牌号赋值给disk的成员major */dev->disk->first_minor = minor;	/* 赋值了一个次设备号 */dev->disk->fops = &disk_fops;		/* 为gendisk的文件操作函数赋值了一个函数指针集结构体 *//* new code *//* dev->lower_bdev = open_bdev_exclusive(dev->lower_dev_name, FMODE_WRITE| FMODE_READ, dev->lower_bdev); */blkdev_get_by_path(dev->lower_dev_name,FMODE_WRITE| FMODE_READ, dev->lower_bdev);		/* 获取底层设备的block_device数据结构指针 */if (IS_ERR(dev->lower_bdev)) {printk("Open thedevice[%s]'s lower dev [%s] failed!\n", dev_name, dev->lower_dev_name);ret = -ENOENT;goto err_out3;}dev->size = get_capacity(dev->lower_bdev->bd_disk) <<SECTOR_BITS;		/* 获取底层设备的容量大小 */set_capacity(dev->disk, (dev->size >> SECTOR_BITS));	/* 设置设备的容量为底层设备的容量大小 *//* bind queue to disk */dev->disk->queue =dev->queue;		/* 把申请的queue地址保存在disk中,这样仓库和关卡就绑定在一起了 *//* add disk to kernel */add_disk(dev->disk);	/*告诉内核我们的仓库需要审核一下,如果通过,那仓库就建好了 */return 0;err_out3:blk_cleanup_queue(dev->queue);
err_out2:put_disk(dev->disk);
err_out1:return ret;
}static void dev_delete(struct fbd_dev *dev, char *name)
{printk("delete the device [%s]!\n", name);/* new code */// close_bdev_excl(dev->lower_bdev);blkdev_put(dev->lower_bdev, FMODE_WRITE| FMODE_READ);blk_cleanup_queue(dev->queue);del_gendisk(dev->disk);put_disk(dev->disk);
}/* 内核模块入口,也是构建块设备驱动的核心部分 */
static int __init fbd_driver_init(void)
{int ret;/* register fbd driver, get the driver major number */fbd_driver_major =register_blkdev(fbd_driver_major, DRIVER_NAME);		/* 第一个参数是初始化的major号,第二参数是块设备驱动的名字。第一参数0时,系统会从它自己管理的情况表上查找是否有可用的号码,如果有就分配,作为regiser_blkdev的返回值 */if (fbd_driver_major < 0) {printk("get majorfail");ret = -EIO;goto err_out1;}/* create the first device */ret = dev_create(&fbd_dev1, DEVICE1_NAME, fbd_driver_major,DEVICE1_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE1_NAME);goto err_out2;}/* create the second device */ret = dev_create(&fbd_dev2, DEVICE2_NAME, fbd_driver_major,DEVICE2_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE2_NAME);goto err_out3;}return ret;err_out3:dev_delete(&fbd_dev1, DEVICE1_NAME);
err_out2:unregister_blkdev(fbd_driver_major, DRIVER_NAME);
err_out1:return ret;
}static void __exit fbd_driver_exit(void)
{/* delete the two devices */dev_delete(&fbd_dev2, DEVICE2_NAME);dev_delete(&fbd_dev1, DEVICE1_NAME);/* unregister fbd driver */unregister_blkdev(fbd_driver_major,DRIVER_NAME);printk("block device driver exit successfuly!\n");
}module_init(fbd_driver_init);
module_exit(fbd_driver_exit);
MODULE_LICENSE("GPL");

和上一个demo比较,查看改动的地方:

diff fbd_driver.c fbd_driver_old.c -u > fbd_driver.patch
cat fbd_driver.patch

补丁的内容如下:

--- fbd_driver_old.c	2020-11-16 16:51:27.344000000 +0800
+++ fbd_driver.c	2020-11-16 17:55:27.850994518 +0800
@@ -8,8 +8,22 @@static int fbd_driver_major = 0;-static struct fbd_dev fbd_dev1 = {NULL};
-static struct fbd_dev fbd_dev2 = {NULL};
+/* new code */
+static struct fbd_dev fbd_dev1 = {
+  .queue = NULL,
+  .disk = NULL,
+  .lower_dev_name = "/dev/sdb",
+  .lower_bdev = NULL,
+  .size = 0
+};
+/* new code */
+static struct fbd_dev fbd_dev2 = {
+  .queue = NULL,
+  .disk = NULL,
+  .lower_dev_name = "/dev/sdc",
+  .lower_bdev = NULL,
+  .size = 0
+};static int fbddev_open(struct inode *inode, struct file *file);static int fbddev_close(struct inode *inode, struct file *file);
@@ -46,7 +60,10 @@(long long)bio->bi_iter.bi_sector,bio_sectors(bio));-  bio_endio(bio);		//结束一个bio请求
+  /* new code */
+  // bio->bi_bdev = dev->lower_bdev;
+  bio_set_dev(bio, dev->lower_bdev);
+  submit_bio(bio);return 0;}
@@ -83,15 +100,29 @@dev->disk->major = major;		/* 把申请到的门牌号赋值给disk的成员major */dev->disk->first_minor = minor;	/* 赋值了一个次设备号 */dev->disk->fops = &disk_fops;		/* 为gendisk的文件操作函数赋值了一个函数指针集结构体 */
-  set_capacity(dev->disk, (dev->size >> SECTOR_BITS));	/* 设置设备的容量大小为512M */
+
+  /* new code */
+  // dev->lower_bdev = open_bdev_exclusive(dev->lower_dev_name, FMODE_WRITE| FMODE_READ, dev->lower_bdev);
+  blkdev_get_by_path(dev->lower_dev_name,FMODE_WRITE| FMODE_READ, dev->lower_bdev);
+  if (IS_ERR(dev->lower_bdev)) {
+    printk("Open thedevice[%s]'s lower dev [%s] failed!\n", dev_name, dev->lower_dev_name);
+    ret = -ENOENT;
+    goto err_out3;
+  }
+
+  dev->size = get_capacity(dev->lower_bdev->bd_disk) <<SECTOR_BITS;
+  set_capacity(dev->disk, (dev->size >> SECTOR_BITS));	/* 设置设备的容量 *//* bind queue to disk */dev->disk->queue =dev->queue;		/* 把申请的queue地址保存在disk中,这样仓库和关卡就绑定在一起了 *//* add disk to kernel */add_disk(dev->disk);	/*告诉内核我们的仓库需要审核一下,如果通过,那仓库就建好了 */
+return 0;+err_out3:
+  blk_cleanup_queue(dev->queue);err_out2:put_disk(dev->disk);err_out1:
@@ -102,6 +133,10 @@{printk("delete the device [%s]!\n", name);+  /* new code */
+  // close_bdev_excl(dev->lower_bdev);
+  blkdev_put(dev->lower_bdev, FMODE_WRITE| FMODE_READ);
+blk_cleanup_queue(dev->queue);del_gendisk(dev->disk);put_disk(dev->disk);

修改完成之后,重新编译:

make clean
make

先卸载内核模块,然后重新加载:

sudo rmmod fbd_driver.ko
sudo dmesg -C
sudo insmod fbd_driver.ko
dmesg

[54554.965217] device is opened by:[systemd-udevd]

[54554.965802] device is opened by:[systemd-udevd]

[54554.998264] device is closed by:[systemd-udevd]

[54555.018357] device is closed by:[systemd-udevd]

ls -l /dev/fbd*

brw-rw---- 1 root disk 252, 0 Nov 13 09:08 /dev/fbd1_dev

brw-rw---- 1 root disk 252, 1 Nov 13 09:08 /dev/fbd2_dev

5. 完善代码(BIO请求回调机制)

fbd_driver.h 的内容:

#ifndef  _FBD_DRIVER_H
#define  _FBD_DRIVER_H#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/genhd.h>#define SECTOR_BITS             (9)
#define DEV_NAME_LEN            32#define DRIVER_NAME            "filter driver"#define DEVICE1_NAME           "fbd1_dev"
#define DEVICE1_MINOR           0
#define DEVICE2_NAME           "fbd2_dev"
#define DEVICE2_MINOR           1struct fbd_dev {struct request_queue *queue;struct gendisk *disk;sector_t size;          /* devicesize in Bytes *//* new code */char lower_dev_name[DEV_NAME_LEN];struct block_device *lower_bdev;};/* new code for bio  call back */
struct bio_context {void *old_private;void *old_callback;
};#endif

fbd_driver.c 的内容:

/***  fbd-driver - filter block device driver*  Author: Talk@studio*  Modified by abin**/#include "fbd_driver.h"static int fbd_driver_major = 0;/* new code */
static struct fbd_dev fbd_dev1 = {.queue = NULL,.disk = NULL,.lower_dev_name = "/dev/loop13",.lower_bdev = NULL,.size = 0
};
/* new code */
static struct fbd_dev fbd_dev2 = {.queue = NULL,.disk = NULL,.lower_dev_name = "/dev/loop14",.lower_bdev = NULL,.size = 0
};static int fbddev_open(struct inode *inode, struct file *file);
static int fbddev_close(struct inode *inode, struct file *file);static struct block_device_operations disk_fops = {.open = (void *)fbddev_open,.release = (void *)fbddev_close,.owner = THIS_MODULE,
};/* 块设备被打开时调用该函数 */
static int fbddev_open(struct inode *inode, struct file *file)
{printk("device is opened by:[%s]\n", current->comm);return 0;
}/* 块设备被关闭时调用该函数 */
static int fbddev_close(struct inode *inode, struct file *file)
{printk("device is closed by:[%s]\n", current->comm);return 0;
}/* new code for bio  call back */
/* bio请求回调时执行的函数 */
static int fbd_io_callback(struct bio *bio,unsigned int bytes_done, int error)
{struct bio_context *ctx = bio->bi_private;bio->bi_private = ctx->old_private;bio->bi_end_io = ctx->old_callback;kfree(ctx);printk("returned [%s] io request, end on sector %lu!\n", bio_data_dir(bio) == READ ?"read" : "write", bio->bi_iter.bi_sector);if (bio->bi_end_io) {int (*callback)(struct bio *bio,unsigned int bytes_done, int error) = (void *)(bio->bi_end_io);callback(bio, bytes_done, error);}return 0;
}/* 仓库的加工函数,在dev_create中被调用 */
static int make_request(struct request_queue *q, struct bio *bio)		//参数1是我们的关卡请求队列,参数2是上层准备好的盒子bio请求描述结构体指针
{struct fbd_dev *dev = (struct fbd_dev *)q->queuedata;/* new code for bio  call back */struct bio_context *ctx;printk("device [%s] recevied [%s] io request, ""access on dev sector[%llu], length is [%u] sectors.\n",dev->disk->disk_name,bio_data_dir(bio) == READ ?"read" : "write",(long long)bio->bi_iter.bi_sector,bio_sectors(bio));/* new code for bio  call back */ctx = kmalloc(sizeof(struct bio_context), GFP_KERNEL);if (!ctx) {printk("alloc memory forbio_context failed!\n");bio_endio(bio);goto out;}memset(ctx, 0, sizeof(struct bio_context));ctx->old_private = bio->bi_private;ctx->old_callback = bio->bi_end_io;bio->bi_private = ctx;bio->bi_end_io = (void *)fbd_io_callback;/* new code */// bio->bi_bdev = dev->lower_bdev;bio_set_dev(bio, dev->lower_bdev);submit_bio(bio);out:return 0;
}/* 创建过滤设备的函数,在init函数中被调用 */
static int dev_create(struct fbd_dev *dev, char *dev_name, int major, int minor)
{int ret = 0;/* init fbd_dev */dev->disk = alloc_disk(1);		/* 申请仓库gendisk,返回值为gendisk结构体 */if (!dev->disk) {printk("alloc diskerror");ret = -ENOMEM;goto err_out1;}dev->queue = blk_alloc_queue(GFP_KERNEL);		/* 建立关卡,关卡申请后,可以用也可以不用,但必须申请 */if (!dev->queue) {printk("alloc queueerror");ret = -ENOMEM;goto err_out2;}/* init queue */blk_queue_make_request(dev->queue, (void *)make_request);		/* 仓库加工函数,即:请求处理函数make_request,第一参数是刚申请到的请求队列,第二个参数是我们写好的make_request函数名 */dev->queue->queuedata = dev;/* init gendisk */strncpy(dev->disk->disk_name, dev_name, DEV_NAME_LEN);	/* 给gendisk的disk_name成员赋值,也就是给仓库取名字 */dev->disk->major = major;		/* 把申请到的门牌号赋值给disk的成员major */dev->disk->first_minor = minor;	/* 赋值了一个次设备号 */dev->disk->fops = &disk_fops;		/* 为gendisk的文件操作函数赋值了一个函数指针集结构体 *//* new code */// dev->lower_bdev = open_bdev_exclusive(dev->lower_dev_name, FMODE_WRITE| FMODE_READ, dev->lower_bdev);blkdev_get_by_path(dev->lower_dev_name,FMODE_WRITE| FMODE_READ, dev->lower_bdev);if (IS_ERR(dev->lower_bdev)) {printk("Open thedevice[%s]'s lower dev [%s] failed!\n", dev_name, dev->lower_dev_name);ret = -ENOENT;goto err_out3;}dev->size = get_capacity(dev->lower_bdev->bd_disk) <<SECTOR_BITS;set_capacity(dev->disk, (dev->size >> SECTOR_BITS));	/* 设置设备的容量 *//* bind queue to disk */dev->disk->queue =dev->queue;		/* 把申请的queue地址保存在disk中,这样仓库和关卡就绑定在一起了 *//* add disk to kernel */add_disk(dev->disk);	/*告诉内核我们的仓库需要审核一下,如果通过,那仓库就建好了 */return 0;err_out3:blk_cleanup_queue(dev->queue);
err_out2:put_disk(dev->disk);
err_out1:return ret;
}static void dev_delete(struct fbd_dev *dev, char *name)
{printk("delete the device [%s]!\n", name);/* new code */// close_bdev_excl(dev->lower_bdev);blkdev_put(dev->lower_bdev, FMODE_WRITE| FMODE_READ);blk_cleanup_queue(dev->queue);del_gendisk(dev->disk);put_disk(dev->disk);
}/* 内核模块入口,也是构建块设备驱动的核心部分 */
static int __init fbd_driver_init(void)
{int ret;/* register fbd driver, get the driver major number */fbd_driver_major =register_blkdev(fbd_driver_major, DRIVER_NAME);		/* 第一个参数是初始化的major号,第二参数是块设备驱动的名字。第一参数0时,系统会从它自己管理的情况表上查找是否有可用的号码,如果有就分配,作为regiser_blkdev的返回值 */if (fbd_driver_major < 0) {printk("get majorfail");ret = -EIO;goto err_out1;}/* create the first device */ret = dev_create(&fbd_dev1, DEVICE1_NAME, fbd_driver_major,DEVICE1_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE1_NAME);goto err_out2;}/* create the second device */ret = dev_create(&fbd_dev2, DEVICE2_NAME, fbd_driver_major,DEVICE2_MINOR);if (ret) {printk("create device[%s] failed!\n", DEVICE2_NAME);goto err_out3;}return ret;err_out3:dev_delete(&fbd_dev1, DEVICE1_NAME);
err_out2:unregister_blkdev(fbd_driver_major, DRIVER_NAME);
err_out1:return ret;
}static void __exit fbd_driver_exit(void)
{/* delete the two devices */dev_delete(&fbd_dev2, DEVICE2_NAME);dev_delete(&fbd_dev1, DEVICE1_NAME);/* unregister fbd driver */unregister_blkdev(fbd_driver_major,DRIVER_NAME);printk("block device driver exit successfuly!\n");
}module_init(fbd_driver_init);
module_exit(fbd_driver_exit);
MODULE_LICENSE("GPL");

加入bio过滤功能和bio请求回调机制后的示意图如下:

图源 https://img-blog.csdn.net/20130614111543781

其中,fbd_driver 接管了来自上层( VFS )的bio请求,经过处理后提交给下层设备( /dev/sdb 和 /dev/sdc ),下层设备处理完后,bio 返回也会被 fbd_driver 捕获,并进行相应处理。可以看出,fbd_driver 的作用就是在 VFS 和 底层设备之间增加了一个中间处理流程。


http://chatgpt.dhexx.cn/article/uI62QyR0.shtml

相关文章

Java Bio编程

IO模型 基本说明 io模型就是数据的发送与接收&#xff0c;这个直接决定了程序之间通信的效率Java的网络编程常见三种Io分别是&#xff1a;bio&#xff0c;nio&#xff0c;aioBio&#xff1a;阻塞并且同步&#xff0c;服务器实现一个连接对应一个线程&#xff0c;如果这个连接…

JAVA BIO 编程

1. JAVA BIO基本介绍 Java BIO 就是传统的 java io 编程&#xff0c;其相关的类和接口在 java.io;BIO(blocking I/O) &#xff1a; 同步阻塞&#xff0c;服务器实现模式为一个连接一个线程&#xff0c;即客户端有连接请求时服务器端就需要启动一个线程进行处理&#xff0c;如果…

Java BIO

BIO通信模型 BIO通信服务端&#xff0c;通常有一个独立的Acceptor线程负责监听客户端的连接。接收到客户端连接请求后会为每个客户端创建一个新的线程进行链路处理&#xff0c;处理完成后返回应答给客户端&#xff0c;也就是经典的请求&#xff0d;应答通信模型。但是随着客户端…

BIO和NIO

两种通信模式BIO和NIO ​ io是指计算机的输入输出操作&#xff0c;广义的讲就是数据在的一种传输&#xff0c;可分为磁盘io&#xff08;硬盘的读写&#xff09;和网络io&#xff08;socket的读写&#xff09;&#xff0c;这里的两种模式都是基于网络io的。 io的分类 阻塞I/O…

BIO、NIO、AIO详解

一、Java的I/O演进之路 Java共支持3种网络编程的I/O模型&#xff1a;BIO、NIO、AIO BIO&#xff1a; 同步并阻塞&#xff08;传统阻塞型&#xff09;&#xff0c;服务器实现模式为一个连接一个线程&#xff0c;即客户端有连接请求时服务器端就需要启动一个线程进行处理&…

OpenSSL BIO源码简析

文章目录 1. BIO简介BIO chainBIO数据结构BIO_METHOD数据结构 2. Base64示例分析初始化构造BIO链写数据free 1. BIO简介 相关文档 /html/man7/bio.html /html/man3/BIO_*.htmlbio - Basic I/O abstraction&#xff0c;即IO抽象层。 BIO有两种: source/sink BIO&#xff0c;…

二、JAVA BIO

NIO 目录 文章目录 二、JAVA BIO1、 Java BIO基本介绍2、 java BIO工作机制3、传统的BIO编程实例回顾3.1、客户端案例如下3.2、服务端案例如下3.3、输出3.4、小结 4、BIO模式下多发和多收消息4.1、客户端代码如下4.2、服务端代码如下4.3、输出 5、BIO模式下接收多个客户端5.1、…

BIO和NIO的区别

1.BIO基本介绍 BIO是传统的Java IO编程&#xff0c;其基本的类和接口在java.io包中BIO(blocking I/O)&#xff1a;同步阻塞&#xff0c;服务器实现模式为一个连接一个线程&#xff0c;即客户端有连接请求时服务器端就需要启动一个线程进行处理&#xff0c;如果这个连接不做任何…

网络编程之中篇——BIO模型详述

1、BIO介绍 1.1、BIO的概念 BIO&#xff08;Blocking IO&#xff09;同步阻塞IO模型&#xff0c;在JDK 1.4之前&#xff0c;建立网络链接采用的只有BIO的模型 需要服务端首先启动建立一个ServerSocket实例&#xff0c;然后客户端启动Socket实例对服务端进行连接通信&#xf…

BIO,NIO,AIO分别是什么?他们有什么区别?

1、BIO 概念&#xff1a; BIO是一种同步阻塞I/O模式&#xff0c;服务实现模式为一个连接对应一个线程&#xff0c;即客户端发送一个连接&#xff0c;服务端要有一个线程来处理。 存在的问题&#xff1a; 一旦有高并发的大量请求,就会有如下问题&#xff1a; 1&#xff09;线程…

JAVA BIO与NIO、AIO的区别(这个容易理解)

IO的方式通常分为几种&#xff0c;同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。 一、BIO 在JDK1.4出来之前&#xff0c;我们建立网络连接的时候采用BIO模式&#xff0c;需要先在服务端启动一个ServerSocket&#xff0c;然后在客户端启动Socket来对服务端进行通信&#…

Quartus II 上手攻略

第一次接触EDA实验&#xff0c;对这方面的相关操作并不熟悉。本篇文章结合上课内容和B站Quartus进行整理&#xff0c;总结一下Quartus 这款软件的基本使用。 参考的B站教学链接&#xff1a;《Quartus II 软件安装与入门教程》 Quartus 软件简介 Quartus II 是Altera公司为其FP…

完全卸载quartus ii 9.0

即将毕业了&#xff0c;把电脑一些不用的软件清清&#xff0c;发现quartus软件贼占空间&#xff0c;删除又貌似找不到卸载的exe&#xff0c;百度了好多都不靠谱 下面介绍一种方法&#xff0c;可以很好的卸载掉quartus&#xff0c;原先我的quartus是安装在D盘下&#xff0c;结果…

Quartus II与Modelsim软件安装教程

Quartus II与Modelsim软件安装教程 一、Quartus II软件安装1、Quartus II安装2、器件安装3、Quartus 破解4、USB Blaster 驱动安装 二、Modelsim软件安装1、modelsim安装2、modelsim注册 三、参考资料 一、Quartus II软件安装 本节主要讲述Quartus II13.1软件的安装使用&#x…

Quartus II13.1安装教程

安装前先关闭杀毒软件和360卫士&#xff0c;注意安装路径不能有中文&#xff0c;安装包路径也不要有中文。 1.鼠标右击【Quartus II 13.1】压缩包选择【解压到Quartus II 13.1】。 2.双击打开解压后的【Quartus II 13.1】文件夹。 3.双击打开【Quartus】文件夹。 4.鼠标右击【Q…

Quartus II下载器件库

Quartus II下载器件库 1、在浏览器中输入网址 https://fpgasoftware.intel.com/18.1/?editionstandard&platformwindows&#xff0c; 或https://fpgasoftware.intel.com/ 进入如下图所示界面。 2、在版本类型和版本中输入Quartus II所对应的版本 3、输入完版本后&#…

Quartus II软件的使用

在这里&#xff0c;我们只是简单的介绍了一下上述的流程图&#xff0c;让大家有个大致的了解&#xff0c;接下来我们就以流水灯实验的工程为例&#xff0c;对每个流程进行详细的操作演示&#xff0c;一步步、手把手带领大家学习使用Quartus II软件。 在创建工程之前&#xff0c…

QuartusII中LPM_COUNTER的使用

ALTERA建议&#xff0c;在设计时时序允许的情况下尽量使用Megafunction的资源&#xff0c;因为在多数情况下Megafunction的综合和实现结果更为优化。现在&#xff0c;就LPM_COUNTER的使用&#xff0c;浅谈一下。 Megafunction中LPM_COUNTER的参数设定主要是以下三部分&#xf…

quartus II 18.1 下载

quartus II 18.1 下载链接 以及解析 链接:https://pan.baidu.com/s/1warS-Vvv1maDmOKu8RsteQ 提取码&#xff1a;awxd 这个链接是已经下好的安装包 链接:https://pan.baidu.com/s/13HuyxUZvZ19vdYUmlLJujQ 提取码&#xff1a;gudn 第二个链接解压密码&#xff1a; wqlx.13542…

Quartus II14.1安装教程

安装前先关闭杀毒软件和360卫士&#xff0c;注意安装路径不能有中文&#xff0c;安装包路径也不要有中文。 1.鼠标右击【Quartus II 14.1】压缩包选择【解压到Quartus II 14.1】。 2.双击打开解压后的【Quartus II 14.1】文件夹。 3.双击打开【Quartus】文件夹。 4.鼠标右击【Q…