Hoe weet fork() wanneer 0 moet worden geretourneerd?

Neem het volgende voorbeeld:

int main(void)
{
     pid_t  pid;
     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}

Dus corrigeer me als ik het mis heb, zodra fork() wordt uitgevoerd, wordt er een onderliggend proces gemaakt. Ga nu langs deze answerfork() keert twee keer terug. Dat is een keer voor het ouderproces en een keer voor het kindproces.

Wat betekent dat er twee afzonderlijke processen ontstaan ​​TIJDENS de fork-aanroep en niet nadat deze is beëindigd.

Nu begrijp ik niet hoe het begrijpt hoe 0 moet worden geretourneerd voor het onderliggende proces en de juiste PID voor het bovenliggende proces.

Hier wordt het echt verwarrend. Dit antwoordstelt dat fork() werkt door de contextinformatie van het proces te kopiëren en de retourwaarde handmatig in te stellen op 0.

Ten eerste heb ik gelijk als ik zeg dat de terugkeer naar een functie in een enkel register wordt geplaatst? Omdat een proces in een omgeving met één processor slechts één subroutine kan aanroepen die slechts één waarde retourneert (corrigeer me als ik het mis heb).

Stel dat ik een functie foo() aanroep binnen een routine en die functie retourneert een waarde, die waarde wordt opgeslagen in een register, zeg BAR. Elke keer dat een functie een waarde wil retourneren, gebruikt deze een bepaald processorregister.Dus als ik de retourwaarde in het procesblok handmatig kan wijzigen, kan ik de waarde wijzigen die naar de functie wordt geretourneerd, toch?

Dus heb ik gelijk als ik denk dat fork() zo werkt?


Antwoord 1, autoriteit 100%

Hoehet werkt is grotendeels irrelevant – als ontwikkelaar die op een bepaald niveau werkt (dwz coderen naar de UNIX API’s), hoef je eigenlijk alleen maar te weten dathet werkt.

Dat gezegd hebbende, en in het besef dat nieuwsgierigheid of de behoefte om tot op zekere hoogte te begrijpen over het algemeen een goede eigenschap is om te hebben, zijn er een aantal manieren waarop dit kanworden gedaan.

>

Ten eerste, uw bewering dat een functie slechts één waarde kan retourneren, is correct voor zover het gaat, maar u moet niet vergeten dat er na de processplitsing in feite tweeinstanties van de functie zijn draaien, één in elk proces. Ze zijn meestal onafhankelijk van elkaar en kunnen verschillende codepaden volgen. Het volgende diagram kan helpen om dit te begrijpen:

Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork     |
               | comes into existence
returns 271828 | returns 0

Je kunt daar hopelijk zien dat een enkeleinstantie van forkslechts één waarde kan retourneren (zoals bij elke andere C-functie), maar er zijn in feite meerdere instanties actief, die daarom wordt in de documentatie gezegd dat het meerdere waarden retourneert.


Hier is een mogelijkheid over hoe het zouzou kunnen werken.

Als de functie fork()begint te lopen, wordt de huidige proces-ID (PID) opgeslagen.

Als het tijd is om terug te keren en de PID hetzelfde is als de opgeslagen PID, is dit de ouder. Anders is het het kind. Pseudo-code volgt:

def fork():
    saved_pid = getpid()
    # Magic here, returns PID of other process or -1 on failure.
    other_pid = split_proc_into_two();
    if other_pid == -1:        # fork failed -> return -1
        return -1
    if saved_pid == getpid():  # pid same, parent -> return child PID
        return other_pid
    return 0                   # pid changed, child, return zero

Merk op dat er veel magie zit in de split_proc_into_two()-aanroep en dat het vrijwel zeker niet zo zal werken onder de dekmantel(a). Het is gewoon om de concepten eromheen te illustreren, wat eigenlijk is:

  • verkrijg de originele PID vóór de splitsing, die voor beide processen identiek blijft nadat ze zijn gesplitst.
  • doe de splitsing.
  • haal de huidige PID op na de splitsing, die verschillendzal zijn in de twee processen.

Misschien wil je ook eens kijken naar dit antwoord, het verklaart de fork/exec-filosofie.


(a)Het is vrijwel zeker ingewikkelder dan ik heb uitgelegd. In MINIX wordt de aanroep van forkbijvoorbeeld uitgevoerd in de kernel, die toegang heeft tot de hele procesboom.

Het kopieert eenvoudig de bovenliggende processtructuur naar een vrije plek voor het kind, in de trant van:

sptr = (char *) proc_addr (k1); // parent pointer
chld = (char *) proc_addr (k2); // child pointer
dptr = chld;
bytes = sizeof (struct proc);   // bytes to copy
while (bytes--)                 // copy the structure
    *dptr++ = *sptr++;

Vervolgens brengt het kleine wijzigingen aan in de onderliggende structuur om ervoor te zorgen dat het geschikt is, inclusief de regel:

chld->p_reg[RET_REG] = 0;       // make sure child receives zero

