分类: LINUX
2008-07-23 16:10:59
一、综述
图1是块设备操作的一个分层实现图。当一个进程调用read读取一个文件时,内核执行如下一个过程:首先,它通过VFS层去读取要到的文件块有没有已经被cache了,这个cache由一个buffer_head结构读取。如果要读取的文件块还没有被cache,则就要从文件系统中去读取了,这就是文件系统的映射层,它通过一个address_space结构来引用,然后调用文件系统读函数(readpage)去读取一个页面大小的数据,这个读函数对于不同的文件系统来说,是不一样的。当它从磁盘中读出数据时,它会将数据页链入cache中,当下次再读取时,就不需要再次从磁盘出去读了。Readpage()函数并不是直接去操作磁盘,而只是将请求初始化成一个bio结构,并提交给通用块层(generic block layer)。
图1
它就通过submit_bio()去完成的。通用块层再调用相应设备的IO调度器,通过这个调度器的调度算法,将这个bio或合并到已存在的request中,或创建一个新的request,并将这个新创建的request插入到设备的请求队列中去。这就完成了IO调度层的工作。最后就是块设备驱动所做的工作了。IO调度器传递给块驱动的是一个请求队列,块驱动就是要处理这个队列中的请求,直到这个队列为空为止。
二、通用块层(generic block layer)
通用块层操作的是一个bio结构,这个结构主要的数据域是,
unsigned short bi_vcnt;
struct bio_vec *bi_io_vec; /* the actual vec list */
这个就是要读写的数据向量,且每个struct bio_vec 为一个segment。
//这个函数主要是调用generic_make_request()去完成工作:
void submit_bio(int rw, struct bio *bio)
{
……
generic_make_request(bio);
}
//这个函数的主要作用是将bio传递给驱动去处理
void generic_make_request(struct bio *bio)
{
……
do {
char b[BDEVNAME_SIZE];
//取得块设备相应的队列,每个设备一个
q = bdev_get_queue(bio->bi_bdev);
/*
* If this device has partitions, remap block n
* of partition p to block n+start(p) of the disk.
*/
blk_partition_remap(bio); //块设备分区信息转换,如将相对于一个分区的的偏移地址转换成相对于整个块设备的绝对偏移等等。
old_sector = bio->bi_sector;
old_dev = bio->bi_bdev->bd_dev;
……
//这个是块设备队列的请求处理函数。由块设备创建请求队列时初始化。
//对于IDE等设备,它是__make_request()。但对于ramdisk就不一样了。
ret = q->make_request_fn(q, bio); // __make_request()等
} while (ret);
}
//这要函数的主要作用就是调用IO调度算法将bio合并,或插入到队列中合适的位置中去
static int __make_request(request_queue_t *q, struct bio *bio)
{
struct request *req;
int el_ret, nr_sectors, barrier, err;
const unsigned short prio = bio_prio(bio);
const int sync = bio_sync(bio);
int rw_flags;
nr_sectors = bio_sectors(bio);
//用于处理高端内存
blk_queue_bounce(q, &bio);
spin_lock_irq(q->queue_lock);
//测试是否能合并,本文忽略IO调度算法
el_ret = elv_merge(q, &req, bio);
switch (el_ret) {
//前两种可以合并
case ELEVATOR_BACK_MERGE:
……
goto out;
case ELEVATOR_FRONT_MERGE:
……
goto out;
//不能合并,需要新创一个request。
/* ELV_NO_MERGE: elevator says don't/can't merge. */
default:
;
}
get_rq:
rw_flags = bio_data_dir(bio);
if (sync)
rw_flags |= REQ_RW_SYNC;
//新创一个request
req = get_request_wait(q, rw_flags, bio);
//初始化这个request。
init_request_from_bio(req, bio);
spin_lock_irq(q->queue_lock);
if (elv_queue_empty(q)) //空队列的处理
blk_plug_device(q);
add_request(q, req); //将新请求加入队列中去
out:
if (sync) //如果需要同步,立即处理请求
__generic_unplug_device(q);
spin_unlock_irq(q->queue_lock);
return 0;
end_io:
bio_endio(bio, nr_sectors << 9, err);
return 0;
}
//触发块设备驱动进行真正的IO操作
void __generic_unplug_device(request_queue_t *q)
{
if (unlikely(blk_queue_stopped(q)))
return;
if (!blk_remove_plug(q))
return;
q->request_fn(q); //设备的请求处理函数,属于驱动层
}
三、块设备驱动层
块设备的关系图如图2,一个分区或一个硬盘都可能是block_device,它一个硬盘只有一个gendisk结构,且有可能有多个分区hd_struct。
图2
我们来看一个IDE硬盘设备的驱动,在此我们不关心IDE总线的驱动,只是将其执行路线列出来。
static int ide_init_queue(ide_drive_t *drive)
{
request_queue_t *q;
ide_hwif_t *hwif = HWIF(drive);
int max_sectors = 256;
int max_sg_entries = PRD_ENTRIES;
//分配一个请求队列,由IDE总线去帮助完成,简化了特定块设备的工作
q = blk_init_queue_node(do_ide_request, &ide_lock, hwif_to_node(hwif));
//初始化队列中的一些参数
q->queuedata = drive;
blk_queue_segment_boundary(q, 0xffff);
……
blk_queue_max_hw_segments(q, max_sg_entries);
blk_queue_max_phys_segments(q, max_sg_entries);
/* assign drive queue */
drive->queue = q;
return 0;
}
request_queue_t *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
request_queue_t *q = blk_alloc_queue_node(GFP_KERNEL, node_id);
q->node = node_id;
if (blk_init_free_list(q)) {
kmem_cache_free(requestq_cachep, q);
return NULL;
}
q->request_fn = rfn; //由上可以看到,在ide-disk中,为do_ide_request
q->prep_rq_fn = NULL;
q->unplug_fn = generic_unplug_device;
q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);
q->queue_lock = lock;
blk_queue_segment_boundary(q, 0xffffffff);
blk_queue_make_request(q, __make_request);
blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
/*
* all done
*/
if (!elevator_init(q, NULL)) { //设置队列的IO调度算法
blk_queue_congestion_threshold(q);
return q;
}
blk_put_queue(q);
return NULL;
}
由上可见,当unplug一个块设备时,它将执行do_ide_request():
void do_ide_request(request_queue_t *q)
{
ide_drive_t *drive = q->queuedata;
ide_do_request(HWGROUP(drive), IDE_NO_IRQ);
}
static void ide_do_request (ide_hwgroup_t *hwgroup, int masked_irq)
{
ide_drive_t *drive;
ide_hwif_t *hwif;
struct request *rq;
ide_startstop_t startstop;
int loops = 0;
/* for atari only: POSSIBLY BROKEN HERE(?) */
ide_get_lock(ide_intr, hwgroup);
/* caller must own ide_lock */
BUG_ON(!irqs_disabled());
while (!hwgroup->busy) {
hwgroup->busy = 1;
drive = choose_drive(hwgroup); //选择硬盘
……
again:
hwif = HWIF(drive);
if (hwgroup->hwif->sharing_irq &&
hwif != hwgroup->hwif &&
hwif->io_ports[IDE_CONTROL_OFFSET]) {
/* set nIEN for previous hwif */
SELECT_INTERRUPT(drive);
}
hwgroup->hwif = hwif;
hwgroup->drive = drive;
drive->sleeping = 0;
/*
* we know that the queue isn't empty, but this can happen
* if the q->prep_rq_fn() decides to kill a request
*/
rq = elv_next_request(drive->queue); //取下一个请求
if (!rq) {
hwgroup->busy = 0;
break;
}
hwgroup->rq = rq;
local_irq_enable_in_hardirq();
/* allow other IRQs while we start this request */
startstop = start_request(drive, rq); //开始向磁盘写入该请求
spin_lock_irq(&ide_lock);
if (masked_irq != IDE_NO_IRQ && hwif->irq != masked_irq)
enable_irq(hwif->irq);
if (startstop == ide_stopped)
hwgroup->busy = 0;
}
}
static ide_startstop_t start_request (ide_drive_t *drive, struct request *rq)
{
ide_startstop_t startstop;
sector_t block;
block = rq->sector;
if (blk_fs_request(rq) &&
(drive->media == ide_disk || drive->media == ide_floppy)) {
block += drive->sect0;
}
SELECT_DRIVE(drive);
if (!drive->special.all) {
ide_driver_t *drv;
……
if (rq->cmd_type == REQ_TYPE_ATA_CMD ||
rq->cmd_type == REQ_TYPE_ATA_TASK ||
rq->cmd_type == REQ_TYPE_ATA_TASKFILE)
return execute_drive_cmd(drive, rq);
else if (blk_pm_request(rq)) {
……
return startstop;
}
drv = *(ide_driver_t **)rq->rq_disk->private_data;
return drv->do_request(drive, rq, block);
}
}
以上均是IDE总线上设备的通用接口,直到do_request开始才执行特定设备的驱动,如CD,HD, floppy等IDE设备。我们来看一下ide-disk:
1、 首先是设备的初始化操作。
IDE设备接口
static ide_driver_t idedisk_driver = {
.gen_driver = {
.owner = THIS_MODULE,
.name = "ide-disk",
.bus = &ide_bus_type,
},
.probe = ide_disk_probe,
.remove = ide_disk_remove,
.shutdown = ide_device_shutdown,
.version = IDEDISK_VERSION,
.media = ide_disk,
.supports_dsc_overlap = 0,
.do_request = ide_do_rw_disk,
.end_request = ide_end_request,
.error = __ide_error,
.abort = __ide_abort,
.proc = idedisk_proc,
};
static struct block_device_operations idedisk_ops = {
.owner = THIS_MODULE,
.open = idedisk_open,
.release = idedisk_release,
.ioctl = idedisk_ioctl,
.getgeo = idedisk_getgeo,
.media_changed = idedisk_media_changed,
.revalidate_disk= idedisk_revalidate_disk
};
//设备注册
static int __init idedisk_init(void)
{
return driver_register(&idedisk_driver.gen_driver);
}
//这个probe函数是在设备注册时由驱动模型去执行
static int ide_disk_probe(ide_drive_t *drive)
{
struct ide_disk_obj *idkp;
struct gendisk *g;
idkp = kzalloc(sizeof(*idkp), GFP_KERNEL);
//分配一个gendisk结构
g = alloc_disk_node(1 << PARTN_BITS,
hwif_to_node(drive->hwif));
ide_init_disk(g, drive);
//用上面的结构注册设备
ide_register_subdriver(drive, &idedisk_driver);
kref_init(&idkp->kref);
//一些初始化操作
idkp->drive = drive;
idkp->driver = &idedisk_driver;
idkp->disk = g;
g->private_data = &idkp->driver;
drive->driver_data = idkp;
idedisk_setup(drive);
g->minors = 1 << PARTN_BITS;
g->driverfs_dev = &drive->gendev;
g->flags = drive->removable ? GENHD_FL_REMOVABLE : 0;
set_capacity(g, idedisk_capacity(drive));
g->fops = &idedisk_ops;
add_disk(g); //插入设备,至此,该设备可用
return 0;
}
2、 处理IDE总线发来的请求
由上可以看到,IDE总线驱动调用设备的do_request()去处理这个请求,我们在上面的注册中可以看到。在ide-disk里,它是ide_do_rw_disk():
/*
* 268435455 == 137439 MB or 28bit limit
* 320173056 == 163929 MB or 48bit addressing
* 1073741822 == 549756 MB or 48bit addressing fake drive
*/
static ide_startstop_t ide_do_rw_disk (ide_drive_t *drive, struct request *rq, sector_t block)
{
ide_hwif_t *hwif = HWIF(drive);
……
if (hwif->rw_disk)
hwif->rw_disk(drive, rq);
return __ide_do_rw_disk(drive, rq, block);
}
//以下就是特定硬盘设备的驱动了,因为我们只关心块设备驱动编程的框架,所以就不深入进去了。
/*
* __ide_do_rw_disk() issues READ and WRITE commands to a disk,
* using LBA if supported, or CHS otherwise, to address sectors.
*/
static ide_startstop_t __ide_do_rw_disk(ide_drive_t *drive, struct request *rq, sector_t block)
{
ide_hwif_t *hwif = HWIF(drive);
unsigned int dma = drive->using_dma;
u8 lba48 = (drive->addressing == 1) ? 1 : 0;
task_ioreg_t command = WIN_NOP;
ata_nsector_t nsectors;
nsectors.all = (u16) rq->nr_sectors;
if (hwif->no_lba48_dma && lba48 && dma) {
if (block + rq->nr_sectors > 1ULL << 28)
dma = 0;
else
lba48 = 0;
}
if (drive->select.b.lba) {
if (lba48) {
……
} else {
……
}
} else {
……
}
if (dma) {
……
/* fallback to PIO */
ide_init_sg_cmd(drive, rq);
}
if (rq_data_dir(rq) == READ) {
if (drive->mult_count) {
hwif->data_phase = TASKFILE_MULTI_IN;
command = lba48 ? WIN_MULTREAD_EXT : WIN_MULTREAD;
} else {
hwif->data_phase = TASKFILE_IN;
command = lba48 ? WIN_READ_EXT : WIN_READ;
}
ide_execute_command(drive, command, &task_in_intr, WAIT_CMD, NULL);
return ide_started;
} else {
……
return pre_task_out_intr(drive, rq);
}
}
3、 块设备驱动小结
我们由上看到, 块设备驱动编程的主要工作包括分配并初始化一个gendisk结构,分配并初始化一个请求队列,请求处理函数的编写(request_fn),还有中断的处理等等。但具体到不同的设备,实现又有一些出入,我们在上面看到的IDE设备,它大部分工作都是在IDE总线级上实现了,它做了许多繁琐但必要的工作,并向下层特定设备提供统一的接口,这样就大大简化了块设备驱动的编写过程。
有兴趣可以参考一下内核ramdisk的实现。