有没有一条`x86`指令可以告诉我们指令正在哪个核心上运行?

19

当我执行cat /proc/cpuinfo命令时,可以看到8个内核,其ID从07

是否有一个x86指令可报告当前指令所在的核心id?

我查了一下cpuid,但似乎在任何参数设置下都不返回coreid

5个回答

14

英特尔® 64和IA-32体系结构软件开发人员手册第3A卷:系统编程指南,第1部分中的8.4.5在MP系统中识别逻辑处理器一节列出了以下内容:

这个APIC ID由CPUID.0BH:EDX[31:0]报告。

请注意,这不直接等同于Linux内核的编号。在内核中,有一个 x86_cpu_to_apicid 表可以查看。当然,内核也知道代码正在执行的CPU,而无需咨询APIC:

 * smp_processor_id(): get the current CPU ID.
 *
 * if DEBUG_PREEMPT is enabled then we check whether it is
 * used in a preemption-safe way. (smp_processor_id() is safe
 * if it's used in a preemption-off critical section, or in
 * a thread that is bound to the current CPU.)

跟进一下,因为你提到了“内核也知道”。如果我正在运行内核模块中,有没有直接的方法来获取Linux内核的核心数? - merlin2011
2
@merlin2011:你是在假设在你查询数字和基于它做出决策的时间之间,核心不会发生变化吗? - user541686
@merlin2011,快来使用源代码! - Jester
@Mehrdad,不,我正在尝试获得一个启发式的理解,即当我执行insmod mymodule.ko时,我的内核模块在哪个核心上进行初始化。 - merlin2011
@Jester,我正在寻找 2.6.32-71.el6.x86_64 的源代码。但我还没有找到它。 - merlin2011
我在这里找到了它:ftp://bo.mirror.garr.it/pub/1/slc/slc60alpha/SRPMS/kernel-2.6.32-71.el6.src.rpm - merlin2011

14

一些较新的 x86/x86_64 CPU 具有 RDTSC 指令的“RDTSCP”变体:

http://ref.x86asm.net/coder32-abc.html#R

RDTSC   EAX EDX IA32_TIM…               0F  31
        P1+         f2              Read Time-Stamp Counter
RDTSCP  EAX EDX ECX ...         0F  01  F9  7   
        C7+         f2              Read Time-Stamp Counter and Processor ID

C7+表示在某些“Core i7”中引入了“0x0F01F9”指令...
操作码
十六进制 助记符 编码 长模式 传统模式 描述
0F 01 F9 RDTSCP A 有效的 有效的
读取64位时间戳计数器和32位IA32_TSC_AUX值到EDX:EAX和ECX中。
操作系统应该将核心ID写入IA32_TSC_AUX(Linux does),并且可以使用RDTSCP访问此值。
Linux encodes numa id(<<12)和核心id(8位)到TSC_AUX中:
341         if (cpu_has(&cpu_data(cpu), X86_FEATURE_RDTSCP))
342                 write_rdtscp_aux((node << 12) | cpu);
343 
344         /*
345          * Store cpu number in limit so that it can be loaded quickly
346          * in user space in vgetcpu. (12 bits for the CPU and 8 bits for the node)
347          */

在Linux中,还有一个vsyscall getcpu("__vdso_getcpu")用于通过rdtscp(如果CPU支持该指令)或通过GDT - GDT_ENTRY_PER_CPU访问CPU ID:来自3.13的include/asm/vsyscall.h中的__getcpu。从手册中可以看到:

getcpu()在2.6.19内核中添加了x86_64和i386。

Linux尽最大努力使此调用尽可能快。getcpu()的目的是允许程序对每个CPU数据进行优化或进行NUMA优化。

以下是一些英特尔手册内容:http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf#page=15

3.2 使用RDTSCP指令进行优化
RDTSCP指令在Intel® 64和IA-32体系结构软件开发人员手册第2B卷([3])中被描述为一种汇编指令,同时读取时间戳寄存器和CPU标识符。时间戳寄存器的值存储在EDX和EAX寄存器中;CPU id的值存储在ECX寄存器中(“在支持Intel 64架构的处理器上,每个RAX、RDX和RCX的高32位都被清除”)。在这种情况下有趣的是RDTSCP的“伪”串行属性。该手册说明:
“RDTSCP指令等待所有先前的指令执行完毕才读取计数器。但是,在执行读取操作之前,后续指令可能开始执行。”
这意味着此指令保证了其调用源代码中位于其上方的所有内容在调用指令本身之前执行。但是,它不能保证为了优化目的,CPU不会在RDTSCP调用之前执行源代码中放置在RDTSCP函数调用本身之后的指令。如果发生这种情况,则会在测量代码下发生由RDTSCP之后的源代码中的指令引起的污染。

