Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1176025
  • 博文数量: 101
  • 博客积分: 110
  • 博客等级: 民兵
  • 技术积分: 1842
  • 用 户 组: 普通用户
  • 注册时间: 2012-08-24 13:26
个人简介

专注linux

文章分类

全部博文(101)

文章存档

2017年(2)

2016年(12)

2015年(17)

2014年(41)

2013年(27)

2012年(3)

分类: LINUX

2015-07-27 14:47:24

这里只是说明每个函数大概是做些什么工作,用于了解块设备子系统工作的原理。

首先,系统初始化时,会调用sysinit_call()加载各个子系统,而对于块设备来说就是在,

Block/genhd.csubsys_initcall(genhd_device_init);

genhd_device_init主要做几件事情


点击(此处)折叠或打开

  1. static int __init genhd_device_init(void)
  2. {
  3.     int error = class_register(&block_class);..
  4.       
  5.     bdev_map = kobj_map_init(base_probe, &block_class_lock);
  6.       
  7.     blk_dev_init();
  8.      
  9. #ifndef CONFIG_SYSFS_DEPRECATED
  10.       
  11.     /* create top-level block dir */
  12.       
  13.     block_depr = kobject_create_and_add("block", NULL);
  14.      
  15. #endif
  16.       
  17.     return 0;
  18. }

class_register(&block_class);///sys/class下创建block设备类

kobj_map_init(base_probe, &block_class_lock);中是创建一个kobject映射域,kobj_map是一个保存了一个255目索引,以主设备号为间隔的哈希表。主要的作用是当kobj_map调用时,会匹配设备,然后把设备主设备号写进哈希表里,然后调用它的base->get,初始化为base_probe

点击(此处)折叠或打开

  1. static struct kobject *base_probe(dev_t devt, int *part, void *data)
  2. {
  3.     if (request_module("block-major-%d-%d", MAJOR(devt), MINOR(devt)) > 0)

  4.         /* Make old-style 2.4 aliases work */
  5.         request_module("block-major-%d", MAJOR(devt));

  6.     return NULL;
  7. }

这函数的主要作用是加载内核模块。

kobject_create_and_add("block", NULL);
sys下创建block目录

blk_dev_init();


点击(此处)折叠或打开

  1. int __init blk_dev_init(void)
  2. {
  3.     int i;
  4.       
  5.     kblockd_workqueue = create_workqueue("kblockd");
  6.       
  7.     if (!kblockd_workqueue)
  8.       
  9.         panic("Failed to create kblockd/n");
  10.       
  11.     request_cachep = kmem_cache_create("blkdev_requests",
  12.         sizeof(struct request), 0, SLAB_PANIC, NULL);
  13.       
  14.     blk_requestq_cachep = kmem_cache_create("blkdev_queue",
  15.         sizeof(struct request_queue), 0, SLAB_PANIC, NULL);
  16.       
  17.     for_each_possible_cpu(i)
  18.       
  19.         INIT_LIST_HEAD(&per_cpu(blk_cpu_done, i));
  20.       
  21.     open_softirq(BLOCK_SOFTIRQ, blk_done_softirq, NULL);
  22.       
  23.     register_hotcpu_notifier(&blk_cpu_notifier);
  24.       
  25.     return 0; 
  26. }

kblockd_workqueue = create_workqueue("kblockd");这函数作用是创建一个工作队列和内核处理线程,这个工作队列会在申请请求时设定的超时处理函数blk_unplug_timeout里得到调用,目的是为是为是超时时调用kblockd_schedule_work(&q->unplug_work); q->unplug_work,这里的这个函数又是在blk_init_queue时指定的,可能很多不明白,但是这里为了说明这个工作队列的作用,不得不大概调出后面的调用顺序,这样更清晰,这里只要记住,这个工作队列是为了让要暂时阻塞不接受request,但又重新恢复接受时调用的。blk_plug_device()以及blk_remove_plug()

