linux进程网络均衡,linux多CPU进程负载均衡解析
在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負載均衡的源碼解析
4.1rebalance_tick()
staticvoidrebalance_tick(intthis_cpu,?runqueue_t?*this_rq,
enumidle_type?idle)
{
unsigned?longold_load,?this_load;
unsigned?longj?=?jiffies?+?CPU_OFFSET(this_cpu);
structsched_domain?*sd;
//當前運行隊列中可運行的進程數決定了當前運行隊列的
//cpu_load參數
old_load?=?this_rq->cpu_load;
this_load?=?this_rq->nr_running?*?SCHED_LOAD_SCALE;
if(this_load?>?old_load)
old_load++;
//將運行隊列的cpu?load值設定為上一次的cpu_load和本次cpu_load的平均值
this_rq->cpu_load?=?(old_load?+?this_load)?/?2;
//從該cpu所屬的調度域開始,依次遍歷各個更高級的調用域
for_each_domain(this_cpu,?sd)?{
unsigned?longinterval;
//在該調度域上不需要做負載均衡
if(!(sd->flags?&?SD_LOAD_BALANCE))
continue;
//若當前cpu不處于空閑狀態的話,其調用load_balance的時間間隔會比較長
interval?=?sd->balance_interval;
if(idle?!=?SCHED_IDLE)
interval?*=?sd->busy_factor;
//將時間間隔ms轉換成jiffies
interval?=?msecs_to_jiffies(interval);
if(unlikely(!interval))
interval?=?1;
//當前的時間戳和上次balance的時間大于其間隔的話,調用load_balance進行負載的均衡
if(j?-?sd->last_balance?>=?interval)?{
//load_balance會去尋找最繁忙的cpu組中的最繁忙的cpu,將其進程遷移過來一部分
if(load_balance(this_cpu,?this_rq,?sd,?idle))?{
/*?We've?pulled?tasks?over?so?no?longer?idle?*/
idle?=?NOT_IDLE;
}
sd->last_balance?+=?interval;
}
}
}
4.2load_balance()
staticintload_balance(intthis_cpu,?runqueue_t?*this_rq,
structsched_domain?*sd,enumidle_type?idle)
{
structsched_group?*group;
runqueue_t?*busiest;
unsigned?longimbalance;
intnr_moved;
spin_lock(&this_rq->lock);
schedstat_inc(sd,?lb_cnt[idle]);
//查找最繁忙的cpu組
group?=?find_busiest_group(sd,?this_cpu,?&imbalance,?idle);
//所有的組都是平衡的,不需要做均衡
if(!group)?{
schedstat_inc(sd,?lb_nobusyg[idle]);
gotoout_balanced;
}
//找到最繁忙的組中最繁忙的運行隊列
busiest?=?find_busiest_queue(group);
if(!busiest)?{
schedstat_inc(sd,?lb_nobusyq[idle]);
gotoout_balanced;
}
//最繁忙的運行隊列是當前cpu的運行隊列,不需要做均衡
if(unlikely(busiest?==?this_rq))?{
WARN_ON(1);
gotoout_balanced;
}
schedstat_add(sd,?lb_imbalance[idle],?imbalance);
nr_moved?=?0;
if(busiest->nr_running?>?1)?{
double_lock_balance(this_rq,?busiest);
//將imbalance個進程從最繁忙的運行隊列上遷移到當前的cpu運行隊列上面
nr_moved?=?move_tasks(this_rq,?this_cpu,?busiest,
imbalance,?sd,?idle);
spin_unlock(&busiest->lock);
}
spin_unlock(&this_rq->lock);
//nr_moved?==?0,表示沒有遷移成功
if(!nr_moved)?{
schedstat_inc(sd,?lb_failed[idle]);
sd->nr_balance_failed++;
if(unlikely(sd->nr_balance_failed?>?sd->cache_nice_tries+2))?{
intwake?=?0;
spin_lock(&busiest->lock);
//active_balance表明該運行隊列是否喚醒遷移線程來進行負載均衡,
//push_cpu記錄了由哪個cpu來喚醒了其遷移線程
if(!busiest->active_balance)?{
busiest->active_balance?=?1;
busiest->push_cpu?=?this_cpu;
wake?=?1;
}
spin_unlock(&busiest->lock);
//喚醒最繁忙運行隊列上的遷移內核線程,對進程進行遷移
if(wake)
wake_up_process(busiest->migration_thread);
sd->nr_balance_failed?=?sd->cache_nice_tries;
}
if(sd->balance_interval?max_interval)
sd->balance_interval++;
}?else{
sd->nr_balance_failed?=?0;
//進程遷移成功,重置調用域的balance_interval參數
sd->balance_interval?=?sd->min_interval;
}
returnnr_moved;
out_balanced:
spin_unlock(&this_rq->lock);
/*?tune?up?the?balancing?interval?*/
//不需要進行進程的遷移,適當的加大負載均衡的間隔時間,說明
//當前的負載均衡做的比較好
if(sd->balance_interval?max_interval)
sd->balance_interval?*=?2;
return0;
}
4.3move_tasks()
staticintmove_tasks(runqueue_t?*this_rq,intthis_cpu,?runqueue_t?*busiest,
unsigned?longmax_nr_move,structsched_domain?*sd,
enumidle_type?idle)
{
prio_array_t?*array,?*dst_array;
structlist_head?*head,?*curr;
intidx,?pulled?=?0;
task_t?*tmp;
if(max_nr_move?<=?0?||?busiest->nr_running?<=?1)
gotoout;
//先從過期隊列上進行進程的遷移,這樣對硬件cache的
//影響比較小
if(busiest->expired->nr_active)?{
array?=?busiest->expired;
dst_array?=?this_rq->expired;
}?else{
array?=?busiest->active;
dst_array?=?this_rq->active;
}
new_array:
idx?=?0;
skip_bitmap:
//從優先級最高的可運行進程開始進行遷移
if(!idx)
idx?=?sched_find_first_bit(array->bitmap);
else
idx?=?find_next_bit(array->bitmap,?MAX_PRIO,?idx);
if(idx?>=?MAX_PRIO)?{
if(array?==?busiest->expired?&&?busiest->active->nr_active)?{
array?=?busiest->active;
dst_array?=?this_rq->active;
gotonew_array;
}
gotoout;
}
//找到對應優先級隊列的隊列末尾的進程,該進程應該是被
//放入的最早的一個進程了
head?=?array->queue?+?idx;
curr?=?head->prev;
skip_queue:
tmp?=?list_entry(curr,?task_t,?run_list);
curr?=?curr->prev;
//判斷該進程能否進行遷移
if(!can_migrate_task(tmp,?busiest,?this_cpu,?sd,?idle))?{
//該優先級別的隊列是否遍歷完畢
if(curr?!=?head)
gotoskip_queue;
idx++;
//該優先級別的任務隊列遍歷完畢,去遍歷下一個優先級的任務隊列
gotoskip_bitmap;
}
schedstat_inc(this_rq,?pt_gained[idle]);
schedstat_inc(busiest,?pt_lost[idle]);
//將隊列遷移到本地的任務隊列
pull_task(busiest,?array,?tmp,?this_rq,?dst_array,?this_cpu);
pulled++;
if(pulled
//該優先級別的隊列是否遍歷完畢
if(curr?!=?head)
gotoskip_queue;
idx++;
//該優先級別的任務隊列遍歷完畢,去遍歷下一個優先級的任務隊列
gotoskip_bitmap;
}
out:
returnpulled;
}
總結
以上是生活随笔為你收集整理的linux进程网络均衡,linux多CPU进程负载均衡解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle方差和协方差函数,[转载]方
- 下一篇: linux 限制单个ip流量,cento