2014-01-07 5 views
8

プロセスディスクリプタの処理方法を理解するためにLinuxカーネルソースコード(3.12.5 x86_64)を読んでいます。gccインラインアセンブリーLinuxカーネルの "m"の上に "P"と制約 "p"を使用する

私は次のように実装されて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; 
} 

そしてIは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コード:

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

this postによれば、入力制約は "m"(kernel_stack)であり、それは私にとって意味があります。しかし、明らかにパフォーマンスを向上させるために、Linusが「P」への制約を変更し、変数のアドレス渡さ:postでも

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. 

Tejunホこのコメント製:修飾子を組み合わせると

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(). 

しかし、私の実験を」 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)のようなもの)を持っていますが、スタック変数のアドレスはありません。

"p"という制約でオペランドとしてアドレスをとり、それを使用することができるいくつかの項目の1つは、静的に(たとえば、0〜255の範囲内で)割り当てられた変数。この場合、有効なアセンブリオペランドであり、即値として表現することができます(例えば、&kernel_stack$kernel_stackと表すことができます)。これは有効なアドレスでもあり、制約を満たします。

Linuxカーネルマクロが動作していて、マクロがそうでない理由です。スタック変数でスタックを使用しようとしていますが、カーネルは静的に割り当てられた変数でのみスタック変数を使用します。

少なくとも、コンパイラには静的に割り当てられた変数のように見えます。実際には、kernel_stackは、CPU単位のデータに使用される特別なセクションに実際に割り当てられます。このセクションは実際には存在しません。代わりに、各CPU用に別々のメモリ領域を作成するためのテンプレートとして使用されます。この特別なセクションのkernel_stackのオフセットは、各CPUデータ・リージョンのオフセットとして使用され、各CPUごとに個別のカーネル・スタック値を保管します。 FSまたはGSセグメントレジスタはこの領域のベースとして使用され、各CPUは異なるアドレスをベースとして使用します。

これは、Linuxカーネルが静的変数のように見えるものにアクセスするためにインラインアセンブリを使用する理由です。このマクロは、静的変数をCPU単位の変数に変換するために使用されます。このようなことをやろうとしていないなら、おそらくカーネルマクロからコピーすることで何も得られません。あなたはおそらく、あなたが達成しようとしていることをやり遂げるための別の方法を検討しているはずです。 Linus Torvalds氏は、一般的にこれを行うには良い考えでなければならない"p""m"制約を置き換えるために、カーネルにこの最適化して来ているので、あなたが考えている場合

さて、あなたはこの最適化がどのように壊れやすい非常に注意する必要があります。何をしようとしているのは、kernel_stackへの参照がメモリに実際にアクセスしないと考えて、GCCがメモリを変更するたびに値を再ロードしないようにすることです。ここでの危険は、kernel_stackが変更された場合、コンパイラはだまされて古い値を使い続けるということです。 Linusは、CPUごとの変数がいつ、どのように変更されるかを知っているので、カーネル内で意図した目的に使用するとマクロが安全であると確信できます。

独自のコードで重複する負荷を排除する場合は、-fstrict-aliasingおよび/またはrestrictというキーワードを使用することをおすすめします。そうすれば、壊れやすい移植性のないインラインアセンブリマクロに依存することはありません。

関連する問題