块设备驱动
Linux 块设备驱动分析(一)
Linux 块设备驱动分析(二)
Linux 块设备驱动分析(三)
在块设备的注册和初始化阶段,与字符设备驱动类似,需要申请设备号, 完成这个任务的函数是register_blkdev,其原型为:
/* major参数是块设备要使用的主设备号,name为设备名,
如果major为0,内核会自动分配一个新的主设备号,函数返回主设备号
*/
int register_blkdev(unsigned int major, const char *name);
除此之外,在块设备驱动初始化过程中,通常需要完成分配、初始化请求队列,绑定请求队列和请求处理函数的工作,并且可能会分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。
一个典型的块设备驱动的初始化过程, 其中包含了register_blkdev、blk_init_queue和add_disk的工作,如下代码清单:
static int __init ramblk_init(void)
{
//申请块设备号
major = register_blkdev(0, "ramblk");
//申请gendisk,4个分区
ram_gendisk = alloc_disk(4);
//申请并初始化请求队列
queue = blk_init_queue(do_request, &ram_lock);
ram_gendisk->major = major;
ram_gendisk->first_minor = 0;
//设置块设备的操作集
ram_gendisk->fops = &ramblock_fops;
sprintf(ram_gendisk->disk_name, "ramblk");
ram_gendisk->queue = queue;
//设置磁盘扇区数
set_capacity(ram_gendisk, RAMBLOCK_SIZE/512);
......
//向内核增添这块磁盘
add_disk(ram_gendisk);
return 0;
}
这里讲一下块设备的block_device_operations的getgeo成员函数,该函数返回磁盘的几何信息:
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量 = heads*sectors*cylinders*512 */
geo->heads = 2; //磁头数
geo->sectors = 32; //扇区数
geo->cylinders = RAMBLOCK_SIZE/2/32/512; //柱面(磁道)数
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
}
分区工具(如fdisk)对磁盘进行分区需要磁盘的几何信息。
块设备驱动的I/O请求处理
使用blk_init_queue函数初始化的request_queue,其make_request_fn成员函数会设置为blk_queue_bio,从通用块层传下的bio会通过blk_queue_bio函数,进行请求合并、排序等操作,对于机械磁盘设备,有助于提高系统的性能。
但是对于RAMDISK、ZRAM等完全可真正随机访问的设备而言,无法从高级的请求队列逻辑中获益。这类块设备驱动初始化的时候最好不要使用blk_init_queue函数申请与初始化request_queue,而是使用blk_alloc_queue函数申请一个request_queue,设置自己的make_request_fn成员函数。
分享一个块设备驱动实例
用一块内存模拟磁盘,在内核版本4.9.88下测试无问题。
#include <linux/major.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/bio.h>
#include <asm/setup.h>
#include <asm/pgtable.h>
#include <linux/highmem.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/sizes.h>
#include <linux/ndctl.h>
#include <linux/fs.h>
#include <linux/nd.h>
#define RAMBLOCK_SIZE (1024*1024) //1M容量
#define KERNEL_SECTOR_SIZE 512
static DEFINE_SPINLOCK(ram_lock);
static struct gendisk *ram_gendisk;
static char *ramblock_buff;
static int major;
static struct request_queue *queue;
static void ramblk_disk_transfer(unsigned long sector, unsigned long nbytes, char *buffer, int write)
{
unsigned long offset = sector*KERNEL_SECTOR_SIZE;
//printk(KERN_ALERT"sector = %ld\n", sector);
if((offset+nbytes) > RAMBLOCK_SIZE)
{
printk(KERN_ALERT"Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if(write)
memcpy(ramblock_buff+offset, buffer, nbytes);
else
memcpy(buffer, ramblock_buff+offset, nbytes);
}
static int ramblk_xfer_bio(struct bio *bio)
{
struct bio_vec bvec;
struct bvec_iter iter;
sector_t sector = bio->bi_iter.bi_sector; //起始扇区号
//遍历bio的片段bio_vec,iter保存当前bio_vec的相关信息,bvec保存当前的bio_vec
bio_for_each_segment(bvec, bio, iter){
/* __bio_kmap_atomic函数作用如下:
* iter保存着指向bio_vec的相关信息,根据这个相关信息在bio的bi_io_vec数组中找到对应的bio_vec
* 然后根据找到的bio_vec,转换出对应的vaddr
*/
char *buffer = __bio_kmap_atomic(bio, iter);
//bio_cur_bytes(bio)获得的是当前bio_vec的段长度
ramblk_disk_transfer(sector, bio_cur_bytes(bio), buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_bytes(bio) >> 9;
__bio_kunmap_atomic(buffer);
}
return 0;
}
//驱动策略函数
static void do_request(struct request_queue *q)
{
struct request *req;
struct bio *bio;
while ((req = blk_fetch_request(q)) != NULL)
{
if(req->cmd_type != REQ_TYPE_FS) //请求类型不是来自fs
{
printk(KERN_ALERT"Skip non-fs request\n");
__blk_end_request_all(req, -EIO);
break;
}
__rq_for_each_bio(bio, req) //遍历request的bio
ramblk_xfer_bio(bio); //处理单个bio
//报告请求处理完成
__blk_end_request_all(req, 0);
}
}
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量 = heads*sectors*cylinders*512 */
geo->heads = 2; //磁头数
geo->sectors = 32; //扇区数
geo->cylinders = RAMBLOCK_SIZE/2/32/512; //柱面(磁道)数
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
static int __init ramblk_init(void)
{
//申请块设备号
major = register_blkdev(0, "ramblk");
//申请gendisk,4个分区
ram_gendisk = alloc_disk(4);
//申请并初始化请求队列
queue = blk_init_queue(do_request, &ram_lock);
ram_gendisk->major = major;
ram_gendisk->first_minor = 0;
//设置块设备的操作集
ram_gendisk->fops = &ramblock_fops;
sprintf(ram_gendisk->disk_name, "ramblk");
ram_gendisk->queue = queue;
//扇区数
set_capacity(ram_gendisk, RAMBLOCK_SIZE/512);
ramblock_buff = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
//向内核增添这块磁盘
add_disk(ram_gendisk);
return 0;
}
static void __exit ramblk_exit(void)
{
//blk_unregister_region(major, Z2MINOR_COUNT);
unregister_blkdev(major, "ramblk");
del_gendisk(ram_gendisk);
put_disk(ram_gendisk);
blk_cleanup_queue(queue);
kfree(ramblock_buff);
}
module_init(ramblk_init);
module_exit(ramblk_exit);
MODULE_LICENSE("GPL");
Makefile:
KERNELBUILD :=/lib/modules/$(shell uname -r)/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions *.mod
obj-m += ramblk.o