c ++ - Hvad er det tætteste, som Windows skal gaffel ()?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg antager, at spørgsmålet siger det hele.


Jeg vil gaffel på vinduer. Hvad er den mest lignende operation, og hvordan bruger jeg den.

Bedste reference


Cygwin har fuldt udstyret gaffel () på Windows. Således hvis man bruger Cygwin, er det acceptabelt for dig, så løser problemet sig i tilfælde af at præstationen ikke er et problem. [15]


Ellers kan du se, hvordan Cygwin implementerer gaffel (). Fra en ret gammel Cygwins arkitektur doc: [16]



  5.6. Procesoprettelse
  Gaffelopkaldet i Cygwin er særligt interessant
  fordi det ikke folder godt ovenpå
  Win32 API. Dette gør det meget
  svært at gennemføre korrekt.
  I øjeblikket er Cygwin gaffel en
  ikke-copy-on-write implementering
  svarende til det, der var til stede tidligt
  smagsstoffer fra UNIX.

  
  Det første der sker, når a
  Forældreprocessen forfalder en børneproces
  er at forældren initialiserer et mellemrum
  i Cygwin proces tabellen for
  barn. Det skaber så en suspenderet
  børneproces ved hjælp af Win32
  CreateProcess-opkald. Derefter forælder
  behandle opkald setjmp for at gemme sin egen
  kontekst og sætter en peger på dette i
  et Cygwin fælles hukommelsesområde (delt
  blandt alle Cygwin opgaver). Det fylder derefter
  i barnets s .data og .bss sektioner
  ved at kopiere fra sit eget adresserum
  ind i det suspenderede barns adresse
  plads. Efter barnets adresserum
  er initialiseret, barnet køres mens
  Forældrene venter på en mutex. Barnet
  opdager det har været forked og
  longjumps ved hjælp af den gemte hoppe buffer.
  Barnet sætter så mutexen på
  forælder venter på og blokerer på
  en anden mutex. Dette er signalet til
  Forældrene skal kopiere sin stak og bunke
  ind i barnet, hvorefter det
  frigiver mutex barnet er
  venter på og vender tilbage fra gaffelen
  opkald. Endelig vågner barnet fra
  blokering på den sidste mutex genskabes
  alle hukommelseskortede områder passeret til den
  via det delte område og vender tilbage fra
  gaffel selv.

  
  Mens vi har nogle ideer til hvordan
  fremskynde vores gaffel implementering af
  reducere antallet af kontekst
  skifter mellem forælder og barn
  proces, gaffel vil næsten helt sikkert
  Vær altid ineffektiv under Win32.
  Heldigvis, i de fleste tilfælde
  spawn familie af opkald fra
  Cygwin kan erstattes af a
  gaffel/exec-par med kun lidt
  indsats. Disse opkald kortene rent ovenpå
  af Win32 API. Som et resultat, de
  er meget mere effektive. Ændring af
  kompilators driverprogram til at ringe
  gyde i stedet for gaffel var et trivielt
  ændring og øget kompilering
  hastigheder med 20 til 30 procent i
  vores test.

  
  Imidlertid praler og eksecere deres
  eget sæt vanskeligheder. Fordi der
  er ingen måde at gøre en faktisk exec under
  Win32, Cygwin er nødt til at opfinde sin egen
  Proces-id'er (PID'er). Som et resultat, hvornår
  en proces udfører flere exec
  Opkald, der vil være flere Windows
  PID'er forbundet med en enkelt Cygwin
  PID. I nogle tilfælde stubber af hver af
  disse Win32-processer kan blive ved at blive ved siden af
  venter på deres eksekutiv Cygwin
  proces for at afslutte.



Det lyder som en masse arbejde, gør det ikke? Og ja det er slooooow.


EDIT: dokumentet er forældet, se venligst dette fremragende svar til en opdatering

Andre referencer 1


Jeg ved bestemt ikke detaljerne herom, fordi jeg aldrig har gjort det, men det native NT API har evnen til at forkaste en proces (POSIX-delsystemet på Windows har brug for denne kapacitet - jeg er ikke sikker på, om POSIX-delsystemet understøttes endda mere).


