web-dev-qa-db-fra.com

Quelle est la chose la plus proche que windows a fork ()?

Je suppose que la question dit tout.

Je veux bifurquer sur Windows. Quelle est l'opération la plus similaire et comment l'utiliser?.

113
rlbond

Cygwin possède toutes les fonctionnalités de fork () sous Windows. Par conséquent, si l'utilisation de Cygwin est acceptable pour vous, le problème est résolu si les performances ne sont pas un problème.

Sinon, vous pouvez regarder comment Cygwin implémente fork (). D'une architecture assez ancienne de Cygwin doc :

5.6 Création de processus L'appel fork dans Cygwin est particulièrement intéressant car il ne correspond pas bien à l'API Win32. Cela rend très difficile la mise en œuvre correcte. Actuellement, Cygwin fork est une implémentation sans copie sur écriture similaire à celle présente dans les versions antérieures d'UNIX.

La première chose qui se produit lorsqu'un processus parent forks 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 y place un pointeur 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 les copiant à partir de son propre espace d'adressage dans l'espace d'adressage de l'enfant suspendu. Une fois l’espace adresse de l’enfant initialisé, celui-ci est exécuté pendant que le parent attend un mutex. L'enfant découvre qu'il a été créé et lance de longs sauts à l'aide du tampon de saut enregistré. L'enfant configure ensuite le mutex que le parent attend et bloque un autre mutex. C'est le signal pour que le parent copie 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 fork. Enfin, l'enfant cesse de bloquer 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 manière d'accélérer notre implémentation fork en réduisant le nombre de changements de contexte entre les processus parent et enfant, fork sera presque toujours inefficace sous Win32. Heureusement, dans la plupart des cas, la famille d'appels d'apparition fournie par Cygwin peut être remplacée par un couple fork/exec avec un minimum d'effort. Ces appels sont clairement associés à l'API Win32. En conséquence, ils sont beaucoup plus efficaces. Changer le programme du pilote du compilateur pour appeler spawn au lieu de fork était un changement trivial et augmentait la vitesse de compilation de 20 à 30% dans nos tests.

Cependant, spawn et exec présentent leurs propres difficultés. Comme il n’existe aucun moyen d’exécuter une exécution réelle sous Win32, Cygwin doit inventer ses propres ID de processus (PID). En conséquence, lorsqu'un processus effectue plusieurs appels exec, plusieurs PID Windows sont associés à un seul PID Cygwin. Dans certains cas, des talons de chacun de ces processus Win32 peuvent persister, en 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, s'il vous plaît voir cet excellent réponse pour une mise à jour

79
Laurynas Biveinis

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 sous Windows a besoin de cette capacité - je ne sais pas si le sous-système POSIX est même encore supporté).

Une recherche sur ZwCreateProcess () devrait vous donner plus de détails - par exemple cette information fournie par Maxim Shatskih :

Le paramètre le plus important ici est SectionHandle. Si ce paramètre est NULL, le noyau divisera le processus en cours. Sinon, ce paramètre doit être un descripteur de l'objet de section SEC_IMAGE créé sur le fichier EXE avant d'appeler ZwCreateProcess ().

Notez cependant que Corinna Vinschen indique que Cygwin a découvert que l'utilisation de ZwCreateProcess () n'était 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 plutôt vieux, environ 10 ans. Bien que nous utilisions toujours des appels Win32 pour émuler fork, la méthode a sensiblement changé. En particulier, nous ne créons plus le processus enfant dans l'état suspendu, à moins que des infrastructures de données spécifiques nécessitent un traitement spécial dans le parent avant qu'elles ne soient copiées dans l'enfant. Dans la version 1.5.25 actuelle, les sockets ouverts du parent sont les seuls cas pour un enfant suspendu. 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 prenions 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 si cela devait être mieux ou du tout documenté, en particulier quelques infrastructures de données et la manière de connecter un processus à un sous-système. Bien que fork ne soit pas un concept Win32, je ne pense pas que ce serait une mauvaise chose de faciliter la mise en œuvre de fork.

58
Michael Burr

Eh bien, Windows n'a vraiment rien de tel. D'autant plus que fork peut être utilisé pour créer conceptuellement un thread ou un processus dans * nix.

Donc, je devrais dire:

CreateProcess() /CreateProcessEx()

et

