1. 程式人生 > 其它 >Linux CPU親緣性詳解

Linux CPU親緣性詳解

一、前言

自動根據CPU數目設定程序個數和繫結CPU親緣性”。當時筆者對CPU親緣性沒有任何概念,當時作者只是下意識的打開了google並輸入CPU親緣性(CPU Affinity)簡單了做了個瞭解。

後來,在筆者參加實際工作以後,就碰到了這麼兩個問題。

問題一:如何在SMP的系統中,保證某個特定程序即使在其他程序都很忙的情況下都能夠獲得足夠的CPU資源?解決的思路主要有以下兩種:

    • 提高程序的處理優先順序
    • 從SMP系統中,專門劃撥出某一個CPU用於執行該程式。 而將其他程序劃撥到其他的CPU上進行執行。

問題二:通過每日監控資料,我們發現伺服器的CPU使用率出現這樣子的情況,除了CPU0,其他CPU的負載都很低

  我們選擇了通過設定CPU親緣性的方式進行優化,在完成相關優化後,我們的應用程式效能得到了一定的提高。(大致有10%的效能提升)

此次,筆者藉著博文的機會將“CPU親緣性”這一特性的學習過程整理下來,以備日後查驗。注意,本文所提到的CPU親緣性均基於Linux。

二、什麼是CPU親緣性

所謂CPU親緣性可以分為兩大類:軟親緣性和硬親緣性

Linux 核心程序排程器天生就具有被稱為 CPU 軟親緣性(soft affinity) 的特性,這意味著程序通常不會在處理器之間頻繁遷移。這種狀態正是我們希望的,因為程序遷移的頻率小就意味著產生的負載小。但不代表不會進行小範圍的遷移。

  CPU 硬親緣性是指通過Linux提供的相關CPU親緣性設定介面,顯示的指定某個程序固定的某個處理器上執行。本文所提到的CPU親緣性主要是指硬親緣性。

三、使用CPU親緣性的好處

  目前主流的伺服器配置都是SMP架構,在SMP的環境下,每個CPU本身自己會有快取,快取著程序使用的資訊,而程序可能會被kernel排程到其他CPU上(即所謂的core migration),如此,CPU cache命中率就低了。設定CPU親緣性,程式就會一直在指定的cpu執行,防止程序在多SMP的環境下的core migration,從而避免因切換帶來的CPU的L1/L2 cache失效。從而進一步提高應用程式的效能。

【自加,每個cpu上都有migration執行緒】:

root@axm:~# ps -eo psr,pid,comm | grep 'migration'
0 9 migration/0
1 66 migration/1
2 73 migration/2
3 80 migration/3
4 87 migration/4
5 94 migration/5
6 101 migration/6
7 108 migration/7
8 115 migration/8
9 122 migration/9
10 129 migration/10
11 136 migration/11
12 143 migration/12
13 150 migration/13
14 157 migration/14
15 164 migration/15

四、Linux CPU親緣性的使用

4.1 方法

我們有兩種辦法指定程式執行的CPU親緣性。

  • 通過Linux提供的taskset工具指定程序執行的CPU。
  • 方式二,glibc本身也為我們提供了這樣的介面。

下面的內容主要為大家講解如何通過程式設計的方式設定程序的CPU親緣性。

4.2 相關介面

  利用glibc庫中的sched_getaffinity介面,我們獲取應用程式當前的cpu親緣性,而通過sched_setaffinity介面則可以把應用程式繫結到固定的某個或某幾cpu上執行。相關定義如下:

 1 #include <sched.h>
 2 
 3 
 4 void CPU_ZERO(cpu_set_t *set);
 5 void CPU_CLR(int cpu, cpu_set_t *set);
 6 void CPU_SET(int cpu, cpu_set_t *set);
 7 int CPU_ISSET(int cpu, cpu_set_t *set);
 8 
 9 int sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask);
10 
11 int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask);

其中的cpu_set_t結構體的具體定義:

 1  /*/usr/include/bits/sched.h*/
 2 
 3 
 4 # define __CPU_SETSIZE  1024
 5 # define __NCPUBITS (8 * sizeof (__cpu_mask))
 6  
 7 /* Type for array elements in ‘cpu_set‘.  */
 8 typedef unsigned long int __cpu_mask;
 9  
10 typedef struct
11 {
12   __cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
13 } cpu_set_t;

可以看到其用每一bit位表示一個cpu的狀態,最多可以表示1024個cpu的親緣狀態,這在目前來說足夠用了。

在 Linux 核心中,所有的程序都有一個相關的資料結構,稱為task_struct。這個結構非常重要,原因有很多;其中與親緣性(affinity)相關度最高的是 cpus_allowed位掩碼。這個位掩碼由n位組成,與系統中的n個邏輯處理器一一對應。 具有 4 個物理 CPU 的系統可以有 4 位。如果這些 CPU 都啟用了超執行緒,那麼這個系統就有一個 8 位的位掩碼

