Chinaunix首页 | 论坛 | 博客
  • 博客访问: 111808
  • 博文数量: 40
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2016-09-22 17:28
文章分类

全部博文(40)

文章存档

2021年(2)

2018年(3)

2017年(29)

2016年(6)

我的朋友

分类: LINUX

2017-01-09 19:47:31

原文地址:blk_register_queue 函数学习 作者:Bean_lee

    add_disk 函数中,有个重要的流程就是blk_register_queue。这是这篇博文的重点。为了防止出现版权或者抄袭的纠纷,本人再次声明:

    本文主要是参考linux 那些事儿,本文不过是学习笔记性质的博文,荣耀属于linux那些事儿的作者。为甚么还要写这篇博文呢。敦促自己学习,驱动自己深入理解,Mark自己的成长足迹,同时想前辈大牛致敬。OK,回正题。

  1. int blk_register_queue(struct gendisk *disk)
  2. {
  3.     int ret;

  4.     struct request_queue *q = disk->queue;

  5.     if (WARN_ON(!q))
  6.         return -ENXIO;

  7.     if (!q->request_fn)
  8.         return 0;

  9.     ret = kobject_add(&q->kobj, kobject_get(&disk->dev.kobj),
  10.              "%s", "queue");
  11.     if (ret < 0)
  12.         return ret;

  13.     kobject_uevent(&q->kobj, KOBJ_ADD);

  14.     ret = elv_register_queue(q);
  15.     if (ret) {
  16.         kobject_uevent(&q->kobj, KOBJ_REMOVE);
  17.         kobject_del(&q->kobj);
  18.         return ret;
  19.     }

  20.     return 0;
  21. }
    这个函数非常简单,基本上就是设备管理的那一套东西。但是很不幸的是出现了一个看下register_queue类型的变量。再看下elv_register_queue:

  1. int elv_register_queue(struct request_queue *q)
  2. {
  3.     elevator_t *e = q->elevator;
  4.     int error;

  5.     error = kobject_add(&e->kobj, &q->kobj, "%s", "iosched");
  6.     if (!error) {
  7.         struct elv_fs_entry *attr = e->elevator_type->elevator_attrs;
  8.         if (attr) {
  9.             while (attr->attr.name) {
  10.                 if (sysfs_create_file(&e->kobj, &attr->attr))
  11.                     break;
  12.                 attr++;
  13.             }
  14.         }
  15.         kobject_uevent(&e->kobj, KOBJ_ADD);
  16.     }
  17.     return error;
  18. }
    我们看到,前面提到的request_queue类型的变量queue中存在一个elvator_t类型的成员elevator。queue他来自何方呢,elevator我们知道是电梯的意思,为什么queue有一个成员变量要电梯这么变态的名字呢?我们再也不能装聋作哑了,我们要勇敢地解决queue的身世之谜?

    首先,我们在gendisk类型中disk变量中看到了queue,那么gendisk类型中的queue是哪里来的呢?
    回想前面提到的sd_probe函数中,有这么一句,但是我们无情的忽略了这条语句:

  1. struct scsi_disk *sdkp;
  2. ...
  3. gd->queue = sdkp->device->request_queue;
    scsi_disk类型中存在一个scsi_device类型的变量device,device中存在request_queue。那么现在问题就变成了,device中的request_queue又是从哪里冒出来的呢。
  1. struct scsi_disk {
  2.     struct scsi_driver *driver;    /* always &sd_template */
  3.     struct scsi_device *device;
  4.     struct device    dev;
  5.     struct gendisk    *disk;
  6.     unsigned int    openers;    /* protected by BKL for now, yuck */
  7.     sector_t    capacity;    /* size in 512-byte sectors */
  8.     u32        index;
  9.     u8        media_present;
  10.     u8        write_prot;
  11.     unsigned    previous_state : 1;
  12.     unsigned    WCE : 1;    /* state of disk WCE bit */
  13.     unsigned    RCD : 1;    /* state of disk RCD bit, unused */
  14.     unsigned    DPOFUA : 1;    /* state of disk DPOFUA bit */
  15. };

    scsi总线扫描的时候,每当探测到一个设备的时候,就会调用scsi_alloc_sdev(),这个函数,解开了我们寻找周杰伦的大幕,我们就沿着这个函数,顺藤摸瓜,顺手牵羊的找出request_queue的前世今生。

    scsi_alloc_sdev函数中存在这么一句:调用了scsi_alloc_queue获取request_queue
  1. struct scsi_device *sdev;
  2. ...

  3. sdev->request_queue = scsi_alloc_queue(sdev);
