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

  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 1 struct 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) modules clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -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. 关键信息

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

序号 函数 功能
1 register_blk_device 注册并申请门牌号
2 alloc_disk 申请仓库
3 alloc_queue 申请仓库的关卡
4 blk_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_t bd_dev 块设备的主设备号和次设备号
struct inode* bd_inode 指向bdev文件系统中块设备对应的文件索引节点的指针
int bd_openers 计数器,统计块设备已经被打开了多少次
struct mutex bd_mutex 打开或关闭的互斥量
struct list_head bd_inodes 已打开的块设备文件的索引节点链表的首部
void* bd_holders 块设备描述符的当前所有者
struct block_device* bd_contains 如果块设备是一个分区,则指向整个磁盘的块设备描述符;否则,指向该块设备描述符
unsigned bd_block_size 块大小
struct hd_struct* bd_part 指向分区描述符的指针(如果该块设备不是一个分区,则为NULL)
unsigned bd_part_count 计数器,统计包含在块设备中的分区已经被打开了多少次
struct gendisk* bd_disk 指向块设备中基本磁盘的gendisk结构的指针
struct list_head bd_list 用于块设备描述符链表的指针
unsigned long bd_private 指向块设备持有者的私有数据的指针

hd_struct关键成员:

类型 字段 说明
sector_t start_sect 磁盘中分区的起始扇区
sector_t nr_sects 分区的长度(总共的扇区数)
int policy 如果分区是只读的,则置为1;否则为0
int partno 磁盘中分区的相对索引

gendisk关键成员:

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

块设备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_head queue_head 待处理请求的链表
make_request_fn* make_request_fn 设备驱动程序的请求处理函数

request描述符的关键字段:

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

bio结构中的关键字段:

类型 字段 说明
sector_t bi_sector 块I/O操作的第一个磁盘扇区
struct bio* bi_next 链接到请求队列中的下一个bio
struct block_device * bi_bdev 指向块设备描述符的指针
unsigned long bi_flags bio的状态标志
unsigned long bi_rw I/O操作标志
unsigned short bi_vcnt bio的bio_vec数组中段的数目
unsigned short bi_idx bio的bio_vec数组中段的当前索引值
unsigned int bi_phys_segments 合并之后bio中物理段的数目
unsigned int bi_size 需要传送的字节数
unsigned int bi_seg_front_size 第一个可合并的段大小
unsigned int bi_seg_back_size 最后一个可合并的段大小
unsigned int bi_max_vecs bio的bio_vec数组中允许的最大段数
struct bio_vec* bi_io_vec 指向bio的bio_vec数组中的段的指针
atomic_t bi_cnt bio的引用计数
bio_end_io_t* bi_end_io bio的I/O操作结束时调用的方法
void* bi_private 通用块层和块设备驱动程序的I/O完成方法使用的指针

bio_vec结构中的字段:

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

核心API函数:

函数名 输入参数 返回值 说明
int register_blkdev unsigned int major, const char* name 成功返回主设备号,失败返回一个负数。
struct gendisk* alloc_disk int minors 成功返回一个指向gendisk描述符的指针,失败返回NULL
struct request_queue* blk_alloc_queue gfp_t gfp_mask 成功返回一个指向request_queue的指针,失败时返回NULL
void blk_queue_make_request struct request_queue* q, make_request_fn* mfn 无返回
void add_disk struct gendisk *disk 无返回
void del_gendisk struct gendisk* disk 无返回
void put_disk struct gendisk* disk 无返回
void unregister_blkdev unsigned int major, const char *name 无返回
void blk_cleanup_queue struct request_queue *q 无返回
void bio_endio struct 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 1 struct 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 1 struct 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 和 底层设备之间增加了一个中间处理流程。

