Wie wird der Stack für den Systemaufruf clone () unter Linux getapert?

8

Der Systemaufruf clone () unter Linux verwendet einen Parameter, der auf den Stapel verweist, damit der neu erstellte Thread verwendet werden kann. Der offensichtliche Weg dies zu tun besteht darin, einfach etwas Speicherplatz zu malloc und übergeben, aber dann müssen Sie sicher sein, dass Sie so viel Stack-Speicherplatz malloc wie dieser Thread jemals verwenden wird (schwer zu prognostizieren).

Ich erinnerte mich, dass ich bei der Verwendung von Pthreads das nicht tun musste, also war ich neugierig, was es stattdessen tat. Ich stieß auf diese Seite , die erklärt, "Die beste Lösung, die von der Linux pthreads-Implementierung verwendet wird, ist die Verwendung von mmap zum Zuordnen Speicher, mit Flags, die einen Speicherbereich angeben, der so zugewiesen wird, wie er verwendet wird. Auf diese Weise wird Speicher für den Stapel wie benötigt zugewiesen, und eine Segmentierungsverletzung tritt auf, wenn das System keinen zusätzlichen Speicher zuordnen kann. "

Der einzige Kontext, in dem ich mal mpap gehört habe, ist das Mappen von Dateien in den Speicher, und das Lesen der mmap man-Seite erfordert einen Dateideskriptor. Wie kann dies verwendet werden, um einen Stapel dynamischer Länge zuzuordnen, der an clone () übergeben wird? Ist diese Seite einfach verrückt? ;)

In jedem Fall muss der Kernel sowieso nicht wissen, wie er einen freien Speicher für einen neuen Stack finden kann, da er das immer tun muss, wenn der Benutzer neue Prozesse startet? Warum muss überhaupt ein Stack-Pointer angegeben werden, wenn der Kernel das schon herausfinden kann?

    
Joseph Garvin 04.07.2009, 23:30
quelle

7 Antworten

2

Joseph, als Antwort auf deine letzte Frage:

Wenn ein Benutzer einen "normalen" neuen Prozess erstellt, geschieht dies durch fork (). In diesem Fall muss sich der Kernel nicht darum sorgen, überhaupt einen neuen Stack zu erstellen, da der neue Prozess ein vollständiges Duplikat des alten ist, bis auf den Stack.

Wenn der Benutzer den aktuell laufenden Prozess mit exec () ersetzt, muss der Kernel einen neuen Stack erstellen - aber in diesem Fall ist das einfach, da er von einem leeren Blatt starten kann. exec () löscht den Speicherbereich des Prozesses und initialisiert ihn neu, so dass der Kernel sagt "nach exec (), der Stack lebt immer HIER".

Wenn wir jedoch clone () verwenden, können wir sagen, dass der neue Prozess einen Speicherplatz mit dem alten Prozess (CLONE_VM) teilen wird. In dieser Situation kann der Kernel den Stack nicht wie im aufrufenden Prozess verlassen (wie fork ()), weil dann unsere beiden Prozesse auf dem Stack des anderen stampfen würden. Der Kernel kann ihn auch nicht einfach in einen Standardspeicherort stellen (wie exec ()), weil dieser Speicherort bereits in diesem Speicherbereich belegt ist. Die einzige Lösung besteht darin, dem aufrufenden Prozess zu erlauben, einen Platz dafür zu finden, was er tut.

    
caf 09.07.2009, 15:02
quelle
5

Sie möchten das MAP_ANONYMOUS-Flag für mmap. Und das MAP_GROWSDOWN, da Sie es als Stapel verwenden möchten.

Etwas wie:

%Vor%

Weitere Informationen finden Sie auf der mmap man-Seite. Und denken Sie daran, Klone ist ein Low-Level-Konzept, das Sie nicht verwenden sollen, wenn Sie wirklich brauchen, was es bietet. Und es bietet eine Menge Kontrolle - wie das Setzen eines eigenen Stacks - nur für den Fall, dass Sie ein paar Tricks machen wollen (wie den Stack in allen zugehörigen Prozessen verfügbar zu haben). Wenn du keinen guten Grund hast, einen Klon zu verwenden, bleibe mit Gabel oder Pthreads.

    
nos 04.07.2009 23:46
quelle
5

Stapel sind nicht unbegrenzt in ihrem Wachstumsraum und können es auch nicht sein. Wie alles andere auch, leben sie im virtuellen Adressraum des Prozesses und der Umfang, in dem sie wachsen können, ist immer durch die Entfernung zum angrenzenden abgebildeten Speicherbereich begrenzt.

