块设备
块设备将数据存储在固定的块中,每个块的大小通常在512字节到32768字节之间。磁盘、SD卡都是常见的块设备。
块设备与字符设备的区别:
- 在与读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个 sector ,而字符设备的基本单元为字节。
- 块设备能够随机访问,而字符设备则只能顺序访问。
块设备体系结构
体系结构层次
-
虚拟文件系统VFS
VFS是对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。
-
Disk Cache
当用户发起文件访问请求的时候,首先会到Disk Cache 中寻找文件是否被缓存了,如果在cache中,则直接从 cache 中读取。如果数据不在缓存中,就必须到具体的文件系统中读取数据了。 -
Mapping layer
(1)首先确定文件系统的 block size ,然后计算所请求的数据包含多少个block
(2)调用具体文件系统的函数来访问文件的 inode,确定所请求的数据在磁盘上的逻辑块地址。 -
Generic Block Layer
Linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据空间。
上层的读写请求在通用块层(Generic Block Layer)被构造成一个或多个bio结构。 -
I/O Secheduler Layer
I/O调度层负责将I/O操作排序,采用某种算法(如:电梯调度算法)来高效地处理操作。
电梯调度算法:
-
Block Device Driver
块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输
块设备驱动程序设计
块设备描述
Linux内核使用 struct gendisk (定义于 <linux/genhd.h>)来描述块设备。
struct gendisk {
int major; /* 主设备号 */
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, mode_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;
};
设备注册
Linux 内核使用 add_disk 函数向内核注册块设备驱动。
void add_disk(struct gendisk *gd)
设备操作
字符 设备通过file_operations 结构来定义使它所支持的操作,块设备使用一个类似的结构:struct block_device_operations.(<linux/blkdev.h>)
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
unsigned long long (*set_capacity) (struct gendisk *,
unsigned long long);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
I/O请求
在Linux内核中,使用 struct request 来表示等待处理的块设备I/O请求。
struct request
{
struct list_head queuelist; //链表结构
sector_t sector; //要操作的首个扇区
unsigned long nr_sectors; //要操作的扇区数目
struct bio *bio; //请求的bio结构体的链表
struct bio *biotail; //请求的bio结构体的链表尾
......
};
请求队列
请求队列就是 I/O请求 request 所形成的队列,在Linux内核中 struct request_queue 描述。
内核中提供了一系列函数用来操作请求队列:
- 初始化请求队列,一般在块设备驱动的模块加载函数中调用。
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
- 清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。
void blk_cleanup_queue(request_queue_t *q)
- 返回下一个要处理的请求(由I/O调度决定),如果没有请求则返回NULL。elv_next_request() 不会清除请求,它仍然将这个请求保留在队列上,因此连续调用它2次,2次返回同一个请求结构体。
struct request *elv_next_request (request_queue_t *queue)
- 从队列中删除1个请求
void blkdev_dequeue_request (struct request *req)
块设备完整驱动程序
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
//#include <linux/kdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/bio.h>
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
#define SIMP_BLKDEV_BYTES (16*1024*1024)
static struct request_queue *simp_blkdev_queue;
static struct gendisk *simp_blkdev_disk;
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
static void simp_blk_dev_do_request(struct request_queue *q)
{
struct request *req;
while ((req = blk_fetch_request(q)) != NULL)
{
if((blk_rq_pos(req) +blk_rq_cur_sectors(req)) << 9
> SIMP_BLKDEV_BYTES)
{
printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad request:block=%llu,count=%u\n",(unsigned long long)blk_rq_pos(req),blk_rq_cur_sectors(req));
blk_end_request_all(req, 0);
continue;
}
switch(rq_data_dir(req))
{
case READ:
memcpy(req->buffer,simp_blkdev_data + (blk_rq_pos(req) << 9),blk_rq_cur_sectors(req) << 9);
blk_end_request_all(req, 1);
break;
case WRITE:
memcpy(simp_blkdev_data + (blk_rq_pos(req) << 9),req->buffer,blk_rq_cur_sectors(req) << 9);
blk_end_request_all(req, 1);
break;
default:
break;
}
}
}
struct block_device_operations simp_blkdev_fops =
{
.owner = THIS_MODULE,
};
static int __init simp_blkdev_init(void)
{
int ret;
simp_blkdev_queue = blk_init_queue(simp_blk_dev_do_request,NULL);
if(!simp_blkdev_queue)
{
ret = -ENOMEM;
goto err_init_queue;
}
simp_blkdev_disk = alloc_disk(1);
if(!simp_blkdev_disk)
{
ret = -ENOMEM;
goto err_alloc_disk;
}
strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = &simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
return ret;
}
static void __exit simp_blkdev_exit(void)
{
del_gendisk(simp_blkdev_disk);
put_disk(simp_blkdev_disk);
blk_cleanup_queue(simp_blkdev_queue);
}
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);
linux2.6.38 内核struct request成员中少了好多成员(或者是改成其他名字了),比如
request -> sectors 变为 blk_rq_pos(request)
request -> current_nr_sectors 变为 blk_rq_nr_sectors(request)
__elev_next_request(request) 变为 blk_fetch_request(request_queue)
end_request(request, error) 变为 blk_end_request_all(request, error)
块设备驱动测试
数据访问流程
BIO
一个struct bio 代表一个块设备I/O请求,I/O调度器可将连续的bio合并成一个请求 struct request
struct bio
{
sector_t bi_sector; //要访问的第一个扇区
unsigned int bi_size; //以字节为单位所需传输的数据大小
struct bio_vec *bi_io_vec; //实际的vec列表
};
struct bio_vec
{
struct page *bv_page; //页指针
unsigned int bv_len; //传输的数据长度
unsigned int bv_offset; //偏移量
};
__make_request
根据以上陈述,在不使用IO调度情况下的块设备驱动程序设计代码如下:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
//#include <linux/kdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/bio.h>
#include <linux/version.h>
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
#define SIMP_BLKDEV_BYTES (16*1024*1024)
MODULE_AUTHOR("silence");
MODULE_LICENSE("GPL");
static struct request_queue *simp_blkdev_queue;
static struct gendisk *simp_blkdev_disk;
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
static int simp_blkdev_make_request(struct request_queue *q,struct bio *bio)
{
struct bio_vec *bvec;
int i;
void *dsk_mem;
if((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES)
{
printk(KERN_ERR SIMP_BLKDEV_DISKNAME ":bad request:block=%llu,count=%u\n",
(unsigned long long)bio->bi_sector,bio->bi_size);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
bio_endio(bio,0,-EIO);
#else
bio_endio(bio,-EIO);
#endif
return 0;
}
dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
bio_for_each_segment(bvec, bio, i)
{
void *iovec_mem;
switch(bio_rw(bio))
{
case READ:
case READA:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(iovec_mem, dsk_mem,bvec->bv_len);
kunmap(bvec->bv_page);
break;
case WRITE:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(dsk_mem,iovec_mem,bvec->bv_len);
kunmap(bvec->bv_page);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": unknown value of bio_rw:%lu\n",bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
bio_endio(bio,0,-EIO);
#else
bio_endio(bio,-EIO);
#endif
return 0;
}
dsk_mem += bvec->bv_len;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
bio_endio(bio,bio->bi_size,0);
#else
bio_endio(bio,0);
#endif
return 0;
}
struct block_device_operations simp_blkdev_fops =
{
.owner = THIS_MODULE,
};
static int __init simp_blkdev_init(void)
{
int ret;
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
if(!simp_blkdev_queue)
{
ret = -ENOMEM;
goto err_alloc_queue;
}
blk_queue_make_request(simp_blkdev_queue,simp_blkdev_make_request);
simp_blkdev_disk = alloc_disk(1);
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = &simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
return ret;
}
static void __exit simp_blkdev_exit(void)
{
del_gendisk(simp_blkdev_disk);
put_disk(simp_blkdev_disk);
blk_cleanup_queue(simp_blkdev_queue);
}
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);