OK ,scsi_alloc_queue函数又调用了__scsi_alloc_queue函数,如下:
  1. struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
  2. {
  3.       struct request_queue *q;
  4.       q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
  5.       ...
  6.       return q;
  7. }
    长话短说吧 :
    __scsi_alloc_queue  -----> blk_init_queue----->blk_init_queue_node,这个函数我们需要进去看下:

  1. struct request_queue *
  2. blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
  3. {
  4.     struct request_queue *q = blk_alloc_queue_node(GFP_KERNEL, node_id);

  5.     if (!q)
  6.         return NULL;

  7.     q->node = node_id;
  8.     if (blk_init_free_list(q)) {
  9.         kmem_cache_free(blk_requestq_cachep, q);
  10.         return NULL;
  11.     }

  12.     /*
  13.      * if caller didn't supply a lock, they get per-queue locking with
  14.      * our embedded lock
  15.      */
  16.     if (!lock) {
  17.         spin_lock_init(&q->__queue_lock);
  18.         lock = &q->__queue_lock;
  19.     }

  20.     q->request_fn        = rfn;
  21.     q->prep_rq_fn        = NULL;
  22.     q->unplug_fn        = generic_unplug_device;
  23.     q->queue_flags        = (1 << QUEUE_FLAG_CLUSTER);
  24.     q->queue_lock        = lock;

  25.     blk_queue_segment_boundary(q, 0xffffffff);

  26.     blk_queue_make_request(q, __make_request);
  27.     blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);

  28.     blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
  29.     blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);

  30.     q->sg_reserved_size = INT_MAX;

  31.     /*
  32.      * all done
  33.      */
  34.     if (!elevator_init(q, NULL)) {
  35.         blk_queue_congestion_threshold(q);
  36.         return q;
  37.     }

  38.     blk_put_queue(q);
  39.     return NULL;
  40. }
我们重点关注绿色的两条语句:

blk_alloc_queue_node主要工作就是分配了一个request_queue类型的结构,毕竟一路下来全是指针,没有干货,干货request_queue就是这个blk_alloc_queue_node函数分配的。

另一个需要关注的是elevator_init,我们最初的疑惑,电梯从哪里来的,终于可以初见端倪了。
  1. int elevator_init(struct request_queue *q, char *name)
  2. {
  3.     struct elevator_type *e = NULL;
  4.     struct elevator_queue *eq;
  5.     int ret = 0;
  6.     void *data;

  7.     INIT_LIST_HEAD(&q->queue_head);
  8.     q->last_merge = NULL;
  9.     q->end_sector = 0;
  10.     q->boundary_rq = NULL;

  11.     if (name) {
  12.         e = elevator_get(name);
  13.         if (!e)
  14.             return -EINVAL;
  15.     }

  16.     if (!e && *chosen_elevator) {
  17.         e = elevator_get(chosen_elevator);
  18.         if (!e)
  19.             printk(KERN_ERR "I/O scheduler %s not found\n",
  20.                             chosen_elevator);
  21.     }

  22.     if (!e) {
  23.         e = elevator_get(CONFIG_DEFAULT_IOSCHED);
  24.         if (!e) {
  25.             printk(KERN_ERR
  26.                 "Default I/O scheduler not found. " \
  27.                 "Using noop.\n");
  28.             e = elevator_get("noop");
  29.         }
  30.     }

  31.     eq = elevator_alloc(q, e);
  32.     if (!eq)
  33.         return -ENOMEM;

  34.     data = elevator_init_queue(q, eq);
  35.     if (!data) {
  36.         kobject_put(&eq->kobj);
  37.         return -ENOMEM;
  38.     }

  39.     elevator_attach(q, eq, data);
  40.     return ret;
  41. }
    首选函数入参指定的那款“电梯”,如果入参没指定,chosen_elevator指定了,那么就通过elevator_get去寻找选定的那款电梯,chosen_elevator那款电梯获取不到,只能在退而求其次,选择
