gcc Inline Assembly mit Modifikator "P" und Constraint "p" über "m" im Linux-Kernel

8

Ich lese Linux Kernel-Quellcode (3.12.5 x86_64), um zu verstehen, wie Prozessdeskriptor behandelt wird.

Ich habe herausgefunden, dass ich den aktuellen Prozessdeskriptor bekommen könnte. Ich könnte die Funktion current_thread_info () verwenden, die wie folgt implementiert ist:

%Vor%

Dann habe ich nach this_cpu_read_stable() gesucht:

%Vor%

Das erweiterte Makro sollte Inline-Asm-Code wie folgt sein:

%Vor%

Laut diesem Beitrag war die Eingabebeschränkung "m" (kernel_stack ), die für mich Sinn macht. Aber um die Leistung zu verbessern, änderte Linus die Bedingung natürlich auf "p" und übergab die Adresse der Variablen:

%Vor%

Auch im Beitrag hat Tejun Heo folgende Kommentare abgegeben:

%Vor%

Aber meine Experimente mit der Kombination von Modifikator "P" Modifikator und Constraint "p (& amp; var)" haben nicht funktioniert. Wenn das Segmentregister nicht angegeben ist, gibt "% P1" immer die Adresse der Variablen zurück. Der Zeiger wurde nicht dereferenziert. Ich muss eine Klammer verwenden, um es zu dereferenzieren, wie "(% P1)". Wenn das Segmentregister angegeben ist, wird gcc ohne Klammer nicht einmal kompiliert. Mein Testcode lautet wie folgt:

%Vor%

Stimmt irgendetwas mit meinem Code nicht? Oder muss ich einige Optionen für gcc hinzufügen? Auch wenn ich gcc -S verwendet habe, um Assembler-Code zu generieren, habe ich keine Optimierung gesehen, indem ich "p" über "m" verwendet habe. Jede Antwort oder Kommentare wird sehr geschätzt.

    
silmerusse 07.01.2014, 06:18
quelle

1 Antwort

8

Der Grund, warum Ihr Beispielcode nicht funktioniert, liegt darin, dass die Einschränkung "p" in der Inline-Assembly nur sehr eingeschränkt verwendet wird. Alle Inline-Assembly-Operanden haben die Anforderung, dass sie als Operand in Assemblersprache darstellbar sind. Wenn der Operand nicht darstellbar ist, dann macht der Compiler dies, indem er ihn zuerst in ein Register verschiebt und diesen als Operanden ersetzt. Die Einschränkung "p" stellt eine zusätzliche Einschränkung dar: Der Operand muss eine gültige Adresse sein. Das Problem ist, dass ein Register keine gültige Adresse ist. Ein Register kann eine Adresse enthalten, aber ein Register ist selbst keine gültige Adresse.

Das bedeutet, dass der Operand der "p" -Restriktion eine gültige Assembly-Repräsentation haben und eine gültige Adresse sein muss. Sie versuchen, die Adresse einer Variablen im Stapel als Operand zu verwenden. Dies ist zwar eine gültige Adresse, aber kein gültiger Operand. Die Stapelvariable selbst hat eine gültige Repräsentation (etwas wie 8(%rbp) ), aber die Adresse der Stapelvariablen nicht. (Wenn es darstellbar wäre, wäre es etwas wie 8 + %rbp , aber dies ist kein legaler Operand.)

Eines der wenigen Dinge, die Sie als Operand mit der Einschränkung "p" annehmen und als Operand verwenden können, ist eine statisch zugewiesene Variable. In diesem Fall ist es ein gültiger Assembly-Operand, da er als unmittelbarer Wert dargestellt werden kann (z. B. &kernel_stack kann als $kernel_stack dargestellt werden). Es ist auch eine gültige Adresse und erfüllt so die Einschränkung.

Das ist der Grund, warum der Linux-Kernel-Makro funktioniert und Ihr Makro nicht. Sie versuchen, es mit Stack-Variablen zu verwenden, während der Kernel es nur mit statisch zugewiesenen Variablen verwendet.

Oder zumindest was wie eine statisch zugewiesene Variable für den Compiler aussieht. Tatsächlich wird kernel_stack tatsächlich in einem speziellen Abschnitt zugewiesen, der für die CPU-Daten verwendet wird. Dieser Abschnitt existiert nicht tatsächlich, stattdessen wird er als Vorlage verwendet, um einen separaten Speicherbereich für jede CPU zu erstellen. Der Offset von kernel_stack in diesem speziellen Abschnitt wird als Offset in jedem CPU-Datenbereich verwendet, um einen separaten Kernel-Stack-Wert für jede CPU zu speichern. Das FS- oder GS-Segmentregister wird als Basis dieser Region verwendet, wobei jede CPU eine andere Adresse als Basis verwendet.

Das ist der Grund, warum der Linux-Kernel Inline-Assemblierung benutzt, um auf etwas zuzugreifen, das ansonsten wie eine statische Variable aussieht. Das Makro wird verwendet, um die statische Variable in eine pro CPU-Variable umzuwandeln. Wenn Sie nicht versuchen, etwas zu tun, dann haben Sie wahrscheinlich nichts zu gewinnen, indem Sie vom Kernel-Makro kopieren. Sie sollten wahrscheinlich eine andere Art zu tun, was Sie versuchen zu erreichen.

Wenn Sie jetzt denken, dass Linus Torvalds mit dieser Optimierung im Kernel gekommen ist, um eine "m" Einschränkung durch eine "p" zu ersetzen, dann muss es eine gute Idee sein, dies allgemein zu tun Diese Optimierung ist. Was es zu tun versucht, ist GCC zu denken, dass der Verweis auf kernel_stack nicht wirklich auf den Speicher zugreift, so dass er den Wert nicht jedes Mal neu lädt, wenn er den Speicher ändert. Die Gefahr hier ist, dass, wenn kernel_stack sich ändert, der Compiler täuschen wird, und weiterhin den alten Wert verwenden. Linus weiß, wann und wie sich die Variablen pro CPU ändern, und kann daher sicher sein, dass das Makro sicher ist, wenn es für den vorgesehenen Zweck im Kernel verwendet wird.

Wenn Sie redundante Ladevorgänge in Ihrem eigenen Code eliminieren möchten, empfehle ich die Verwendung von -fstrict-aliasing und / oder das Schlüsselwort restrict . Auf diese Weise sind Sie nicht auf zerbrechliche und nicht tragbare Inline-Assembly-Makros angewiesen.

    
Ross Ridge 22.07.2014, 20:06
quelle

Tags und Links