在linux中,支持对称smp的处理器模型,在多处理器的情况下,每个处理器都有自己的一个运行队列,这样就存在着分配不均的情况,有的cpu运行队列很多进程,导致一直很忙,有的cpu运行队列可能很少的进程甚至没有任何运行进程,导致cpu经常处于空转的状态,因此我们需要一种机制,来均衡各个cpu上运行队列的进程数。
1数据结构
为了支持多种多处理器模型,linux提出了调用域及组的概念,一个调用域可以包含其它的调用域或者多个组,一个组通常包含一个或者多个cpu,组的数据结构是:

struct sched_group {
struct sched_group *next;//一个调用域可能会包含多个组,该next用于将 //sched_group串到调用域的链表上面
cpumask_t cpumask; //每个group中可能会包含一个或者多个cpu,这里的mask表 //示了该group所包含的cpu
unsigned long cpu_power;//通常是cpu的个数
};
下面是调用域的数据结构:
struct sched_domain {
struct sched_domain *parent; //调用域可以被别的调用域所包含,parent指向父调用域
struct sched_group *groups;    //该调用域所包含的组
cpumask_t span;
unsigned long min_interval; //最小的时间间隔,用于检查进行负载均衡操作的时机是否到了
unsigned long max_interval; //同上
unsigned int busy_factor;   //当处理器在不空闲的状态下时,进行负载均衡操作的时间间隔一般也长很多,该factor为其乘数银子
unsigned int imbalance_pct;
unsigned long long cache_hot_time;
unsigned int cache_nice_tries;
unsigned int per_cpu_gain;
int flags;  
unsigned long last_balance;
unsigned int balance_interval;  //负载均衡进行的时间间隔  
unsigned int nr_balance_failed; //负载均衡迁移进程失败的次数
 
};

下图表现出了调用域和组之间的关系,这里我们关注2-cpu的smp和8-cpu的numa;2-cpu的SMP有一个调用域,调用域含两个组,每个组含有一个cpu;8-cpu的numa中包含两个调用域,最底层的调用域代表一个节点,每个最底层的调用域包含四个组,每个组有1个cpu,上层调用域包含两个基础的调用域。

2进行cpu负载均衡的时机
每经过一次时钟中断,scheduler_tick()就调用rebalance_tick()函数,rebalance_tick()函数会去触发cpu运行队列的负载均衡操作。该函数从最底层的调度域开始,一直到最上层的调度域进行检查,对于每个调度域都去查看是否到了调用load_balance()函数的时间,该函数会进行cpu的负载均衡操作。由当前cpu的idle状态和sched_domain的参数来确定调用load_balance()的时间间隔。

3 2-cpu smp和8-cpu numa cpu模型的调用域初始化
对调用域的初始化部分的代码在linux2.6.11版本里面是在arch_init_sched_domains()函数中,被sched_init_smp()函数所调用。
在sched.c中定义了一个数组static struct sched_group sched_group_phys[NR_CPUS];每个数组元素代表一个cpu组。另外定义了一个每cpu变量Sched.c (kernel):static DEFINE_PER_CPU(struct sched_domain, phys_domains);,系统为每个物理cpu都生成了一个调度域数据结构。
对于2-cpu smp的情形来说,其初始化后,调度域和各个组之间的关系是:

在这个里面虽然每个cpu都有个调度域的数据结构,但调度域的groups链表指向的都是同一个group链表

8-cpu numa的组和调度域关系:

4cpu负载均衡的源码解析

