您的位置:首页 > 运维架构 > Linux

linux块设备驱动之请求过程剖析

2015-07-12 21:31 411 查看
当设备完成在一个I/O请求的部分或者全部的扇区时,必须调用下面的函数通知块设备子系统:int end_that_request_first(struct request *req, int success, int count);该函数告诉块设备代码:驱动程序从前一次结束的地方开始,完成了规定数目的扇区的传输。请注意必须报告从第一个扇区到最后一个扇区的完成情况;
end_that_request_first 的返回值表明该请求中的所有扇区是否被传输。返回0表示把count个扇区已经传输完成,此时必须调用blkdev_dequeue_request函数删除请求,并把其传递给 void end_that_request_last(struct request *req);

end_that_request_last 通知任何等待已经完成请求的对象,并重复利用该request结构。

dev->q = blk_init_queue(sbull_full_request, &dev->lock);

static void sbull_full_request(request_queue_t *q)
 {
     struct request *req;
     int sectors_xferred;
     struct sbull_dev *dev = q->queuedata;
 
     // elv_next_request函数表示从队列中获取下一个req
     while ((req = elv_next_request(q)) != NULL){
         if (!blk_fs_request(req)) {
             printk(KERN_NOTICE "Skip non-fs requeest!\n");
             end_request(req, 0);
             continue;
         }
         // sbull_xfer_request函数才是真正处理req请求的
         sectors_xferred = sbull_xfer_request(dev, req);
         if (! end_that_request_first(req, 1, sectors_xferred)){
             blkdev_dequeue_request(req);
             end_that_request_last(req);
         }
     }   
 }  

void end_request(struct request *req, int uptodate)
 {
     if (! end_that_request_first(req, uptodate, req->hard_cur_sectors)){
         add_disk_randomness(req->rq_disk);
         blkdev_dequeue_request(req);
         end_that_request_last(req);
     }
 }


下面的函数是处理一个req,其中最主要的是rq_for_each_bio宏,该宏是遍历req请求中的每一个bio;而sbull_xfer_bio()函数是处理一个bio结构的函数;
static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
 {   
     struct bio *bio;
     int nset = 0;
 
     rq_for_each_bio(bio, req){// 遍历req中的每一个bio
         sbull_xfer_bio(dev, bio);// 该函数处理bio
         nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
     }
     return nsect;
 }
下面的函数sbull_xfer_bio是处理一个bio结构,而bio结构主要的是数组结构,所以bio_for_each_segment宏是遍历bio中的数组结构的,其中bvec指向当前的bio_vec入口,i是当前的段号。使用这些值来建立DMA传输。
如果需要直接访问这些页,需要首先保证正确的内核虚拟地址是存在的,可以使用下面的函数进行映射: char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);这个函数直接映射了当前段号为i的bio_vec中的缓冲区。

bio_for_each_segment宏遍历了bio结构中的每一个段,获取内核虚拟地址以访问缓冲地址,然后调用sbull_transfer函数实现真正的数据操作。

static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
 {
     int i;
     struct bio_vec *bvec;
     sector_t sector = bio->bi_sector;
 
     bio_for_each_segment(bvec, bio, i){
         char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
         sbull_transfer(dev, sector, bio_cur_sector(bio),
                 buffer, bio_data_dir(bio) == WRITE);
         sector += bio_cur_sectors(bio);
         __bio_kunmap_atomic(bio, KM_USER0);
     }
     return 0;
 }

// sector开始扇区的索引号,指的是512字节的扇区,如果是2048字节的扇区,则要sector/4再传递
// nsect 表示要传递多少个扇区; buffer 数据缓存的地址指针;write 表示数据传递的方向,即:read/write;
 static void sbull_transfer(struct sbull_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)
         memcpy(dev->data + offset, buffer, nbytes);
     else
         memcpy(buffer, dev->data + offset, nbytes);
 }
转载地址: linux块设备驱动之请求过程剖析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: