首页
登录 | 注册

块设备驱动笔记

块设备驱动


一:模块的加载与卸载

在块设备驱动的模块加载函数中通常需要完成如下工作:
① 分配、初始化请求队列,绑定请求队列和请求函数。
② 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。
③ 注册块设备驱动。

 

下面是2种不同情况下的块设备驱动模块加载函数模板

代码1.1使用blk_alloc_queue()分配请求队列并使用blk_queue_make_request()绑定“请求队列”和“制造请求”函数.

 

代码2.1使用blk_init_queue()初始化请求队列并绑定请求队列与请求处理函数
代码1.1 块设备驱动模块加载函数模板(使用blk_alloc_queue)
1  static int __init xxx_init(void)
2  {
3    //分配gendisk
4    xxx_disks = alloc_disk(1);
5    if (!xxx_disks)
6    {
7      goto out;
8    }

10   //块设备驱动注册
11   if (register_blkdev(XXX_MAJOR, "xxx"))
12   {
13     err =  - EIO;
14     goto out;
15   }
16
17   //“请求队列”分配
18   xxx_queue = blk_alloc_queue(GFP_KERNEL);
19   if (!xxx_queue)
20   {
21     goto out_queue;
22   }
23
24   blk_queue_make_request(xxx_queue, &xxx_make_request); //绑定“制造请求”函数
25   blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺寸设置
26
27   //gendisk初始化
28   xxx_disks->major = XXX_MAJOR;
29   xxx_disks->first_minor = 0;
30   xxx_disks->fops = &xxx_ops;
31   xxx_disks->queue = xxx_queue;
32   sprintf(xxx_disks->disk_name, "xxx%d", i);
33   set_capacity(xxx_disks, xxx_size); //xxx_size512bytes为单位
34   add_disk(xxx_disks); //添加gendisk
35
36   return 0;
37   out_queue: unregister_blkdev(XXX_MAJOR, "xxx");
38   out: put_disk(xxx_disks);
39   blk_cleanup_queue(xxx_queue);
40
41   return  - ENOMEM;
42 }


代码2.1块设备驱动模块加载函数模板(使用blk_init_queue)
1  static int __init xxx_init(void)
2  {…………
3    //块设备驱动注册
4    if (register_blkdev(XXX_MAJOR, "xxx"))
5    {
6      err =  - EIO;
7      goto out;
8    }

10   //请求队列初始化
11   xxx_queue = blk_init_queue(xxx_request, xxx_lock);
12   if (!xxx_queue)
13   {
14     goto out_queue;
15   }
16  
17   blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺寸设置
18  
19   //gendisk初始化
20   xxx_disks->major = XXX_MAJOR;
21   xxx_disks->first_minor = 0;
22   xxx_disks->fops = &xxx_ops;
23   xxx_disks->queue = xxx_queue;
24   sprintf(xxx_disks->disk_name, "xxx%d", i);
25   set_capacity(xxx_disks, xxx_size *2);
26   add_disk(xxx_disks); //添加gendisk
27
28   return 0;
29   out_queue: unregister_blkdev(XXX_MAJOR, "xxx");
30   out: put_disk(xxx_disks);
31   blk_cleanup_queue(xxx_queue);
32
33   return  - ENOMEM;
34 }
在块设备驱动的模块卸载函数中通常需要与模块加载函数相反的工作:
① 清除请求队列。
② 删除gendisk和对gendisk的引用。
③ 删除对块设备的引用,注销块设备驱动。

块设备驱动模块卸载函数模板
1  static void __exit xxx_exit(void)
2  {
3    if (bdev)
4    {
5      invalidate_bdev(xxx_bdev, 1);
6      blkdev_put(xxx_bdev);
7    }
8    del_gendisk(xxx_disks); //删除gendisk
9    put_disk(xxx_disks);
10   blk_cleanup_queue(xxx_queue[i]); //清除请求队列
11   unregister_blkdev(XXX_MAJOR, "xxx"); //注销块设备驱动
12 }

 

二:block_device_operation操作

块设备的打开与释放
块设备驱动的open()和release()函数并非是必须的,1个简单的块设备驱动可以不提供open()和release()函数。
块设备驱动的open()函数和其字符设备驱动中的对等体非常类似,都以相关的inode和file结构体指针作为参数。当一个节点引用一个块设备时,inode->i_bdev->bd_disk 包含一个指向关联 gendisk 结构体的指针。因此,类似于字符设备驱动,我们也可以将gendisk的private_data赋给file的private_data,private_data同样最好是指向描述该设备的设备结构体xxx_dev的指针,如下代码清单。
块设备的open()函数中赋值private_data
1 static int xxx_open(struct inode *inode, struct file *filp)
2 {
3   struct xxx_dev *dev = inode->i_bdev->bd_disk->private_data;
4   filp->private_data = dev;  //赋值file的private_data
5   ...
6   return 0;
7 }
在一个处理真实的硬件设备的驱动中,open()和release()方法还应当设置驱动和硬件的状态,这些工作可能包括启停磁盘、加锁一个可移出设备和分配DMA缓冲等。

块设备驱动的ioctl函数
与字符设备驱动一样,块设备可以包含一个 ioctl()函数以提供对设备的I/O控制能力。实际上,高层的块设备层代码处理了绝大多数ioctl(),因此,具体的块设备驱动中通常不再需要实现很多ioctl命令。
代码清单给出的ioctl()函数只实现1个命令HDIO_GETGEO,用于获得磁盘的几何信息(geometry,指CHS,即Cylinder、Head、Sector/Track)。

块设备驱动的I/O控制函数模板
1  int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
2    unsigned long arg)
3  {
4    long size;
5    struct hd_geometry geo;
6    struct xxx_dev *dev = filp->private_data; //通过file->private获得设备结构体

8    switch (cmd)
9    {
10     case HDIO_GETGEO:
11       size = dev->size *(hardsect_size / KERNEL_SECTOR_SIZE);
12       geo.cylinders = (size &~0x3f) >> 6;
13       geo.heads = 4;
14       geo.sectors = 16;
15       geo.start = 4;
16       if (copy_to_user((void __user*)arg, &geo, sizeof(geo)))
17       {
18         return  - EFAULT;
19       }
20       return 0;
21   }
22
23   return  - ENOTTY; //不知道的命令
24 }

 

三:块设备驱动I/O请求处理
3.1 使用请求队列
块设备驱动请求函数的原型为:
void request(request_queue_t *queue);
这个函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才调用这个函数。
请求函数可以在没有完成请求队列中的所有请求的情况下返回,甚至它1个请求不完成都可以返回。但是,对大部分设备而言,在请求函数中处理完所有请求后再返回通常是值得推荐的方法。代码清单给出了1个简单的request()函数的例子。

块设备驱动请求函数例程
1  static void xxx_request(request_queue_t *q)
2  {
3    struct request *req;
4    while ((req = elv_next_request(q)) != NULL)
5    {
6      struct xxx_dev *dev = req->rq_disk->private_data;
7      if (!blk_fs_request(req)) //不是文件系统请求
8      {
9        printk(KERN_NOTICE "Skip non-fs request\n");
10       end_request(req, 0);//通知请求处理失败
11       continue;
12     }
13     xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,
14       rq_data_dir(req));   //处理这个请求,rq_data_dir(req)函数来确定是读还是写
15     end_request(req, 1); //通知成功完成这个请求
16   }
17 }
18
19 //完成具体的块设备I/O操作
20 static void xxx_transfer(struct xxx_dev *dev, unsigned long sector, unsigned
21   long nsect, char *buffer, int write)
22 {
23   unsigned long offset = sector * KERNEL_SECTOR_SIZE;
24   unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;
25   if ((offset + nbytes) > dev->size)
26   {
27     printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
28     return ;
29   }
30   if (write)
31   {
32     write_dev(offset, buffer, nbytes);  //向设备些nbytes个字节的数据
33   }
34   else
35   {
36     read_dev(offset, buffer, nbytes); //从设备读nbytes个字节的数据
37   }
38 }
上述代码第4行使用elv_next_request()获得队列中第一个未完成的请求,end_request()会将请求从请求队列中剥离。第7行判断请求是否为文件系统请求,如果不是,则直接清除,调用end_request(),传递给end_request()的第2个参数为0意味着处理该请求失败。而第15行传递给end_request()的第2个参数为1意味着该请求处理成功。


end_request()函数非常重要
end_request()函数源代码
1  void end_request(struct request *req, int uptodate)
2  {
3   
4    if (!end_that_request_first(req, uptodate, req->hard_cur_sectors))
5    {
6      add_disk_randomness (req->rq_disk);
7      blkdev_dequeue_request (req);
8      end_that_request_last(req);
9    }
10 }
当设备已经完成1个I/O请求的部分或者全部扇区传输后,它必须通告块设备层,上述代码中的第4行完成这个工作。end_that_request_first()函数的原型为:
int end_that_request_first(struct request *req, int success, int count);
这个函数告知块设备层,块设备驱动已经完成count个扇区的传送。end_that_request_first()的返回值是一个标志,指示是否这个请求中的所有扇区已经被传送。返回值为0表示所有的扇区已经被传送并且这个请求完成,之后,我们必须使用 blkdev_dequeue_request()来从队列中清除这个请求。最后,将这个请求传递给end_that_request_last()函数:
void end_that_request_last(struct request *req);
end_that_request_last()通知所有正在等待这个请求完成的对象请求已经完成并回收这个请求结构体。
第6行的add_disk_randomness()函数的作用是使用块 I/O 请求的定时来给系统的随机数池贡献熵,它不影响块设备驱动。但是,仅当磁盘的操作时间是真正随机的时候(大部分机械设备如此),才应该调用它。


代码清单3.1给出了1个更复杂的请求函数,它进行了3层遍历:遍历请求队列中的每个请求;遍历请求中的每个bio;遍历bio中的每个段。
代码清单3.1 请求函数遍历请求、bio和段
1  static void xxx_full_request(request_queue_t *q)
2  {
3    struct request *req;
4    int sectors_xferred;
5    struct xxx_dev *dev = q->queuedata;
6    /* 遍历每个请求 */
7    while ((req = elv_next_request(q)) != NULL)
8    {
9      if (!blk_fs_request(req))
10     {
11       printk(KERN_NOTICE "Skip non-fs request\n");
12
13       end_request(req, 0);
14       continue;
15     }
16     sectors_xferred = xxx_xfer_request(dev, req);
17     if (!end_that_request_first(req, 1, sectors_xferred))
18     {
19       blkdev_dequeue_request(req);
20       end_that_request_last(req);
21     }
22   }
23 }
24 /* 请求处理 */
25 static int xxx_xfer_request(struct xxx_dev *dev, struct request *req)
26 {
27   struct bio *bio;
28   int nsect = 0;
29   /* 遍历请求中的每个bio */
30   rq_for_each_bio(bio, req)
31   {
32     xxx_xfer_bio(dev, bio);
33     nsect += bio->bi_size / KERNEL_SECTOR_SIZE;
34   }
35   return nsect;
36 }
37 /* bio处理 */
38 static int xxx_xfer_bio(struct xxx_dev *dev, struct bio *bio)
39 {
40   int i;
41   struct bio_vec *bvec;
42   sector_t sector = bio->bi_sector;
43
44   /* 遍历每1段 */
45   bio_for_each_segment(bvec, bio, i)
46   {
47     char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
48     xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio)
49       == WRITE);
50     sector += bio_cur_sectors(bio);
51     __bio_kunmap_atomic(bio, KM_USER0);
52   }
53   return 0;
54 }


3.2不使用请求队列
使用请求队列对于一个机械的磁盘设备而言的确有助于提高系统的性能,但是对于许多块设备,如数码相机的存储卡、RAM盘等完全可真正随机访问的设备而言,无法从高级的请求队列逻辑中获益。对于这些设备,块层支持“无队列”的操作模式,为使用这个模式,驱动必须提供一个“制造请求”函数,而不是一个请求函数,“制造请求”函数的原型为:
typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
上述函数的第1个参数仍然是“请求队列”,但是这个“请求队列”实际不包含任何请求。因此,“制造请求”函数的主要参数是bio结构体,这个bio结构体表示1个或多个要传送的缓冲区。“制造请求”函数或者直接进行传输,或者把请求重定向给其它设备。
在“制造请求”函数中处理bio的方式与13.6.1节中描述的完全一致,但是在处理完成后应该使用bio_endio()函数通知处理结束:
void bio_endio(struct bio *bio, unsigned int bytes, int error);
参数bytes 是已经传送的字节数,它可以比这个bio所代表的字节数少,这意味着“部分完成”,同时bio结构体中的当前缓冲区指针需要更新。当设备进一步处理这个bio后,驱动应该再次调用 bio_endio(),如果不能完成这个请求,应指出一个错误,错误码赋值给error参数。
不管对应的I/O处理成功与否,“制造请求”函数都应该返回0。如果“制造请求”函数返回一个非零值,bio 将被再次提交。
代码清单4.1给出了1个“制造请求”函数的例子。
代码清单4.1 “制造请求”函数例程
1 static int xxx_make_request(request_queue_t *q, struct bio *bio)
2 {
3   struct xxx_dev *dev = q->queuedata;
4   int status;
5   status = xxx_xfer_bio(dev, bio); //处理bio
6   bio_endio(bio, bio->bi_size, status); //通告结束
7   return 0;
8 }
为了使用无队列的I/O请求处理,驱动模块加载函数应遵循代码清单1.1的模板而非2.1的模板;而使用请求队列时,驱动模块加载函数应遵循代码清单2.1的模板。

 

四:实例分析

块设备驱动模板与RAMDISK设备驱动的映射
代码清单4.1给出了RAMDISK设备驱动的模块加载与卸载函数,实现的功能与模块是一致的。
代码清单4.1 RAMDISK设备驱动的模块加载与卸载函数
1  static int __init rd_init(void)
2  {
3    int i;
4    int err =  - ENOMEM;
5    //调整块尺寸
6    if (rd_blocksize > PAGE_SIZE || rd_blocksize < 512 || (rd_blocksize &
7      (rd_blocksize - 1)))
8    {
9      printk("RAMDISK: wrong blocksize %d, reverting to defaults\n", rd_blocksize) ;
10      
11     rd_blocksize = BLOCK_SIZE;
12   }
13   //分配gendisk
14   for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++)
15   {
16     rd_disks[i] = alloc_disk(1); //分配gendisk
17     if (!rd_disks[i])
18       goto out;
19   }
20   //块设备注册
21   if (register_blkdev(RAMDISK_MAJOR, "ramdisk"))
22   //注册块设备
23   {
24     err =  - EIO;
25     goto out;
26   }
27
28   devfs_mk_dir("rd"); //创建devfs目录
29
30   for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++)
31   {
32     struct gendisk *disk = rd_disks[i];
33     //分配并绑定请求队列与“制造请求”函数
34     rd_queue[i] = blk_alloc_queue(GFP_KERNEL);
35     if (!rd_queue[i])
36       goto out_queue;
37
38    blk_queue_make_request(rd_queue[i], &rd_make_request);   //绑定“制造请求”函数
39     blk_queue_hardsect_size(rd_queue[i], rd_blocksize); //硬件扇区尺寸设置
40
41     //初始化gendisk
42     disk->major = RAMDISK_MAJOR;
43     disk->first_minor = i;
44     disk->fops = &rd_bd_op;
45     disk->queue = rd_queue[i];
46     disk->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO;
47     sprintf(disk->disk_name, "ram%d", i);
48     sprintf(disk->devfs_name, "rd/%d", i);
49     set_capacity(disk, rd_size *2);
50     add_disk(rd_disks[i]); //添加gendisk
51   }
52
53   // rd_size以kB为单位
54   printk("RAMDISK driver initialized: "
55     "%d RAMDISKs of %dK size %d blocksize\n",
56     CONFIG_BLK_DEV_RAM_COUNT,rd_size, rd_blocksize);
57
58   return 0;
59   out_queue: unregister_blkdev(RAMDISK_MAJOR, "ramdisk");
60   out:
61   while (i--)
62   {
63     put_disk(rd_disks[i]);
64     blk_cleanup_queue(rd_queue[i]);
65   }
66   return err;
67 }
68
69 static void __exit rd_cleanup(void)
70 {
71   int i;
72
73   for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++)
74   {
75     struct block_device *bdev = rd_bdev[i];
76     rd_bdev[i] = NULL;
77     if (bdev)
78     {
79       invalidate_bdev(bdev, 1);
80       blkdev_put(bdev);
81     }
82     del_gendisk(rd_disks[i]); //删除gendisk
83     put_disk(rd_disks[i]); //释放对gendisk的引用
84     blk_cleanup_queue(rd_queue[i]); //清除请求队列
85   }
86   devfs_remove("rd");
87   unregister_blkdev(RAMDISK_MAJOR, "ramdisk"); //块设备注销
88 }

 

RAMDISK设备驱动block_device_operations及成员函数
RAMDISK提供block_device_operations结构体中2个成员函数open()和ioctl()的实现,代码清单4.3给出了RAMDISK设备驱动的block_device_operations结构体定义及open()和ioctl()函数。
 代码清单4.3RAMDISK设备驱动block_device_operations结构体及成员函数
1  static struct block_device_operations rd_bd_op =
2  {
3    .owner = THIS_MODULE,
4    .open = rd_open,
5    .ioctl = rd_ioctl,
6  };

8  static int rd_open(struct inode *inode, struct file *filp)
9  {
10  unsigned unit = iminor(inode);//获得次设备号
11
12  if (rd_bdev[unit] == NULL) {
13   struct block_device *bdev = inode->i_bdev;//获得block_device结构体指针
14   struct address_space *mapping; //地址空间
15   unsigned bsize;
16   gfp_t gfp_mask;
17     /* 设置inode成员 */
18   inode = igrab(bdev->bd_inode);
19   rd_bdev[unit] = bdev;
20   bdev->bd_openers++;
21   bsize = bdev_hardsect_size(bdev);
22   bdev->bd_block_size = bsize;
23   inode->i_blkbits = blksize_bits(bsize);
24   inode->i_size = get_capacity(bdev->bd_disk)<<9;
25
26   mapping = inode->i_mapping;   
27   mapping->a_ops = &ramdisk_aops;
28   mapping->backing_dev_info = &rd_backing_dev_info;
29   bdev->bd_inode_backing_dev_info = &rd_file_backing_dev_info;
30
31   gfp_mask = mapping_gfp_mask(mapping);
32   gfp_mask &= ~(__GFP_FS|__GFP_IO);
33   gfp_mask |= __GFP_HIGH;
34   mapping_set_gfp_mask(mapping, gfp_mask);
35  }
36
37  return 0;
38 }
39
40 static int rd_ioctl(struct inode *inode, struct file *file,
41    unsigned int cmd, unsigned long arg)
42 {
43  int error;
44  struct block_device *bdev = inode->i_bdev;
45
46  if (cmd != BLKFLSBUF) /* 不是flush buffer cache 命令 */
47   return -ENOTTY;
48  /* 刷新buffer cache */
49  error = -EBUSY;
50  down(&bdev->bd_sem);
51  if (bdev->bd_openers <= 2) {
52   truncate_inode_pages(bdev->bd_inode->i_mapping, 0);
53   error = 0;
54  }
55  up(&bdev->bd_sem);
56  return error;
57 }

 

RAMDISK I/O请求处理
鉴于RAMDISK是一种完全随机设备,其驱动中宜使用“制造请求”函数而非请求函数,这个函数的实现如代码清单5.1。
代码清单5.1 RAMDISK设备驱动“制造请求”函数
1  static int rd_make_request(request_queue_t *q, struct bio *bio)
2  {
3    struct block_device *bdev = bio->bi_bdev;
4    struct address_space *mapping = bdev->bd_inode->i_mapping;
5    sector_t sector = bio->bi_sector;
6    unsigned long len = bio->bi_size >> 9;
7    int rw = bio_data_dir(bio);//数据传输方向:读/写?
8    struct bio_vec *bvec;
9    int ret = 0, i;
10
11   if (sector + len > get_capacity(bdev->bd_disk))
12   //超过容量
13     goto fail;
14
15   if (rw == READA)
16     rw = READ;
17   //遍历每个段
18   bio_for_each_segment(bvec, bio, i)
19   {
20     ret |= rd_blkdev_pagecache_IO(rw, bvec, sector, mapping);
21     sector += bvec->bv_len >> 9;
22   }
23   if (ret)
24     goto fail;
25
26   bio_endio(bio, bio->bi_size, 0); //处理结束
27   return 0;
28   fail: bio_io_error(bio, bio->bi_size);
29   return 0;
30 }


相关文章


2020 unjeep.com webmaster#unjeep.com
12 q. 0.012 s.
京ICP备10005923号