linux的qos机制 - dm-ioband篇 (4)

这篇延续上一篇的内容,通过几个典型场景来分析dm-ioband的工作流程。

第一个场景是 http://sourceforge.net/apps/trac/ioband/wiki/dm-ioband/man/examples 中的example 1,首先调用命令创建两个ioband device,

# echo "0 $(blockdev --getsize /dev/sda1) ioband /dev/sda1 1 0 0 none weight 0 :80" | dmsetup create ioband1
# echo "0 $(blockdev --getsize /dev/sda2) ioband /dev/sda2 1 0 0 none weight 0 :40" | dmsetup create ioband2

dmsetup会调用ioband_ctr来创建ioband设备,从ioband_ctr的注释可以看出参数顺序

/*
 * Create a new band device:
 *   parameters:  <device> <device-group-id> <io_throttle> <io_limit>
 *     <type> <policy> <policy-param...> <group-id:group-param...>
 */

其中 <device-group-id>ioband_device->g_name,用以唯一标识一个ioband设备。P.S. 这里ioband1, ioband2两个设备其实对应同一个ioband_device,其ioband-group-id为1 。而传入的<device> 为底层真实的块设备,如 /dev/sda1, /dev/sda2。会调用dm_get_device把这个块设备加到dm_table->devices 列表中。下面调用alloc_ioband_device( <device-group-id>, <io_throttle>, <io_limit> ) 来创建ioband_device,由于此时已经有了<device-group-id>为 1 的ioband_device,因此只是简单的把ioband_device->g_ref++就结束了。内核全局的有一个 ioband_device_list 的list_head,所有的ioband_device都属于这个list_head,这些ioband_device通过ioband_device->g_list 组织起来。

下面调用policy_init,传入的参数包括 <policy> <policy-param...> <group-id:group_param...>,首先进行policy 名字的比对,在全局变量 dm_ioband_policy_type 中查询名字叫weight的policy,之后会调用对应的policy_weight_init 函数。 P.S. ioband_device的g_groups链接了attach在上面的所有ioband_group,这个list_head的变量对应于ioband_group的 c_list 成员,通过container_of宏可以得到ioband_group。 得到的struct ioband_policy_type 赋值给 ioband_device->g_policy。

调用policy_weight_init的时候,传入的参数为 <policy-param...> <group-id: group-param...>这些参数。weight policy 第一个参数是<token base>,之后是<group-id:group-param> 的数组,policy_weight_init 不对 <group-id:group-param> 的队列做处理。

下面会调到 ioband_group_init 首先创建 default ioband group,在这个场景中,会有同一个ioband_device出现了两个default ioband_group,其中c_id为 IOBAND_ID_ANY。ioband_device->g_root_groups保存了同一个 <device-group-id> 中所有的根group,ioband_group_init 会通过list_add_tail,把ioband_device->g_root_groups加到ioband_group->c_sibling里面,可以看出每个ioband_group->c_sibling保存了同级别的所有ioband_group,而ioband_device->g_root_groups里所有的ioband_group互相都是sibling。本场景下,两个ioband_group的c_parent, c_children都为空,c_sibling有两个ioband_group。

对于ioband_group其他的成员: c_children表示这个group之下的所有子group,c_parent表示父group,c_sibling表示兄弟group,由于ioband_group是按照红黑树的结构组织的,c_group_root表示这颗红黑树的树根,c_group_node表示当前group在这颗红黑树上的节点。

最后调用ioband_group_type_select 选择 ioband_group_type,这里的 type为 none

############################

