当前位置:   article > 正文

Kernel Scheduler学习之五:RT 调度器_rt tasks

rt tasks
  1. Overview
    RT task全称为realtime task,即一种实时任务。和之前的调度器一样,本文件主要研究如下几个问题:
    什么样的task属于RT task
    RT task如何管理
    如何从一个Runqueue中选择一个task出来执行
    如何为RT task选择CPU
  2. 什么样的task属于RT task
    如下对是否是RT task的判断,可以看出,优先级小于100的即为rt task。
    1. static inline int rt_prio(int prio)
    2. {
    3. if (unlikely(prio < MAX_RT_PRIO))
    4. return 1;
    5. return 0;
    6. }
    7. #define MAX_USER_RT_PRIO 100
    8. #define MAX_RT_PRIO MAX_USER_RT_PRIO
  3. RT task如何管理呢?
    需要从rt scheduler的enqueue与dequeue开始讲起。
    1. /*
    2. * Adding/removing a task to/from a priority array:
    3. */
    4. static void
    5. enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
    6. {
    7. struct sched_rt_entity *rt_se = &p->rt;
    8. if (flags & ENQUEUE_WAKEUP)
    9. rt_se->timeout = 0;
    10. enqueue_rt_entity(rt_se, flags); //enqueue到rt调度器正统的queueu当中
    11. if (!task_current(rq, p) && p->nr_cpus_allowed > 1) //可push,从这个条件判断可以看出rt中也是维护了一个可以Push的qeueue,将可以push的task放到这个queue当中去。
    12. enqueue_pushable_task(rq, p);
    13. }
    1. static void enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
    2. {
    3. struct rq *rq = rq_of_rt_se(rt_se);
    4. dequeue_rt_stack(rt_se, flags); //先将原来的task dequeue,如果task已经不在runqueue当中,其实相当于什么都不做。
    5. for_each_sched_rt_entity(rt_se) //这个地方为什么要用for each的方式呢?其实只对于CONFIG_RT_GROUP_SCHED才需要循环,而对于非CONFIG_RT_GROUP_SCHED来讲,不需要循环的。
    6. __enqueue_rt_entity(rt_se, flags);//将当前task enqueue到rt 的runqueue当中
    7. enqueue_top_rt_rq(&rq->rt);
    8. }
    1. static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
    2. {
    3. struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
    4. struct rt_prio_array *array = &rt_rq->active;
    5. struct rt_rq *group_rq = group_rt_rq(rt_se);
    6. struct list_head *queue = array->queue + rt_se_prio(rt_se);//通过此处可以判断,rt task的管理为每个优先级都维护一个list,每个优先级都对应一个list。
    7. /*
    8. * Don't enqueue the group if its throttled, or when empty.
    9. * The latter is a consequence of the former when a child group
    10. * get throttled and the current group doesn't have any other
    11. * active members.
    12. */
    13. if (group_rq && (rt_rq_throttled(group_rq) || !group_rq->rt_nr_running)) {
    14. if (rt_se->on_list)
    15. __delist_rt_entity(rt_se, array);
    16. return;
    17. }//如果task被throttled,则不做enqueue的操作。
    18. if (move_entity(flags)) {
    19. WARN_ON_ONCE(rt_se->on_list);
    20. if (flags & ENQUEUE_HEAD) //通过flags可以操控其放在队尾还是对的开始。
    21. list_add(&rt_se->run_list, queue);
    22. else
    23. list_add_tail(&rt_se->run_list, queue);
    24. __set_bit(rt_se_prio(rt_se), array->bitmap); //每个Priority一个标志位,如果被置位表示对应的Priority还有task。提供了一种快速判断某一个priority是否还有task未处理的方式。
    25. rt_se->on_list = 1;
    26. }
    27. rt_se->on_rq = 1;
    28. inc_rt_tasks(rt_se, rt_rq);
    29. }
    根据以上代码,看上去,rt task的管理类似如下图所示:



    其中active:
    1. /*
    2. * This is the priority-queue data structure of the RT scheduling class:
    3. */
    4. struct rt_prio_array {
    5. DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
    6. struct list_head queue[MAX_RT_PRIO];
    7. };

    queue相当于是一个二维数组,纵向根据优先级排序,横向则同优先级的task。同时为加速访问,维护一个bitmap,而bitmap的每个bit位的0或者1表示对应的优先级是否有task。即对应的queue是否为空。

    pushable task的管理

    在enqueue task的时候,会判断这个task是否可以在其他cpu上面run,如果可以则将其添加到pushable list当中。
     

    1. static void enqueue_pushable_task(struct rq *rq, struct task_struct *p)
    2. {
    3. plist_del(&p->pushable_tasks, &rq->rt.pushable_tasks);//如果已经存在plist当中,就先删除,此函数会自动判断,如果不在list当中就不会删除。
    4. plist_node_init(&p->pushable_tasks, p->prio); //重新初始化
    5. plist_add(&p->pushable_tasks, &rq->rt.pushable_tasks); //重新加入
    6. //为什么要经历一次重新删除又重新加入呢?主要是在add的时候,可以加入到队尾,所以,原本已经在plist中的话,会重新操作一次,使其到队尾当中。
    7. /* Update the highest prio pushable task */
    8. if (p->prio < rq->rt.highest_prio.next)
    9. rq->rt.highest_prio.next = p->prio;
    10. }
    11. 关于plist:
    12. struct plist_head {
    13. struct list_head node_list;
    14. };
    15. struct plist_node {
    16. int prio;
    17. struct list_head prio_list;
    18. struct list_head node_list;
    19. };
    20. 可以看到其实,此处有机会处于两个表当中,一个是priolist一个nodelist。
    21. 而下面的plist_add则反映了pushable task的管理。
    22. /**
    23. * plist_add - add @node to @head
    24. *
    25. * @node: &struct plist_node pointer
    26. * @head: &struct plist_head pointer
    27. */
    28. void plist_add(struct plist_node *node, struct plist_head *head)
    29. {
    30. struct plist_node *first, *iter, *prev = NULL;
    31. struct list_head *node_next = &head->node_list;
    32. plist_check_head(head);
    33. WARN_ON(!plist_node_empty(node));
    34. WARN_ON(!list_empty(&node->prio_list));
    35. if (plist_head_empty(head))
    36. goto ins_node;
    37. first = iter = plist_first(head);
    38. do {
    39. if (node->prio < iter->prio) { //在prio list中查找当前node prio应该处于的位置。priolist按照Prio从低到高排序。
    40. node_next = &iter->node_list;
    41. break;
    42. }
    43. prev = iter;
    44. iter = list_entry(iter->prio_list.next,
    45. struct plist_node, prio_list);
    46. } while (iter != first);
    47. if (!prev || prev->prio != node->prio)//没有合适的priolist存在,则将当前node的设置为此prio的第1个节点
    48. list_add_tail(&node->prio_list, &iter->prio_list);
    49. ins_node:
    50. list_add_tail(&node->node_list, node_next);//加入到node list的最后。
    51. plist_check_head(head);
    52. }

    其结果如下所示:

  4. 如何从一个run queue中选择一个task出来执行

    1. static struct task_struct *
    2. pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
    3. {
    4. struct task_struct *p;
    5. WARN_ON_ONCE(prev || rf);
    6. if (!sched_rt_runnable(rq)) //如果队列中没有task,肯定选不到task。
    7. return NULL;
    8. p = _pick_next_task_rt(rq);
    9. set_next_task_rt(rq, p, true);
    10. return p;
    11. }
    1. static struct task_struct *_pick_next_task_rt(struct rq *rq)
    2. {
    3. struct sched_rt_entity *rt_se;
    4. struct rt_rq *rt_rq = &rq->rt;
    5. do {
    6. rt_se = pick_next_rt_entity(rq, rt_rq);//从rt的rt_rq中选优先级最高的且最选入队的task出来执行
    7. BUG_ON(!rt_se);
    8. rt_rq = group_rt_rq(rt_se);//rt_group相关。这个后续研究
    9. } while (rt_rq);
    10. return rt_task_of(rt_se);
    11. }
    12. static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,
    13. struct rt_rq *rt_rq)
    14. {
    15. struct rt_prio_array *array = &rt_rq->active;//如前所述,active是一个rt_prio_array结构体,这个结构体对象中保存了一个bitmap,一个类似二维数组的list。bitmap用来表示对应的优级的queue是否为空。list存放着每一个优先级对应的queue。
    16. struct sched_rt_entity *next = NULL;
    17. struct list_head *queue;
    18. int idx;
    19. idx = sched_find_first_bit(array->bitmap);//第1个不为0的bit位置。
    20. BUG_ON(idx >= MAX_RT_PRIO);
    21. queue = array->queue + idx; //取出对应的queue
    22. next = list_entry(queue->next, struct sched_rt_entity, run_list);//取这个queue的首个元素。
    23. return next;
    24. }

    如上代码所示,根据前面的rt task的管理知道,rt的task采用list管理,list的元素都又是一个优先级相等的rt task list。采用bitmap的方式标记哪些优先级的list还不为空,先找这个list再取出元素。

  5. 为task选择CPU
    为task选择cpu是调度器的核心工作之一。和其他调度器一样,当task被wakeup的时候,会为这个task重新选择一个合适的cpu。而给RT task选CPU就是通过如下的function进行的。
     

    1. static int
    2. select_task_rq_rt(struct task_struct *p, int cpu, int sd_flag, int flags)
    3. {
    4. struct task_struct *curr;
    5. struct rq *rq;
    6. /* For anything but wake ups, just return the task_cpu */
    7. if (sd_flag != SD_BALANCE_WAKE && sd_flag != SD_BALANCE_FORK)
    8. goto out;
    9. rq = cpu_rq(cpu);
    10. rcu_read_lock();
    11. curr = READ_ONCE(rq->curr); /* unlocked access */
    12. /*
    13. * If the current task on @p's runqueue is an RT task, then
    14. * try to see if we can wake this RT task up on another
    15. * runqueue. Otherwise simply start this RT task
    16. * on its current runqueue.
    17. *
    18. * We want to avoid overloading runqueues. If the woken
    19. * task is a higher priority, then it will stay on this CPU
    20. * and the lower prio task should be moved to another CPU.
    21. * Even though this will probably make the lower prio task
    22. * lose its cache, we do not want to bounce a higher task
    23. * around just because it gave up its CPU, perhaps for a
    24. * lock?
    25. *
    26. * For equal prio tasks, we just let the scheduler sort it out.
    27. *
    28. * Otherwise, just let it ride on the affined RQ and the
    29. * post-schedule router will push the preempted task away
    30. *
    31. * This test is optimistic, if we get it wrong the load-balancer
    32. * will have to sort it out.
    33. */
    34. if (curr && unlikely(rt_task(curr)) &&
    35. (curr->nr_cpus_allowed < 2 ||
    36. curr->prio <= p->prio)) {//当前cpu不是idle,且正在run的是一个rt task且当前cpu只允许run在一个cpu当中或者当前task的优先级更高,则要从别的cpu中选一个出来。
    37. int target = find_lowest_rq(p);//看名字应该是选一个正在run的优先级比较低task的cpu出来。
    38. /*
    39. * Don't bother moving it if the destination CPU is
    40. * not running a lower priority task.
    41. */
    42. if (target != -1 &&
    43. p->prio < cpu_rq(target)->rt.highest_prio.curr) //不抢优先级比当前task高的cpu。
    44. cpu = target;
    45. }
    46. rcu_read_unlock();
    47. out:
    48. return cpu;
    49. }

    总结选择CPU的标准如下:

  6. 1. 优先考虑来选择cpu的task之前在的cpu,这应该是尽量减少对cpu cache的干扰。
    2. 满足如下条件则从其他cpu当中选择
        a. 之前所在的cpu中有task正在run,而且还是一个rt task
        b. 这个正在run的rt task只能跑在一颗cpu上面或者其优先级比来选择cpu的task优先高
        c. 其他cpu中rt 优先级最低的cpu
        d. 如果其他cpu的task优先级都比这个task优先级高,那就还保留在原来的cpu。
    综上可以看出,rt task在选择CPU,将优先级考虑在第1位的。尽量选择那些正在run或者马上就要run的优先级比自己小的cpu去摆。不会去考虑cpu的能力状况,例如大小核之类的。可能是满足优先级高的响应的latency最短吧。

    1. static int find_lowest_rq(struct task_struct *task)
    2. {
    3. struct sched_domain *sd;
    4. struct cpumask *lowest_mask = this_cpu_cpumask_var_ptr(local_cpu_mask);
    5. int this_cpu = smp_processor_id();
    6. int cpu = task_cpu(task);
    7. /* Make sure the mask is initialized first */
    8. if (unlikely(!lowest_mask))
    9. return -1;
    10. if (task->nr_cpus_allowed == 1)
    11. return -1; /* No other targets possible */
    12. if (!cpupri_find(&task_rq(task)->rd->cpupri, task, lowest_mask)) //寻找cpu的优先级比当前task还低的cpu出来
    13. return -1; /* No targets found */
    14. /*
    15. * At this point we have built a mask of CPUs representing the
    16. * lowest priority tasks in the system. Now we want to elect
    17. * the best one based on our affinity and topology.
    18. *
    19. * We prioritize the last CPU that the task executed on since
    20. * it is most likely cache-hot in that location.
    21. */
    22. if (cpumask_test_cpu(cpu, lowest_mask))//如果当前cpu位于其中,则直接返回这个cpu即可。
    23. return cpu;
    24. /*
    25. * Otherwise, we consult the sched_domains span maps to figure
    26. * out which CPU is logically closest to our hot cache data.
    27. */
    28. if (!cpumask_test_cpu(this_cpu, lowest_mask))//v如果当前来选核的cpu的优先级比task的要高,直接忽略掉这颗cpu,但是如果比自己低的话,则要看是否一个共同域当中。
    29. this_cpu = -1; /* Skip this_cpu opt if not among lowest */
    30. rcu_read_lock();
    31. for_each_domain(cpu, sd) {
    32. if (sd->flags & SD_WAKE_AFFINE) {
    33. int best_cpu;
    34. /*
    35. * "this_cpu" is cheaper to preempt than a
    36. * remote processor.
    37. */
    38. if (this_cpu != -1 && //当前cpu的优先级比这个task的还要低,且处在共同域当中
    39. cpumask_test_cpu(this_cpu, sched_domain_span(sd))) {
    40. rcu_read_unlock();
    41. return this_cpu;
    42. }
    43. best_cpu = cpumask_first_and(lowest_mask,
    44. sched_domain_span(sd)); //选择lowest_mask与对应domain共域的cpui出来。
    45. if (best_cpu < nr_cpu_ids) {
    46. rcu_read_unlock();
    47. return best_cpu;
    48. }
    49. }
    50. }
    51. rcu_read_unlock();
    52. /*
    53. * And finally, if there were no matches within the domains
    54. * just give the caller *something* to work with from the compatible
    55. * locations.
    56. */
    57. if (this_cpu != -1)
    58. return this_cpu;
    59. cpu = cpumask_any(lowest_mask);
    60. if (cpu < nr_cpu_ids)
    61. return cpu;
    62. return -1;
    63. }

     

  7. Summary
    RT task是一种对响应延时比较敏感的task。但是cpu里面总是会有多个task在run,为了区分,只能采用优先级的概念。优先级高的优先run,优先级低的后run。RT对task的管理采用类似二维数组的方式,纵向根据优先级排,横向的为相同优先级的task。相同优先级的则按照入队顺序支行。在选择cpu时,则找优先级最低的cpu。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/67315
推荐阅读
  

闽ICP备14008679号