我是一名帮助翻译的助手。以下是需要翻译的内容:

此外,这里还提供了描述http://www.felixcloutier.com/x86/RDTSCP.html的链接,该链接是https://github.com/zneak/x86doc的克隆版本。

更新:将会有RDPID指令,专门用于读取IA32_TSC_AUX寄存器,而无需使用时间戳计数器(就像RDTSCP指令一样)。

https://hjlebbink.github.io/x86doc/html/RDPID.html

Reads the value of the IA32_TSC_AUX MSR (address C0000103H) into the destination register. The value of CS.D and operand-size prefixes (66H and REX.W) do not affect the behavior of the RDPID instruction.

F3 0F C7 /7 RDPID r32 M   N.E./V  RDPID   Read IA32_TSC_AUX into r32.
F3 0F C7 /7 RDPID r64 M   V/N.E.  RDPID   Read IA32_TSC_AUX into r64.

根据https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf 319433-030 OCTOBER 2017的声明,自从"Ice Lake"微架构(2018)以来,它将被启用。


5
除了已经描述的CPUID和RDTSCP指令之外,还有一种新的RDPID指令(Intel SDM下载页面)专门用于此目的。

描述

将IA32_TSC_AUX MSR(地址C0000103H)的值读入目标寄存器。 CS.D的值和操作数大小前缀(66H和REX.W)不会影响RDPID指令的行为。

注意:

RDPID将处理器核心ID作为uint32_r或uint64_r读取,因此读取的值不会在顺序范围[0,MAX_CPU_COUNT]内。

RDPID是一种新指令,因此硬件支持程度不广泛。


哪些CPU支持它,以及可以检查其支持的标志是什么? - BeeOnRope
1
@BeeOnRope,rdpid的cpuid在这里:https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf eax=07H - ecx位22“位22:RDPID。如果为1,则支持读取处理器ID。” - osgx

2
你看到的是8个“虚拟CPU”,而不是核心,因此如果你有一个4核Ivy Bridge CPU并且每个核心有2个硬件线程,你可以通过/sys/devices/system/cpu/cpu[0-7]/topology/thread_siblings_list中的条目查看哪些vCPU对共享一个核心。

另一个答案提出了使用cpuid的好建议,但我认为你无法确定cpuid指令是否在与感兴趣的指令相同的vCPU上执行,除非你将线程固定到一个vCPU上(在这种情况下,它有些多余),因为你无法知道内核是否在执行感兴趣的指令和执行cpuid指令之间将你的线程从一个vCPU迁移到另一个vCPU。

简而言之,大部分时间,两个“接近”的指令将在同一个vCPU上执行,但如果没有固定,则不能保证,如果你已经将线程固定,那么你已经知道它正在运行的vCPU是什么,所以有点无意义。

1
这并不是真正无用的,因为通常 CPU ID 被用作性能优化而不是功能行为。例如,您可能会使用当前 CPU ID 作为索引进入每个 CPU 计数器中以避免争用,但在非常罕见的情况下,当您执行 getcpu 和使用该值之间 CPU 发生变化时,代码仍将执行正确操作(可能略微增加争用)。 - BeeOnRope

1
taskset + __rdtscp 可执行示例 最后,对于那些想要使用x86内部函数和taskset玩耍的人:

rdtscp.c

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <x86intrin.h>

int main(void) {
    uint32_t pid;
    printf("0x%016" PRIX64 "\n", (uint64_t)__rdtscp(&pid));
    printf("0x%08" PRIX32 "\n", pid);
    return EXIT_SUCCESS;
}

GitHub upstream

然后使用taskset控制编译和运行时所在的核心:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o rdtscp.out rdtscp.c
./taskset -c 0 ./rdtscp.out
./taskset -c 1 ./rdtscp.out

然后对于每个运行,第二行显示的CPU ID与taskset设置的值匹配。

在Ubuntu 19.04 amd64上测试,使用Intel Core i7-7820HQ。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接