Linux驱动_块设备驱动

        块设备是Linux驱动三大设备之一。与字符设备有很大的区别。块设备是针对存储设备的,比如 SD 卡、 EMMC、 NAND Flash、 Nor Flash、 SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动。

一、块设备和字符设备的区别

摘自Linux驱动设备开发详解(宋宝华)

Linux驱动_块设备驱动_第1张图片

二、块设备驱动结构

1、block_device_operations

        block_device_operations是和file_operations字符设备操作集类似的块设备操作集。它的具体定义如下:

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	void (*release) (struct gendisk *, fmode_t);
	int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	long (*direct_access)(struct block_device *, sector_t,
					void **, unsigned long *pfn, long size);
	unsigned int (*check_events) (struct gendisk *disk,
				      unsigned int clearing);
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
	int (*media_changed) (struct gendisk *);
	void (*unlock_native_capacity) (struct gendisk *);
	int (*revalidate_disk) (struct gendisk *);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);
	struct module *owner;
};

 主要用到的的函数: 

        open:当块设备打开的时候会调用此函数
        release:当块设备关闭的时候会调用此函数
        getgeo:此函数用来根据驱动器的几何信息填充一个hd_geometry结构体,该结构体包含磁头、扇区、柱面等信息。 

2、gendisk结构体

        Linux内核使用gendisk结构体描述一个独立的磁盘,该结构体的定义如下:

struct gendisk {
	/* major, first_minor and minors are input parameters only,
	 * don't use directly.  Use disk_devt() and disk_max_parts().
	 */
	int major;			/* major number of driver */
	int first_minor;
	int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

	char disk_name[DISK_NAME_LEN];	/* name of major driver */
	char *(*devnode)(struct gendisk *gd, umode_t *mode);

	unsigned int events;		/* supported events */
	unsigned int async_events;	/* async events, subset of all */

	/* Array of pointers to partitions indexed by partno.
	 * Protected with matching bdev lock but stat and other
	 * non-critical accesses use RCU.  Always access through
	 * helpers.
	 */
	struct disk_part_tbl __rcu *part_tbl;
	struct hd_struct part0;

	const struct block_device_operations *fops;
	struct request_queue *queue;
	void *private_data;

	int flags;
	struct device *driverfs_dev;  // FIXME: remove
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct blk_integrity *integrity;
#endif
	int node_id;
};

        major:为磁盘设备的主设备号。
        first_minor:为磁盘的第一个次设备号。
        minors:为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一
样, 次设备号不同。
        part_tbl:为磁盘对应的分区表,为结构体 disk_part_tbl 类型, disk_part_tbl 的核心是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息。
        part0:disk->part_tbl->part[0] = &part0;
        fops:块设备操作集,为 block_device_operations 结构体类型。
        queue:磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求

3、request_queue、request、bio

     request_queue直译为请求队列,定义在gendisk中,因此每个块设备都对应于有一个请求队列。而请求队列中包含很多request直译为请求队列项。请求队列项又由一个或者多个bio结构体组成。

        上层应用程序对于块设备的读写会被构造成一个或多个 bio 结构, bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio 提交给 I/O 调度器, I/O 调度器会将这些 bio 构造成 request 结构,而一个物理存储设备对应一个request_queue,request_queue 里面顺序存放着一系列的 request。新产生的 bio 可能被合并到 request_queue 里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由 I/O 调度器来完成的。 request_queue、 request 和 bio 之间的关系如图:

Linux驱动_块设备驱动_第2张图片

         bio结构体的定义如下:

struct bio {
	struct bio		*bi_next;	/* request queue link */
	struct block_device	*bi_bdev;
	unsigned long		bi_flags;	/* status, command, etc */
	unsigned long		bi_rw;		/* bottom bits READ/WRITE,
						 * top bits priority
						 */

	struct bvec_iter	bi_iter;

	/* Number of segments in this BIO after
	 * physical address coalescing is performed.
	 */
	unsigned int		bi_phys_segments;

	/*
	 * To keep track of the max segment size, we account for the
	 * sizes of the first and last mergeable segments in this bio.
	 */
	unsigned int		bi_seg_front_size;
	unsigned int		bi_seg_back_size;

	atomic_t		bi_remaining;

	bio_end_io_t		*bi_end_io;

	void			*bi_private;
#ifdef CONFIG_BLK_CGROUP
	/*
	 * Optional ioc and css associated with this bio.  Put on bio
	 * release.  Read comment on top of bio_associate_current().
	 */
	struct io_context	*bi_ioc;
	struct cgroup_subsys_state *bi_css;
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
		struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
	};

	unsigned short		bi_vcnt;	/* how many bio_vec's */

	/*
	 * Everything starting with bi_max_vecs will be preserved by bio_reset()
	 */

	unsigned short		bi_max_vecs;	/* max bvl_vecs we can hold */

	atomic_t		bi_cnt;		/* pin count */

	struct bio_vec		*bi_io_vec;	/* the actual vec list */

	struct bio_set		*bi_pool;

	/*
	 * We can inline a number of vecs at the end of the bio, to avoid
	 * double allocations for a small number of bio_vecs. This member
	 * MUST obviously be kept at the very end of the bio.
	 */
	struct bio_vec		bi_inline_vecs[0];
};

        主要的成员变量如下:

bvec_iter:描述了要操作的设备扇区等信息,其结构体定义如下:

struct bvec_iter {
    sector_t bi_sector; /* I/O 请求的设备起始扇区(512 字节) */
    unsigned int bi_size; /* 剩余的 I/O 数量 */
    unsigned int bi_idx; /* blv_vec 中当前索引 */
    unsigned int bi_bvec_done; /* 当前 bvec 中已经处理完成的字节数 */
};

bio_vec:page 指定了所在的物理页, offset 表示所处页的偏移地址, len 就是数据长度。

struct bio_vec {
    struct page *bv_page; /* 页 */
    unsigned int bv_len; /* 长度 */
    unsigned int bv_offset; /* 偏移 */
};

        其中 bi_iter 这个结构体成员变量就用于描述物理存储设备地址信息,比如要操作的扇
区地址。 bi_io_vec 指向 bio_vec 数组首地址, bio_vec 数组就是 RAM 信息,比如页地址、页偏
移以及长度。

三、块设备驱动编写函数

        1、注册和注销块设备

int register_blkdev(unsigned int major, const char *name)
void unregister_blkdev(unsigned int major, const char *name)

        major: 主设备号。
        name: 块设备名字。

        2、gendisk

struct gendisk *alloc_disk(int minors)    //        注册gendisk 
void del_gendisk(struct gendisk *gp)    //注销gendisk
void add_disk(struct gendisk *disk)    //向内核添加gendisk
void set_capacity(struct gendisk *disk, sector_t size)    //设置gendisk容量

        3、请求队列 request_queue

申请并初始化:

request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
//初始化请求队列,并绑定rfn请求处理函数

        rfn: 请求处理函数指针,每个 request_queue 都要有一个请求处理函数,请求处理函数
request_fn_proc 原型如下:

void (request_fn_proc) (struct request_queue *q)

        请求处理函数需要驱动编写人员自行实现。
        lock: 自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用
这个自旋锁。

删除请求队列:

void blk_cleanup_queue(struct request_queue *q)

分配请求队列并绑定制造请求函数:

        blk_init_queue 函数完成了请求队列的申请已经请求处理函数的绑定,这个一般用于像机械
硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程。但是对于 EMMC、 SD 卡这样的
非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了。对于非机械设备可以先申请 request_queue,然后将申请到的 request_queue 与“制造请求”函数绑定在一起。

        分配一个request_queue

struct request_queue *blk_alloc_queue(gfp_t gfp_mask)

        将request_queue和制造请求函数绑定 、

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

        mfn:制造请求函数,该函数需要自己编写,函数原型如下:

void (make_request_fn) (struct request_queue *q, struct bio *bio)

四、 使用blk_init_queue进行试验

        本实验利用开发板上的RAM模拟一段内存,编写块设备驱动,代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


/*定义磁盘大小*/
#define RAMDISK_SIZE (2 * 1024 * 1024)	/*大小2MB*/
#define RAMDISK_NAME "ramdisk"
#define RAMDISK_MINOR 3	/*磁盘分区*/

struct ramdisk_dev {
	int major;	/* major主设备号*/
	unsigned char *ramdiskbuffer;	/*ramdisk内存空间,用来模拟块设备*/
	spinlock_t spinlock;	/*spinlock自旋锁*/
	struct gendisk *gendisk;	/*gendisk*/
	struct request_queue *queue;	/*request_queue请求队列*/
};

struct ramdisk_dev ramdisk;	/*定义一个ramdisk设备*/


/*块设备操作函数集合*/
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release\r\n");
}


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.ramdiskbuffer + start, len);
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(ramdisk.ramdiskbuffer + 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);
	}
}


static int __init ramdisk_init(void)
{
	int ret = 0;
	printk("ramdisk init\r\n");

	/*1、给准备模拟块设备的内存申请空间*/
	ramdisk.ramdiskbuffer = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(ramdisk.ramdiskbuffer == NULL) {	/*内存申请失败*/
		ret = -EINVAL;
		goto failed_ram;
	}

	/*2、初始化自旋锁*/
	spin_lock_init(&ramdisk.spinlock);

	/*3、注册块设备*/
	ramdisk.major = register_blkdev(0, RAMDISK_NAME);	/*自动分配设备号*/
	if(ramdisk.major < 0) {	/*块设备注册失败*/
		goto failed_register;
	}

	/*4、分配初始化gendisk*/
	ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);	/*初始化3个分区*/
	if(!ramdisk.gendisk) {	/*初始化gendisk失败*/
		ret = -EINVAL;
		goto failed_gendisk;
	}

	/*5、分配并初始化请求队列*/
	ramdisk.queue = blk_init_queue(&ramdisk_request_fn, &ramdisk.spinlock);
	if(!ramdisk.queue) {
		ret = -EINVAL;
		goto failed_queue;
	}

	/*6、注册添加gendisk*/
	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;
failed_queue:
	put_disk(ramdisk.gendisk);
failed_gendisk:
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);
failed_register:
	kfree(ramdisk.ramdiskbuffer);
failed_ram:
	return ret;	
}


static void __exit ramdisk_exit(void)
{
	printk("ramdisk exit\r\n");

}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");

        实验验证: 

Linux驱动_块设备驱动_第3张图片

        可以看出, ramdisk 已经识别出来了,大小为 2MB,但是同时也提示/dev/ramdisk
没有分区表,因为我们还没有格式化/dev/ramdisk。

     格式化/dev/ramdisk

mkfs.vfat /dev/ramdisk

     格式化完成后如下图所示: 

你可能感兴趣的:(linux驱动,#,IMX6ULL,#,Linux设备驱动(宋宝华),驱动开发,linux)