kmem_cache_create
创建一些缓冲池,让一些请求或者请求队列在里面创建。

open_softirq(BLOCK_SOFTIRQ, blk_done_softirq, NULL);
这个函数主要是初始化了一个软件中断,具体调用是在当处理请求完成后,调用raise_softirq_irqoff(BLOCK_SOFTIRQ);然后激活,进入blk_done_softirq,最后处理指定的函数完成完成处理命令。Xxx_ finish_command(cmd);

register_hotcpu_notifier(&blk_cpu_notifier);
这个函数是为了让系统支持热插拔。


完成了子系统的始初化,就要看看设备驱动是如何跟子系统联系的。

块设备驱动要做的工作先是register_blkdev()这没什么用,就是在proc/device里显示一下。

然后就是alloc_disk(),主要作用是申请一个struct gendisk结构,并初始化必要的值

接着是add_disk()

点击(此处)折叠或打开

  1. void add_disk(struct gendisk *disk)
  2. {
  3.     struct backing_dev_info *bdi;
  4.     
  5.     disk->flags |= GENHD_FL_UP;
  6.       
  7.     blk_register_region(MKDEV(disk->major, disk->first_minor),
  8.       
  9.     disk->minors, NULL, exact_match, exact_lock, disk);
  10.       
  11.     register_disk(disk);
  12.       
  13.     blk_register_queue(disk);
  14.       
  15.     bdi = &disk->queue->backing_dev_info;
  16.       
  17.     bdi_register_dev(bdi, MKDEV(disk->major, disk->first_minor));
  18.       
  19.     sysfs_create_link(&disk->dev.kobj, &bdi->dev->kobj, "bdi");
  20.       
  21. }

这函数看似简单,却非常复杂。

blk_register_region(MKDEV(disk->major, disk->first_minor),

                                disk->minors, NULL, exact_match, exact_lock, disk);

首先这一个函数是为了保存一些信息并加载这模块到bdev_map,即子系统创建的kobject_map里。

register_disk(disk);
这函数的主要作用是创建一些目录,创建以及初始化struct block_device结构体。其中指定的文件信息保存在default_attrs里,而default_attrs又在blk_queue_ktype中。

blk_register_queue(disk);
这个函数注册请求队列(申请在驱动程序blk_init_queue),然后注册IO调试函数即所谓的电梯函数。

bdi_register_dev(bdi, MKDEV(disk->major, disk->first_minor));这函数生成一些信息文件。

也看完了驱动程序的作用,那么两者如何联系的呢?

首先应用程序通过系统调用read  write后,通过不同的文件系统,最终调用__blockdev_direct_io>>>> direct_io_worker>>>> dio_bio_submit>>> submit_bio>>> generic_make_request>>> __generic_make_request>>>           ret = q->make_request_fn(q, bio);最终调用make_request

这就是指定的make_request的作用。假如不自己定一个make_request的话,那么在驱动程序调用blk_init_queue里会有blk_queue_make_request(q, __make_request);默认指定__make_request.


