欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cpuinfo 函数

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cpuinfo 函数

2025/2/22 8:12:15 来源:https://blog.csdn.net/weixin_41812346/article/details/145775260  浏览:    关键词:Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cpuinfo 函数

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)设置缓存行大小。

  • 逻辑分析

    1. 家族号提取

      • (cpu[0] & 0xf00) >> 8 提取处理器家族号。
      • 家族号用于区分不同的处理器系列(如 Pentium、Core 等)。
    2. Pentium(家族号 5)

      • 缓存行大小为 32 字节。
    3. Pentium Pro/II/III(家族号 6)

      • 默认缓存行大小为 32 字节。
      • 对于较新的型号(如 Core、Core 2、Atom),缓存行大小为 64 字节。
      • 使用 ((cpu[0] & 0xf0000) >> 8) | (cpu[0] & 0xf0) 计算具体型号。
    4. 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

 

如果目标平台或编译器不满足条件,则提供一个空实现。

确保代码在不支持的平台上也能正常编译,避免错误。

​​​​

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com