成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux 內核是如何感知到硬件上的 NUMA 信息的?

系統 Linux
在現代服務器的非統一內存訪問(NUMA)是一種用于多處理器硬件架構下,識別和保存每個 CPU 核和內存條之間的連接拓撲非常的重要。因為 CPU 只是和它直連的內存訪問速度最快,訪問和其它 CPU 連接的內存速度將會大大下降。

在 Linux 程序運行過程中,有一個對性能影響較大的特性,那就是 NUMA。在不少公司中,都通過 numactl 等命令對運行的服務進行了 NUMA 綁定,進而提高程序的運行性能。

那么我們今天來深入了解一下 NUMA 的原理。在硬件上的 NUMA 組成為什么會影響程序的運行性能,Linux 操作系統又是如何識別 NUMA 信息,來將 CPU 和內存進行分組劃分 node 的。

一、NUMA 介紹

NUMA 全稱是 Non-uniform memory access,是非一致性內存訪問的意思。不過這段話還是由點費解,我們需要看看硬件才能更好地理解它。

現代的 CPU 都在硬件內部實現了一個內存控制器,內存條都會和這個內存控制器進行相連。

圖片圖片

之前我們在 深入了解服務器 CPU 的型號、代際、片內與片間互聯架構 一文中提到過,服務器 CPU 和個人 PC CPU 的一個很大的區別就是擴展性。在一臺服務器的內部是支持插2/4/8等多 CPU 的。每個 CPU 都可以連接幾條的內存。兩個 CPU 之間如果想要訪問對方上連接的內存條,中間就得跨過 UPI 總線。

圖片圖片

下面是一臺服務器的實際內部圖片。中間兩個銀色長方形的東東是罩著散熱片的 CPU,每個 CPU 旁邊都有一些內存插槽,支持插入多條內存。

圖片圖片

CPU 擴展性的設計極大地提升了服務器上的 CPU 核數與內存容量。但同時也帶來了另外一個問題,那就是 CPU 物理核在訪問不同的內存條的時候延遲是不同的。這就是非一致性內存訪問的含義。

其實不僅僅是跨 CPU 訪問存在延時差異。在服務器高核心 CPU 上,由于 Mesh 架構、以及存在兩個內存控制器,物理核訪問不同的內存控制器上的內存條也會有差異。只不過這個差異沒有跨 CPU 差異大。

這種問題的出現使得 Linux 操作系統不得不關注內存訪問速度不平均的問題。你在 Linux 上執行 numactl 命令可以查看你機器上的 NUMA 配置情況。拿我手頭的一臺虛擬機來舉例。

# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3
node 0 size: 7838 MB
node 0 free: 6208 MB
node 1 cpus: 4 5 6 7
node 1 size: 7934 MB
node 1 free: 6589 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10

上面的輸出中展示了 Linux 把所有的 CPU 核心和內存分成了兩個 node。其中 node 0 中的擁有的 CPU 核心是 0、1、2、3 這四個核,總共擁有 7838 MB 的內存。node 1 中擁有的核心是 4、5、6、7 四個核,擁有的內存是 7934 MB。

另外 node distances 這里顯示了跨 node 進行內存訪問時一個大概的延時差距。同 node 內部的內存訪問肯定是最快的,跨 node 則相對較慢。

那么內核是如何識別到底層的 NUMA 信息的呢?

二、Linux 對 NUMA 信息的讀取

2.1 Linux 內核識別如何識別內存屬于哪個節點

在計算機的體系結構中,除了操作系統和硬件外,其實中間還存在著一層固件,英文名叫 firmware。它是位于主板上的使用 SPI Nor Flash 存儲著的軟件。起著在硬件和操作系統中間承上啟下的作用。它負責著硬件自檢、初始化硬件設備、加載操作系統引導程序,并提供接口將控制權轉移到操作系統。

圖片圖片

回到我們今天的話題。那么 CPU 和內存條之間這種訪問非一致性特點,Linux 就是通過固件來獲得這個知識的。其中在 Linux 和固件中間的接口規范是 ACPI(Advanced Configuration and Power Interface),高級配置和電源接口。

這是較新的 6.5 版本的文檔地址: https://uefi.org/sites/default/files/resources/ACPI_Spec_6_5_Aug29.pdf。感興趣的同學可以下載下來。

在這個接口規范中的第 17 章中描述了 NUMA 相關的內容。在 ACPI 中定義了兩個表,分別是:

  • SRAT(System Resource Affinity Table)。在這個表中表示的是 CPU 核和內存的關系圖。包括有幾個 node,每個 node 里面有那幾個 CPU 邏輯核,有哪些內存。
  • SLIT(System Locality Information Table)。在這個表中記錄的是各個結點之間的距離。

有了這個規范,CPU 讀取這兩個表就可以獲得 NUMA 系統的 CPU 及物理內存分布信息。操作系統在啟動的時候會執行 start_kernel 這個核心函數,然后會調用到

//file:arch/x86/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
 ...
// 保存物理內存檢測結果
 e820__memory_setup();
 ...

// membloc內存分配器初始化
 e820__memblock_setup();

// 內存初始化(包括 NUMA 機制初始化)
 initmem_init();
}

在 setup_arch 中顯示調用了 e820__memory_setup 來保存物理內存檢測結果。然后調用 e820__memblock_setup 初始化內存分配器。詳情參見Linux 內核“偷吃”了你的內存! 一文。在 initmem_init 完成了 NUMA 的初始化。

在 initmem_init 中,依次調用了 x86_numa_init、numa_init、x86_acpi_numa_init,最后執行到了 acpi_numa_init 函數中來讀取 ACPI 中的 SRAT 表,獲取到各個 node 中的 CPU 邏輯核、內存的分布信息。

//file:drivers/acpi/numa/srat.c
int __init acpi_numa_init(void)
{
 ...
 // 解析 SRAT 表中的 NUMA 信息
 // 具體包括:CPU_AFFINITY、MEMORY_AFFINITY 等
 if (!acpi_table_parse(ACPI_SIG_SRAT, acpi_parse_srat)) {
  ...
 }
 ...
}

在 SRAT 表讀取并解析完成后,Linux 操作系統就知道了內存和 node 的關系了。numa 信息都最后保存在了 numa_meminfo 這個數據結構中,這是一個全局的列表,每一項都是(起始地址, 結束地址, 節點編號)的三元組,描述了內存塊與 NUMA 節點的關聯關系。

//file:arch/x86/mm/numa.c
static struct numa_meminfo numa_meminfo __initdata_or_meminfo;

//file:arch/x86/mm/numa_internal.h
struct numa_meminfo {
 int   nr_blks;
 struct numa_memblk blk[NR_NODE_MEMBLKS];
};

2.2 memblock 分配器 關聯 NUMA 信息

在此之后,Linux 就可以通過 numa_meminfo 數組來獲取硬件 NUMA 信息了。前面在 一文中我們提到了內核的 memblock 內存分配器。有了 numa_meminfo 數組,memblock 就可以根據這個信息讀取到自己各個 region 分別是屬于哪個 node 的了。

這件工作是在 numa_init 中開始的。

//file:arch/x86/mm/numa.c
static int __init numa_init(int (*init_func)(void))
{
 ...

//2.1 把numa相關的信息保存在 numa_meminfo 中
 init_func();

//2.2 memblock 添加 NUMA 信息,并為每個 node 申請對象
 numa_register_memblks(&numa_meminfo);

 ...
// 用于將各個CPU core與NUMA節點關聯
 numa_init_array();
return0;
}

在 numa_register_memblks 中完成了三件事情

  • 將每一個 memblock region 與 NUMA 節點號關聯
  • 為每一個 node 都申請一個表示它的內核對象(pglist_data)
  • 再次打印 memblock 信息
//file:arch/x86/mm/numa.c
static int __init numa_register_memblks(struct numa_meminfo *mi)
{
 ...
//1.將每一個 memblock region 與 NUMA 節點號關聯
for (i = 0; i < mi->nr_blks; i++) {
struct numa_memblk *mb = &mi->blk[i];
  memblock_set_node(mb->start, mb->end - mb->start,
      &memblock.memory, mb->nid);
 }
 ...
//2.為所有可能存在的node申請pglist_data結構體空間 
 for_each_node_mask(nid, node_possible_map) {
  ...
//為nid申請一個pglist_data結構體
  alloc_node_data(nid);
 }

//3.打印MemBlock內存分配器的詳細調試信息
 memblock_dump_all();
}

這個函數的詳細邏輯就不展開了。我們來看下 memblock_dump_all。如果你開啟了 memblock=debug 啟動參數,在它執行完后,memblock 內存分配器的信息再次被打印了出來。