En søgning efter ZwCreateProcess () skal give dig nogle flere detaljer - for eksempel denne information fra Maxim Shatskih: [18]



  Den vigtigste parameter her er SectionHandle. Hvis denne parameter
  er NULL, kernen vil gaffel den nuværende proces. Ellers dette
  parameter skal være et håndtag af SEC\_IMAGE sektionsobjektet, der er oprettet på
  EXE filen før du kalder ZwCreateProcess ().



Selvom Corinna Vinschen indikerer, at Cygwin fandt brug af ZwCreateProcess () stadig upålitelig: [19]



  Iker Arizmendi skrev:

  
   
> Because the Cygwin project relied solely on Win32 APIs its fork
> implementation is non-COW and inefficient in those cases where a fork
> is not followed by exec.  It's also rather complex. See here (section
> 5.6) for details:
>  
> http://www.redhat.com/support/wpapers/cygnus/cygnus\_cygwin/architecture.html

  
  Dette dokument er ret gammelt, 10 år eller deromkring. Mens vi stadig bruger
  Win32 opfordrer til at efterligne gaffel, metoden er ændret notikabelt.
  Især skaber vi ikke barneprocessen i suspenderet tilstand
  længere, medmindre specifikke datastrukturer har brug for en særlig håndtering i
  forælder, før de bliver kopieret til barnet. I den nuværende 1.5.25
  slip det eneste tilfælde, hvor et suspenderet barn er åbne stikkontakter i
  forælder. Den kommende 1.7.0 udgivelse vil slet ikke suspendere.

  
  En grund til ikke at bruge ZwCreateProcess var det op til 1.5.25
  frigive vi 'stadig støtter Windows 9x brugere. Men to
  forsøg på at bruge ZwCreateProcess på NT-baserede systemer mislykkedes for en
  årsag eller anden.

  
  Det ville være rigtig rart, hvis det her ville være bedre eller overhovedet
  dokumenteret, især et par datastrukturer og hvordan man forbinder en
  Process til et delsystem. Mens gaffel er ikke et Win32 koncept, gør jeg det ikke
  se, at det ville være en dårlig ting at gøre gaffel lettere at gennemføre.


Andre referencer 2


Godt, Windows har ikke noget, der kan lide det. Specielt siden gaffel kan bruges til konceptuelt at skabe en tråd eller en proces i * nix.


Så jeg må sige:


CreateProcess()/CreateProcessEx() [20]


og


CreateThread() (Jeg har hørt, at for C-applikationer er \_beginthreadex() bedre). [21] [22]

Andre referencer 3


Folk har forsøgt at implementere gaffel på Windows. Dette er den nærmeste ting jeg kan finde:


Hentet fra: http://doxygen.scilab.org/5.3/d0/d8f/forkWindows\_8c\_source.html#l00216[23]


static BOOL haveLoadedFunctionsForFork(void);