如果為給定的程序設定了給定的位,那麼這個程序就可以在相關的 CPU 上執行。因此,如果一個程序可以在任何 CPU 上執行,並且能夠根據需要在處理器之間進行遷移,那麼位掩碼就全是 1。實際上,這就是 Linux 中程序的預設狀態。相關核心排程程式碼如下:

 1 static inline
 2 int select_task_rq(struct task_struct *p, int sd_flags, int wake_flags)
 3 {
 4     int cpu = p->sched_class->select_task_rq(p, sd_flags, wake_flags);
 5 
 6     /*
 7      * In order not to call set_task_cpu() on a blocking task we need
 8      * to rely on ttwu() to place the task on a valid ->cpus_allowed
 9      * cpu.
10      *
11      * Since this is common to all placement strategies, this lives here.
12      *
13      * [ this allows ->select_task() to simply return task_cpu(p) and
14      *   not worry about this generic constraint ]
15      */
16     if (unlikely(!cpumask_test_cpu(cpu, &p->cpus_allowed) ||
17                  !cpu_online(cpu)))
18         cpu = select_fallback_rq(task_cpu(p), p);
19 
20     return cpu;
21 }

  另外的幾個巨集CPU_CLR\CPU_ISSET\CPU_SET\CPU_ZERO定義也都定義在標頭檔案/usr/include/bits/sched.h內:

 1 /* Access functions for CPU masks.  */
 2 # define __CPU_ZERO(cpusetp)  
 3   do {                                         
 4     unsigned int __i;                                  
 5     cpu_set_t *__arr = (cpusetp);                         
 6     for (__i = 0; __i < sizeof (cpu_set_t) / sizeof (__cpu_mask); ++__i)      
 7       __arr->__bits[__i] = 0;                               
 8   } while (0)
 9 # define __CPU_SET(cpu, cpusetp) 
10   ((cpusetp)->__bits[__CPUELT (cpu)] |= __CPUMASK (cpu))
11 # define __CPU_CLR(cpu, cpusetp) 
12   ((cpusetp)->__bits[__CPUELT (cpu)] &= ~__CPUMASK (cpu))
13 # define __CPU_ISSET(cpu, cpusetp) 
14   (((cpusetp)->__bits[__CPUELT (cpu)] & __CPUMASK (cpu)) != 0)
15 #endif

  利用這幾個巨集方便我們操作指定cpu的對應bit位,比如清零,置位等。看一個完整的demo程式:

 1 /**
 2  * FileName: affinity_demo.c
 3  */
 4  
 5 #define _GNU_SOURCE
 6 
 7 #include <stdint.h>
 8 #include <stdio.h>
 9 #include <sched.h>
10 #include <pthread.h>
11 #include <stdlib.h>
12 
13 
14 static inline void print_cpu_mask(cpu_set_t cpu_mask)
15 {
16     unsigned char flag = 0;
17     printf("Cpu affinity is ");
18     for (unsigned int i = 0; i < sizeof(cpu_set_t); i ++)
19     {
20         if (CPU_ISSET(i, &cpu_mask))
21         {
22             if (flag == 0)
23             {
24                 flag = 1;
25                 printf("%d", i);
26             }
27             else
28             {
29                 printf(",%d", i);
30             }
31         }
32     }
33     printf(".\n");
34 }
35 
36 static inline void get_cpu_mask(pid_t pid, cpu_set_t *mask)
37 {
38     if (sched_getaffinity(pid, sizeof(cpu_set_t), mask) == -1)
39     {
40         perror("get cpu affinity failed.\n");
41         abort();
42     }
43 }
44 
45 static inline void set_cpu_mask(pid_t pid, cpu_set_t *mask)
46 {
47     if (sched_setaffinity(pid, sizeof(cpu_set_t), mask) == -1)
48     {
49         perror("set cpu affinity failed.\n");
50         abort();
51     }
52 }
53 
54 int main(int argc, char *argv[])
55 {
56     unsigned int active_cpu = 0;
57     cpu_set_t cpu_mask;
58 
59     get_cpu_mask(0, &cpu_mask);
60     print_cpu_mask(cpu_mask);
61 
62     CPU_ZERO(&cpu_mask);
63     CPU_SET(active_cpu, &cpu_mask);
64     set_cpu_mask(0, &cpu_mask);
65 
66     get_cpu_mask(0, &cpu_mask);
67     print_cpu_mask(cpu_mask);
68 
69     for(;;)
70     {
71         ;
72     }
73     return 0;
74 }

本文來自部落格園,作者:Mr-xxx,轉載請註明原文連結:https://www.cnblogs.com/MrLiuZF/p/15154067.html