日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

【Linux进程、线程、任务调度】四多核下负载均衡 中断负载均衡,RPS软中断负载均衡 cgroups与CPU资源分群分配 Linux为什么不是硬实时 preempt-rt对Linux实时性的改造

發布時間:2023/12/10 47 豆豆

學習交流加

  • 個人qq:
    1126137994
  • 個人微信:
    liu1126137994
  • 學習交流資源分享qq群:
    962535112

上一篇文章(點擊鏈接:點擊鏈接閱讀上一篇文章)講了:

  • CPU/IO消耗型進程
  • 吞吐率 vs. 響應
  • SCHED_FIFO算法 與 SCHED_RR算法
  • SCHED_NORMAL算法 和 CFS算法
  • nice與renice
  • chrt

本篇文章接著上一篇文章講解以下內容:

  • 多核下負載均衡
  • 中斷負載均衡,RPS軟中斷負載均衡
  • cgroups與CPU資源分群分配
  • Linux為什么不是硬實時
  • preempt-rt對Linux實時性的改造

文章目錄

    • 1、多核下負載均衡
    • 2、CPU task affinity
    • 3、IRQ affinity
    • 4、進程間的分群(cgroup)
    • 5、 Hard realtime - 可預期性
    • 6、PREEMPT_RT補丁
    • 7、總結

1、多核下負載均衡

我們知道現在的CPU都是多核的。我們可以認為在同一時刻,同一個核中只能有一個進程(task_struct,調度單位但是task_struct)在運行。但是多核的時候,在同一個時刻,不同的核中的進程是可以同時運行的。

假設你電腦有四個核,現在每個核都在跑一個線程。各個核上都是獨立的使用SCHED_FIFO算法、SCHED_RR算法與CFS完全公平調度算法去調度自己核上的task_struct,但是為了能夠使整個系統的負載能夠達到均衡(各個核的調度情況盡量保持一致,不要使某一個核太忙也不能使某一個核太輕松),某一個核也有可能會把自己的task_struct給另一個核,讓另一個核來調度它。各個核都是以勞動為樂,會接收更多的任務,核與核之間進行pull與push操作將各自的task_struct給其他核或者拿其他核的task_struct來調度。這樣的話,整個系統就會達到一種負載均衡的效果。我們稱之為多核下的負載均衡。

那么不同的進程如何做到負載均衡呢?

  • RT進程

N個優先級最高的進程分不到N個不同的核,使用pull_rt_task與push_rt_task來達到負載均衡的效果。RT進程的話,實際上強調的是實時性而不是負載均衡。

  • 普通進程