int fork(void) 
{
    HANDLE hProcess = 0, hThread = 0;
    OBJECT\_ATTRIBUTES oa = { sizeof(oa) };
    MEMORY\_BASIC\_INFORMATION mbi;
    CLIENT\_ID cid;
    USER\_STACK stack;
    PNT\_TIB tib;
    THREAD\_BASIC\_INFORMATION tbi;

    CONTEXT context = {
        CONTEXT\_FULL | 
        CONTEXT\_DEBUG\_REGISTERS | 
        CONTEXT\_FLOATING\_POINT
    };

    if (setjmp(jenv) != 0) return 0; /* return as a child */

    /* check whether the entry points are 
       initilized and get them if necessary */
    if (!ZwCreateProcess && !haveLoadedFunctionsForFork()) return -1;

    /* create forked process */
    ZwCreateProcess(&hProcess, PROCESS\_ALL\_ACCESS, &oa,
        NtCurrentProcess(), TRUE, 0, 0, 0);

    /* set the Eip for the child process to our child function */
    ZwGetContextThread(NtCurrentThread(), &context);

    /* In x64 the Eip and Esp are not present, 
       their x64 counterparts are Rip and Rsp respectively. */
#if \_WIN64
    context.Rip = (ULONG)child\_entry;
#else
    context.Eip = (ULONG)child\_entry;
#endif

#if \_WIN64
    ZwQueryVirtualMemory(NtCurrentProcess(), (PVOID)context.Rsp,
        MemoryBasicInformation, &mbi, sizeof mbi, 0);
#else
    ZwQueryVirtualMemory(NtCurrentProcess(), (PVOID)context.Esp,
        MemoryBasicInformation, &mbi, sizeof mbi, 0);
#endif

    stack.FixedStackBase = 0;
    stack.FixedStackLimit = 0;
    stack.ExpandableStackBase = (PCHAR)mbi.BaseAddress + mbi.RegionSize;
    stack.ExpandableStackLimit = mbi.BaseAddress;
    stack.ExpandableStackBottom = mbi.AllocationBase;

    /* create thread using the modified context and stack */
    ZwCreateThread(&hThread, THREAD\_ALL\_ACCESS, &oa, hProcess,
        &cid, &context, &stack, TRUE);

    /* copy exception table */
    ZwQueryInformationThread(NtCurrentThread(), ThreadBasicInformation,
        &tbi, sizeof tbi, 0);
    tib = (PNT\_TIB)tbi.TebBaseAddress;
    ZwQueryInformationThread(hThread, ThreadBasicInformation,
        &tbi, sizeof tbi, 0);
    ZwWriteVirtualMemory(hProcess, tbi.TebBaseAddress, 
        &tib->ExceptionList, sizeof tib->ExceptionList, 0);

    /* start (resume really) the child */
    ZwResumeThread(hThread, 0);

    /* clean up */
    ZwClose(hThread);
    ZwClose(hProcess);

    /* exit with child's pid */
    return (int)cid.UniqueProcess;
}
static BOOL haveLoadedFunctionsForFork(void)
{
    HANDLE ntdll = GetModuleHandle("ntdll");
    if (ntdll == NULL) return FALSE;

    if (ZwCreateProcess && ZwQuerySystemInformation && ZwQueryVirtualMemory &&
        ZwCreateThread && ZwGetContextThread && ZwResumeThread &&
        ZwQueryInformationThread && ZwWriteVirtualMemory && ZwClose)
    {
        return TRUE;
    }

    ZwCreateProcess = (ZwCreateProcess\_t) GetProcAddress(ntdll,
        "ZwCreateProcess");
    ZwQuerySystemInformation = (ZwQuerySystemInformation\_t)
        GetProcAddress(ntdll, "ZwQuerySystemInformation");
    ZwQueryVirtualMemory = (ZwQueryVirtualMemory\_t)
        GetProcAddress(ntdll, "ZwQueryVirtualMemory");
    ZwCreateThread = (ZwCreateThread\_t)
        GetProcAddress(ntdll, "ZwCreateThread");
    ZwGetContextThread = (ZwGetContextThread\_t)
        GetProcAddress(ntdll, "ZwGetContextThread");
    ZwResumeThread = (ZwResumeThread\_t)
        GetProcAddress(ntdll, "ZwResumeThread");
    ZwQueryInformationThread = (ZwQueryInformationThread\_t)
        GetProcAddress(ntdll, "ZwQueryInformationThread");
    ZwWriteVirtualMemory = (ZwWriteVirtualMemory\_t)
        GetProcAddress(ntdll, "ZwWriteVirtualMemory");
    ZwClose = (ZwClose\_t) GetProcAddress(ntdll, "ZwClose");

    if (ZwCreateProcess && ZwQuerySystemInformation && ZwQueryVirtualMemory &&
        ZwCreateThread && ZwGetContextThread && ZwResumeThread &&
        ZwQueryInformationThread && ZwWriteVirtualMemory && ZwClose)
    {
        return TRUE;
    }
    else
    {
        ZwCreateProcess = NULL;
        ZwQuerySystemInformation = NULL;
        ZwQueryVirtualMemory = NULL;
        ZwCreateThread = NULL;
        ZwGetContextThread = NULL;
        ZwResumeThread = NULL;
        ZwQueryInformationThread = NULL;
        ZwWriteVirtualMemory = NULL;
        ZwClose = NULL;
    }
    return FALSE;
}

Andre referencer 4


Følgende dokument indeholder nogle oplysninger om porting kode fra UNIX til Win32:
https://msdn.microsoft.com/en-us/library/y23kc048.aspx[24]


Det indikerer blandt andet, at procesmodellen er helt anderledes mellem de to systemer og anbefaler overvejelse af CreateProcess og CreateThread hvor gaffel () - lignende adfærd er påkrævet.

Andre referencer 5


Før Microsoft introducerede deres nye 'Linux-subsystem til Windows' -option, var CreateProcess() det nærmeste, som Windows skal fork(), men Windows kræver, at du angiver en eksekverbar for at køre i den proces.


