ngx_cpuinfo
声明在 src/core/ngx_core.h
void ngx_cpuinfo(void);
定义在 src/core/ngx_cpuinfo.c
这里 ngx_cpuinfo 的定义可以找到 2 个
使用 gcc -E 处理一下来确认当下环境中使用的是哪一个
gcc -E src/core/ngx_cpuinfo.c \-I src/core \-I src/event \-I src/event/modules \-I src/os/unix \-I objs \> ngx_cpuinfo_preprocessed.c
在 输出文件中 查找
ngx_cpuinfo
void ngx_cpuinfo(void) {u_char *vendor;uint32_t vbuf[5], cpu[4], model;vbuf[0] = 0;vbuf[1] = 0;vbuf[2] = 0;vbuf[3] = 0;vbuf[4] = 0;ngx_cpuid(0, vbuf);vendor = (u_char *) &vbuf[1];if (vbuf[0] == 0) {return;}ngx_cpuid(1, cpu);if (strcmp((const char *) vendor, (const char *) "GenuineIntel") == 0) {switch ((cpu[0] & 0xf00) >> 8) {case 5:ngx_cacheline_size = 32;break;case 6:ngx_cacheline_size = 32;model = ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0);if (model >= 0xd0) {ngx_cacheline_size = 64;}break;case 15:ngx_cacheline_size = 128;break;}} else if (strcmp((const char *) vendor, (const char *) "AuthenticAMD") == 0) {ngx_cacheline_size = 64;} }
详解 src\core\ngx_cpuinfo.c
/** Copyright (C) Igor Sysoev* Copyright (C) Nginx, Inc.*/#include <ngx_config.h> #include <ngx_core.h>#if (( __i386__ || __amd64__ ) && ( __GNUC__ || __INTEL_COMPILER ))static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);#if ( __i386__ )static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) {/** we could not use %ebx as output parameter if gcc builds PIC,* and we could not save %ebx on stack, because %esp is used,* when the -fomit-frame-pointer optimization is specified.*/__asm__ (" mov %%ebx, %%esi; "" cpuid; "" mov %%eax, (%1); "" mov %%ebx, 4(%1); "" mov %%edx, 8(%1); "" mov %%ecx, 12(%1); "" mov %%esi, %%ebx; ": : "a" (i), "D" (buf) : "ecx", "edx", "esi", "memory" ); }#else /* __amd64__ */static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) {uint32_t eax, ebx, ecx, edx;__asm__ ("cpuid": "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (i) );buf[0] = eax;buf[1] = ebx;buf[2] = edx;buf[3] = ecx; }#endif/* auto detect the L2 cache line size of modern and widespread CPUs */void ngx_cpuinfo(void) {u_char *vendor;uint32_t vbuf[5], cpu[4], model;vbuf[0] = 0;vbuf[1] = 0;vbuf[2] = 0;vbuf[3] = 0;vbuf[4] = 0;ngx_cpuid(0, vbuf);vendor = (u_char *) &vbuf[1];if (vbuf[0] == 0) {return;}ngx_cpuid(1, cpu);if (ngx_strcmp(vendor, "GenuineIntel") == 0) {switch ((cpu[0] & 0xf00) >> 8) {/* Pentium */case 5:ngx_cacheline_size = 32;break;/* Pentium Pro, II, III */case 6:ngx_cacheline_size = 32;model = ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0);if (model >= 0xd0) {/* Intel Core, Core 2, Atom */ngx_cacheline_size = 64;}break;/** Pentium 4, although its cache line size is 64 bytes,* it prefetches up to two cache lines during memory read*/case 15:ngx_cacheline_size = 128;break;}} else if (ngx_strcmp(vendor, "AuthenticAMD") == 0) {ngx_cacheline_size = 64;} }#elsevoid ngx_cpuinfo(void) { }#endif
代码整体结构
#if (( __i386__ || __amd64__ ) && ( __GNUC__ || __INTEL_COMPILER ))static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);#if ( __i386__ ) static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) { ... } #else /* __amd64__ */ static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) { ... } #endifvoid ngx_cpuinfo(void) { ... }#elsevoid ngx_cpuinfo(void) { }#endif
条件编译指令
#if (( __i386__ || __amd64__ ) && ( __GNUC__ || __INTEL_COMPILER ))
- 作用
这段代码仅在以下条件下编译:
- 目标平台是 x86 或 x86-64 架构 。
- 编译器是 GCC 或 Intel 编译器 。
- 原因
CPUID
是 x86/x86-64 架构特有的指令,其他架构(如 ARM)不支持。- GCC 和 Intel 编译器支持类似的内联汇编语法,而其他编译器可能不支持。
ngx_cpuid
函数声明static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
- 作用
声明一个内联函数ngx_cpuid
,用于调用 CPUID 指令并存储结果。参数说明
uint32_t i
:输入参数,表示 CPUID 的功能号。uint32_t *buf
:输出缓冲区,用于存储 CPUID 返回的四个寄存器值(EAX、EBX、ECX、EDX)。修饰符
static
:限制函数的作用域为当前文件,避免符号冲突。ngx_inline
:提示编译器将函数展开为内联代码,减少函数调用开销。内联函数可以减少函数调用的开销,适合频繁调用的场景。
实现
ngx_cpuid
(32 位 x86 架构)#if ( __i386__ ) static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) {__asm__ (" mov %%ebx, %%esi; "" cpuid; "" mov %%eax, (%1); "" mov %%ebx, 4(%1); "" mov %%edx, 8(%1); "" mov %%ecx, 12(%1); "" mov %%esi, %%ebx; ": : "a" (i), "D" (buf) : "ecx", "edx", "esi", "memory" ); }
这段代码的核心作用是通过内联汇编调用 CPUID 指令,并将结果存储到指定的缓冲区中
使用 GCC 风格的内联汇编语法
__asm__ ("汇编指令": 输出操作数: 输入操作数: 被修改的寄存器列表 );
" mov %%ebx, %%esi; "
作用
将%ebx
寄存器的值保存到%esi
寄存器中。原因
- 在 32 位模式下,GCC 在生成位置无关代码(PIC,Position Independent Code)时会使用
%ebx
作为基址寄存器。- 因此,不能直接将
%ebx
作为输出参数,否则会导致编译错误或运行时问题。- 解决方法是先将
%ebx
的值保存到%esi
,然后在后续操作中恢复。
" cpuid; "
作用
调用 CPUID 指令,根据输入的功能号i
查询处理器信息。工作原理
- 输入参数通过
%eax
寄存器传递。- 输出结果存储在以下寄存器中:
%eax
:主信息(如处理器型号)。%ebx
、%edx
、%ecx
:附加信息(如厂商字符串、特性标志等): : "a" (i) 这里指示将 输入的参数 i,加载到 eax 寄存器
CPUID 指令会根据
%eax
中的功能号执行相应的查询操作。获取结果
查询结果会存储在
%eax
、%ebx
、%ecx
和%edx
寄存器中。随后,这些寄存器的值会被逐一存储到
buf
数组中。
" mov %%eax, (%1); "
作用
将%eax
寄存器的值存储到buf[0]
中。细节
%1
是内联汇编中的占位符,对应输入操作数buf
。(buf)
表示将%eax
的值存储到buf
指向的内存地址中。: : "a" (i), "D" (buf) 对于这个输入输出操作数列表 %0是指 a(i),也就是 eax
"D" (buf) 指示 将变量
buf
的值(即地址)放入%edi
寄存器中所以%1 指的是 edi(输入参数buf 的值)
在 x86 汇编中,括号
()
用于表示间接寻址 ,即访问括号内地址所指向的内存内容
" mov %%ebx, 4(%1); "
作用
将%ebx
寄存器的值存储到buf[1]
中。
4(%1)
表示将%ebx
的值存储到buf + 4
的地址中(即buf[1]
)
4(%1)
是 x86 汇编语言中的一种内存寻址模式 ,称为基址加偏移量寻址 (Base + Offset Addressing)。它表示从某个寄存器(基址寄存器)所指向的地址开始,加上一个固定的偏移量,访问对应的内存位置
基本语法是:
offset(base_register)
base_register
- 基址寄存器,存储内存地址的基础值。
%1
对应的是%edi
寄存器。
offset
- 偏移量,表示从基址寄存器所指向的地址开始,向后偏移的字节数。
- 在
4(%1)
中,偏移量是4
。
" mov %%edx, 8(%1); "
作用
将%edx
寄存器的值存储到buf[2]
中。
8(%1)
表示将%edx
的值存储到buf + 8
的地址中(即buf[2]
)
" mov %%ecx, 12(%1); "
作用
将%ecx
寄存器的值存储到buf[3]
中。
12(%1)
表示将%ecx
的值存储到buf + 12
的地址中(即buf[3]
)
" mov %%esi, %%ebx; "
作用
将之前保存在%esi
中的值恢复到%ebx
寄存器。原因
- 恢复
%ebx
的原始值,确保不会破坏调用者的状态。
: : "a" (i), "D" (buf)
"a" (i)
:将变量i
的值放入%eax
寄存器。"D" (buf)
:将变量buf
的地址放入%edi
寄存器
: "ecx", "edx", "esi", "memory"
作用
告诉编译器哪些寄存器或内存被内联汇编修改了。
"ecx", "edx", "esi"
:这些寄存器在汇编代码中被修改。"memory"
:标记内存操作,确保编译器正确处理缓存一致性
实现
ngx_cpuid
(64 位 x86-64 架构)#else /* __amd64__ */static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf) {uint32_t eax, ebx, ecx, edx;__asm__ ("cpuid": "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (i) );buf[0] = eax;buf[1] = ebx;buf[2] = edx;buf[3] = ecx; }
同样调用 CPUID 指令,但 64 位环境下可以直接使用
%rbx
,无需额外保存
%rbx
不再是特殊用途寄存器。因此,我们可以直接使用
%rbx
,而不需要像 32 位环境中那样担心破坏编译器的逻辑
实现
ngx_cpuinfo
函数void ngx_cpuinfo(void) {u_char *vendor;uint32_t vbuf[5], cpu[4], model;vbuf[0] = 0;vbuf[1] = 0;vbuf[2] = 0;vbuf[3] = 0;vbuf[4] = 0;ngx_cpuid(0, vbuf);vendor = (u_char *) &vbuf[1];if (vbuf[0] == 0) {return;}ngx_cpuid(1, cpu);if (ngx_strcmp(vendor, "GenuineIntel") == 0) {switch ((cpu[0] & 0xf00) >> 8) {case 5: ngx_cacheline_size = 32; break;case 6:ngx_cacheline_size = 32;model = ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0);if (model >= 0xd0) {ngx_cacheline_size = 64;}break;case 15: ngx_cacheline_size = 128; break;}} else if (ngx_strcmp(vendor, "AuthenticAMD") == 0) {ngx_cacheline_size = 64;} }
函数签名
void ngx_cpuinfo(void)
返回值类型:
void
作用
- 函数没有返回值。
- 它的主要目的是通过检测 CPU 特性来设置全局变量
ngx_cacheline_size
,而不是返回具体的结果。原因
- 全局变量的设计使得其他模块可以直接使用
ngx_cacheline_size
,无需通过函数返回值传递数据。
定义局部变量
u_char *vendor; uint32_t vbuf[5], cpu[4], model;
- 作用
vendor
:指向厂商字符串的指针,用于存储 CPU 厂商信息(如 "GenuineIntel" 或 "AuthenticAMD")。vbuf[5]
:用于存储 CPUID 功能号0
的返回结果(厂商字符串和其他信息)。cpu[4]
:用于存储 CPUID 功能号1
的返回结果(处理器型号和其他特性)。model
:用于计算处理器的具体型号。
初始化缓冲区
vbuf[0] = 0; vbuf[1] = 0; vbuf[2] = 0; vbuf[3] = 0; vbuf[4] = 0;
将
vbuf
数组的所有元素初始化为0
,避免未初始化的值导致意外行为
调用 CPUID 功能号
0
ngx_cpuid(0, vbuf);
作用
调用 CPUID 指令的功能号0
,获取厂商字符串和其他信息。功能号
0 的返回值
vbuf[0] = EAX // 最大基本功能号 vbuf[1] = EBX // 厂商字符串第一部分 vbuf[2] = EDX // 厂商字符串第二部分 vbuf[3] = ECX // 厂商字符串第三部分
获取厂商字符串
vendor = (u_char *) &vbuf[1];
作用
将vbuf[1]
的地址强制转换为u_char*
类型,指向厂商字符串的起始位置。原因
- CPUID 功能号
0
返回的厂商字符串分布在vbuf[1]
、vbuf[2]
和vbuf[3]
中。- 通过指针操作可以方便地访问完整的字符串。
检查功能号支持
if (vbuf[0] == 0) {return; }
作用
如果vbuf[0] == 0
,表示 CPU 不支持 CPUID 指令或不支持更高的功能号,直接返回。原因
- 早期的处理器可能不支持 CPUID 指令,或者仅支持有限的功能号。
- 在这种情况下,无法继续检测 CPU 特性。
调用 CPUID 功能号
1
ngx_cpuid(1, cpu);
作用
调用 CPUID 指令的功能号1
,获取处理器的型号和其他特性。结果存储
cpu[0]
:包含处理器家族号、型号号等信息cpu[1]
、cpu[2]
、cpu[3]
:包含其他特性标志(如缓存特性、指令集支持等)
处理 Intel 处理器
if (ngx_strcmp(vendor, "GenuineIntel") == 0) {switch ((cpu[0] & 0xf00) >> 8) {case 5:ngx_cacheline_size = 32;break;case 6:ngx_cacheline_size = 32;model = ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0);if (model >= 0xd0) {ngx_cacheline_size = 64;}break;case 15:ngx_cacheline_size = 128;break;} }
作用
根据处理器家族号(cpu[0] & 0xf00
)设置缓存行大小。逻辑分析
家族号提取
(cpu[0] & 0xf00) >> 8
提取处理器家族号。- 家族号用于区分不同的处理器系列(如 Pentium、Core 等)。
Pentium(家族号 5)
- 缓存行大小为 32 字节。
Pentium Pro/II/III(家族号 6)
- 默认缓存行大小为 32 字节。
- 对于较新的型号(如 Core、Core 2、Atom),缓存行大小为 64 字节。
- 使用
((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0)
计算具体型号。Pentium 4(家族号 15)
- 缓存行大小为 128 字节(由于预取机制,实际缓存行为更复杂)。
处理 AMD 处理器
else if (ngx_strcmp(vendor, "AuthenticAMD") == 0) {ngx_cacheline_size = 64; }
作用
对于 AMD 处理器,统一设置缓存行大小为 64 字节。原因
- AMD 处理器的缓存行大小通常为 64 字节。
- 无需进一步区分型号。
默认实现
#elsevoid ngx_cpuinfo(void) { }#endif
如果目标平台或编译器不满足条件,则提供一个空实现。
确保代码在不支持的平台上也能正常编译,避免错误。