点击(此处)折叠或打开

  1. static int __make_request(struct request_queue *q, struct bio *bio)
  2. {

  3.     ……

  4.     /*
  5.      * low level driver can indicate that it wants pages above a
  6.      * certain limit bounced to low memory (ie for highmem, or even
  7.      * ISA dma in theory)
  8.      */

  9.     blk_queue_bounce(q, &bio);

  10.     ……

  11.     el_ret = elv_merge(q, &req, bio);

  12.     switch (el_ret) {
  13.         case ELEVATOR_BACK_MERGE:

  14.             BUG_ON(!rq_mergeable(req));

  15.             if (!ll_back_merge_fn(q, req, bio))

  16.                 break;

  17.             blk_add_trace_bio(q, bio, BLK_TA_BACKMERGE);

  18.             req->biotail->bi_next = bio;

  19.             req->biotail = bio;

  20.             req->nr_sectors = req->hard_nr_sectors += nr_sectors;

  21.             req->ioprio = ioprio_best(req->ioprio, prio);

  22.             drive_stat_acct(req, 0);

  23.             if (!attempt_back_merge(q, req))

  24.                 elv_merged_request(q, req, el_ret);

  25.             goto out;

  26.         case ELEVATOR_FRONT_MERGE:

  27.             BUG_ON(!rq_mergeable(req));

  28.             if (!ll_front_merge_fn(q, req, bio))

  29.                 break;

  30.             blk_add_trace_bio(q, bio, BLK_TA_FRONTMERGE);

  31.             bio->bi_next = req->bio;

  32.             req->bio = bio;

  33.             /*

  34.              * may not be valid. if the low level driver said

  35.              * it didn't need a bounce buffer then it better

  36.              * not touch req->buffer either...

  37.              */

  38.             req->buffer = bio_data(bio);

  39.             req->current_nr_sectors = bio_cur_sectors(bio);

  40.             req->hard_cur_sectors = req->current_nr_sectors;

  41.             req->sector = req->hard_sector = bio->bi_sector;

  42.             req->nr_sectors = req->hard_nr_sectors += nr_sectors;

  43.             req->ioprio = ioprio_best(req->ioprio, prio);

  44.             drive_stat_acct(req, 0);

  45.             if (!attempt_front_merge(q, req))

  46.                 elv_merged_request(q, req, el_ret);

  47.             goto out;


  48.             /* ELV_NO_MERGE: elevator says don't/can't merge. */

  49.         default:

  50.             ;
  51.     }

  52. get_rq:

  53.     /*

  54.      * This sync check and mask will be re-done in init_request_from_bio(),

  55.      * but we need to set it earlier to expose the sync flag to the

  56.      * rq allocator and io schedulers.

  57.      */

  58.     rw_flags = bio_data_dir(bio);

  59.     if (sync)

  60.         rw_flags |= REQ_RW_SYNC;

  61.     /*

  62.      * Grab a free request. This is might sleep but can not fail.

  63.      * Returns with the queue unlocked.

  64.      */

  65.     req = get_request_wait(q, rw_flags, bio);

  66.     /*

  67.      * After dropping the lock and possibly sleeping here, our request

  68.      * may now be mergeable after it had proven unmergeable (above).

  69.      * We don't worry about that case for efficiency. It won't happen

  70.      * often, and the elevators are able to handle it.

  71.      */

  72.     init_request_from_bio(req, bio);

  73.     spin_lock_irq(q->queue_lock);

  74.     if (elv_queue_empty(q))
  75.         blk_plug_device(q);

  76.     add_request(q, req);
  77. out:
  78.     if (sync)
  79.         __generic_unplug_device(q);

  80.     spin_unlock_irq(q->queue_lock);

  81.     return 0;
  82. end_io:
  83.     bio_endio(bio, err);

  84.     return 0;
  85. }

el_ret = elv_merge(q, &req, bio);判断是否有电梯函数,如果有,就处理一下IO操作,然后把它们都放在一个request里,再到

out:

         if (sync)

                   __generic_unplug_device(q);

         spin_unlock_irq(q->queue_lock);

         return 0;

__generic_unplug_device(q);主要是调用q->request_fn(q);调用驱动程序中的处理函数。

如果不支持电梯函数就获得一个空的
request然后把bio填进去,再调用__generic_unplug_device(q)

交给驱动程序处理,因为结束一个
bio要采用bio_endio(bio, err);,而如果不出错,需要在驱动程序中自己加上去。一般驱动程序如果用默认make_request有两种方式处理,一种是一次一个请求,结束用end_request,另一种是采用rq_for_each_bio>>bio_for_each_segment一次性处理一连续bio,这种结束采用end_that_request_first>>blkdev_dequeue_request>>end_that_request_last

最后整一副整个块设备工作流程关系图:


阅读(1648) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~