块设备驱动、bio理解的更多相关文章

  1. 【转】 bio 与块设备驱动

    原文地址: bio 与块设备驱动      系统中能够随机访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称作块.块设备文件都是以安装文件系统的方式使用,此也是块设备通常的访问方式.块 ...

  2. Linux 块设备驱动 (一)

    1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...

  3. linux下的块设备驱动(二)

    上一章主要讲了请求队列的一系列问题.下面主要说一下请求函数.首先来说一下硬盘类块设备的请求函数. 请求函数可以在没有完成请求队列的中的所有请求的情况下就返回,也可以在一个请求都不完成的情况下就返回. ...

  4. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  5. 乾坤合一~Linux设备驱动之块设备驱动

    1. 题外话 在蜕变成蝶的一系列学习当中,我们已经掌握了大部分Linux驱动的知识,在乾坤合一的分享当中,以综合实例为主要讲解,在一个月的蜕茧成蝶的学习探索当中,觉得数据结构,指针,链表等等占据了代码 ...

  6. linux块设备驱动

    块设备驱动程序<1>.块设备和字符设备的区别1.读取数据的单元不同,块设备读写数据的基本单元是块,字符设备的基本单元是字节.2.块设备可以随机访问,字符设备只能顺序访问. 块设备的访问:当 ...

  7. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

  8. Linux块设备驱动_WDS

    推荐书:<Linux内核源代码情景分析> 1.字符设备驱动和使用中等待某一事件的方法①查询方式②休眠唤醒,但是这种没有超时时间③poll机制,在休眠唤醒基础上加一个超时时间④异步通知,异步 ...

  9. linux 块设备驱动 (三)块设备驱动开发

    一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...

  10. linux块设备驱动(一)——块设备概念介绍

    本文来源于: 1. http://blog.csdn.net/jianchi88/article/details/7212370 2. http://blog.chinaunix.net/uid-27 ...

随机推荐

  1. ELB UDP健康检查常见故障分析

    本文分享自天翼云开发者社区<ELB UDP健康检查常见故障分析>,作者:王****宁 什么是UDP健康检查 UDP是面向非连接的一种协议,在发送数据前不会通过进行三次握手建立连接,UDP健 ...

  2. Ansible之二playbook

    反星系 连接https://galaxy.ansible.com下载相应的roles 列出所有安装的   galaxy ansible-galaxy  list 安装galaxy     ansibl ...

  3. HT-014 Div3 跳棋 题解 [ 黄 ] [ 并查集 ] [ 链表 ]

    分析 依旧是一个连通块题. 观察题面不难发现两个重要性质: 一个跳棋只能以它旁边的两个跳棋为中点跳跃,且满足跳跃路线中 除中点以外没有其它跳棋阻挡. 只有我们的跳棋可以移动. 跳棋的操作具有可逆性/对 ...

  4. Typecho 博客文章评论添加显示 UserAgent(UA)的功能

    本篇文章实现了为 Typecho 博客文章评论添加显示 UserAgent(UA)的功能 本功能可替代 UserAgent 插件,更美观.简洁且好看 效果显示 大概就是这样了,实际效果请看我的评论! ...

  5. 奥特曼autMan对接QQ机器人框架go-cqhttp的详细教程

    前言 node-onebo用pm2,screen守护都不稳定,node-onebot扫码后ctrl+c,pm2没守护直接掉了,screen只能守护几个小时.因为服务器地区与你常登q的位置不同,时常有密 ...

  6. linux怎么判断服务器的cpu架构

    在部署应用程序和服务时,确认服务器的CPU架构是非常重要的,因为这会直接影响软件的兼容性和性能.在Linux系统中,有许多方法可以获取服务器的CPU架构信息.本篇文章将介绍几种常用的方法,并提供代码示 ...

  7. 【数学公式】mathtype和word2016集成

    mathtype 安装好了以后,word 没有相应的选项卡怎么办? 问题 解决办法 找到word的启动路径 2. 找到mathtype 安装好后的mathpage文件夹 进入文件夹,找到MathPag ...

  8. pandas 删除指定条件的行

    inplace=True:不创建新的对象,直接对原始对象进行修改: inplace=False:对数据进行修改,创建并返回新的对象承载其修改结果. 删除工作日餐补为0的记录 row_index=df[ ...

  9. 注册全局图标ts写法

    https://element-plus.gitee.io/zh-CN/component/icon.html#使用图标 仓库地址:https://github.com/Megasu/element- ...

  10. AI编程:最疯狂的MCP服务器你一定要试试

    我还记得我第一次听说MCP(模型上下文协议)的时候,我心想: "哈?又一个花里胡哨的缩写,这个世界已经快被这些缩写淹没了!" 但当我意识到MCP就像AI界的通用连接器--就像USB ...