Wenn die Leute darüber sprechen, dass der Stack dynamisch wächst, ist das vielleicht eines von zwei Dingen:

  • Seiten des Stapels können Kopien-auf-Schreiben-Null-Seiten sein, die keine privaten Kopien erhalten, bis der erste Schreibvorgang ausgeführt wird.
  • Untere Teile des Stack-Bereichs sind möglicherweise noch nicht reserviert (und zählen daher nicht zur Commit-Gebühr des Prozesses, dh die Menge an physischem Speicher / Swap, die der Kernel für den Prozess reserviert hat), bis eine Guard-Seite getroffen wird In diesem Fall schreibt der Kernel mehr Commits und verschiebt die Guard-Seite oder beendet den Prozess, wenn kein Commit mehr zur Verfügung steht.

Der Versuch, sich auf die MAP_GROWSDOWN -Flagge zu verlassen, ist unzuverlässig und gefährlich , da Sie nicht vor mmap schützen können, indem Sie ein neues Mapping direkt neben Ihrem Stack erstellen, das dann verfälscht wird. (Siehe Ссылка ) Für den Haupt-Thread reserviert der Kernel automatisch den Stack-Size ulimit -Wert von Adressraum (nicht Speicher ) unter dem Stapel und verhindert, dass mmap es zuweist. (Aber Vorsicht! Einige defekte Vendor-Patched-Kernel deaktivieren dieses Verhalten, was zu zufälliger Speicherkorruption führt!) Für andere Threads müssen Sie einfach % könnten das meiste anfänglich nicht schreibbar / nicht lesbar machen und das bei Fehlern ändern, aber dann würden Sie Signalhandler benötigen und diese Lösung ist in einer POSIX-Thread-Implementierung nicht akzeptabel, weil dies der Fall wäre Interferieren Sie mit den Signalhandlern der Anwendung. (Als Erweiterung kann der Kernel spezielle mmap -Flags anbieten, um ein anderes Signal anstelle von MAP_ bei illegalem Zugriff auf das Mapping zu liefern, und dann könnte die Threads-Implementierung einfangen und handeln auf dieses Signal. Aber Linux hat derzeit keine solche Funktion.)

Beachten Sie schließlich, dass% syscall% co_de kein Stack-Pointer-Argument benötigt, weil es es nicht benötigt. Der Syscall muss aus Assemblycode ausgeführt werden, da der Wrapper des Benutzerbereichs den Stapelzeiger im "untergeordneten" Thread so ändern muss, dass er auf den gewünschten Stapel zeigt und es vermeidet, etwas in den Stapel des übergeordneten Elements zu schreiben.

Tatsächlich nimmt SIGSEGV ein Stapelzeigerargument, weil es nicht sicher ist, zu warten, bis der Stapelzeiger im "Kind" nach dem Zurückkehren zum Benutzerbereich geändert wird. Solange die Signale nicht blockiert sind, könnte ein Signal-Handler sofort auf dem falschen Stack laufen, und auf einigen Architekturen muss der Stack-Pointer gültig sein und auf einen Bereich zeigen, in dem jederzeit geschrieben werden kann.

Es ist nicht nur unmöglich, den Stack-Zeiger von C aus zu ändern, sondern Sie konnten auch nicht vermeiden, dass der Compiler nach dem Syscall, aber vor dem Ändern des Stack-Pointers, den Stapel des Elterns überlistete.

    
R.. 20.03.2011 14:20
quelle
1

Beachten Sie, dass der clone Systemaufruf kein Argument für den Stapelspeicherort verwendet. Es funktioniert tatsächlich wie fork . Es ist nur der Glibc-Wrapper, der dieses Argument übernimmt.

    
agl 06.07.2009 21:18
quelle
0

mmap ist mehr als nur das Mappen einer Datei in den Speicher. Tatsächlich werden einige malloc-Implementierungen mmap für große Zuordnungen verwenden. Wenn Sie die Fine-Man-Seite lesen, werden Sie das MAP_ANONYMOUS-Flag bemerken, und Sie werden sehen, dass Sie keinen Dateideskriptor benötigen müssen.

Warum kann der Kernel nicht einfach "einen Haufen freien Speicher finden"? Nun, wenn Sie möchten, dass jemand für Sie arbeitet, verwenden Sie entweder fork, oder verwenden Sie pthreads.

    
Logan Capaldo 04.07.2009 23:43
quelle
0

Ich denke, dass der Stack nach unten wächst, bis er nicht wachsen kann, zum Beispiel wenn er zu einem Speicher wächst, der vorher zugewiesen wurde, vielleicht wird ein Fehler gemeldet. Ein Standard ist die minimal verfügbare Stapelgröße, falls vorhanden ist redundanter Speicherplatz nach unten, wenn der Stack voll ist, kann er nach unten wachsen, ansonsten könnte das System einen Fehler melden.

    
venuswu 13.02.2012 07:34
quelle
0

Hier ist der Code, der eine Stapelregion & amp; Weist den Klon-Systemaufruf an, diese Region als Stapel zu verwenden.

%Vor%     
Venkatram Tummala 21.04.2010 23:23
quelle