分类: LINUX
2007-07-16 16:19:06
参考“分析对块设备驱动程序的读写请求过程”一文
原文见:
ll_rw_block() [drives/block/ll_rw_blk.c中定义]请求块设备驱动程序将多个物理块读出或写入块设备,块设备的读写都在块缓冲内进行.
块设备的请求函数由blk_dev[]数组描述,在调用块设备的请求函数之前,需要为每个缓冲块安装相应的bh->b_end_io函数指针,当设备驱动程序读写完成时,将会调用b_end_io()来通知自已读写完成的情况.
其中要写的bh对应的记录块不一定是连续的,但必须是同一设备的。
每种设备都有一个操作请求队列,队列头部是一个request_queque_t数据结构。即请求队列描述符[include/linux/blkdev.h定义]
struct request_queue
{
/* the queue request freelist, one for reads and one for writes */
struct request_list rq;
struct list_head queue_head;
elevator_t elevator;
request_fn_proc * request_fn; [指针对应结构blkdev.h定义]
merge_request_fn * back_merge_fn;
merge_request_fn * front_merge_fn;
merge_requests_fn * merge_requests_fn;
make_request_fn * make_request_fn;
plug_device_fn * plug_device_fn;
void * queuedata;
struct tq_struct plug_tq;
char plugged;
char head_active;
spinlock_t queue_lock;
wait_queue_head_t wait_for_request
};
typedef struct request_queue request_queue_t
当内核初始化一个驱程时,为驱程所处理的每个请求队列都创建一个请求队列描述符并填充它。
请求队列是一个双向链表,其元素就是请求描述符(request数据结构,其结构的组后一个元素为指向请求队列描述符的指针request_queue_t * q)
为了把各种块设备的操作请求队列有效的组这起来,内核中还设置了一个结构数组blk_dev[],见driver/block/ll_rw_blk.c
struct blk_dev_struct blk_dev[MAX_BLKDEV];
这个数组以主设备号为下标,数组中的每个元素都是一个blk_dev_struct数据结构,定义在include/linux/blkdev.h中:
struct blk_dev_struct {
/*
*主体为操作请求队列request_queue
*/
request_queue_t request_queue;
/*
*queue非0时调用此函数来找到具体设备的请求队列
*针对同一主设备号的多项设备而设
*/
queue_proc *queue;
/*
*data提供辅助性信息,queue会利用到
*/
void *data;
};
所有块设备的描述符放在blk_dev表中,由块设备的主设备号索引。
解析ll_rw_block函数
void ll_rw_block(int rw, int nr, struct buffer_head * bhs[])
{
unsigned int major;
int correct_size;
int i;
if (!nr)
return;
/*通过bh的虚拟设备标识符求出主设备号*/
major = MAJOR(bhs[0]->b_dev);
/* Determine correct block size for this device. */
correct_size = BLOCK_SIZE;
if (blksize_size[major]) {
i = blksize_size[major][MINOR(bhs[0]->b_dev)]; /*确定设备块长*/
if (i)
correct_size = i;
}
/* Verify requested block sizes. */
/*扫描请求块描述符,其块长必须为设备块长的整数倍*/
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[ i ];
if (bh->b_size % correct_size) {
printk(KERN_NOTICE "ll_rw_block: device %s: "
"only %d-char blocks implemented (%u)\n",
kdevname(bhs[0]->b_dev),
correct_size, bh->b_size);
goto sorry;
}
}
/*
*如果操作是write,确定块设备不是只读的
*内核中有个二维数组ro_bits[MAX_BLKDEV][8](drivers/block/ll_rw_blk.c中定义)
*每个设备在此数组中有标志位,通过iotcl可以将一个标志位设置为0/1
*is_read_only根据设备号就差这个数组中的标志位是否为1来检查读写允许情况
*/
if ((rw & WRITE) && is_read_only(bhs[0]->b_dev)) {
printk(KERN_NOTICE "Can't write to read-only device %s\n",
kdevname(bh s[0]->b_dev));
goto sorry;
}
/*对于bhs数组中的每个bh*/
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[ i ];
/*
* don't lock any more buffers if we are above the high
* water mark. instead start I/O on the queued stuff.
*/
/*
*如果等待传送的块数量大于某个上限值
*则运行块设备驱动程序所生成的任务队列,直到低于某个下限
*/
if (atomic_read(&queued_sectors) >= high_queued_sectors) {
run_task_queue(&tq_disk);
wait_event(blk_buffers_wait,
atomic_read(&queued_sectors) < low_queued_sectors);
}
/* Only one thread can actually submit the I/O. */
/*
*BH_Lock标志置位,锁定将要传送的块,返回原值
*如果其他内核线程设置了它则跳过
*/
if (test_and_set_bit(BH_Lock, &bh->b_state))
continue;
/* We have the buffer lock */
/* IO操作结束则更新缓冲区首部 */
bh->b_end_io = end_buffer_io_sync;
switch(rw) {
/*写操作*/
case WRITE:
/*
* 清除bh的BH_Dirty标志,并返回原值
* 如果原值为0则执行b_end_io方法,跳过此块,不用写了
*/
if (!atomic_set_buffer_clean(bh))
/* Hmmph! Nothing to write */
goto end_io;
/*
*在上面测试并清除bh的BH_Dirty标志时,返回原值为1,则是需要要写
*既然要写了(逻辑上)把BH_Dirty置了0,而BH_Locked之前置1
*__mark_buffer_clean调用refile_buffer把bh转移到加锁bh的LRU队列中
*/
__mark_buffer_clean(bh);
break;
/*预读为空操作*/
case READA:
/*读操作*/
case READ:
/*
* 检查BH_Uptodate标志,如果要读的块数据已经被更新
* 则调用b_end_io跳过此块
*/
if (buffer_uptodate(bh))
/* Hmmph! Already have it */
goto end_io;
break;
default:
BUG();
end_io:
/* 调用b_end_io方法,继承BH_Uptodate状态 */
bh->b_end_io(bh, test_bit(BH_Uptodate, &bh->b_state));
continue;
}
/* 向块设备驱动程序发出读写请求 */
submit_bh(rw, bh);
}
return;
sorry:
/* Make sure we don't get infinite dirty retries.. */
for (i = 0; i < nr; i++)
mark_buffer_clean(bhs[ i ]);
}
static void end_buffer_io_sync(struct buffer_head *bh, int uptodate)
{
mark_buffer_uptodate(bh, uptodate);
unlock_buffer(bh);
put_bh(bh);
}
void submit_bh(int rw, struct buffer_head * bh)
{
/* 检查BH_Lock字段,调用submit_bh之前缓冲块必须上锁 */
if (!test_bit(BH_Lock, &bh->b_state))
BUG();
/*
* 设置BH_Req字段,表示块已被请求,正在执行IO请求,
* 互斥其他进程请求该块
*/
set_bit(BH_Req, &bh->b_state);
/*
* First step, 'identity mapping' - RAID or LVM might
* further remap this.
*/
/*用虚拟设备标识符bh->b_dev 初始bh->b_rdev实际设备标识符 */
bh->b_rdev = bh->b_dev;
/* 求逻辑块的物理扇区号*/
bh->b_rsector = bh->b_blocknr * (bh->b_size >> 9);
/* 向块设备发出读写请求 */
generic_make_request(rw, bh);
switch (rw) {
case WRITE:
kstat.pgpgout++;
break;
default:
kstat.pgpgin++;
break;
}
}