CreateThread() (j'ai entendu dire que pour les applications C, _beginthreadex() est préférable).

35
Evan Teran

Les gens ont essayé d'implémenter fork sur Windows. C'est la chose la plus proche que je puisse trouver:

Extrait 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;
}
15
Eric des Courtis

Avant que Microsoft n'introduise son nouveau "sous-système Linux pour Windows", CreateProcess() était l'élément le plus proche de Windows pour fork(), mais Windows vous oblige à spécifier un exécutable à exécuter dans ce processus.

La création du processus UNIX est assez différente de Windows. Son appel fork() duplique en gros le processus en cours presque au total, chacun dans son propre espace adresse, 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.

En revenant en arrière, l'équivalent de Windows CreateProcess() est le couple fork()/exec() de fonctions sous UNIX. .

Si vous portiez un logiciel sous Windows et que la couche de traduction ne vous dérangeait pas, Cygwin vous fournissait les fonctionnalités que vous souhaitiez, mais c’était plutôt kludgey.

Bien sûr, avec le nouveau sous-système Linux , l'élément le plus proche de Windows à fork() est en fait fork() :-)

6
paxdiablo

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, cela indique que le modèle de processus est assez différent entre les deux systèmes et recommande de prendre en compte CreateProcess et CreateThread lorsqu'un comportement similaire à fork () est requis.

6
Brandon E Taylor

"dès que vous voulez faire un accès aux fichiers ou printf alors 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 utilise elle-même lpc pour communiquer avec le sous-système de la console (csrss.exe). La connexion avec csrss est lancée au démarrage du processus, ce qui signifie que tout processus commençant son exécution "au milieu" verra cette étape ignorée. Si vous n'avez pas accès au code source du système d'exploitation, il est inutile de tenter de vous connecter manuellement à csrss. Au lieu de cela, vous devez créer votre propre sous-système et éviter en conséquence 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 des parents pour le processus enfant ;-)

"En outre, vous ne devriez probablement pas utiliser les fonctions Zw * à moins d'être en mode noyau, vous devriez probablement utiliser les fonctions Nt * à la place."

  • Ceci est une erreur. En mode utilisateur, il n’ya absolument aucune différence entre Zw *** Nt ***; ce sont simplement deux noms exportés différents (ntdll.dll) qui font référence à la même adresse virtuelle (relative).

ZwGetContextThread (NtCurrentThread (), & context);

  • obtenir le contexte du thread (en cours d'exécution) en cours en appelant ZwGetContextThread est incorrect, risque de planter et (en raison de l'appel système supplémentaire) n'est pas non plus le moyen le plus rapide d'accomplir la tâche.
3
user3502619

la sémantique de fork () est nécessaire lorsque l'enfant a besoin d'accéder à l'état réel de la mémoire du parent dès l'appel de fork (). J'ai un logiciel qui s'appuie sur le mutex implicite de la copie en mémoire dès l'instant où fork () est appelé, ce qui rend les threads impossibles à utiliser. (Ceci est émulé sur les plates-formes modernes * nix via une sémantique de 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 gèle tous les autres threads pendant qu'il copie de la mémoire dans le nouvel espace mémoire, puis les décongèle. Ni la classe Cygwin frok [sic] ni le code Scilab publié par Eric des Courtis ne font le gel des threads, ce que je peux voir.

En outre, vous ne devriez probablement pas utiliser les fonctions Zw * à moins d'être en mode noyau, vous devriez probablement utiliser les fonctions Nt * à la place. Il existe une branche supplémentaire qui vérifie si vous êtes en mode noyau et, sinon, effectue toutes les vérifications de bornes et de paramètres que Nt * effectue toujours. Il est donc très légèrement moins efficace de les appeler en mode utilisateur.

3
sjcaged

Le plus proche que vous dites ... Laissez-moi réfléchir ... Ce doit être fork () Je suppose :)

Pour plus de détails, voir Interix implémente-t-il fork ()?

2
Piotr Dobrogost

Vos meilleures options sont CreateProcess () ou CreateThread () . Il y a plus d'informations sur le portage ici .

2
John T

Il n'y a pas de moyen facile d'émuler fork () sous Windows.

Je vous suggère d'utiliser des discussions à la place.

2
VVS

Si vous ne vous souciez que de créer un sous-processus et de l'attendre, les API _spawn * dans process.h sont peut-être suffisantes. Voici plus d'informations à ce sujet:

https://docs.Microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-controlhttps://en.wikipedia.org/ wiki/Process.h

1
solstice333