[    0.010796] MEMBLOCK configuration:
[    0.010797]  memory size = 0x00000003fff78c00 reserved size = 0x0000000003d7bd7e
[    0.010797]  memory.cnt  = 0x4
[    0.010799]  memory[0x0] [0x0000000000001000-0x000000000009efff], 0x000000000009e000 bytes on node 0 flags: 0x0
[    0.010800]  memory[0x1] [0x0000000000100000-0x00000000bffd9fff], 0x00000000bfeda000 bytes on node 0 flags: 0x0
[    0.010801]  memory[0x2] [0x0000000100000000-0x000000023fffffff], 0x0000000140000000 bytes on node 0 flags: 0x0
[    0.010802]  memory[0x3] [0x0000000240000000-0x000000043fffffff], 0x0000000200000000 bytes on node 1 flags: 0x0
[    0.010803]  reserved.cnt  = 0x7
[    0.010804]  reserved[0x0] [0x0000000000000000-0x00000000000fffff], 0x0000000000100000 bytes on node 0 flags: 0x0
[    0.010806]  reserved[0x1] [0x0000000001000000-0x000000000340cfff], 0x000000000240d000 bytes on node 0 flags: 0x0
[    0.010807]  reserved[0x2] [0x0000000034f31000-0x000000003678ffff], 0x000000000185f000 bytes on node 0 flags: 0x0
[    0.010808]  reserved[0x3] [0x00000000bffe0000-0x00000000bffe3d7d], 0x0000000000003d7e bytes on node 0 flags: 0x0
[    0.010809]  reserved[0x4] [0x000000023fffb000-0x000000023fffffff], 0x0000000000005000 bytes flags: 0x0
[    0.010810]  reserved[0x5] [0x000000043fff9000-0x000000043fffdfff], 0x0000000000005000 bytes flags: 0x0
[    0.010811]  reserved[0x6] [0x000000043fffe000-0x000000043fffffff], 0x0000000000002000 bytes on node 1 flags: 0x0

不過這次不同的是,每一段內存地址范圍后面都跟上了 node 的信息,例如 on node 0、on node 1 等。

三、操作系統內存識別過程總結

在剛開始操作系統啟動的時候,操作系統通過 e820 讀取到了內存的布局,并將它打印到了日志中。

[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bffd9fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000bffda000-0x00000000bfffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000feff4000-0x00000000feffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000043fffffff] usable

接著內核創建了 memblock 內存分配器來進行系統啟動時的內存管理。如果開啟了 memblock=debug 啟動參數,同樣能把它打印出來。

[    0.010238] MEMBLOCK configuration:
[    0.010239]  memory size = 0x00000003fff78c00 reserved size = 0x0000000003c6d144
[    0.010240]  memory.cnt  = 0x3
[    0.010241]  memory[0x0] [0x0000000000001000-0x000000000009efff], 0x000000000009e000 bytes flags: 0x0
[    0.010243]  memory[0x1] [0x0000000000100000-0x00000000bffd9fff], 0x00000000bfeda000 bytes flags: 0x0
[    0.010244]  memory[0x2] [0x0000000100000000-0x000000043fffffff], 0x0000000340000000 bytes flags: 0x0
[    0.010245]  reserved.cnt  = 0x4
[    0.010246]  reserved[0x0] [0x0000000000000000-0x0000000000000fff], 0x0000000000001000 bytes flags: 0x0
[    0.010247]  reserved[0x1] [0x00000000000f5a40-0x00000000000f5b83], 0x0000000000000144 bytes flags: 0x0
[    0.010248]  reserved[0x2] [0x0000000001000000-0x000000000340cfff], 0x000000000240d000 bytes flags: 0x0
[    0.010249]  reserved[0x3] [0x0000000034f31000-0x000000003678ffff], 0x000000000185f000 bytes flags: 0x0

不過到這里,Linux 操作系統還不知道內存的 NUMA 信息。它通過 ACPI 接口讀取固件中的 SRAT 表,將 NUMA 信息保存到 numa_meminfo 數組中。從此,Linux 就知道了硬件上的 NUMA 信息,并對 memblock 內存分配器也設置了 node 信息。并再次將其打印了出來。這次 memblock 的每一個 region 中就都攜帶了 node 信息。

