Réponses:
Cygwin a un fork () complet sur Windows. Ainsi, si l'utilisation de Cygwin est acceptable pour vous, le problème est résolu dans le cas où les performances ne sont pas un problème.
Sinon, vous pouvez voir comment Cygwin implémente fork (). D'après un assez vieux doc d' architecture de Cygwin :
5.6. Création de processus L'appel de fourche dans Cygwin est particulièrement intéressant car il ne correspond pas bien à l'API Win32. Cela rend la mise en œuvre correcte très difficile. Actuellement, le fork Cygwin est une implémentation sans copie sur écriture similaire à ce qui était présent dans les premières versions d'UNIX.
La première chose qui se produit lorsqu'un processus parent forque un processus enfant est que le parent initialise un espace dans la table de processus Cygwin pour l'enfant. Il crée ensuite un processus enfant suspendu à l'aide de l'appel Win32 CreateProcess. Ensuite, le processus parent appelle setjmp pour enregistrer son propre contexte et place un pointeur sur celui-ci dans une zone de mémoire partagée Cygwin (partagée entre toutes les tâches Cygwin). Il remplit ensuite les sections .data et .bss de l'enfant en copiant depuis son propre espace d'adressage dans l'espace d'adressage de l'enfant suspendu. Une fois l'espace d'adressage de l'enfant initialisé, l'enfant est exécuté pendant que le parent attend un mutex. L'enfant découvre qu'il a été forké et fait des sauts longs en utilisant le tampon de saut enregistré. L'enfant définit ensuite le mutex que le parent attend et bloque sur un autre mutex. C'est le signal pour le parent de copier sa pile et son tas dans l'enfant, après quoi il libère le mutex que l'enfant attend et revient de l'appel de fork. Enfin, l'enfant se réveille du blocage sur le dernier mutex, recrée toutes les zones mappées en mémoire qui lui sont transmises via la zone partagée, et revient de fork lui-même.
Bien que nous ayons quelques idées sur la façon d'accélérer notre implémentation de fork en réduisant le nombre de changements de contexte entre le processus parent et enfant, fork sera presque certainement toujours inefficace sous Win32. Heureusement, dans la plupart des cas, la famille d'appels spawn fournie par Cygwin peut être remplacée par une paire fork / exec avec seulement un petit effort. Ces appels correspondent parfaitement à l'API Win32. En conséquence, ils sont beaucoup plus efficaces. Changer le programme pilote du compilateur pour appeler spawn au lieu de fork a été un changement trivial et a augmenté la vitesse de compilation de vingt à trente pour cent dans nos tests.
Cependant, spawn et exec présentent leurs propres difficultés. Puisqu'il n'y a aucun moyen de faire une exécution réelle sous Win32, Cygwin doit inventer ses propres ID de processus (PID). Par conséquent, lorsqu'un processus exécute plusieurs appels d'exécution, plusieurs PID Windows seront associés à un seul PID Cygwin. Dans certains cas, les stubs de chacun de ces processus Win32 peuvent persister, attendant que leur processus Cygwin exécuté se termine.
Cela ressemble à beaucoup de travail, n'est-ce pas? Et oui, c'est slooooow.
EDIT: la doc est obsolète, veuillez voir cette excellente réponse pour une mise à jour
fork
mais il accomplit cela avec une solution qui fuit et vous devez être préparé à des situations inattendues.
Je ne connais certainement pas les détails à ce sujet car je ne l'ai jamais fait, mais l'API NT native a la capacité de créer un processus (le sous-système POSIX sur Windows a besoin de cette capacité - je ne suis pas sûr que le sous-système POSIX est même plus pris en charge).
Une recherche sur ZwCreateProcess () devrait vous donner plus de détails - par exemple cette information de Maxim Shatskih :
Le paramètre le plus important ici est SectionHandle. Si ce paramètre est NULL, le noyau bifurquera le processus en cours. Sinon, ce paramètre doit être un handle de l'objet de section SEC_IMAGE créé sur le fichier EXE avant d'appeler ZwCreateProcess ().
Cependant, notez que Corinna Vinschen indique que Cygwin trouvé en utilisant ZwCreateProcess () n'est toujours pas fiable :
Iker Arizmendi a écrit:
> 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
Ce document est assez vieux, 10 ans environ. Bien que nous utilisions toujours des appels Win32 pour émuler fork, la méthode a sensiblement changé. Surtout, nous ne créons plus le processus enfant dans l'état suspendu, à moins que des infrastructures de données spécifiques nécessitent une gestion spéciale dans le parent avant d'être copiées sur l'enfant. Dans la version 1.5.25 actuelle, le seul cas pour un enfant suspendu est l'ouverture de sockets dans le parent. La prochaine version 1.7.0 ne sera pas suspendue du tout.
Une des raisons de ne pas utiliser ZwCreateProcess était que jusqu'à la version 1.5.25, nous prenons toujours en charge les utilisateurs de Windows 9x. Cependant, deux tentatives d'utilisation de ZwCreateProcess sur des systèmes NT ont échoué pour une raison ou une autre.
Ce serait vraiment bien que ce truc soit meilleur ou pas du tout documenté, en particulier quelques structures de données et comment connecter un processus à un sous-système. Bien que fork ne soit pas un concept Win32, je ne vois pas que ce serait une mauvaise chose de rendre fork plus facile à implémenter.
fork
avec immédiat exec
", alors peut-être que CreateProcess est un candidat. Mais fork
sans exec
c'est souvent souhaitable et c'est ce que les conducteurs demandent un vrai fork
.
Eh bien, les fenêtres n'ont vraiment rien de tel. D'autant plus que fork peut être utilisé pour créer conceptuellement un thread ou un processus dans * nix.
Alors, je dois dire:
CreateProcess()
/CreateProcessEx()
et
CreateThread()
(J'ai entendu dire que pour les applications C, _beginthreadex()
c'est mieux).
Les gens ont essayé d'implémenter fork sur Windows. C'est la chose la plus proche que je puisse trouver:
Tiré de: 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
plante, plante le programme ou le thread plante-t-il simplement? Si cela plante le programme, ce n'est pas vraiment une fourche. Juste curieux, car je cherche une vraie solution, et j'espère que cela pourrait être une alternative décente.
Avant que Microsoft n'introduise sa nouvelle option «sous-système Linux pour Windows», CreateProcess()
c'était la chose la plus proche que Windows doit faire fork()
, mais Windows vous oblige à spécifier un exécutable à exécuter dans ce processus.
La création de processus UNIX est assez différente de Windows. Son fork()
appel duplique essentiellement le processus actuel presque au total, chacun dans son propre espace d'adressage, et continue de les exécuter séparément. Bien que les processus eux-mêmes soient différents, ils exécutent toujours le même programme. Voir ici pour un bon aperçu du fork/exec
modèle.
Pour revenir en arrière, l'équivalent de Windows CreateProcess()
est la fork()/exec()
paire de fonctions sous UNIX.
Si vous portiez un logiciel sur Windows et que la couche de traduction ne vous dérangeait pas, Cygwin fournissait la capacité que vous souhaitiez, mais c'était plutôt dérangeant.
Bien sûr, avec le nouveau sous - système Linux , la chose la plus proche de Windows doit fork()
est en fait fork()
:-)
fork
dans une application moyenne non WSL?
Le document suivant fournit des informations sur le portage du code d'UNIX vers Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Entre autres choses, il indique que le modèle de processus est assez différent entre les deux systèmes et recommande de considérer CreateProcess et CreateThread là où un comportement semblable à fork () est requis.
"dès que vous souhaitez accéder aux fichiers ou imprimer, les io sont refusés"
Vous ne pouvez pas avoir votre gâteau et le manger aussi ... dans msvcrt.dll, printf () est basé sur l'API de la console, qui en elle-même utilise lpc pour communiquer avec le sous-système de la console (csrss.exe). La connexion avec csrss est initiée au démarrage du processus, ce qui signifie que tout processus qui commence son exécution "au milieu" verra cette étape ignorée. Sauf si vous avez accès au code source du système d'exploitation, il est inutile d'essayer de vous connecter manuellement à csrss. Au lieu de cela, vous devez créer votre propre sous-système, et par conséquent éviter les fonctions de console dans les applications qui utilisent fork ().
une fois que vous avez implémenté votre propre sous-système, n'oubliez pas de dupliquer également tous les descripteurs du parent pour le processus enfant ;-)
"De plus, vous ne devriez probablement pas utiliser les fonctions Zw * sauf si vous êtes en mode noyau, vous devriez probablement utiliser les fonctions Nt * à la place."
ZwGetContextThread (NtCurrentThread (), & context);
La sémantique de fork () est nécessaire là où l'enfant a besoin d'accéder à l'état réel de la mémoire du parent dès que l'instant fork () est appelé. J'ai un logiciel qui repose sur le mutex implicite de la copie de mémoire à partir du moment où l'instant fork () est appelé, ce qui rend les threads impossibles à utiliser. (Ceci est émulé sur les plates-formes * nix modernes via la sémantique copie sur écriture / mise à jour-mémoire-table.)
Le plus proche qui existe sous Windows en tant qu'appel système est CreateProcess. Le mieux que l'on puisse faire est que le parent fige tous les autres threads pendant le temps qu'il copie de la mémoire vers l'espace mémoire du nouveau processus, puis les dégèle. Ni la classe Cygwin frok [sic] ni le code Scilab qu'Eric des Courtis a posté n'effectuent le blocage des threads, ce que je peux voir.
De plus, vous ne devriez probablement pas utiliser les fonctions Zw * sauf si vous êtes en mode noyau, vous devriez probablement utiliser les fonctions Nt * à la place. Il y a une branche supplémentaire qui vérifie si vous êtes en mode noyau et, sinon, effectue toutes les vérifications de limites et de paramètres que Nt * fait toujours. Ainsi, il est très légèrement moins efficace de les appeler depuis le mode utilisateur.
Vos meilleures options sont CreateProcess () ou CreateThread () . Il y a plus d'informations sur le portage ici .
Il n'y a pas de moyen simple d'émuler fork () sous Windows.
Je vous suggère d'utiliser des threads à la place.
fork
était exactement ce que CygWin a fait. Mais, si jamais vous lisez comment ils l'ont fait, votre "pas facile" est un gros malentendu :-)
Le plus proche que vous dites ... Laissez-moi réfléchir ... Ce doit être fork () je suppose :)
Pour plus de détails, consultez Interix implémente - t - il fork ()?
Comme d'autres réponses l'ont mentionné, NT (le noyau sous-jacent aux versions modernes de Windows) a un équivalent de Unix fork (). Ce n'est pas le problème.
Le problème est que le clonage de l'état entier d'un processus n'est généralement pas une chose saine à faire. C'est aussi vrai dans le monde Unix que dans Windows, mais dans le monde Unix, fork () est utilisé tout le temps et les bibliothèques sont conçues pour y faire face. Les bibliothèques Windows ne le sont pas.
Par exemple, les DLL système kernel32.dll et user32.dll maintiennent une connexion privée au processus serveur Win32 csrss.exe. Après un fork, il y a deux processus du côté client de cette connexion, ce qui va poser des problèmes. Le processus enfant doit informer csrss.exe de son existence et établir une nouvelle connexion - mais il n'y a pas d'interface pour le faire, car ces bibliothèques n'ont pas été conçues avec fork () à l'esprit.
Vous avez donc deux choix. La première consiste à interdire l'utilisation de kernel32 et user32 et d'autres bibliothèques qui ne sont pas conçues pour être fourchues - y compris toutes les bibliothèques qui lient directement ou indirectement à kernel32 ou user32, qui sont pratiquement toutes. Cela signifie que vous ne pouvez pas du tout interagir avec le bureau Windows et que vous êtes coincé dans votre propre monde Unixy séparé. C'est l'approche adoptée par les différents sous-systèmes Unix pour NT.
L'autre option est de recourir à une sorte de piratage horrible pour essayer de faire fonctionner des bibliothèques inconscientes avec fork (). C'est ce que fait Cygwin. Il crée un nouveau processus, le laisse s'initialiser (y compris s'enregistrer avec csrss.exe), puis copie la majeure partie de l'état dynamique de l'ancien processus et espère le meilleur. Cela m'étonne que cela fonctionne toujours . Cela ne fonctionne certainement pas de manière fiable - même s'il n'échoue pas de manière aléatoire en raison d'un conflit d'espace d'adressage, toute bibliothèque que vous utilisez peut être laissée silencieusement dans un état cassé. L'affirmation de la réponse actuellement acceptée selon laquelle Cygwin a un "fork complet ()" est ... douteuse.
Résumé: Dans un environnement de type Interix, vous pouvez fork en appelant fork (). Sinon, essayez de vous sevrer du désir de le faire. Même si vous ciblez Cygwin, n'utilisez pas fork () sauf si vous le devez absolument.
Si vous ne vous souciez que de créer un sous-processus et de l'attendre, peut-être que les API _spawn * dans process.h sont suffisantes. Voici plus d'informations à ce sujet:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h