Linux 块设备驱动分析(三)

块设备驱动

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值