分為:

  • 周期性負載均衡(普通進程不會搶占,就所有的進程周期性的被各個核調度達到多個CPU的負載均衡)
  • IDLE時負載均衡(某一個核假設為CPU1是空閑的,0號進程想要過來讓CPU1跑,CPU1才不會去跑0號進程,CPU1會去看其他核是否在忙,如果其他核在忙,CPU1就會拿其他核的任務過來跑,CPU是盡量不會去跑0號進程的,因為一旦跑了0號進程,說明整個系統處于一種低功耗的狀態,這種狀態下整個系統只有0號進程會跑,其他進程都在休眠)
  • fork和exec時負載均衡(fork會創建一個新的task_struct,而exec只是替換進程虛擬地址空間的.data與.text,當創建一個新的進程或者替換了一個新進程,就會把這個新的task_struct推給一個最空閑的核去調度。)
    • 實驗

    two-loops.c

    #include <stdio.h> #include <pthread.h> #include <sys/types.h>void *thread_fun(void *param) {printf("thread pid:%d, tid:%lu\n", getpid(), pthread_self());while (1) ;return NULL; }int main(void) {pthread_t tid1, tid2;int ret;printf("main pid:%d, tid:%lu\n", getpid(), pthread_self());ret = pthread_create(&tid1, NULL, thread_fun, NULL);if (ret == -1) {perror("cannot create new thread");return 1;}ret = pthread_create(&tid2, NULL, thread_fun, NULL);if (ret == -1) {perror("cannot create new thread");return 1;}if (pthread_join(tid1, NULL) != 0) {perror("call pthread_join function fail");return 1;}if (pthread_join(tid2, NULL) != 0) {perror("call pthread_join function fail");return 1;}return 0; }

    編譯運行:

    $ gcc two-loops.c -pthread $ time ./a.out

    • 結果分析

    由于我的虛擬機中Linux是兩個核的,在兩個核中分別跑的時間加起來,大概等于我們用戶態的時間。這說明兩個線程被分配到兩個核中分別跑的。

    2、CPU task affinity

    affinity的意思是親和,實際上我們這里是指,讓task_struct對某一個或若干個CPU親和。也就是讓task_struct只在某幾個核上跑,不去其他核上跑。這樣實際上破壞了多核的負載均衡。

    如何實現CPU task affinity?

  • 可以在程序中直接寫代碼設置掩碼
  • int pthread_attr_setaffinity_np(pthread_attr_t *, size_t, const cpu_set_t *); 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工具
  • 比如:

    $ taskset -a -p 01 19999

    -a:進程中的所有線程,01掩碼,19999進程pid

    • 實驗

    編譯上述two-loops.c, gcc two-loops.c -pthread,運行一份

    $ ./a.out &

    top查看CPU占用率:

    把它的所有線程affinity設置為01, 02, 03后分辨來看看CPU利用率

    $ taskset -a -p 02 進程PID $ taskset -a -p 01 進程PID $ taskset -a -p 03 進程PID
    • 前兩次設置后,a.out CPU利用率應該接近100%,最后一次接近200%

    3、IRQ affinity

    中斷也可以達到負載均衡。

    假設有四個網卡,當網卡收到數據,會觸發中斷,將四個網卡隊列的中斷均分給四個CPU

    • my ethernet
      /proc/irq/74/smp_affinity 000001
      /proc/irq/75/smp_affinity 000002
      /proc/irq/76/smp_affinity 000004
      /proc/irq/77/smp_affinity 000008


    以上四個網卡的中斷全部均分給了四個CPU。

    當然中斷也可以像進程一樣讓其affinity某個進程,比如向下面這樣,可以讓01號中斷分配給某個CPU,讓其affinity該CPU。

    分配IRQ到某個CPU

    [root@boss ~]# echo 01 > /proc/irq/145/smp_affinity [root@boss ~]# cat /proc/irq/145/smp_affinity 00000001

    有一種情況比較特殊:假設一個CPU0上有一個中斷IRQ,該中斷處理函數中可能會調用軟中斷(soft_irq)處理函數。那么這個軟中斷處理函數又會占用該CPU0。那么該CPU0就會處于非常忙的狀態,達不到負載均衡。如何使軟中斷去其他核執行?

    使用RPS解決多核間的softIRQ scaling 。

    RPS可以將包處理(中斷里面的處理,其實就是軟中斷)負載均衡到多個CPU

    例如:

    [root@machine1 ~]# echo fffe > /sys/class/net/eth1/queues/rx-0/rps_cpus 將中斷分配給0~15的核,這樣可以使所有核共同處理中斷以及中斷內部的軟中斷,處理TCP/IP包的解析過程

    4、進程間的分群(cgroup)

    進程間的分群:假設有一個編譯Android系統的服務器,兩個人A與B同時要使用該服務器編譯程序,A編譯程序創建了1000個線程,B編譯程序創建了32個線程,那么如果按正常的CFS調度的話,A的程序會獲得1000/1032的CPU時間,B的程序會獲得32/1032的CPU時間,這樣的話就會導致編譯B可能會花與A相同的時間才能將程序編譯完,這樣就顯得很對B不公平(想想我B本身可能是一個小程序,卻要編譯半天,多難受啊)。Linux為了解決類似的這種問題,采用了進程間的分群思想:讓A的線程放在一個群中,B的線程放在一個群中,給A群與B群采用CFS調度各個群,然后再在A群與B群內部采用CFS調度群內的進程。這樣的話,就顯得公平一些。不會說編譯一個小程序花費太多時間。

    實際上分群使用的是樹結構,上圖可以清晰的理解。

    • 實驗

    編譯two-loops.c, gcc two-loops.c -pthread,運行三份

    $ ./a.out & $ ./a.out & $ ./a.out &

    用top觀察CPU利用率,大概各自66%。

    • 創建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/A$ cd …
      /sys/fs/cgroup/cpu$ cd B/
      /sys/fs/cgroup/cpu/B$ sudo sh -c ‘echo 3410 > cgroup.procs’

    • 這次發現3個a.out的CPU利用率大概是50%, 50%, 100%。

    5、 Hard realtime - 可預期性

    硬實時:可預期性。當一個線程被喚醒,直到這個線程被調度的這個時間段,不超過某一個預定的截止期限。稱為硬實時。如果該線程被喚醒后,被調度的時間允許超過那個截止期限,那么就不是硬實時。Linux系統不是硬實時。

    以上圖我們可以看到。Linux系統并不是硬實時系統,也就是說對于一個進程,什么時間之前(注意我們不能說在什么時候能夠被調度到,因為我們無法確定一個進程什么時候能夠被調度到)能夠被調度到,我們并不知道。那么Linux為什么不是硬實時?

    首先我們需要理解Linux系統中,有四類區間:


    當Linux跑起來后,CPU的時間都花在上述四類區間上。

    • 中斷狀態:

    當系統中有中斷,CPU不能再調度任何其他進程,就算RT進程來了也一樣得等著中斷結束后的一瞬間才能搶占CPU。而且,在中斷中,不能再進行中斷,也就是說,中斷必須結束才能干其他事。中斷是必須要被處理的。

    • 軟中斷

    軟中斷中可以被中斷。但是軟中斷中如果喚醒一個RT進程,此RT進程也不會被調度。

    • 進程處于spin_lock(自旋鎖)

    自旋鎖是發生在兩個核之間的。當某一個核如CPU0上的進程獲取spin_lock后,該核的調度器將被關閉。如果另一個核如CPU1的進程task_struct1此時想要獲取spin_lock,那么task_struct1將自旋。自旋的意思就是不停的來查看是否spin_lock被解鎖,不停的占用CPU直到可以獲取spin_lock為止。

    所以進程如果處于spin_lock,那么其他任何進程不會被調度。

    • 進程處于THREAD_RUNNING態

    當進程處于THREAD_RUNNING態,它就是可調度的,只有在這種狀態下,CPU才支持搶占。也就是說在這種狀態,加入CPU正在運行一個普通進程,此時如果某一個RT進程被喚醒,那么該RT進程就會去搶占CPU。

    上述四類區間:如果可搶占的RT進程被喚醒在前三類區間,那么該RT進程,必須等待這三類區間的事件完成結束的一瞬間搶占,否則RT進程也不會被CPU調度。如果在第四類區間上喚醒一個RT進程,則該RT進程立即搶占CPU。

    理解了以上四類區間,就很容易理解Linux為什么不是硬實時了,看下圖:

    分析:

    上圖橫軸為時間軸。T0,T1,T2…為某一時刻。

    • 系統運行分析:

    T0時刻,假設有一個系統調用陷入到內核中。此時在跑的是一個普通進程(Normal task),在T1時刻,該Normal task獲取了一個spin_lock。
    到了T2時刻,突然來了一個中斷IRQ1,則系統執行中斷處理函數IRQ1 handle人(),再中斷處理函數中又調用軟中斷(Soft IRQ),在軟中斷中的T3時刻,喚醒了一個RT進程。此時由于系統處于軟中斷狀態,所以RT進程無法搶占CPU(紅色虛線部分為無法搶占CPU)。在T4(軟中斷執行期間)時刻,又來了一個中斷IRQ2(說明軟中斷中可以中斷),然后系統執行中斷處理函數IRQ2 handler(),然后執行軟中斷處理函數。到T5時刻中斷與軟中斷執行完畢。但是由于此時Normal task還處于spin_lock狀態,所以之前被喚醒的RT進程還是依然無法占用CPU。直到T6時刻,Normal task釋放了spin_lock的一瞬間,RT進程搶占了CPU。當RT進程執行完,才會把CPU還給最開始還沒有執行完的Normal task。Normal task執行完后,退出內核的系統調用。

    • 結果分析:

    從以上分析可以看出,從T3時刻RT進程被喚醒,到T6時刻,RT進程開始執行,這段時間,我們是無法預測的,我們無法給出一個有限的上限值來度量T6-T3的值。因為在這期間,有可能會來各種中斷,有可能進程會一直處于spin_lock狀態不放。所以,我們無法確定T6-T3的時間段。所以根據硬實時的概念知,Linux系統,不是硬實時。

    6、PREEMPT_RT補丁

    可以對Linux系統打實時補丁來增加Linux 的實時性。

    比如PREEMPT_RT補丁,主要的原理如下:

    • spinlock遷移為可調度的mutex,同時報了raw_spinlock_t
    • 實現優先級繼承協議
    • 中斷線程化
    • 軟中斷線程化

    將spin_lock與中斷,軟中斷,都改造成第四類區間的可調度區間,就可以實現Linux系統的硬實時性。比如當中斷線程化,當產生中斷時,不執行中斷處理程序,直接返回。只有很小的區間是不可搶占的。

    • 以上四種方法原理,后期會詳細研究,這里不再贅述。

    7、總結

    本文主要掌握:

    • 多核下負載均衡
    • 中斷負載均衡,RPS軟中斷負載均衡
    • cgroups與CPU資源分群分配
    • Linux為什么不是硬實時
    • preempt-rt對Linux實時性的改造

    探討學習加:
    qq:1126137994
    微信:liu1126137994

    總結

    以上是生活随笔為你收集整理的【Linux进程、线程、任务调度】四多核下负载均衡 中断负载均衡,RPS软中断负载均衡 cgroups与CPU资源分群分配 Linux为什么不是硬实时 preempt-rt对Linux实时性的改造的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。