UNIX-processen er helt anderledes end Windows. Dens fork() opkald duplikerer i det væsentlige den nuværende proces næsten i alt, hver i deres eget adresserum og fortsætter med at køre dem separat. Selv om processerne selv er forskellige, kører de stadig det samme program. Se her for et godt overblik over fork/exec modellen.


Til gengæld er den tilsvarende [[Windows]] CreateProcess() fork()/exec() paret af funktioner i UNIX.


Hvis du portede software til Windows, og du ikke har noget imod et oversættelseslag, gav Cygwin den kapacitet, du vil have, men det var ret kludgey.


Selvfølgelig med det nye Linux-delsystem, er det nærmeste, som Windows skal fork() faktisk fork(): -) [26]

Andre referencer 6


gaffel () semantik er nødvendig, hvor barnet har brug for adgang til den faktiske hukommelse tilstand for forældrene, som for øjeblikket gaffel () kaldes. Jeg har et stykke software, der afhænger af den implicitte mutex af hukommelseskopiering, som i øjeblikket fork () kaldes, hvilket gør tråde umulige at bruge. (Dette emuleres på moderne * nix-platforme via semantik til kopi-på-skriv/opdatering-memory-table).


Det nærmeste, der findes på Windows som en syscall, er CreateProcess. Det bedste, der kan gøres, er, at forældrene fryser alle andre tråde i løbet af den tid, hvor det kopierer hukommelsen til den nye proces s hukommelse, og derefter tøer dem. Hverken Cygwin Frok [[sic]] -klassen eller Scilab-koden, der Eric des Courtis postede gør trådfrysningen, som jeg kan se.


Du skal sandsynligvis ikke bruge Zw * -funktionerne, medmindre du er i kernel-tilstand, skal du nok bruge Nt * -funktionerne i stedet. Der er en ekstra filial, der kontrollerer, om du er i kernel mode og, hvis ikke, udfører alle de grænsekontrol og parameterverifikation, som Nt * altid gør. Således er det meget lidt mindre effektivt at kalde dem fra brugertilstand.

Andre referencer 7


'så snart du vil gøre filadgang eller printf så bliver Io nægtet'



  • Du kan ikke have din kage og spise den også ... i msvcrt.dll, printf () er baseret på Console API, som i sig bruger lpc til at kommunikere med konsol delsystemet (csrss.exe). Forbindelse med csrss indledes ved processtart, hvilket betyder, at enhver proces, der starter dens udførelse 'i midten', vil have det skridt, der hoppes over. Medmindre du har adgang til kilden til operativsystemet, er der intet punkt i at forsøge at oprette forbindelse til csrss manuelt. I stedet skal du oprette dit eget delsystem og undgå konsolfunktionerne i applikationer, der bruger gaffel ().

  • Når du har implementeret dit eget delsystem, skal du også glemme at også kopiere alle forældrenes håndtag til børneprocessen ;-)



'Du skal sandsynligvis ikke bruge Zw * -funktionerne, medmindre du er i kernel-tilstand, skal du nok bruge Nt * -funktionerne i stedet.'



  • Dette er forkert. Når der er adgang i brugertilstand, er der absolut ingen forskel mellem Zw *** Nt ***; disse er kun to forskellige (ntdll.dll) eksporterede navne, der henviser til den samme (relative) virtuelle adresse.



ZwGetContextThread (NtCurrentThread (), og kontekst);



  • Hent konteksten for den aktuelle (kørende) tråd ved at kalde ZwGetContextThread er forkert, vil sandsynligvis gå ned, og det er heller ikke den hurtigste måde at udføre opgaven på.


Andre referencer 8


Dine bedste muligheder er CreateProcess () eller CreateThread (). Der er flere oplysninger om porting her. [27] [28] [29]

Andre referencer 9


Der er ingen nem måde at efterligne fork () på Windows.


Jeg foreslår at du bruger tråde i stedet.

Andre referencer 10


Det nærmeste du siger ... Lad mig tænke ... Dette skal være gaffel () jeg gætter :)


For detaljer se Se Interix implementer gaffel ()?

Andre referencer 11


Hvis du kun bekymrer dig om at lave en underproces og venter på det, er det måske nok, at programmerne i processen er tilstrækkelige. Her er flere oplysninger om det:


https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control
https://en.wikipedia.org/wiki/Process.h[31][32]