【问题标题】:How are percpu pointers implemented in the Linux kernel?percpu 指针在 Linux 内核中是如何实现的?
【发布时间】:2013-06-03 10:58:19
【问题描述】:

在多处理器上,每个内核都可以有自己的变量。我以为它们是不同地址中的不同变量,尽管它们在同一个进程中并且具有相同的名称。

但我想知道,内核如何实现这一点?它是否会分配一块内存来存放所有 percpu 指针,并且每次它都会通过 shift 或其他方式将指针重定向到某个地址?

【问题讨论】:

    标签: linux linux-kernel smp


    【解决方案1】:

    普通的全局变量不是每个 CPU 的。自动变量在栈上,不同的CPU使用不同的栈,自然会得到不同的变量。

    我猜您指的是 Linux 的 per-CPU 可变基础架构。
    大部分魔法都在这里(asm-generic/percpu.h):

    extern unsigned long __per_cpu_offset[NR_CPUS];
    
    #define per_cpu_offset(x) (__per_cpu_offset[x])
    
    /* Separate out the type, so (int[3], foo) works. */
    #define DEFINE_PER_CPU(type, name) \
        __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
    
    /* var is in discarded region: offset to particular copy we want */
    #define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
    #define __get_cpu_var(var) per_cpu(var, smp_processor_id())
    

    RELOC_HIDE(ptr, offset) 只是将ptr 前进给定的字节偏移量(无论指针类型如何)。

    它有什么作用?

    1. 在定义DEFINE_PER_CPU(int, x) 时,会在特殊的.data.percpu 部分中创建一个整数__per_cpu_x
    2. 加载内核时,此部分会加载多次 - 每个 CPU 一次(这部分魔法不在上面的代码中)。
    3. __per_cpu_offset 数组填充了副本之间的距离。假设每 CPU 使用 1000 个字节的数据,__per_cpu_offset[n] 将包含 1000*n
    4. 符号 per_cpu__x 将在加载期间重新定位到 CPU 0 的 per_cpu__x
    5. __get_cpu_var(x),在 CPU 3 上运行时,将转换为 *RELOC_HIDE(&per_cpu__x, __per_cpu_offset[3])。这从 CPU 0 的 x 开始,添加 CPU 0 的数据和 CPU 3 之间的偏移量,并最终取消引用结果指针。

    【讨论】:

    • 感谢您的分析,但我还有一些问题,对于 smp 来说是新的,所以,没有冒犯您的想法。首先,我认为同一个进程应该有相同的堆栈,这里是 POSIX 中的线程定义“......和自动变量,同一进程中的所有线程都可以访问。”。自动变量由线程共享。不同的处理器可能有不同的堆栈段寄存器,但内容应该相同。第二,我们能不能说我们也可以访问其他cpu的变量,只要回滚percpu得到的偏移量?
    • 当两个线程调用函数foo,它有一个自动变量x,有两个堆栈和两个x的实例。每个都有不同的地址,两个线程都可以访问两者,如果它们有地址的话。使用 Linux 的 per-cpu 变量,per_cpu(var, cpu) 允许您访问任何 cpu 的变量。
    • .data.percpu部分如何判断percpu变量是声明在栈上还是堆上?
    • durnig kenrel加载,每个cpu都有自己的GDT表。这个 gdt 表中的每个条目都代表一个可以从这个 CPU 上的线程访问的内存段。 GDT 表条目 0 存储每 CPU 内存的内存段。要访问 per-cpu 内存,linux 使用 gs:{variable-offset} 来访问 per-cpu 变量,如下所示: mov %gs:0x41(%rcx),%dl
    • 我一直在研究代码。自从写下这个答案以来,情况似乎发生了一些变化。 DEFINE_PER_CPU(int, x) 定义了一个名为 x 的符号,而不是 per_cpu__x__per_cpu_offset[0] 不是在内核加载时将x 重新定位到CPU 0 的副本,而是保存链接器分配给x 的地址与实际存储CPU 0 的副本的地址之间的差异。另外:x86 内核不是一直访问__per_cpu_offset,而是将该偏移存储为段fs 的基础。然后解引用段fs 中的指针直接转到当前CPU 的副本!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 2015-02-26
    • 2012-09-07
    • 1970-01-01
    相关资源
    最近更新 更多