Dazu gibt es zwei Techniken (drei, wenn man eine MMU hat, also nicht in
diesem Fall):
- Jeder Thread kriegt seinen eigenen Stack, und man lässt auf gut Glück
genug Platz zwischen den Stacks, dass das dann schon passen wird (mit
Erfahrungswerten, Daumen drücken und hoffen und so)
- Man benutzt einen sogenannten segmentierten Stack. Das ist hierbei der
übliche Stand der Technik.
Der Compiler, nicht das OS, steuert das: Er kann für jede Funktion
während des Compilierens feststellen, wie viel Stack die Funktion
braucht (ausser man benutzt alloca oder variabel große Arrays, in dem
Fall wird der Compiler das gesondert behandeln). Wenn er das weiß, kann
er zu Beginn der Funktion bereits allen nötigen Stack-Speicher
reservieren, so dass bei einem Thread-Wechsel gefahrlos eine anderer
Thread Stack belegen kann. Jeder Thread hat also seinen Stack-Pointer,
und ein zusätzlicher Pointer zeigt an, wo neuer Speicher belegt werden
kann.
Da das Verfahren in dieser Weise viel Verwaltungsaufwand bei jedem
einzelnen Funktionsaufruf benötigt und zudem zu Speicherfragmentation
(d.h. ungenutzte Speicherbereiche, die nicht neu belegt werden können,
weil sie z.B. zu klein sind) führen kann, wird einem Thread
üblicherweise Stack-Speicher in fester Blockgröße (z.B. 128 Byte)
zugewiesen. Eine Funktion muss dann am Anfang nur prüfen, ob der
aktuelle Block noch genug Platz hat.
Das erledigen manche Compiler in nur 2 Maschinenbefehlen, ist also für
den häufigsten Fall sehr schnell. Passt das, dann arbeitet die Funktion
als ob es keinen segmentierten Stäck gäbe. Reicht der aktuelle Block
nicht aus, oder benötigt eine Funktion mehr als einen ganzen Block, dann
wird erst die langsame Speicherverwaltung durchgeführt.
In der Mitte freigewordene Blöcke können dann auch problemlos
wiederverwendet werden, so dass die Fragmentation wesentlich geringer
ausfällt. Sie entsteht aber trotzdem, da ein Block am Ende üblicherweise
immer ein paar ungenutzte Bytes hat, weil dort die nächste Funktion
nicht mehr passte.