分类: LINUX
2010-04-28 21:44:44
4.块设备驱动的I/O请求
Ø 使用请求队列
块设备驱动请求函数的原型为:
void request(request_queue_t* q);
这个函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才会调用这个函数.请求函数可以在没有完成请求队列中的所有请求的情况下返回,甚至它一个请求不完成都可以返回.但对大部分设备而言,一般会在请求函数中处理完所有请求后才返回.
static void xxx_request(request_queue_t* q){
struct request* req;
//elv_next_request()用于获取队列中第一个未完成的请求
//end_request()会将请求从请求队列中剥离
while((req = elv_next_request(q)) != NULL){
struct xxx_dev* dev = req->rq_disk->private_data;
if(!blk_fs_request(req)){//如果不是文件系统请求,直接清除,调用end_request().
printk(KERN_NOTICE "Skip non-fs request\n");
end_request(req, 0);//通知请求处理失败.第二个参数0代表请求失败.
continue;
}
xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,\
rq_data_dir(req));//处理这个请求.
end_request(req, 1);//通知成功完成这个请求.1,表示请求成功.
}
}
static void xxx_transfer(struct xxx_dev* dev, unsigned long sector,\
unsigned long nsect, char* buffer, int write){
unsigned long offset = sector * KERNEL_SECTOR_SIZE;
unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;
if((offset + nbytes) > dev->size){
printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
return ;
}
if(write)
write_dev(offset, buffer, nbytes);//向设备写nbytes个字节的数据.
else
read_dev(offset, buffer, nbytes);//从设备读取nbytes个字节的数据.
}
下面是end_that_request_first()的源码和分析
//end_request()源码清单
void end_request(struct request* req, int uptodate){
//当设备完成一个IO请求的部分或全部扇区传输后,必须告知块设备层.end_that_request_first
//原型为:int end_that_request_first(struct request* req, int success, int count);
//此函数高数块设备层,已经完成count各扇区的传送.返回表示所有扇区传送完毕.
if(!end_that_request_first(req, uptodate, req->hard_cur_sectors)){
//add_disk_randomness()作用是使用块IO请求的定时来给系统的随机数池贡献熵,它不影响
//块设备,但仅当磁盘的操作时间是真正随机的时候,才调用它.
add_disk_randomness(req->rq_disk);
blkdev_dequeue_request(req);//清除此请求.
end_that_request_last(req);//通知等待此请求的对象,此请求已经完成
}
}
下面是一个更复杂的请求函数,分别遍历了request,bio,以及bio中的segment
//请求函数遍历请求,bio和段
static void xxx_full_request(request_queue_t* q){
struct request* req;
int sectors_xferred;
struct xxx_dev* dev = q->queuedata;
//XXX 遍历每个请求
while((req = elv_next_request(q)) != NULL){
if(!blk_fs_request(req)){
printk(KERN_NOTICE "Skip non-fs request\n");
end_request(req, 0);
continue;
}
sectors_xferred = xxx_xfer_reqeust(dev, req);
if(!end_that_request_first(req, 1, sectors_xferred)){
blkdev_dequeue_reqeust(req);
end_that_request_last(req);
}
}
}
//XXX 请求处理
static int xxx_xfer_request(struct xxx_dev* dev, struct reqeust* req){
struct bio* bio;
int nsect = 0;
//遍历请求中的每个bio
rq_for_each_bio(bio, req){
xxx_xfer_bio(dev, bio);
nsect += bio->bi_size / KERNEL_SECTOR_SIZE;
}
return nsect;
}
//XXX bio处理
static int xxx_xfer_bio(struct xxx_dev* dev, struct bio* bio){
int i;
struct bio_vec* bvec;
sector_t sector = bio->bi_sector;
//遍历每一个segment
bio_for_each_segment(bvec, bio, i){
char* buffer = __bio_kmap_atomic(bio, i, KM_USER0);
xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer,\
bio_data_dir(bio) == WRITE);
sector += bio_cur_sectors(bio);
__bio_kunmap_atomic(bio, KMUSER0);
}
return 0;
}
Ø 不使用请求队
对于机械的磁盘设备而言,请求队列有助于提高系统性能.但对于如SD卡,RAM盘等可随机访问的块设备,请求队列无法获益.对于这些设备,块层支持"无队列"的操作模式,驱动为此必须提供一个"制造请求"函数(注意:这不是请求函数哦),"制造请求"函数的原型为:
typedef int (make_request_fn) (request_queue_t* q, struct bio* bio);
此函数的第一个参数,是一个"请求队列",但实际并不包含任何请求.所以主要参数是bio,它表示一个或多个要传送的缓冲区.此函数或直接进行传输,或将请求重定向给其他设备.在处理完成之后,应使用bio_endio()通知处理结束.bio_endio()原型如下:
void bio_endio(struct bio* bio, unsigned int byetes, int error);
bytes是已经传送的字节数(注意:bytes≤bio->bi_size),这个函数同时更新了bio的当前缓冲区指针.当设备进一步处理bio后,驱动应再次调用bio_endio(),如不能完成请求,将错误码赋给error参数,并在函数中得以处理.此函数无论处理IO成功与否都返回0,如果返回非零值,则bio将再次被提交:
static int xxx_make_request(request_queue_t* q, struct bio* bio){
struct xxx_dev* dev = q->queuedata;
int status = xxx_xfer_bio(dev, bio);//处理bio
bio_endio(bio, bio->bi_size, status);//报告结束
return 0;
}
说明:这里要指出,如果是无队列的IO请求处理,其加载模块应使用<<代码1:使用blk_alloc_queue函数完成块设备驱动的模块加载模板>>,否则应使用<<代码2:使用blk_init_queue函数完成块设备驱动的模块加载模板>>.代码1与代码2见:Linux块设备驱动(3)--块设备驱动相关模块模板