Dus in principe identiek aan het schema dat ik heb geponeerd, maar met behulp van gegevensaanpassingen in plaats van codepadselectie om te beslissen wat er naar de beller moet worden teruggestuurd – met andere woorden, je zou zoiets zien als:

return rpc->p_reg[RET_REG];

aan het einde van fork()zodat de juiste waarde wordt geretourneerd, afhankelijk van of het het bovenliggende of onderliggende proces is.


Antwoord 2, autoriteit 53%

In Linux gebeurt fork()in de kernel; de werkelijke plaats is de _do_forkhier. Vereenvoudigd, de fork()systeemaanroep zou zoiets kunnen zijn als

pid_t sys_fork() {
    pid_t child = create_child_copy();
    wait_for_child_to_start();
    return child;
}

Dus in de kernel retourneert fork()echt eenmaal, in het bovenliggende proces. De kernel maakt echter ook het onderliggende proces aan als een kopie van het bovenliggende proces; maar in plaats van terug te keren van een gewone functie, zou het synthetisch een nieuwe kernelstapelcreëren voor de nieuw gecreëerde thread van het onderliggende proces; en vervolgens context-switch naar die thread (en proces); als het nieuw gemaakte proces terugkeert van de contextomschakelfunctie, zou de thread van het onderliggende proces uiteindelijk terugkeren naar de gebruikersmodus met 0 als de retourwaarde van fork().


In feite is fork()in userland slechts een dunne wrapper die de waarde retourneert die de kernel op zijn stack/in return register heeft gezet. De kernel stelt het nieuwe onderliggende proces zo in dat het via dit mechanisme 0 retourneert vanuit zijn enige thread; en de onderliggende pid wordt geretourneerd in de bovenliggende systeemaanroep zoals elke andere retourwaarde van een systeemaanroep zoals read(2)zou zijn.


Antwoord 3, autoriteit 18%

Je moet eerst weten hoe multitasking werkt. Het is niet handig om alle details te begrijpen, maar elk proces wordt uitgevoerd in een soort virtuele machine die wordt bestuurd door de kernel: een proces heeft zijn eigen geheugen, processor en registers, enz. Deze virtuele objecten worden in kaart gebracht op de echte (de magie zit in de kernel), en er zijn machines die virtuele contexten (processen) naar fysieke machines omwisselen naarmate de tijd verstrijkt.

Als de kernel vervolgens een proces splitst (fork()is een ingang naar de kernel), en een kopie maakt van bijna alles in het ouderproces naar de kindproces, is het in staat om alles aan te passen wat nodig is. Een daarvan is de wijziging van de corresponderende structuren om 0 te retourneren voor het kind en de pid van het kind in de ouder van de huidige aanroep naar de fork.

Opmerking: zeg ook niet “fork retourneert twee keer”, een functieaanroep retourneert slechts één keer.

Denk maar aan een kloonmachine: je komt alleen binnen, maar twee personen gaan naar buiten, de ene ben jij en de andere is je kloon (heel iets anders); tijdens het klonen kan de machine een andere naam instellen dan de jouwe voor de kloon.


Antwoord 4, autoriteit 14%

De fork-systeemaanroep creëert een nieuw proces en kopieert veel status van het bovenliggende proces. Dingen zoals de bestandsdescriptortabel worden gekopieerd, de geheugentoewijzingen en hun inhoud, enz. Die staat bevindt zich in de kernel.

Een van de dingen die de kernel voor elk proces bijhoudt, zijn de waarden van registers die dit proces moet hebben hersteld bij de terugkeer van een systeemaanroep, trap, onderbreking of context-switch (de meeste context-switches gebeuren bij systeemaanroepen of interrupts) . Die registers worden opgeslagen op een syscall/trap/interrupt en vervolgens hersteld bij terugkeer naar userland. Systeem roept retourwaarden aan door in die status te schrijven. Dat is wat vork doet. Bovenliggende vork krijgt één waarde, kind verwerkt een andere.

Omdat het gevorkte proces anders is dan het bovenliggende proces, kan de kernel er alles aan doen. Geef het alle waarden in registers, geef het alle geheugentoewijzingen. Om er echt voor te zorgen dat bijna alles behalve de retourwaarde hetzelfde is als in het bovenliggende proces, vergt meer inspanning.


Antwoord 5, autoriteit 4%

Voor elk lopend proces heeft de kernel een tabel met registers, om terug te laden wanneer een contextomschakeling wordt gemaakt. fork()is een systeemaanroep; een speciale aanroep die, wanneer gemaakt, het proces een context-switch krijgt en de kernelcode die de aanroep uitvoert, in een andere (kernel)thread wordt uitgevoerd.

De waarde die door systeemaanroepen wordt geretourneerd, wordt in een speciaal register (EAX in x86) geplaatst dat uw toepassing na de aanroep leest. Wanneer de fork()-aanroep wordt gedaan, maakt de kernel een kopie van het proces en schrijft in elke tabel met registers van elke procesdescriptor de juiste waarde: 0 en de pid.

Other episodes