CR4.PCE 用于 rdpmc 已清除

CR4.PCE for rdpmc is cleared

我尝试在我的英特尔 i7-4770K 上启用 CR4 的位 8(PCE 位)以使用 rdpmc。 然后我写了这个模块:

#include <linux/module.h>
#include <linux/kernel.h>

void printcr4(void)
{
    unsigned long long cr4;
    asm volatile(
        "xor %%rax,%%rax\n\t"
        "mov %%cr4,%%rax\n\t":"=a"(cr4));
    printk("CR4 : %llx\n",cr4);
}
void CR4_enablePMC(void)
{
    asm volatile(
        "mov %cr4,%rax\n\t"
        "or $(1<<8),%rax\n\t"
        "mov %rax,%cr4\n\t"
    );
}
int init_module(void)
{
   printcr4();
   CR4_enablePMC();
   printcr4();
   return 0;
}
void cleanup_module(void)
{
   printcr4();
}

我用insmod加载模块。

printcr4 的第一次调用(在 init_module 中,在使用 CR4_enablePMC 启用 PCE 之前)打印我 CR4 : 1406e0 所以第 8 位是 0。

printcr4 的第二次调用(在 init_module 中并且在使用 CR4_enablePMC 启用 PCE 之后)打印我 CR4 : 1407e0 所以位 8 是 1.

然后我用 rmmod 删除了我的模块,最后所有 printcr4(在 cleanup_module 中)打印我 CR4 : 1406e0。 所以第 8 位再次为 0,但我预计它为 1,因为我没有在 printcr4 的第二次和最后一次调用之间进行任何操作。 所以我猜其他东西清除了这个位,但我不知道它是什么。

我试图用 on_each_cpu 函数执行我的模块,但我得到了相同的结果。 我也尝试 运行 在一个最小的环境中使用一个进程(bash)和一个启用的核心(我通过 grub 做到这一点)但结果相同。

你知道如何长时间设置这个PCE位吗?

如果有帮助,我使用 Ubuntu 16.04 LTS。

编辑(带有 smp 的代码):

#include <linux/module.h>
#include <linux/kernel.h>

void printcr4(void)
{
    unsigned long cr4=0;
    /* OLD CODE
     asm volatile(
        "push %%rax\n\t"
        "xor %%rax,%%rax\n\t"
        "mov %%cr4,%%rax\n\t":"=a"(cr4));
    asm volatile(
        "pop %rax\n\t");*/
    /* NEW */
    asm volatile(
    "mov %%cr4,%0\n\t":"=r"(cr4));
    printk("Proc: %d, CR4 : %llx\n",smp_processor_id(), cr4);
}
void CR4_enablePMC(void)
{
    asm volatile(
        "push %rax\n\t"
        "mov %cr4,%rax\n\t"
        "or $(1<<8),%rax\n\t"
        "mov %rax,%cr4\n\t"
        "pop %rax\n\t"
    );
}
void init_module_smp(void *param)
{
    printcr4();
    CR4_enablePMC();
    printcr4();

}
void cleanup_module_smp(void *param)
{
    printcr4();
}
int init_module(void)
{
    printk("\nInit module\n");
    on_each_cpu(init_module_smp, NULL, 1);
    return 0;
}
void cleanup_module(void)
{
    printk("\nCleanup module\n");
    on_each_cpu(cleanup_module_smp, NULL, 1);
}

然后我 运行 insmod 然后 rmmod 我得到 (dmesg) :

[ 3438.920809] 
               Init module
[ 3438.920813] Proc: 5, CR4 : 1406e0
[ 3438.920814] Proc: 2, CR4 : 1406e0
[ 3438.920815] Proc: 6, CR4 : 1406e0
[ 3438.920815] Proc: 2, CR4 : 1407e0
[ 3438.920817] Proc: 6, CR4 : 1407e0
[ 3438.920818] Proc: 7, CR4 : 1406e0
[ 3438.920818] Proc: 3, CR4 : 1406e0
[ 3438.920819] Proc: 7, CR4 : 1407e0
[ 3438.920820] Proc: 3, CR4 : 1407e0
[ 3438.920824] Proc: 5, CR4 : 1407e0
[ 3438.920826] Proc: 0, CR4 : 1406f0
[ 3438.920827] Proc: 4, CR4 : 1406e0
[ 3438.920829] Proc: 4, CR4 : 1407e0
[ 3438.920830] Proc: 0, CR4 : 1407f0
[ 3438.920832] Proc: 1, CR4 : 1406e0
[ 3438.920833] Proc: 1, CR4 : 1407e0
[ 3442.120602] 
               Cleanup module 
[ 3442.120610] Proc: 0, CR4 : 1406f0
[ 3442.120624] Proc: 7, CR4 : 1406e0
[ 3442.120625] Proc: 3, CR4 : 1406e0
[ 3442.120626] Proc: 1, CR4 : 1406e0
[ 3442.120627] Proc: 2, CR4 : 1406e0
[ 3442.120628] Proc: 5, CR4 : 1406e0
[ 3442.120629] Proc: 6, CR4 : 1406e0
[ 3442.120643] Proc: 4, CR4 : 1406e0

经过一些阅读和内核版本之间的 diff 后,内核文件 arch/x86/include/asm/mmu_context.h 中的以下函数似乎负责禁用 CR4.PCE :

static inline void load_mm_cr4(struct mm_struct *mm)
{
    if (static_key_false(&rdpmc_always_available) ||
        atomic_read(&mm->context.perf_rdpmc_allowed))
            cr4_set_bits(X86_CR4_PCE);
    else
            cr4_clear_bits(X86_CR4_PCE);
}

在内核 4.1.5(包括它)之后 static_key_falsestatic_key_true 取代。

我不知道具体是什么rdpmc_always_available但是这个名字很好理解

无需修改代码,我们可以 echo 2 > /sys/devices/cpu/rdpmc 作为 root 用户。

然后我们可以根据需要配置 PMC(并且要小心,因为可能有其他进程使用它...)。

@PeterCordes @DavidWohlferd @Michael Petch:非常感谢您的帮助。

这个 CR4 寄存器是每个 cpu。因此,如果您在一个函数中执行所有操作,则不太可能(但由于中断仍然可能)您的进程上下文可以从当前 CPU 切换开,并且当切换回来继续执行时,它是 运行 在另一个 cpu.

每个 CPU 的 CR4 都完全独立于另一个 CPU 的 CR4。 (或者实际上是任何寄存器)。

但是如果有任何像/sys/xxxx或sysctl命令这样的全局内存,那么这些值要么存储到全局内存中,要么同时更新到所有CPU中。