Odpowiedzi:
Cygwin ma w pełni funkcjonalny fork () w systemie Windows. Tak więc, jeśli używanie Cygwin jest dla Ciebie akceptowalne, problem zostanie rozwiązany w przypadku, gdy wydajność nie jest problemem.
W przeciwnym razie możesz przyjrzeć się, jak Cygwin implementuje fork (). Z dość starego dokumentu architektury Cygwin :
5.6. Tworzenie procesu Wywołanie fork w Cygwin jest szczególnie interesujące, ponieważ nie mapuje dobrze w górnej części API Win32. To bardzo utrudnia prawidłowe wdrożenie. Obecnie rozwidlenie Cygwin jest implementacją bez kopiowania przy zapisie, podobną do tej, która była obecna we wczesnych wersjach UNIX.
Pierwszą rzeczą, która się dzieje, gdy proces nadrzędny forkuje proces potomny, jest inicjowanie przez niego spacji w tabeli procesów Cygwin. Następnie tworzy zawieszony proces potomny przy użyciu wywołania Win32 CreateProcess. Następnie proces nadrzędny wywołuje setjmp, aby zapisać swój własny kontekst i ustawia wskaźnik do tego w obszarze pamięci współdzielonej Cygwin (współdzielonej przez wszystkie zadania Cygwin). Następnie wypełnia sekcje .data i .bss dziecka, kopiując z własnej przestrzeni adresowej do przestrzeni adresowej zawieszonego dziecka. Po zainicjowaniu przestrzeni adresowej dziecka, dziecko jest uruchamiane, podczas gdy rodzic czeka na muteks. Dziecko odkrywa, że został rozwidlony i wykonuje długie skoki, korzystając z zapisanego bufora skoku. Następnie dziecko ustawia muteks, na który czeka rodzic, i blokuje inny muteks. Jest to sygnał dla rodzica, aby skopiował swój stos i stertę do dziecka, po czym zwalnia muteks, na który czeka dziecko, i wraca z wywołania fork. Wreszcie, dziecko budzi się z blokowania na ostatnim muteksie, odtwarza wszystkie obszary mapowane w pamięci przekazane do niego przez wspólny obszar i wraca z samego forka.
Chociaż mamy kilka pomysłów, jak przyspieszyć implementację rozwidlenia poprzez zmniejszenie liczby przełączeń kontekstu między procesem nadrzędnym i podrzędnym, rozwidlenie prawie na pewno zawsze będzie nieefektywne pod Win32. Na szczęście w większości przypadków rodzina wywołań spawn dostarczana przez Cygwin może zostać zastąpiona parą fork / exec przy niewielkim wysiłku. Te wywołania są dokładnie odwzorowywane w górnej części interfejsu API Win32. Dzięki temu są znacznie wydajniejsze. Zmiana programu sterownika kompilatora tak, aby wywoływał spawn zamiast fork, była trywialną zmianą i zwiększyła prędkość kompilacji o dwadzieścia do trzydziestu procent w naszych testach.
Jednak spawn i exec stwarzają własny zestaw trudności. Ponieważ nie ma sposobu na wykonanie faktycznego exec pod Win32, Cygwin musi wymyślić własne identyfikatory procesów (PID). W rezultacie, gdy proces wykonuje wiele wywołań exec, z jednym PID Cygwin będzie powiązanych wiele identyfikatorów PID systemu Windows. W niektórych przypadkach, kody pośredniczące każdego z tych procesów Win32 mogą pozostawać w oczekiwaniu na zamknięcie ich wykonanego procesu Cygwin.
Brzmi jak dużo pracy, prawda? I tak, jest powolny.
EDYTUJ: dokument jest nieaktualny, zapoznaj się z tą doskonałą odpowiedzią na aktualizację
fork
ale osiąga to za pomocą nieszczelnego rozwiązania i musisz być przygotowany na nieoczekiwane sytuacje.
Na pewno nie znam szczegółów na ten temat, ponieważ nigdy tego nie robiłem, ale natywne API NT ma możliwość rozwidlenia procesu (podsystem POSIX w Windows potrzebuje tej możliwości - nie jestem pewien, czy podsystem POSIX jest już nawet obsługiwany).
Poszukiwanie ZwCreateProcess () powinno dać ci więcej szczegółów - na przykład ta informacja od Maxima Shatskiha :
Najważniejszym parametrem jest tutaj SectionHandle. Jeśli ten parametr ma wartość NULL, jądro rozwidli bieżący proces. W przeciwnym razie ten parametr musi być uchwytem obiektu sekcji SEC_IMAGE utworzonego w pliku EXE przed wywołaniem funkcji ZwCreateProcess ().
Chociaż zauważ, że Corinna Vinschen wskazuje, że Cygwin stwierdził, że używanie ZwCreateProcess () jest nadal niewiarygodne :
Iker Arizmendi napisał:
> 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
Ten dokument jest dość stary, mniej więcej 10 lat. Chociaż nadal używamy wywołań Win32 do emulowania rozwidlenia, metoda znacznie się zmieniła. W szczególności nie tworzymy już procesu potomnego w stanie zawieszonym, chyba że określone dane wymagają specjalnej obsługi przez rodzica, zanim zostaną skopiowane do dziecka. W obecnej wersji 1.5.25 jedynym przypadkiem dla zawieszonego dziecka są otwarte gniazda w rodzicu. Nadchodząca wersja 1.7.0 w ogóle nie zostanie zawieszona.
Jednym z powodów, dla których nie warto używać ZwCreateProcess, było to, że do wersji 1.5.25 nadal obsługujemy użytkowników Windows 9x. Jednak dwie próby użycia ZwCreateProcess w systemach opartych na systemie NT nie powiodły się z tego czy innego powodu.
Byłoby naprawdę miło, gdyby te rzeczy były lepsze lub w ogóle udokumentowane, zwłaszcza kilka struktur danych i jak podłączyć proces do podsystemu. Chociaż fork nie jest koncepcją Win32, nie sądzę, aby uczynienie go łatwiejszym do wdrożenia byłoby złą rzeczą.
fork
natychmiastowego exec
”, być może kandydatem jest CreateProcess. Ale fork
bez exec
jest często pożądane i właśnie o to kierowcy ludzie proszą o prawdziwe fork
.
Cóż, okna tak naprawdę nie mają czegoś podobnego. Zwłaszcza, że fork może służyć do koncepcyjnego tworzenia wątku lub procesu w * nix.
Muszę więc powiedzieć:
CreateProcess()
/CreateProcessEx()
i
CreateThread()
(Słyszałem, że dla aplikacji C _beginthreadex()
jest lepsze).
Ludzie próbowali wdrożyć fork w systemie Windows. To jest najbliższa rzecz, jaką mogę znaleźć:
Zaczerpnięte z: http://doxygen.scilab.org/5.3/d0/d8f/forkWindows_8c_source.html#l00216
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;
}
fork
awaria ulegnie awarii, spowoduje awarię programu lub czy wątek po prostu się zawiesi? Jeśli to zawiesza program, to tak naprawdę nie jest rozwidlenie. Po prostu ciekawy, ponieważ szukam prawdziwego rozwiązania i mam nadzieję, że to może być przyzwoita alternatywa.
Zanim Microsoft wprowadził nową opcję „podsystemu Linux dla Windows”, CreateProcess()
była to najbliższa rzecz, jaką musiał wykonać fork()
system Windows, ale system Windows wymaga określenia pliku wykonywalnego do uruchomienia w tym procesie.
Tworzenie procesu w systemie UNIX przebiega zupełnie inaczej niż w systemie Windows. Jego fork()
wywołanie zasadniczo powiela bieżący proces prawie w całości, każdy w swojej własnej przestrzeni adresowej i kontynuuje uruchamianie ich oddzielnie. Chociaż same procesy są różne, nadal korzystają z tego samego programu. Zobacz tutaj dobry przegląd fork/exec
modelu.
Wracając w drugą stronę, odpowiednikiem Windowsa CreateProcess()
jest fork()/exec()
para funkcji w systemie UNIX.
Jeśli przenosisz oprogramowanie na Windows i nie masz nic przeciwko warstwie tłumaczeniowej, Cygwin zapewnił ci możliwość, ale była raczej niezdarna.
Oczywiście, z nowego podsystemu Linux , najbliższa rzecz Windows ma się fork()
to rzeczywiście fork()
:-)
fork
w przeciętnej aplikacji innej niż WSL?
Poniższy dokument zawiera informacje na temat przenoszenia kodu z systemu UNIX na Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Między innymi wskazuje, że model procesu jest zupełnie inny między dwoma systemami i zaleca rozważenie CreateProcess i CreateThread, gdzie wymagane jest zachowanie podobne do fork ().
„gdy tylko zechcesz uzyskać dostęp do pliku lub printf, odmówi się dostępu we / wy”
Nie możesz mieć ciastka i też go zjeść ... w msvcrt.dll printf () jest oparte na API konsoli, które samo w sobie używa lpc do komunikacji z podsystemem konsoli (csrss.exe). Połączenie z csrss jest inicjowane przy starcie procesu, co oznacza, że każdy proces rozpoczynający wykonywanie „w środku” będzie miał pominięty ten krok. Jeśli nie masz dostępu do kodu źródłowego systemu operacyjnego, nie ma sensu próbować łączyć się ręcznie z csrss. Zamiast tego powinieneś stworzyć swój własny podsystem i odpowiednio unikać funkcji konsoli w aplikacjach używających fork ().
po zaimplementowaniu własnego podsystemu nie zapomnij również zduplikować wszystkich uchwytów rodzica dla procesu potomnego ;-)
„Poza tym prawdopodobnie nie powinieneś używać funkcji Zw *, chyba że jesteś w trybie jądra, prawdopodobnie powinieneś zamiast tego używać funkcji Nt *”.
ZwGetContextThread (NtCurrentThread (), & kontekst);
Semantyka fork () jest konieczna, gdy dziecko potrzebuje dostępu do aktualnego stanu pamięci rodzica w momencie wywołania funkcji fork (). Mam oprogramowanie, które opiera się na niejawnym muteksie kopiowania pamięci od momentu wywołania natychmiastowego fork (), co uniemożliwia użycie wątków. (Jest to emulowane na nowoczesnych platformach * nix za pomocą semantyki kopiowania przy zapisie / aktualizacji pamięci tabeli).
Najbliższy, który istnieje w systemie Windows jako wywołanie systemowe, to CreateProcess. Najlepsze, co można zrobić, to zamrozić wszystkie inne wątki w czasie kopiowania pamięci do przestrzeni pamięci nowego procesu, a następnie je rozmrozić. Ani klasa Cygwin frok [sic], ani kod Scilab, który opublikował Eric des Courtis, nie zamrażają wątków, które widzę.
Ponadto prawdopodobnie nie powinieneś używać funkcji Zw *, chyba że jesteś w trybie jądra, prawdopodobnie powinieneś zamiast tego używać funkcji Nt *. Istnieje dodatkowa gałąź, która sprawdza, czy jesteś w trybie jądra, a jeśli nie, wykonuje wszystkie sprawdzanie granic i weryfikację parametrów, które zawsze wykonuje Nt *. W związku z tym wywoływanie ich z trybu użytkownika jest nieco mniej wydajne.
Twoje najlepsze opcje to CreateProcess () lub CreateThread () . Więcej informacji na temat przenoszenia można znaleźć tutaj .
Nie ma łatwego sposobu na emulację fork () w systemie Windows.
Proponuję zamiast tego używać wątków.
fork
było dokładnie tym , co zrobił CygWin. Ale jeśli kiedykolwiek przeczytasz, jak oni to zrobili, twój „niełatwy sposób” to rażące nieporozumienie :-)
Najbliżej mówisz ... Niech pomyślę ... To musi być fork () chyba :)
Aby uzyskać szczegółowe informacje, zobacz Czy Interix implementuje fork ()?
Jak wspomniały inne odpowiedzi, NT (jądro będące podstawą nowoczesnych wersji Windows) ma odpowiednik Unix fork (). To nie jest problem.
Problem polega na tym, że klonowanie całego stanu procesu nie jest na ogół rozsądną rzeczą. Jest to tak samo prawdziwe w świecie uniksowym, jak w systemie Windows, ale w świecie uniksowym fork () jest używany przez cały czas i biblioteki są zaprojektowane tak, aby sobie z tym radzić. Biblioteki systemu Windows nie są.
Na przykład systemowe biblioteki DLL kernel32.dll i user32.dll utrzymują prywatne połączenie z procesem serwera Win32 csrss.exe. Po rozwidleniu istnieją dwa procesy po stronie klienta tego połączenia, co spowoduje problemy. Proces potomny powinien poinformować csrss.exe o swoim istnieniu i nawiązać nowe połączenie - ale nie ma takiego interfejsu, ponieważ te biblioteki nie zostały zaprojektowane z myślą o fork ().
Masz więc dwie możliwości. Jednym z nich jest zakaz korzystania z kernel32 i user32 oraz innych bibliotek, które nie są przeznaczone do rozwidlania - w tym bibliotek, które bezpośrednio lub pośrednio łączą się z kernel32 lub user32, czyli praktycznie wszystkimi z nich. Oznacza to, że nie możesz w ogóle wchodzić w interakcję z pulpitem Windows i utkniesz w swoim własnym, oddzielnym świecie Unixy. Jest to podejście przyjęte przez różne podsystemy Unix dla NT.
Inną opcją jest uciekanie się do jakiegoś okropnego hackowania, aby spróbować zmusić nieświadome biblioteki do pracy z fork (). To właśnie robi Cygwin. Tworzy nowy proces, pozwala mu się zainicjować (w tym rejestruje się w csrss.exe), a następnie kopiuje większość stanu dynamicznego ze starego procesu i ma nadzieję na najlepsze. Zadziwia mnie, że to zawsze działa. Z pewnością nie działa niezawodnie - nawet jeśli nie przypadkowo zawiedzie z powodu konfliktu przestrzeni adresowej, każda używana biblioteka może po cichu zostać zepsuta. Twierdzenie obecnie akceptowanej odpowiedzi, że Cygwin ma „w pełni funkcjonalny fork ()” jest ... wątpliwe.
Podsumowanie: W środowisku podobnym do Interix można forować, wywołując fork (). W przeciwnym razie spróbuj odzwyczaić się od chęci zrobienia tego. Nawet jeśli celujesz w Cygwin, nie używaj fork (), chyba że absolutnie musisz.
Jeśli zależy Ci tylko na utworzeniu podprocesu i czekaniu na niego, być może API _spawn * jest w toku.h są wystarczające. Oto więcej informacji na ten temat:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h