[cpp] view plaincopy
  1. 4.1rebalance_tick()
  2. static void rebalance_tick(int this_cpu, runqueue_t *this_rq,
  3. enum idle_type idle)
  4. {
  5. unsigned long old_load, this_load;
  6. unsigned long j = jiffies + CPU_OFFSET(this_cpu);
  7. struct sched_domain *sd;
  8. //当前运行队列中可运行的进程数决定了当前运行队列的
  9. //cpu_load参数
  10. old_load = this_rq->cpu_load;
  11. this_load = this_rq->nr_running * SCHED_LOAD_SCALE;
  12. if (this_load > old_load)
  13. old_load++;
  14. //将运行队列的cpu load值设定为上一次的cpu_load和本次cpu_load的平均值
  15. this_rq->cpu_load = (old_load + this_load) / 2;
  16. //从该cpu所属的调度域开始,依次遍历各个更高级的调用域
  17. for_each_domain(this_cpu, sd) {
  18. unsigned long interval;
  19. //在该调度域上不需要做负载均衡
  20. if (!(sd->flags & SD_LOAD_BALANCE))
  21. continue;
  22. //若当前cpu不处于空闲状态的话,其调用load_balance的时间间隔会比较长
  23. interval = sd->balance_interval;
  24. if (idle != SCHED_IDLE)
  25. interval *= sd->busy_factor;
  26. //将时间间隔ms转换成jiffies
  27. interval = msecs_to_jiffies(interval);
  28. if (unlikely(!interval))
  29. interval = 1;
  30. //当前的时间戳和上次balance的时间大于其间隔的话,调用load_balance进行负载的均衡
  31. if (j - sd->last_balance >= interval) {
  32. //load_balance会去寻找最繁忙的cpu组中的最繁忙的cpu,将其进程迁移过来一部分
  33. if (load_balance(this_cpu, this_rq, sd, idle)) {
  34. /* We've pulled tasks over so no longer idle */
  35. idle = NOT_IDLE;
  36. }
  37. sd->last_balance += interval;
  38. }
  39. }
  40. }
  41. 4.2load_balance()
  42. static int load_balance(int this_cpu, runqueue_t *this_rq,
  43. struct sched_domain *sd, enum idle_type idle)
  44. {
  45. struct sched_group *group;
  46. runqueue_t *busiest;
  47. unsigned long imbalance;
  48. int nr_moved;
  49. spin_lock(&this_rq->lock);
  50. schedstat_inc(sd, lb_cnt[idle]);
  51. //查找最繁忙的cpu组
  52. group = find_busiest_group(sd, this_cpu, &imbalance, idle);
  53. //所有的组都是平衡的,不需要做均衡
  54. if (!group) {
  55. schedstat_inc(sd, lb_nobusyg[idle]);
  56. goto out_balanced;
  57. }
  58. //找到最繁忙的组中最繁忙的运行队列
  59. busiest = find_busiest_queue(group);
  60. if (!busiest) {
  61. schedstat_inc(sd, lb_nobusyq[idle]);
  62. goto out_balanced;
  63. }
  64. //最繁忙的运行队列是当前cpu的运行队列,不需要做均衡
  65. if (unlikely(busiest == this_rq)) {
  66. WARN_ON(1);
  67. goto out_balanced;
  68. }
  69. schedstat_add(sd, lb_imbalance[idle], imbalance);
  70. nr_moved = 0;
  71. if (busiest->nr_running > 1) {
  72. double_lock_balance(this_rq, busiest);
  73. //将imbalance个进程从最繁忙的运行队列上迁移到当前的cpu运行队列上面
  74. nr_moved = move_tasks(this_rq, this_cpu, busiest,
  75. imbalance, sd, idle);
  76. spin_unlock(&busiest->lock);
  77. }
  78. spin_unlock(&this_rq->lock);
  79. //nr_moved == 0,表示没有迁移成功
  80. if (!nr_moved) {
  81. schedstat_inc(sd, lb_failed[idle]);
  82. sd->nr_balance_failed++;
  83. if (unlikely(sd->nr_balance_failed > sd->cache_nice_tries+2)) {
  84. int wake = 0;
  85. spin_lock(&busiest->lock);
  86. //active_balance表明该运行队列是否唤醒迁移线程来进行负载均衡,
  87. //push_cpu记录了由哪个cpu来唤醒了其迁移线程
  88. if (!busiest->active_balance) {
  89. busiest->active_balance = 1;
  90. busiest->push_cpu = this_cpu;
  91. wake = 1;
  92. }
  93. spin_unlock(&busiest->lock);
  94. //唤醒最繁忙运行队列上的迁移内核线程,对进程进行迁移
  95. if (wake)
  96. wake_up_process(busiest->migration_thread);
  97. sd->nr_balance_failed = sd->cache_nice_tries;
  98. }
  99. if (sd->balance_interval < sd->max_interval)
  100. sd->balance_interval++;
  101. } else {
  102. sd->nr_balance_failed = 0;
  103. //进程迁移成功,重置调用域的balance_interval参数
  104. sd->balance_interval = sd->min_interval;
  105. }
  106. return nr_moved;
  107. out_balanced:
  108. spin_unlock(&this_rq->lock);
  109. /* tune up the balancing interval */
  110. //不需要进行进程的迁移,适当的加大负载均衡的间隔时间,说明
  111. //当前的负载均衡做的比较好
  112. if (sd->balance_interval < sd->max_interval)
  113. sd->balance_interval *= 2;
  114. return 0;
  115. }
  116. 4.3move_tasks()
  117. static int move_tasks(runqueue_t *this_rq, int this_cpu, runqueue_t *busiest,
  118. unsigned long max_nr_move, struct sched_domain *sd,
  119. enum idle_type idle)
  120. {
  121. prio_array_t *array, *dst_array;
  122. struct list_head *head, *curr;
  123. int idx, pulled = 0;
  124. task_t *tmp;
  125. if (max_nr_move <= 0 || busiest->nr_running <= 1)
  126. goto out;
  127. //先从过期队列上进行进程的迁移,这样对硬件cache的
  128. //影响比较小
  129. if (busiest->expired->nr_active) {
  130. array = busiest->expired;
  131. dst_array = this_rq->expired;
  132. } else {
  133. array = busiest->active;
  134. dst_array = this_rq->active;
  135. }
  136. new_array:
  137. idx = 0;
  138. skip_bitmap:
  139. //从优先级最高的可运行进程开始进行迁移
  140. if (!idx)
  141. idx = sched_find_first_bit(array->bitmap);
  142. else
  143. idx = find_next_bit(array->bitmap, MAX_PRIO, idx);
  144. if (idx >= MAX_PRIO) {
  145. if (array == busiest->expired && busiest->active->nr_active) {
  146. array = busiest->active;
  147. dst_array = this_rq->active;
  148. goto new_array;
  149. }
  150. goto out;
  151. }
  152. //找到对应优先级队列的队列末尾的进程,该进程应该是被
  153. //放入的最早的一个进程了
  154. head = array->queue + idx;
  155. curr = head->prev;
  156. skip_queue:
  157. tmp = list_entry(curr, task_t, run_list);
  158. curr = curr->prev;
  159. //判断该进程能否进行迁移
  160. if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) {
  161. //该优先级别的队列是否遍历完毕
  162. if (curr != head)
  163. goto skip_queue;
  164. idx++;
  165. //该优先级别的任务队列遍历完毕,去遍历下一个优先级的任务队列
  166. goto skip_bitmap;
  167. }
  168. schedstat_inc(this_rq, pt_gained[idle]);
  169. schedstat_inc(busiest, pt_lost[idle]);
  170. //将队列迁移到本地的任务队列
  171. pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu);
  172. pulled++;
  173. if (pulled < max_nr_move) {
  174. //该优先级别的队列是否遍历完毕
  175. if (curr != head)
  176. goto skip_queue;
  177. idx++;
  178. //该优先级别的任务队列遍历完毕,去遍历下一个优先级的任务队列
  179. goto skip_bitmap;
  180. }
  181. out:
  182. return pulled;
  183. }

