linux进程网络均衡,linux多CPU进程负载均衡解析
在linux中,支持對稱smp的處理器模型,在多處理器的情況下,每個處理器都有自己的一個運行隊列,這樣就存在著分配不均的情況,有的cpu運行隊列很多進程,導(dǎo)致一直很忙,有的cpu運行隊列可能很少的進程甚至沒有任何運行進程,導(dǎo)致cpu經(jīng)常處于空轉(zhuǎn)的狀態(tài),因此我們需要一種機制,來均衡各個cpu上運行隊列的進程數(shù)。
1數(shù)據(jù)結(jié)構(gòu)
為了支持多種多處理器模型,linux提出了調(diào)用域及組的概念,一個調(diào)用域可以包含其它的調(diào)用域或者多個組,一個組通常包含一個或者多個cpu,組的數(shù)據(jù)結(jié)構(gòu)是:
struct sched_group {
struct sched_group *next;//一個調(diào)用域可能會包含多個組,該next用于將//sched_group串到調(diào)用域的鏈表上面
cpumask_t cpumask; //每個group中可能會包含一個或者多個cpu,這里的mask表//示了該group所包含的cpu
unsigned long cpu_power;//通常是cpu的個數(shù)
};
下面是調(diào)用域的數(shù)據(jù)結(jié)構(gòu):
struct sched_domain {
struct sched_domain *parent;//調(diào)用域可以被別的調(diào)用域所包含,parent指向父調(diào)用域
struct sched_group *groups;//該調(diào)用域所包含的組
cpumask_t span;
unsigned long min_interval;//最小的時間間隔,用于檢查進行負載均衡操作的時機是否到了
unsigned long max_interval; //同上
unsigned int busy_factor; ? //當處理器在不空閑的狀態(tài)下時,進行負載均衡操作的時間間隔一般也長很多,該factor為其乘數(shù)銀子
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; //負載均衡遷移進程失敗的次數(shù)
};
下圖表現(xiàn)出了調(diào)用域和組之間的關(guān)系,這里我們關(guān)注2-cpu的smp和8-cpu的numa;2-cpu的SMP有一個調(diào)用域,調(diào)用域含兩個組,每個組含有一個cpu;8-cpu的numa中包含兩個調(diào)用域,最底層的調(diào)用域代表一個節(jié)點,每個最底層的調(diào)用域包含四個組,每個組有1個cpu,上層調(diào)用域包含兩個基礎(chǔ)的調(diào)用域。
2進行cpu負載均衡的時機
每經(jīng)過一次時鐘中斷,scheduler_tick()就調(diào)用rebalance_tick()函數(shù),rebalance_tick()函數(shù)會去觸發(fā)cpu運行隊列的負載均衡操作。該函數(shù)從最底層的調(diào)度域開始,一直到最上層的調(diào)度域進行檢查,對于每個調(diào)度域都去查看是否到了調(diào)用load_balance()函數(shù)的時間,該函數(shù)會進行cpu的負載均衡操作。由當前cpu的idle狀態(tài)和sched_domain的參數(shù)來確定調(diào)用load_balance()的時間間隔。
3 2-cpu smp和8-cpu numa cpu模型的調(diào)用域初始化
對調(diào)用域的初始化部分的代碼在linux2.6.11版本里面是在arch_init_sched_domains()函數(shù)中,被sched_init_smp()函數(shù)所調(diào)用。
在sched.c中定義了一個數(shù)組static struct sched_group sched_group_phys[NR_CPUS];每個數(shù)組元素代表一個cpu組。另外定義了一個每cpu變量Sched.c (kernel):static DEFINE_PER_CPU(struct sched_domain, phys_domains);,系統(tǒng)為每個物理cpu都生成了一個調(diào)度域數(shù)據(jù)結(jié)構(gòu)。
對于2-cpu smp的情形來說,其初始化后,調(diào)度域和各個組之間的關(guān)系是:
在這個里面雖然每個cpu都有個調(diào)度域的數(shù)據(jù)結(jié)構(gòu),但調(diào)度域的groups鏈表指向的都是同一個group鏈表
8-cpu numa的組和調(diào)度域關(guān)系:
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;
//當前運行隊列中可運行的進程數(shù)決定了當前運行隊列的
//cpu_load參數(shù)
old_load?=?this_rq->cpu_load;
this_load?=?this_rq->nr_running?*?SCHED_LOAD_SCALE;
if(this_load?>?old_load)
old_load++;
//將運行隊列的cpu?load值設(shè)定為上一次的cpu_load和本次cpu_load的平均值
this_rq->cpu_load?=?(old_load?+?this_load)?/?2;
//從該cpu所屬的調(diào)度域開始,依次遍歷各個更高級的調(diào)用域
for_each_domain(this_cpu,?sd)?{
unsigned?longinterval;
//在該調(diào)度域上不需要做負載均衡
if(!(sd->flags?&?SD_LOAD_BALANCE))
continue;
//若當前cpu不處于空閑狀態(tài)的話,其調(diào)用load_balance的時間間隔會比較長
interval?=?sd->balance_interval;
if(idle?!=?SCHED_IDLE)
interval?*=?sd->busy_factor;
//將時間間隔ms轉(zhuǎn)換成jiffies
interval?=?msecs_to_jiffies(interval);
if(unlikely(!interval))
interval?=?1;
//當前的時間戳和上次balance的時間大于其間隔的話,調(diào)用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);
//喚醒最繁忙運行隊列上的遷移內(nèi)核線程,對進程進行遷移
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;
//進程遷移成功,重置調(diào)用域的balance_interval參數(shù)
sd->balance_interval?=?sd->min_interval;
}
returnnr_moved;
out_balanced:
spin_unlock(&this_rq->lock);
/*?tune?up?the?balancing?interval?*/
//不需要進行進程的遷移,適當?shù)募哟筘撦d均衡的間隔時間,說明
//當前的負載均衡做的比較好
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:
//從優(yōu)先級最高的可運行進程開始進行遷移
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;
}
//找到對應(yīng)優(yōu)先級隊列的隊列末尾的進程,該進程應(yīng)該是被
//放入的最早的一個進程了
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))?{
//該優(yōu)先級別的隊列是否遍歷完畢
if(curr?!=?head)
gotoskip_queue;
idx++;
//該優(yōu)先級別的任務(wù)隊列遍歷完畢,去遍歷下一個優(yōu)先級的任務(wù)隊列
gotoskip_bitmap;
}
schedstat_inc(this_rq,?pt_gained[idle]);
schedstat_inc(busiest,?pt_lost[idle]);
//將隊列遷移到本地的任務(wù)隊列
pull_task(busiest,?array,?tmp,?this_rq,?dst_array,?this_cpu);
pulled++;
if(pulled
//該優(yōu)先級別的隊列是否遍歷完畢
if(curr?!=?head)
gotoskip_queue;
idx++;
//該優(yōu)先級別的任務(wù)隊列遍歷完畢,去遍歷下一個優(yōu)先級的任務(wù)隊列
gotoskip_bitmap;
}
out:
returnpulled;
}
總結(jié)
以上是生活随笔為你收集整理的linux进程网络均衡,linux多CPU进程负载均衡解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle方差和协方差函数,[转载]方
- 下一篇: linux 限制单个ip流量,cento