[    0.010796] MEMBLOCK configuration:
[    0.010797]  memory size = 0x00000003fff78c00 reserved size = 0x0000000003d7bd7e
[    0.010797]  memory.cnt  = 0x4
[    0.010799]  memory[0x0] [0x0000000000001000-0x000000000009efff], 0x000000000009e000 bytes on node 0 flags: 0x0
[    0.010800]  memory[0x1] [0x0000000000100000-0x00000000bffd9fff], 0x00000000bfeda000 bytes on node 0 flags: 0x0
[    0.010801]  memory[0x2] [0x0000000100000000-0x000000023fffffff], 0x0000000140000000 bytes on node 0 flags: 0x0
[    0.010802]  memory[0x3] [0x0000000240000000-0x000000043fffffff], 0x0000000200000000 bytes on node 1 flags: 0x0
[    0.010803]  reserved.cnt  = 0x7
[    0.010804]  reserved[0x0] [0x0000000000000000-0x00000000000fffff], 0x0000000000100000 bytes on node 0 flags: 0x0
[    0.010806]  reserved[0x1] [0x0000000001000000-0x000000000340cfff], 0x000000000240d000 bytes on node 0 flags: 0x0
[    0.010807]  reserved[0x2] [0x0000000034f31000-0x000000003678ffff], 0x000000000185f000 bytes on node 0 flags: 0x0
[    0.010808]  reserved[0x3] [0x00000000bffe0000-0x00000000bffe3d7d], 0x0000000000003d7e bytes on node 0 flags: 0x0
[    0.010809]  reserved[0x4] [0x000000023fffb000-0x000000023fffffff], 0x0000000000005000 bytes flags: 0x0
[    0.010810]  reserved[0x5] [0x000000043fff9000-0x000000043fffdfff], 0x0000000000005000 bytes flags: 0x0
[    0.010811]  reserved[0x6] [0x000000043fffe000-0x000000043fffffff], 0x0000000000002000 bytes on node 1 flags: 0x0

以上就是 Linux 內存中 NUMA 機制的初始化大概過程。

總結

在現代服務器的非統一內存訪問(NUMA)是一種用于多處理器硬件架構下,識別和保存每個 CPU 核和內存條之間的連接拓撲非常的重要。因為 CPU 只是和它直連的內存訪問速度最快,訪問和其它 CPU 連接的內存速度將會大大下降。

Linux 通過固件讀取 ACPI 規范中的 SRAT 和 SLIT 表識別 NUMA 信息,在系統啟動過程中,經一系列函數調用完成 NUMA 初始化,將信息保存到numa_meminfo,并使 memblock 分配器關聯 NUMA 信息。最后通過 e820 讀取內存布局,再結合 ACPI 獲取的 NUMA 信息完成內存識別及相關設置。

當內核有了硬件 NUMA 信息的拓撲圖后,我們在應用側就可以通過 numactl 等命令來優化程序的性能了!

不過最后要補充說一點,關于 NUMA 綁定并不是有益無害。在業界也有不同的聲音。比如 Oracal 的技術大咖們認為綁定 NUMA 可能在全局內存并未用盡的情況下出現內存分配錯誤,導致系統出現劇烈抖動。

責任編輯:武曉燕 來源: 開發內功修煉
相關推薦

2019-08-05 13:40:52

LinuxUbuntu硬件規格

2018-10-10 14:02:30

Linux系統硬件內核

2024-12-31 10:47:10

2009-03-27 18:27:48

2009-09-25 10:48:07

Linux硬件信息操作系統

2014-01-13 15:00:51

InxiLinux硬件

2015-09-11 15:56:52

內核構建Linux

2009-12-11 15:47:54

Linux硬件信息

2022-08-03 11:00:20

Linux內核

2019-05-24 14:15:30

Linux硬件信息命令

2021-08-23 06:59:38

Linux內核代碼

2016-01-04 15:17:50

Linux命令行硬件

2015-12-21 13:34:31

LinuxGPU顯卡硬件

2024-03-20 15:36:54

2023-04-19 08:13:02

EpollLinux

2021-01-26 09:14:19

Linux內核模塊

2015-05-25 19:37:17

InxiLinux

2021-05-21 14:11:15

機器人系統技術

2009-02-16 20:16:52

Linux網卡硬件查看

2022-01-27 23:32:03

Linux操作系統TCP
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久综合| 日韩在线欧美 | 国产不卡一区 | 99精品在线观看 | 一区二区三区四区在线免费观看 | 亚洲国产精品一区二区三区 | 日韩成人免费视频 | 一区二区av | 国产99久久 | 欧美日韩在线一区 | 久久tv在线观看 | 亚洲人的av | 亚洲日韩中文字幕 | 亚洲视频免费在线看 | 中文字幕在线欧美 | 久久精品亚洲精品国产欧美 | 日韩欧美亚洲综合 | 6996成人影院网在线播放 | 男女爱爱福利视频 | 999免费网站 | 一区二区三区视频在线观看 | 尤物在线精品视频 | 韩日精品一区 | 色婷婷国产精品 | 一区中文字幕 | 久久久久国产 | 狠狠干av | 国产精品久久国产精品99 gif | 亚洲欧美日韩电影 | 久草福利| 毛片视频免费 | 国产欧美日韩综合精品一区二区 | 精品国产91亚洲一区二区三区www | 日韩一级免费电影 | 欧美激情99 | 国产高清视频在线观看 | 夜夜爽99久久国产综合精品女不卡 | 日本高清aⅴ毛片免费 | 国产精品美女久久久 | 91久久久久久久久 | 成人黄视频在线观看 |