linux多CPU进程负载均衡解析相关推荐

  1. linux进程网络均衡,linux多CPU进程负载均衡解析

    在linux中,支持对称smp的处理器模型,在多处理器的情况下,每个处理器都有自己的一个运行队列,这样就存在着分配不均的情况,有的cpu运行队列很多进程,导致一直很忙,有的cpu运行队列可能很少的进程 ...

  2. 查看linux cpu负载均衡,关于linux内核cpu进程的负载均衡

    2.6内核中进程调度模块的负载均衡行为分为"拉"和"推",推这里不考虑,关于拉均衡有一篇文章特别好,具体出处就不记得了,我当时用的百度快照,那篇文章我认为最精彩 ...

  3. Linux schedule 4、负载均衡

    4.负载均衡 4.1.SMP负载均衡 4.1.1.Scheduling Domains 4.1.1.1.Scheduling Domains概念 借用Linux Scheduling Domains的 ...

  4. Linux下Nginx+Resin负载均衡,session问题解决实例

    Linux下Nginx+Resin负载均衡,session问题解决实例 转载:http://blog.chinaunix.net/uid-14007440-id-3150269.html https: ...

  5. 负载均衡解析与Nginx实战

    1. 负载均衡的概念 1.1 什么是负载均衡 Load Balancing,即负载均衡,是一种计算机技术,用来在多个计算机(计算机集群).网络连接.CPU.磁盘驱动器或其他资源中分配负载,以达到最优化 ...

  6. 【Linux学习九】负载均衡

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 一.高并发 随着应用访问量的增加,带来高并发处理问题. 具体有两个: ...

  7. Nginx学习系列二Linux下Nginx实现负载均衡

    关于在本地虚拟机(VMware 14)下安装Linux同时安装Nginx,请参考Nginx学习系列之搭建环境 1.启动Nginx 在Nginx安装成功的前提下,启动Nginx 已root模式登陆(权限 ...

  8. 云计算之路-阿里云上:4000IOPS的RDS+16核CPU的负载均衡

    继续向大家汇报,上周在阿里云的帮助下我们重点解决的是以下两个问题: 1. 在专门跑博客站点的负载均衡中,如果单台云服务器处理的并发请求高(比如超过200 Get Requests/s),CPU有时会出 ...

  9. linux监控cpu进程,Linux性能监控之CPU篇详解

    监控CPU的性能就是以上3点,运行队列.CPU使用率和上下文切换.以下是一些对于Linux性能监控CPU很普遍的性能要求: Linux性能监控CPU篇 1. 对于每一个CPU来说运行队列不要超过3,例 ...

  10. linux查看cpu的负载

    linux可以通过top命令查看cpu负载信息,这个信息的后半部分会显示load average.它的意思是系统的平均载荷.数字越小,则载荷越小.三个数字分别表示最近1分钟,5分钟,15分钟内系统的平 ...

最新文章

  1. 如何使用 OpenStack CLI - 每天5分钟玩转 OpenStack(22)
  2. linux ipc 漏洞,1月19日Linux发现内核0Day漏洞,编号”CVE-2016-0728“
  3. transition.tween
  4. jq输出html 单引号引号转义符,javascript - 由于JSON中的单引号转义,jQuery.parseJSON抛出“无效的JSON”错误...
  5. Explorer.exe报错故障解决一例
  6. 中画图title函数_Matlab对量子力学中的一维无限深势阱的模拟计算
  7. ajax beforesend xhr对象,浅谈jQuery中Ajax事件beforesend及各参数含义
  8. 设计模式必须遵守的六大原则
  9. 类人猿学院--懒人精灵脚本系列教程(最新)
  10. Java程序线上运行CPU占用100%的处理方法
  11. 2020 JUSTCTF F@k3 0ff1c@l REVERSE WP
  12. 传输栅输入端异常导通
  13. 如何使丑陋的Arial看起来好看
  14. python命名规则数字开头的成语_浅谈Python中带_的变量或函数命名
  15. Lession10 常用类(正则表达式、Date Time结构、string类、Math类)
  16. 1.2 SpringBoot构建Docker镜像并推送到Harbor
  17. Windows和Ubuntu系统文件无法粘贴问题
  18. DBA所需要具备技能
  19. pythonGUI之wxpython控件总结
  20. 瓦工 -- 叫我怎么相信你

热门文章

  1. 显示器不能全屏及开机慢解决方案
  2. 线程不安全 静态变量_【高并发】面试官问我:为啥局部变量是线程安全的?...
  3. IDC:“互联网+流通”将进一步释放活力
  4. 请详细描述LVS DR模式的原理
  5. dubbo入门级梳理
  6. LR 报错误: C interpreter run time error: Error -- Unresolved symbol : vuser_end解决方法
  7. SpringBoot自动装配原理解析
  8. Assimp Android 编译
  9. Python一些常用模块
  10. 《进化——我们在互联网上奋斗的故事》一一1.9 职业素养中的品德细节