OK,现在我们来看请求执行流,请求进入device mapper之后,最终会落到 ioband_map 函数中,ioband_map执行步骤如下:

  1. 调用ioband_group_get,获取bio对应的ioband_group。如果 t_getid 函数指针为空,说明是default group,否则根据bio调用dm_ioband_group_type相应函数获取group type
  2. 由于<device-group-id>为1的ioband_device只有两个default group,在group初始化函数 policy_weight_ctr 时,会对其分配token。policy_weight_ctr 会调用 policy_weight_param,最终会调用 set_weight 来设置weight 。
  3. set_weight 先填充 ioband_group->c_weight,计算出c_sibling所有的c_weight之和,取出base_token,base_io_limit 等值(也就是parent 的token, io_limit值,如果parent为空就是ioband_device的相应值),再调用 __set_weight 。 __set_weight 里面根据c_weight 占的比重基于base_token分配token,基于 base_io_limit 分配io_limit,如果有必要修改parent里的相应值。 至此,ioband_group->c_token, ioband_group->c_token_initial, ioband_group->c_token_bucket 值都被设为被分配的token值。
  4. 处理请求时,判断 is_token_left 是否有剩余token可用,如果有就调用 consume_token,这样bio就被执行;否则会调用delayed_work机制延迟处理
  5. 延迟处理的 ioband_conduct 方法会进行一次判断,如果 nr_blocked(dp),room_for_bio_sync(dp, BLK_RW_SYNC/ASYNC),issue_list, pushback_list都为空,这三个条件成立,说明剩下还block的bio都是由于token耗尽,调用dp->g_restart_bios 重新放出一轮token,这个行为由 make_global_epoch 完成
  6. make_global_epoch 是个很有意思的函数,首先它找出ioband_device中拥有token最多的group,如果该group拥有的token过多,同时此时该group的IO负载很高的话(iopriority(gp) * PROCESS_THRESHOLD > IOBAND_IOPRIO_BASE && nr_issued(dp) >= dp->g_io_throttle),此时会优先服务这个group,不会放token出来,否则ioband_device增加一个epoch,对于所有group而言,增加 ioband_group->c_token_initial 个数个token
对于本场景的一种极端情况,即一个default group一直有IO请求,而另外一个一直没有,这种机制会保证没有group会token-starving。


-------------------------------------------------华丽的分割线-----------------------------------------------------


第二个场景是 http://sourceforge.net/apps/trac/ioband/wiki/dm-ioband/man/examples 的 example 3,这种场景下,ioband2上多了个uid=1000,weight=20的group。

首先是__ioband_message把type设置为uid

其次在__ioband_message中调用ioband_group_attach(struct ioband_group* head, 0, 1000, NULL) 时,head为原有default group的指针,只是现在head->c_type变成了dm_ioband_group_type中的uid group type。parent_id为0,group id为1000。之后再调用ioband_group_init(dp, head, NULL, gp, 1000, NULL),这里gp为新分配出来的一块内存,用于保存ioband_group,这里gp->c_id = id,这时ioband_group的id就不是default group中的IOBAND_ID_ANY了,而是uid 1000。这个新分配的ioband_group被挂在ioband_device的g_groups下,因此这个ioband_device目前就有了3个ioband_group。同时ioband2这个group的红黑树下面,有两个node,一个属于default group,一个属于uid group。这两个group的c_dev,c_target都是相同的。

最后调用__ioband_message中的ioband_set_param来设置weight。最后还是调用policy_weight_param(gp, "weight", "20") 。


现在ioband_device下面有3个ioband_group了,那么如何知道请求到底是属于哪个group呢?还记得ioband_map中的ioband_group_get 么,对于目的为ioband2的user=1000的进程发出的请求,传入ioband_group_get 中第一个参数为 default group 的指针。由于此时default group 的c_type已经变成了uid group type,会调用ioband_group_find 在红黑树上查找 c_id =1000 的ioband_group,这个查找算法要么返回 c_id 为1000的 rb_node,要么返回c_id 为 IOBAND_ID_ANY的default group的rb_node,因此不是uid=1000的进程发出的bio,最后都会落到default group上。而uid=1000的进程发出的bio会落到c_id=1000的ioband_group上。


最后总结下来,这个<device-group-id>包含了g_ref为2的ioband_device,里面有两个块设备/dev/mapper/ioband1, /dev/mapper/ioband2,同时有3个ioband_group,按照权重来分享token


你可能感兴趣的:(c,linux,list,IO,Parameters,token)