2014-01-07 45 views
8

我正在阅读Linux内核源代码(3.12.5 x86_64)以了解如何处理进程描述符。在Linux内核中使用修饰符“P”和约束“p”在“m”上的gcc内联汇编

我发现得到当前进程的描述符,我可以使用current_thread_info()函数,该函数执行如下:

static inline struct thread_info *current_thread_info(void) 
{ 
    struct thread_info *ti; 
    ti = (void *)(this_cpu_read_stable(kernel_stack) + 
     KERNEL_STACK_OFFSET - THREAD_SIZE); 
    return ti; 
} 

然后我看着this_cpu_read_stable()

#define this_cpu_read_stable(var)  percpu_from_op("mov", var, "p" (&(var))) 

#define percpu_from_op(op, var, constraint) \ 
({ \ 
typeof(var) pfo_ret__; \ 
switch (sizeof(var)) { \ 
... 
case 8: \ 
    asm(op "q "__percpu_arg(1)",%0" \ 
    : "=r" (pfo_ret__) \ 
    : constraint); \ 
    break; \ 
default: __bad_percpu_size(); \ 
} \ 
pfo_ret__; \ 
}) 

#define __percpu_arg(x)   __percpu_prefix "%P" #x 

#ifdef CONFIG_SMP 
#define __percpu_prefix "%%"__stringify(__percpu_seg)":" 
#else 
#define __percpu_prefix "" 
#endif 

#ifdef CONFIG_X86_64 
#define __percpu_seg gs 
#else 
#define __percpu_seg fs 
#endif 

扩展宏应该是内联汇编代码:

asm("movq %%gs:%P1,%0" : "=r" (pfo_ret__) : "p"(&(kernel_stack))); 

根据this post,输入约束用于“m”(kernel_stack),这对我很有意义。但显然,以提高性能莱纳斯改变了约束至“P”,并通过变量的地址:“

Added the magical undocumented "P" modifier to UP __percpu_arg() 
to force gcc to dereference the pointer value passed in via the 
"p" input constraint. Without this, percpu_read_stable() returns 
the address of the percpu variable. Also added comment explaining 
the difference between percpu_read() and percpu_read_stable(). 

但随着合并修改我的实验:

It uses a "p" (&var) constraint instead of a "m" (var) one, to make gcc 
think there is no actual "load" from memory. This obviously _only_ works 
for percpu variables that are stable within a thread, but 'current' and 
'kernel_stack' should be that way. 
post

而且Tejun许做这个评论P“修饰符和约束”p(& var)“不起作用。如果未指定段寄存器,“%P1”将始终返回变量的地址。指针并未解除引用。我必须使用括号来解引用它,如“(%P1)”。如果指定了段寄存器,不使用括号gcc就不会编译。我的测试代码如下:

#include <stdio.h> 

#define current(var) ({\ 
     typeof(var) pfo_ret__;\ 
     asm(\ 
       "movq %%es:%P1, %0\n"\ 
       : "=r"(pfo_ret__)\ 
       : "p" (&(var))\ 
     );\ 
     pfo_ret__;\ 
     }) 

int main() { 
     struct foo { 
       int field1; 
       int field2; 
     } a = { 
       .field1 = 100, 
       .field2 = 200, 
     }; 
     struct foo *var = &a; 

     printf ("field1: %d\n", current(var)->field1); 
     printf ("field2: %d\n", current(var)->field2); 

     return 0; 
} 

我的代码有什么问题吗?或者我需要为gcc添加一些选项?当我使用gcc -S生成汇编代码时,我没有看到通过在“m”上使用“p”来优化。任何答复或评论非常感谢。

+1

询问'gcc-help @ gcc.gnu.org'并给出编译器的确切版本...... –

回答

8

您的示例代码无法正常工作的原因是因为"p"约束仅限于内联汇编的非常有限的用途。所有内联汇编操作数都要求它们可以用汇编语言表示为操作数。如果操作数不可表示,那么编译器通过先将其移到一个寄存器并将其替换为操作数来实现。约束条件限制了一个额外的限制:操作数必须是有效地址。问题是一个寄存器不是一个有效的地址。寄存器可以包含地址,但寄存器本身不是有效地址。

这意味着"p"约束的操作数必须具有有效的汇编表示,并且是有效的地址。你正试图使用​​堆栈中变量的地址作为操作数。虽然这是一个有效的地址,但它不是一个有效的操作数。堆栈变量本身有一个有效的表示(类似8(%rbp)),但堆栈变量的地址不是。 (如果它是可表示的,它将类似于8 + %rbp,但这不是合法的操作数。)

可以将地址和作为操作数用于约束条件的少数几件事之一是静态分配变量。在这种情况下,它是一个有效的汇编操作数,因为它可以表示为一个立即值(例如,&kernel_stack可以表示为$kernel_stack)。这也是一个有效的地址,因此可以满足约束条件。

所以这就是为什么Linux内核宏的作品,而你的宏没有。你正在试图将它与栈变量一起使用,而内核只使用静态分配的变量。

或者至少是什么看起来像编译器静态分配的变量。实际上kernel_stack实际上分配在每个CPU数据使用的特殊部分中。这部分实际上并不存在,而是用作模板为每个CPU创建单独的内存区域。此特殊部分中的kernel_stack的偏移量用作每个CPU数据区域中的偏移量,以便为每个CPU存储单独的内核堆栈值。 FS或GS段寄存器用作该区域的基础,每个CPU使用不同的地址作为基础。

所以这就是为什么Linux内核使用内联汇编来访问其他类似静态变量的原因。该宏用于将静态变量转换为每个CPU变量。如果你不想这样做,那么你可能没有任何通过从内核宏复制获得的东西。你应该考虑以不同的方式去做你想做的事情。

现在,如果您在思考Linus Torvalds在内核中使用此优化来取代"m"约束与"p",那么一般来说这是一个好主意,您应该非常清楚此优化的脆弱程度。它试图做的是愚弄GCC认为参考kernel_stack实际上并不访问内存,所以它不会每次更改内存时都重新加载值。这里的危险是,如果kernel_stack确实发生了变化,那么编译器将被愚弄,并继续使用旧值。 Linus知道每个CPU变量何时以及如何改变,因此可以确信,宏在内核中用于其预期目的时是安全的。

如果你想消除你自己的代码中的冗余负载,我建议使用-fstrict-aliasing和/或restrict关键字。这样你就不依赖于易碎和不可移植的内联汇编宏。