一、多核負載均衡
負載均衡這個概念是針對多核CPU而言的,希望達到的狀態(tài)是各個核的調度情況盡量保持一致,不要使某一個核太忙也不能使某一個核太輕松,核與核之間相對均勻分配任務。而每個獨立的核上的調度策略依然是使用SCHED_FIFO算法、SCHED_RR算法與SCHED_NORMAL算法。
不同進程如何做到負載均衡:
- RT進程
N個優(yōu)先級最高的進程分不到N個不同的核,使用pull_rt_task與push_rt_task來達到負載均衡的效果。RT進程的話,實際上強調的是實時性而不是負載均衡。
- 普通進程
周期性負載均衡:所有的進程周期性的被各個核調度達到多個CPU的負載均衡。
IDLE時負載均衡:一旦跑了0號進程,說明整個系統(tǒng)處于一種低功耗的狀態(tài),這種狀態(tài)下整個系統(tǒng)只有0號進程會跑,其他進程都在休眠。這里的負載均衡說的是只要其他核還在忙,當前核就會想辦法去幫忙,而不是進入IDLE狀態(tài)。
fork和exec時負載均衡:當創(chuàng)建一個新的進程或者替換了一個新進程,就會把這個新的task_struct推給一個最空閑的核去調度。
二、負載均衡限制
這里講的是如何打破常規(guī)的負責均衡,主要有兩個策略:
1)cpu task affinity
affinity的意思是親和的意思,讓task_struct對某一個或若干個CPU親和。也就是讓task_struct只在某幾個核上跑,不去其他核上跑。
如何實現(xiàn)CPU task affinity?
- 代碼API實現(xiàn):
int pthread_attr_setaffinity_np(pthread_attr_t *, size_t, const cpu_set_t *);1
int pthread_attr_getaffinity_np(pthread_attr_t *, size_t, cpu_set_t *);
int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask);
設置掩碼來保證某一個線程對某幾個核親和,比如下方的0x6(110),就是設置線程只能在核2與核1上運行。
- taskset工具實現(xiàn):
taskset -a -p 01 23421
注:這里01是掩碼(0001)表示0核,02(0010)表示1核,03(0011)表示0核或者1核,以此類推。 23421是進程pid。-a 是所有線程。
2)cgroup
調度思想:進程分群,群與群之間CFS調度,群內部再CFS調度。這種分層思想能保證1000個線程的A程序與10個線程的B程序能相對公平地得到調度。如果僅僅只在線程間CFS,那么A程序得到調度的概率遠大于B程序。
這里如何限制呢?
編譯two-loops.c, gcc two-loops.c -pthread,運行三份
$ ./a.out &
$ ./a.out &
$ ./a.out &

用top觀察CPU利用率,大概各自66%。
限制方法1:換cgroup
創(chuàng)建A,B兩個cgroup:
/sys/fs/cgroup/cpu$ sudo mkdir A
/sys/fs/cgroup/cpu$ sudo mkdir B
把3個a.out中的2個加到A,1個加到B:
/sys/fs/cgroup/cpu/A$ sudo sh -c ‘echo 3407 > cgroup.procs’
/sys/fs/cgroup/cpu/A$ sudo sh -c ‘echo 3413 > cgroup.procs’
/sys/fs/cgroup/cpu/B$ sudo sh -c ‘echo 3410 > cgroup.procs’
這時發(fā)現(xiàn)3個a.out的CPU利用率大概是50%, 50%, 100%。
限制方法2:調整cgroup的權重
/sys/fs/cgroup/cpu/A$ sudo sh -c ‘echo 2048 > cpu.shares’
然后B的權重保持1024,那么3個a.out的CPU利用率大概又回到了各占60%多的樣子。
限制方法3:限制一個cgroup在一個周期內最多跑多久。
cpu.cfs_quota_us:限制一個cgroup組在period時間周期內跑多長時間。
cpu.cfs_period_us:時間周期。
/sys/fs/cgroup/cpu/A$ cat cpu.cfs_period_us
100000
/sys/fs/cgroup/cpu/A$ sudo sh -c ‘echo 20000 > cpu.cfs_quota_us’
A這個group里面cfs進程100000us周期內,最多可以跑20000us,那么相當于CPU利用率為20%。若超過cpu.cfs_period_us,按N核*100 %算。
三、實時性
硬實時:任務從喚醒到調度,時間不超過某一個預定的截止期限,具有可預期性。
軟實時:相比硬實時,被調度的時間允許超過那個截止期限,無法做到精確可控。
Linux就屬于軟實時系統(tǒng)。

但是kernel隨著版本的迭代,越發(fā)地往硬實時靠了。
Linux為什么不是硬實時的?
因為總結起來有3個不可搶占區(qū)間:
中斷狀態(tài):當系統(tǒng)中有中斷,CPU不能再調度任何其他進程,就算RT進程來了也一樣得等著中斷結束后的一瞬間才能搶占CPU。而且在中斷中,不能再進行中斷,也就是說,中斷必須結束才能干其他事。中斷是必須要被處理的。軟中斷狀態(tài):軟中斷中可以被中斷。但是軟中斷中如果喚醒一個RT進程,此RT進程也不會被調度。進程處于spin_lock(自旋鎖)狀態(tài):自旋鎖是發(fā)生在兩個核之間的。當某一個核如CPU0上的進程獲取spin_lock后,該核的調度器將被關閉。如果另一個核如CPU1的進程task_struct1此時想要獲取spin_lock,那么task_struct1將自旋。自旋的意思就是不停的來查看是否spin_lock被解鎖,不停的占用CPU直到可以獲取spin_lock為止。所以進程如果處于spin_lock,那么其他任何進程不會被調度。
實例說明:

運行分析:
- T0時刻:假設有一個系統(tǒng)調用陷入到內核中。此時在跑的是一個普通進程(Normal task)。
- T1時刻:該Normal task獲取了一個spin_lock。
- T2時刻:突然來了一個中斷IRQ1,則系統(tǒng)執(zhí)行中斷處理函數(shù)IRQ1 handle,在中斷處理函數(shù)中又調用軟中斷(Soft IRQ)。
- T3時刻:在軟中斷中喚醒了一個RT進程。此時由于系統(tǒng)處于軟中斷狀態(tài),所以RT進程無法搶占CPU(紅色虛線部分為無法搶占CPU)。
- T4時刻:又來了一個中斷IRQ2(說明軟中斷中可以中斷),然后系統(tǒng)執(zhí)行中斷處理函數(shù)IRQ2 handler,然后執(zhí)行軟中斷處理函數(shù)。
- T5時刻:中斷與軟中斷執(zhí)行完畢。但是由于此時Normal task還處于spin_lock狀態(tài),所以之前被喚醒的RT進程還是依然無法占用CPU。
- T6時刻:Normal task釋放了spin_lock的一瞬間,RT進程搶占了CPU。當RT進程執(zhí)行完,才會把CPU還給最開始還沒有執(zhí)行完的Normal task。Normal task執(zhí)行完后,退出內核的系統(tǒng)調用。
RT在T3時刻被喚醒,因為中斷、軟中斷、自旋鎖的影響,直到T6才得到調度,因此T3-T6這個階段是不可控的,根據硬實時的概念知,Linux系統(tǒng)不是硬實時的。
參考:
宋寶華Linux的進程、線程以及調度
《 Linux內核設計與實現(xiàn)》