块设备的基本概念
-
页、段、块、扇区之间的关系图如下:
- 块设备驱动是基于扇区(sector)来访问底层物理磁盘,基于块(block)来访问上层文件系统。
- 扇区一般是2的n次方大小,典型为512B,内核也要求块是2的n次方大小,且块大小通常为扇区大小的整数倍,并且块大小要小于页面大小,典型大小为512B、1K或4K。
块设备在linux中的结构
块设备与字符设备的区别
块设备驱动程序的特点
1.块设备接口相对复杂,不如字符设备明晰易用
2.块设备驱动程序对整个系统的性能影响较大,速度和效率是设计块设备驱动程要重点考虑的问题
3.系统中使用缓冲区与访问请求的优化管理(合并与重新排序)来提高系统性能
块设备驱动的实现步骤
- 确定主设备号和次设备号
- 确定设备名称
- 创建设备文件
- 实现块设备驱动程序
1.实现block_device_operations结构体
2.实现request(请求)
3.实现初始化函数,注册块设备
4.实现销毁函数,取消块设备
几个关键的数据结构
block_device_operations结构体
struct block_device_operations {
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
long (*compat_ioctl) (struct file *, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
gendisk结构体
struct gendisk {
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[32]; /* name of major driver */
struct hd_struct **part; /* [indexed by minor] */
int part_uevent_suppress;
struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
sector_t capacity;
int flags;
struct device *driverfs_dev;
struct kobject kobj;
struct kobject *holder_dir;
struct kobject *slave_dir;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
unsigned long stamp;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
}
- 每一个块设备物理实体由一个gendisk结构体来表示
- 每个gendisk可以支持若干个分区
- 每个gendisk中包含本物理实体的全部信息及操作函数接口
- 整个块设备的注册过程都是围绕gendisk来开展的
- 各种设备操作
struct block_device_operations *fops;
- 管理设备I/O请求
struct request_queue *queue;
- sector_t capacity
以512字节为一个扇区时,该驱动器可包含的扇区数,驱动程序不能直接设置该成员,而要将扇区数传递给set_capacity - 内核扇区和硬件扇区的转换
内核总是认为扇区大小是512字节,而硬件扇区却不都是512字节,所以要相应的转换。 - 将硬件扇区数转换成内核扇区数
sectors = nsectors*(hardsect_size/KERNEL_SECTOR_SIZE);
nsecotrs:硬件实际的扇区数
hardsect_size:硬件扇区大小
KERNEL_SECTOR_SIZE:内核扇区大小512字节
sectors:对应于内核扇区大小的扇区总数
- 通过set_capacity()可以设置硬件存储器的实际容量
set_capacity(struct gendisk *gd, sector_t sectors);
请求与请求队列
- 用户对块设备上文件的操作会转化为一个“请求”,请求由结构体request表示,它定义在文件<linux/blkdev.h>中;
- request(请求)结构的主要成员
....
sector_t sector; //还没有传输的第一个扇区
unsigned long nr_sectors;//等待传输扇区的总量
unsigned int current_nr_sectors; //当前Bio中剩余的扇区数
struct bio *bio //请求的bio结构链表,需要使用rq_for_each_bio访问
char *buffer//用来查找需要传输的缓冲区
....
- 请求队列(request_queue)
- 块设备将它们挂起的块I/O请求保存在请求队列中,该队列由reques_queue结构体表示
- 块设备的请求队列指包含块设备I/O请求的序列。
- 创建和初始化请求队列的函数是:
request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
- 释放和删除请求队列
void blk_cleanup_queue(request_queue_t *);
- 返回下一个需要处理的请求指针
struct request *blk_fetch_request(request_queue_t *queue);
- 将请求从队列中删除
void blkdev_dequeue_request(struct request *req);
- 将删除的请求再次加入到队列
void elv_requeue_request(request_queue_t *queue,struct request *req);
- 设置请求参数
void blk_queue_max_sectors(request_queue_t *queue,
unsigned short max);
void blk_queue_max_phys_segments(request_queue_t *queue,
unsigned short max);
void blk_queue_max_hw_segments(request_queue_t *queue,
unsigned short max);
void blk_queue_max_segment_size(request_queue_t *queue,
unsigned int max);
void blk_queue_hardsect_size(request_queue_t *queue,
unsigned short max);
块设备驱动的注册与注销
- 块设备驱动注册函数
int register_blkdev(unsigned int major, const char *name);
这个函数不是真正的注册函数,他实际起到的作用只是获取一个设备号。 - 块设备驱动注销函数
int unregister_blkdev(unsigned int major, const char *name);
- 块设备驱动初始化函数主要工作
- 分配、初始化请求队列,绑定请求队列和请求函数
- 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。
- 注册块设备驱动
static int ramblock_init(void)
{
ramblock_disk = alloc_disk(16); /* 16为次设备号个数: 分区个数+1 */
/* 设置队列 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;
major = register_blkdev(0, "ramblock"); /* cat /proc/devices */
ramblock_disk->major = major;
ramblock_disk->first_minor = 0;
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops;
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
/* 注册 */
add_disk(ramblock_disk);
return 0;
}
- 块设备驱动退出函数主要工作
- 清除请求队列;
- 删除gendisk和对gendisk的引用;
- 删除对块设备的引用,注销块设备驱动;
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
kfree(ramblock_buf);
}
块设备的I/O请求处理
- 块设备驱动对块设备进行读、写主要通过request来实现。
块设备驱动请求函数的原型为:
void request(request_queue_t *queue);
-
块设备驱动程序处理请求的过程
static void do_ramblock_request(struct request_queue *q)
{
static int r_cnt = 0;
static int w_cnt = 0;
struct request *req;
req = blk_fetch_request(q); /*取出一个请求*/
while (req) {
unsigned long offset = blk_rq_pos(req)<<9; /* 请求里的起始地址 */
unsigned long len = blk_rq_cur_bytes(req);/*要操作数据的大小*/
if (rq_data_dir(req) == READ) /*判断是否是读操作*/
{
memcpy(req->buffer, ramblock_buf+offset, len);
}
else
{
memcpy(ramblock_buf+offset, req->buffer, len);
}
if(!__blk_end_request_cur(req, 0)) /*完成当前请求*/
req = blk_fetch_request(q); /*取下一个请求*/
}
}
代码实战
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
#define MYDISK_SIZE (1024*1024) //
#define MYDISK_NAME "ram_sice"
static DEFINE_SPINLOCK(mydisk_lock);
static const struct block_device_operations mydisk_fops = {
.owner = THIS_MODULE,
};
static struct gendisk *my_disk;
static struct request_queue *my_queue;
static unsigned char *ram_buffer;
static int major;
static void myblock_do_request(struct request_queue * q)
{
struct request *req;
req = blk_fetch_request(q);
while (req) {
unsigned long offset = blk_rq_pos(req) * 512;
unsigned long len = blk_rq_cur_bytes(req);
if (rq_data_dir(req) == READ){
memcpy(req->buffer, ram_buffer+offset, len);
printk("### read \n");
}
else{
printk("### write offset %x\n",offset);
memcpy(ram_buffer+offset, req->buffer, len);
}
if (!__blk_end_request_cur(req, 0))
req = blk_fetch_request(q);
}
}
static int my_ram_init(void)
{
major = register_blkdev(major, MYDISK_NAME);
my_disk = alloc_disk(8);
my_queue = blk_init_queue(myblock_do_request, &mydisk_lock);
my_disk->queue = my_queue;
my_disk->major = major;
my_disk->first_minor = 0;
sprintf(my_disk->disk_name, MYDISK_NAME);
my_disk->fops = &mydisk_fops;
set_capacity(my_disk, MYDISK_SIZE/512);
ram_buffer = kzalloc(MYDISK_SIZE, GFP_KERNEL);
printk("ram_buffer = %x\n",ram_buffer);
add_disk(my_disk);
return 0;
}
static void my_ram_exit(void)
{
unregister_blkdev(major, MYDISK_NAME);
del_gendisk(my_disk);
put_disk(my_disk);
blk_cleanup_queue(my_queue);
kfree(ram_buffer);
}
module_init(my_ram_init);
module_exit(my_ram_exit);
MODULE_LICENSE("GPL");