CONFIG_DEFAULT_IOSCHED类型的“电梯”,如果这款也获取不到,那就只能认命了,选noop类型的电梯。

    在这里可以讲述一下所谓的电梯是什么东东。磁盘是一种物理设备,需要磁头寻道,寻道时间占磁盘读写请求需要时间的一大部分,如果让磁盘高效的工作呢。linux那些事的作者以欧洲足球为例,我是足球盲,是篮球迷,我就以CBA为例。对于CBA的球队而言,有很多之球队,如果你是一直球队的球员,你肯定不希望,今天打完广东宏远,第二天就飞到东北打辽宁,打完辽宁再打新疆,打完新疆再打福建,时间全耗在路上了。我们可能是愿意打完辽宁打北京,打完北京打山东,打完山东打江苏,打完江苏打上海,打完上海打浙江,打完浙江打福建。。。。为啥?旅途顺畅啊,从北到南一路打下来了。磁盘也是同样的道理。为了让磁盘高效的工作,我们需要调度,按照先到先服务的策略,那么就会出现打完辽宁打广东,打完广东打新疆,打完新疆打吉林,打完吉林打福建的坑爹情况。这个调度,被形象的比喻成电梯调度。想想电梯的调度,我们不可能按照先按先运送的策略服务。

    elevator_init函数还没完,我们看到elevator_init调用了elevator_alloc函数,
我们进去看下:


  1. static elevator_t *elevator_alloc(struct request_queue *q,
  2.                  struct elevator_type *e)
  3. {
  4.     elevator_t *eq;
  5.     int i;

  6.     eq = kmalloc_node(sizeof(elevator_t), GFP_KERNEL | __GFP_ZERO, q->node);
  7.     if (unlikely(!eq))
  8.         goto err;

  9.     eq->ops = &e->ops;
  10.     eq->elevator_type = e;
  11.     kobject_init(&eq->kobj, &elv_ktype);
  12.     mutex_init(&eq->sysfs_lock);

  13.     eq->hash = kmalloc_node(sizeof(struct hlist_head) * ELV_HASH_ENTRIES,
  14.                     GFP_KERNEL, q->node);
  15.     if (!eq->hash)
  16.         goto err;

  17.     for (i = 0; i < ELV_HASH_ENTRIES; i++)
  18.         INIT_HLIST_HEAD(&eq->hash[i]);

  19.     return eq;
  20. err:
  21.     kfree(eq);
  22.     elevator_put(e);
  23.     return NULL;
  24. }
    这个函数其实是分配了一个elevator_queue类型的变量eq。   elevator_queue结构描述了该块设备驱动使用何种IO调度算法。通常每个物理块设备都自己维护一个请求队列,每个请求队列上单独执行I/O调度。在请求对列中有一个字段elevator,它的类型指向sturct elevator_queue的指针。elevator_queue结构如下:
  1. struct elevator_queue
  2. {
  3.     struct elevator_ops *ops;
  4.     void *elevator_data;
  5.     struct kobject kobj;
  6.     struct elevator_type *elevator_type;
  7.     struct mutex sysfs_lock;
  8.     struct hlist_head *hash;
  9. };

字段ops定义了调度算法的所有可能的操作:链接和断开elevator,增加和合并队列中的请求,从队列中删除请求,获得队列中下一个待处理的请求等等。

字段是elevator_type,它是指向struct elevator_type类型的指针,它描述了块设备所使用io调度算法。

字段hash包含了处理请求队列所需的所有信息,通过这个哈希表可以快速索引我们需要的某个request

字段elevator_data用于描述IO调度程序用来处理请求的附加数据结构,如果使用Deadline算法,elevator_data指向struct deadline_data结构。




阅读(1645) | 评论(0) | 转发(0) |
0

上一篇:GCC命令

下一篇:内核日志及printk结构浅析

给主人留下些什么吧!~~