块设备驱动程序就是支持以块的方式进行读写的设备。块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的基本单元为字节。从实现角度来看,字符设备的实现比较简单,内核例程和用户态API一一对应,这种映射关系由字符设备的file_operations维护。块设备接口则相对复杂,读写API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。
block_device结构代表了内核中的一个块设备。它可以表示整个磁盘或一个特定的分区。当这个结构代表一个分区时,它的bd_contains成员指向包含这个分区的设备,bd_part成员指向设备的分区结构。当这个结构代表一个块设备时,bd_disk成员指向设备的gendisk结构。
struct block_device {
dev_t bd_dev;
struct inode * bd_inode; /*分区结点*/
int bd_openers;
struct semaphore bd_sem; /*打开/关闭锁*/
struct semaphore bd_mount_sem; /* 加载互斥锁*/
struct list_head bd_inodes;
void * bd_holder;
int bd_holders;
struct block_device * bd_contains;
unsigned bd_block_size;//分区块大小
struct hd_struct * bd_part;
unsigned bd_part_count;//打开次数
int bd_invalidated;
struct gendisk * bd_disk;
struct list_head bd_list;
struct backing_dev_info *bd_inode_backing_dev_info;
unsigned long bd_private;
};
gendisk表示一个独立的磁盘设备或分区
struct gendisk {
int major; //主设备号
int first_minor;
int minors; //最大的次设备号数量,如果设备不能分区,该值为1
char disk_name[32]; //主设备名
struct hd_struct **part; //分区信息,有minors个
struct block_device_operations *fops;//设备操作
struct request_queue *queue; //设备管理I/O请求
void *private_data;
sector_t capacity;
int flags;
char devfs_name[64];
int number;
struct device *driverfs_dev;
struct kobject kobj;
struct timer_rand_state *random;
int policy;
atomic_t sync_io;
unsigned long stamp, stamp_idle;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
};
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;
};
block_device_operations并不能完全提供文件操作全部的API,实际上只提供了open、release等函数,其他的文件操作依赖于def_blk_fops:
const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write= generic_file_aio_write_nolock,
.mmap = generic_file_mmap,
.fsync = block_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
系统对块设备进行读写操作时,通过块设备通用的读写操作函数将一个请求保存在该设备的操作请求队列(request queue)中,然后调用这个块设备的底层处理函数,对请求队列中的操作请求进行逐一执行。request_queue结构描述了块设备的请求队列,该结构定义如下:
struct request_queue
{
struct list_head queue_head;
struct request *last_merge;
elevator_t elevator;
/*请求队列列表*/
struct request_list rq;
request_fn_proc *request_fn;
merge_request_fn *back_merge_fn;
merge_request_fn *front_merge_fn;
merge_requests_fn *merge_requests_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unplug_fn *unplug_fn;
merge_bvec_fn *merge_bvec_fn;
activity_fn *activity_fn;
/*自动卸载状态*/
struct timer_list unplug_timer;
int unplug_thresh;
unsigned long unplug_delay; /*自动卸载延时*/
struct work_struct unplug_work;
struct backing_dev_info backing_dev_info;
void *queuedata;
void *activity_data;
unsigned long bounce_pfn;
int bounce_gfp;
unsigned long queue_flags;//各种队列标志
/*保护队列结构,避免重入*/
spinlock_t *queue_lock;
/* 请求的核心结构*/
struct kobject kobj;
/*请求的配置*/
unsigned long nr_requests; /* 请求的最大数*/
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned short max_sectors;
unsigned short max_phys_segments;
unsigned short max_hw_segments;
unsigned short hardsect_size;
unsigned int max_segment_size;
unsigned long seg_boundary_mask;
unsigned int dma_alignment;
struct blk_queue_tag *queue_tags;
atomic_t refcnt;
unsigned int in_flight;
/*sg 参数配置*/
unsigned int sg_timeout;
unsigned int sg_reserved_size;
};
块设备驱动的I/O请求处理有两种方式:使用请求队列和不使用请求队列。
使用请求队列有助于提高系统性能,但对于一些完全可随机访问的块设备,使用请求队列不能获得更大的益处,这时候通用块层提供了一种无队列的操作模式,使用这种模式,驱动必须提供制造请求函数。
/*请求处理函数,请求队列的处理流程如下:
*首先:从请求队列中拿出一条请求
*其次:判断这一条请求的方向,是向设备写还是读,然后将数据装入缓冲区
*最后:通知请求完成*/
static void ramdisk_do_request(struct request_queue_t *queue)
{
struct request *req;
/*使用循环一条请求一条请求的来处理,elv_next_request函数是遍历队列中的每一条请求*/
while(req = elv_next_request(queue) != NULL)
{
/*判断要传输数据的总长度大小是否超过范围*/
if ((req->sector + req->current_nr_sectors) << 9 > RAMDISK_SIZE)
{
/*如果超过范围就直接报告请求失败*/
end_request(req, 0);
continue;
}
/*判断请求处理的方向*/
switch (rq_data_dir(req))
{
case READ:
memcpy(req->buffer, disk_data + (req->sector << 9), req->current_nr_sectors << 9);
end_request(req, 1);/*报告请求处理成功*/
break;
case WRITE:
memcpy(disk_data + (req->sector << 9), req->buffer, req->current_nr_sectors << 9);
end_request(req, 1);/*报告请求处理成功*/
break;
default:
break;
}
}
}
static int __int ramdisk_init(void)
{
/*块设备驱动注册*/
register_blkdev(RAMDISK_MAJOR, RAMDISK_NAME);
/*使用请求队列的方式*/
ramdisk_queue = blk_init_queue(ramdisk_do_request, NULL);
/*分配gendisk*/
/*初始化gendisk*/
/*添加gendisk到系统中*/
}
/*绑定请求制造函数。注意:第一个参数仍然是请求队列,但在这里实际不包含任何请求。
所以这里要处理的重点对象的bio中的每个bio_vec,他表示一个或多个要传送的缓冲区。*/
static int ramdisk_make_request(struct request_queue_t *queue, struct bio *bio)
{
int i;
struct bio_vec *bvec;
void *disk_mem;
void *bvec_mem;
/*在遍历段之前先判断要传输数据的总长度大小是否超过范围*/
if((bio->bi_sector << 9) + bio->bi_size > RAMDISK_SIZE)
{
/*如果超出范围就通知这个bio处理失败*/
bio_endio(bio, 0, -EIO);
return 0;
}
/*获得这个bio请求在块设备内存中的起始位置*/
disk_mem = disk_data + (bio->bi_sector << 9);
/*开始遍历这个bio中的每个bio_vec*/
bio_for_each_segment(bvec, bio, i)
{
/*因bio_vec中的内存地址是使用page *描述的,故在高端内存中需要用kmap进行映射后才能访问,
再加上在bio_vec中的偏移位置,才是在高端物理内存中的实际位置*/
bvec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
/*判断bio请求处理的方向*/
switch(bio_data_dir(bio))
{
case READ:
case READA:
memcpy(bvec_mem, disk_mem, bvec-> bv_len);
break;
case WRITE :
memcpy(disk_mem, bvec_mem, bvec-> bv_len);
break;
default :
kunmap(bvec->bv_page);
}
/*处理完每一个bio_vec都应把kmap映射的地址取消掉*/
kunmap(bvec->bv_page);
/*累加当前bio_vec中的内存长度,以确定下一个bio_vec在块设备内存中的位置*/
disk_mem += bvec->bv_len;
}
/*bio中所有的bio_vec处理完后报告处理结束*/
bio_endio(bio, bio->bi_size, 0);
return 0;
}
static int __int ramdisk_init(void)
{
/*块设备驱动注册*/
ramdisk_major = register_blkdev(RAMDISK_MAJOR, RAMDISK_NAME);
/*使用制造请求的方式,先分配ramdisk_queue*/
ramdisk_queue = blk_alloc_queue(GFP_KERNEL);
/*再绑定请求制造函数*/
blk_queue_make_request(ramdisk_queue, &ramdisk_make_request);
/*分配gendisk*/
/*初始化gendisk*/
/*添加gendisk到系统中*/
}
见:内存模拟块设备驱动程序
分区:
~ # fdisk /dev/sbulla
sbull_media_change
sbull_getgeo
Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-16, default 1): Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-16, default 16): Using default value 16
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table
sbull_revalidate
sbulla: sbulla1
格式化:mkfs.vfat /dev/sbulla1
挂载:mount /dev/sbulla1 /mnt