分享

块设备驱动初步

 WUCANADA 2012-05-09
块设备驱动初步
2010-05-02 19:55
  单击,返回主页,查看更多内容

  本文的素材以及源代码(稍有改动)均来源于《Linux Device Driver》,因此本文可视为该书块设备驱动相关章节的阅读理解。

  一、体验块设备驱动(单击下载驱动源码)

  本驱动模拟了一个硬盘。想想你去中关村(不过我更喜欢去成都@世界)买了一个硬盘,迫不及待地安装在你的Linux机器上,你怎么样才能使用这个新硬盘呢?当然要先把它驱动起来。

  1、make生成sbull.ko后加载驱动:sudo insmod sbull.ko

  2、对硬盘分区 执行suod fdisk /dev/sbulla

  输入x,进入高级菜单

  输入h,change number of heads 为4

  输入c,change number of cylinders 为4

  输入r,退回主菜单

  顺次输入n、p、1、1、4,创建一个主分区占有cylinders 1-4

  输入w,保存并退出

  3、输入 sudo mkfs.ext2 /dev/sbulla1 在硬盘分区上格式化ext2文件系统

  4、挂载新硬盘上的分区 输入 mkdir testdir创建空目录

  输入mount –t ext2 /dev/sbulla1 ./testdir

  之后,你就可以通过testdir目录来访问新硬盘分区了。

  二、块设备驱动框架介绍

  1、驱动接口简介 上层接口

  用户接口

  块设备 b:/dev/sda、/dev/sda2、 /dev/ram0

  用户空间程序

  mkfs、mount、fdisk;

  直接调用open、release、ioctl接口

  读写时,不直接调用读写接口,而是将读写request提交给OS的block layer层

  操作系统接口

  block layer I/O scheduler决定需要执行磁盘I/O时,调用驱动的读写接口

  抽象物理设备为 cylinder、header、sector组成,视其为编号从0开始的flat型sector集合。(注1)

  总假定物理设备一个sector大小为512byte(注2)

  下层接口

  抽象物理设备为编号从0开始的flat型sector集合,但转换sector大小为物理设备的实际值,通过物理寄存器的读写,将数据写入指定的sector位置

  由物理设备的中断(读写完成)来唤醒用户进程(或内核线程)

  注1: 借助minor number辨别partition编号(也可能是整个磁盘),再借助分区表决定分区的起始sector号

  注2: #define KERNEL_SECTOR_SHIFT 9 #define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT)

  2、块设备结构体

  块设备结构体 struct gendisk 是块设备驱动中最重要的数据结构,它在操作系统和驱动中代表一个物理磁盘。其主要字段有: major:磁盘对应的主设备号,出现在/proc/devices中。register_blkdev(sbull_major, "sbull");

  first_minor:磁盘对应的第1个次设备号。dev->gd->first_minor = which*SBULL_MINORS;

  minors :磁盘拥有的次设备号的总数。dev->gd = alloc_disk(SBULL_MINORS);

  disk_name:磁盘的名称。出现在/proc/partitions中

  fops:块设备驱动中的功能函数。包括:open\release\media_change\revalidate_disk\ioctl等

  queue:OS回调读写函数时使用的request queue队列(它绑定了读写函数)

  private_data:私有数据,常用于存放包裹设备结构体

  capacity:512字节大小的sector的总数量

  set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));

  3、块设备结构体注册 申请major number

  sbull_major = register_blkdev(sbull_major, "sbull"); //sbull出现在/proc/devices中

  分配(初始生成)块设备结构体

  dev->gd = alloc_disk(SBULL_MINORS); //同时也关联设备号数量

  将块设备结构体与设备号、设备操作函数、读写队列关联

  dev->gd->major = sbull_major; //关联主设备号

  dev->gd->first_minor = which*SBULL_MINORS; //关联次设备号

  dev->gd->fops = &sbull_ops; //关联功能函数

  dev->queue = blk_init_queue(sbull_request, &dev->lock);

  dev->gd->queue = dev->queue; //关联读写队列

  将块设备结构体注册进OS

  add_disk(dev->gd);

  4、块设备结构体注销 将块设备结构体从OS中注销

  del_gendisk(dev->gd);

  销毁块设备结构体(移除对kobject的最后ref.,释放结构体内存)

  put_disk(dev->gd);

  释放major number

  unregister_blkdev(sbull_major, "sbull");

  5、块设备的简单读写-原理 用户程序或内核组件提出读写request时,会将该request提交给block layer层

  block layer层构造request结构,并将其链入块设备结构体绑定的request queue中

  在适当的时候,block layer层回调request queue中绑定的驱动中的读写函数,并将request queue的指针作为参数传给驱动中的读写函数

  驱动中 的读写函数根据传给它的request queue的指针,从request queue中取出一个request,根据request中指定的方向(读或写)、数据在内存中的位置、在设备上的位置(起始sector编号)、传输数 据量的大小(sector数量),完成物理读写

  驱动通知block layer层实际完成的读写量,block layer层据此更新其内部各个数据结构;并告知驱动是否整个request已经处理完成,若是,驱动则负责将request从request queue中摘下,并释放request结构的内存,唤醒等待该request完成的所有进程

  6、读写队列结构体-注册与注销 分配(初始生成)读写队列结构体,并与读写函数绑定

  dev->queue = blk_init_queue(sbull_request, &dev->lock);

  block layer在回调读写函数sbull_request时,会先获得自旋锁dev->lock,这样block layer就可以与驱动的其它函数共享相同的临界区

  blk_queue_hardsect_size(dev->queue, hardsect_size);

  设置实际设备的扇区大小,这样block layer层就会根据实际设备能够处理的扇区大小来构造request结构体,从而不会出现实际设备处理不了的request

  dev->queue->queuedata = http://blog.soso.com/qz.q/dev

  将块设备结构体与读写队列关联

  dev->gd->queue = dev->queue;

  将读写队列结构体间接注册进OS

  add_disk(dev->gd);

  将读写队列结构体间接从OS中注销

  del_gendisk(dev->gd);

  销毁读写队列结构体(移除对kobject的最后ref.,释放结构体内存)

  blk_cleanup_queue(dev->queue);

  7、块设备的简单读写-实现

  108 static void sbull_request(request_queue_t *q)

  109 {

  110 struct request *req;

  112 while ((req = elv_next_request(q)) != NULL) { //从request queue中取出一个request,循环直到request queue中的所有request被传送完

  //因为读写队列与块设备结构体已关联,所以block layer层在将读写请求链入request queue时,能将rq_disk字段指向块设备结构体

  113 struct sbull_dev *dev = req->rq_disk->private_data;

  114 if (! blk_fs_request(req)) {

  116 end_request(req, 0);

  117 continue;

  118 }

   123 sbull_transfer(dev, req->sector, req->current_nr_sectors, //根据request中指定的方向(rq_data_dir)、数据在内存中的位置( req->buffer )、

  124 req->buffer, rq_data_dir(req)); //在设备上的位置( req->sector )、传输数据量的大小( req->current_nr_sectors ),完成物理读写

  125 end_request(req, 1); //通知block layer层;将request从request queue中摘下;释放request结构的内存,唤醒等待该request完成的所有进程

  126 }

  127 }

  void end_request(struct request *req, int uptodate)

  {

  //驱动通知block layer层实际完成的读写量,block layer层据此更新其内部各个数据结构;并告知驱动是否整个request已经处理完成

  if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) {

  add_disk_randomness(req->rq_disk);

  blkdev_dequeue_request(req); //若是,驱动则负责将request从request queue中摘下

  end_that_request_last(req); //释放request结构的内存,唤醒等待该request完成的所有进程

  }

  }

  8、简单读写的不足 一次只命令硬件传输1个数据块segment(内存中不超过1page的连续单元),其只是request中的一小部分而已

  虽然简单读写采用while循环请求elv_next_request,但block层会认为硬件可能由于某种原因不能一次性完成一个完整request的传输,因此相邻2次elv_next_request极有可能返回的是不同的request

  block layer尽了很大努力,实施电梯调度算法(drivers/block/ll_rw_block.c and elevator.c ),使得一个request结构体中包含多个在内存中离散,但在物理设备上却连续的数据块。

  先后2次elv_next_request的简单读写,使得磁头必须寻道,产生较大延迟

  简单读写忽视block layer层的工作,对同一个request结构体中包含的多个segment在物理设备上连续,不予理睬,实在是暴殄天物

  9、请求队列 一个块设备的I/O请求的序列

  跟踪未完成的块I/O请求

  允许使用多I/O调度器,以最大化性能的方式提交I/O请求给你的驱动,I/O调度器还负责合并邻近的请求

  请求队列的实现

  drivers/block/Ll_rw_block.c和elevator.c

  10、块设备的高效读写-原理与实现

  一个request结构体中包含多个在内存中离散,但在物理设备上却连续的数据块,由bio和bio_vec结构体来表示

  

  

  337 static void setup_device(struct sbull_dev *dev, int which)

  362 switch (request_mode) {

  370 case RM_FULL:

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

  374 break;

  385 }

  387 dev->queue->queuedata = http://blog.soso.com/qz.q/dev;

  409 }

  175 static void sbull_full_request(request_queue_t *q)

  176 {

  177 struct request *req;

  178 int sectors_xferred;

  179 struct sbull_dev *dev = q->queuedata;

  181 while ((req = elv_next_request(q)) != NULL) { //每次取出读写队列中的一个request

  187 sectors_xferred = sbull_xfer_request(dev, req);

  188 if (! end_that_request_first(req, 1, sectors_xferred)) {

  189 blkdev_dequeue_request(req);

  191 end_that_request_last(req, 1);

  192 }

  193 }

  194 }

  #define rq_for_each_bio(_bio, rq) \

  if ((rq->bio)) \

  for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)

  157 static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)

  158 {

  159 struct bio *bio;

  160 int nsect = 0;

  162 rq_for_each_bio(bio, req) { //while循环,每次取出req中的一个bio

  163 sbull_xfer_bio(dev, bio);

  164 nsect += bio->bi_size/KERNEL_SECTOR_SIZE; //bi_size记录一个bio中数据的总字节数

  165 }

  167 return nsect;

  168 }

  #define bio_for_each_segment(bvl, bio, i) \

  __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)

  #define __bio_for_each_segment(bvl, bio, i, start_idx) \

  for (bvl = bio_iovec_idx((bio), (start_idx)), i = (start_idx); \

  i < (bio)->bi_vcnt; \

  bvl++, i++)

  133 static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)

  134 {

  135 int i;

  136 struct bio_vec *bvec;

  137 sector_t sector = bio->bi_sector; //bi_sector记录bio中首字节应位于硬件的哪个扇区。bio中的所有segment在硬件上的sector位置全部连续

  139 /* Do each segment independently. */

  140 bio_for_each_segment(bvec, bio, i) { //while循环,每次取出bio中的一个bio_vec来传输

  141 char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); //获取bv_page的内核virtual address

  143 sbull_transfer(dev, sector, bio_cur_sectors(bio), //bio_cur_sectors(bio)求出bio当前segment的大小(sector数目)

  144 buffer, bio_data_dir(bio) == WRITE);

  145 sector += bio_cur_sectors(bio); //累进数据在硬件上的位置(sector)

  147 __bio_kunmap_atomic(bio, KM_USER0);

  149 }

  151 return 0; /* Always "succeed" */

  152 }

  11、块设备的其它操作接口fops(open、release、media_change、revalidate_disk、ioctl)

  1) open与release(本驱动可以模拟光盘从光驱中更换)

  216 static int sbull_open(struct inode *inode, struct file *filp)

  217 {

   218 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; //inode->i_bdev->bd_disk指向关联的gendisk structure

  223 if (! dev->users)

  224 check_disk_change(inode->i_bdev); //check_disk_change将导致对media_change的调用,若介质已改变,将导致调用revalidate_disk

  225 dev->users++;

  228 } media_changed不为NULL时,则会被内核API check_disk_change所调用;

  在media_changed返回true的情况下, revalidate_disk会被内核API check_disk_change所调用

  int check_disk_change(struct block_device *bdev)

  {

  struct gendisk *disk = bdev->bd_disk;

  struct block_device_operations * bdops = disk->fops;

  if (!bdops->media_changed) //media_changed为NULL

  return 0;

  if (!bdops->media_changed(bdev->bd_disk)) //media_changed不为NULL时,则会被内核API check_disk_change所调用

  return 0;

  if (__invalidate_device(bdev))

  printk("VFS: busy inodes on changed media.\n");

  if (bdops->revalidate_disk) // 在media_changed返回true的情况下,revalidate_disk不为NULL时,则会被内核API check_disk_change所调用

  bdops->revalidate_disk(bdev->bd_disk);

  if (bdev->bd_disk->minors > 1)

  bdev->bd_invalidated = 1;

  return 1;

  }

  230 static int sbull_release(struct inode *inode, struct file *filp)

  231 {

  232 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;

  235 dev->users--;

  244 } 12、media_change与revalidate_disk media_changed不为NULL时,则会被内核API check_disk_change所调用

  若磁盘介质已更改,应返回true;否则返回false

  在media_changed返回true的情况下, revalidate_disk会被内核API check_disk_change所调用

  应完成对新磁盘介质进行操作的准备工作

  249 int sbull_media_changed(struct gendisk *gd)

  250 {

  251 struct sbull_dev *dev = gd->private_data;

  253 return dev->media_change;

  254 } 260 int sbull_revalidate(struct gendisk *gd)

  261 {

  262 struct sbull_dev *dev = gd->private_data;

  264 if (dev->media_change) {

  265 dev->media_change = 0;

  266 // memset (dev->data, 0, dev->size);

  267 }

  269 } 13、ioctl 大部分的ioctl命令都已经被block layer层所截获并处理,到达不了驱动程序

  驱动ioctl函数中需要处理的命令是HDIO_GETGEO (用户,例如fdisk,要求获得磁盘的几何参数)(测试结果似乎OS并未调用ioctl,原因待查)

  291 int sbull_ioctl (struct inode *inode, struct file *filp,

  292 unsigned int cmd, unsigned long arg)

  293 {

  294 long size;

  295 struct hd_geometry geo;

  296 struct sbull_dev *dev = filp->private_data;

  298 switch(cmd) {

  299 case HDIO_GETGEO:

  301 /*

  302 * Get geometry: since we are a virtual device, we have to make

  303 * up something plausible. So we claim 16 sectors, four heads,

  304 * and calculate the corresponding number of cylinders. We set the

  305 * start of data at sector four.

  306 */

  307 // size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);

  308 size = dev->size / KERNEL_SECTOR_SIZE;

  309 geo.cylinders = (size & ~0x3f) >> 6;

  310 geo.heads = 4;

  311 geo.sectors = 16;

  312 geo.start = 4;

  313 if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))

  314 return -EFAULT;

  315 return 0;

  316 }

  318 return -ENOTTY; /* unknown command */

  319 } 单击,与作者交流

more http://www.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多