Respuestas:
Cygwin ha presentado fork () en Windows. Por lo tanto, si usar Cygwin es aceptable para usted, entonces el problema se resuelve en caso de que el rendimiento no sea un problema.
De lo contrario, puede echar un vistazo a cómo Cygwin implementa fork (). De un documento de arquitectura bastante antiguo de Cygwin :
5.6. Creación de procesos La llamada fork en Cygwin es particularmente interesante porque no se asigna bien en la parte superior de la API Win32. Esto hace que sea muy difícil de implementar correctamente. Actualmente, la bifurcación Cygwin es una implementación sin copia en escritura similar a la que estaba presente en los primeros sabores de UNIX.
Lo primero que sucede cuando un proceso padre bifurca un proceso hijo es que el padre inicializa un espacio en la tabla de proceso Cygwin para el hijo. Luego crea un proceso hijo suspendido utilizando la llamada Win32 CreateProcess. Luego, el proceso padre llama a setjmp para guardar su propio contexto y establece un puntero a esto en un área de memoria compartida de Cygwin (compartida entre todas las tareas de Cygwin). Luego completa las secciones .data y .bss del niño copiando desde su propio espacio de direcciones en el espacio de direcciones del niño suspendido. Después de que se inicializa el espacio de direcciones del niño, el niño se ejecuta mientras el padre espera en un mutex. El niño descubre que se ha bifurcado y saltos largos utilizando el búfer de salto guardado. Luego, el niño establece el mutex que el padre está esperando y bloquea otro mutex. Esta es la señal para que el padre copie su pila y su montón en el niño, después de lo cual libera el mutex que el niño está esperando y regresa de la llamada fork. Finalmente, el niño se despierta del bloqueo en el último mutex, recrea cualquier área asignada a la memoria que se le haya pasado a través del área compartida y regresa de la bifurcación misma.
Si bien tenemos algunas ideas sobre cómo acelerar nuestra implementación de la bifurcación reduciendo el número de cambios de contexto entre el proceso primario y secundario, la bifurcación casi siempre será ineficiente en Win32. Afortunadamente, en la mayoría de los casos, la familia de llamadas generadas por Cygwin puede ser sustituida por un par tenedor / ejecutivo con solo un pequeño esfuerzo. Estas llamadas se asignan limpiamente en la parte superior de la API Win32. Como resultado, son mucho más eficientes. Cambiar el programa del controlador del compilador para generar spawn en lugar de fork fue un cambio trivial y aumentó las velocidades de compilación en un veinte a treinta por ciento en nuestras pruebas.
Sin embargo, spawn y exec presentan su propio conjunto de dificultades. Debido a que no hay forma de hacer un ejecutivo real en Win32, Cygwin tiene que inventar sus propios ID de proceso (PID). Como resultado, cuando un proceso realiza múltiples llamadas ejecutivas, habrá múltiples PID de Windows asociados con un solo PID de Cygwin. En algunos casos, los resguardos de cada uno de estos procesos Win32 pueden persistir, esperando que salga su proceso Cygwin ejecutado.
Suena a mucho trabajo, ¿no? Y sí, es muy lento.
EDITAR: el documento está desactualizado, vea esta excelente respuesta para una actualización
Ciertamente no conozco los detalles sobre esto porque nunca lo he hecho, pero la API nativa de NT tiene la capacidad de bifurcar un proceso (el subsistema POSIX en Windows necesita esta capacidad; no estoy seguro de si el subsistema POSIX incluso es más compatible).
Una búsqueda de ZwCreateProcess () debería proporcionarle más detalles, por ejemplo, este bit de información de Maxim Shatskih :
El parámetro más importante aquí es SectionHandle. Si este parámetro es NULL, el núcleo bifurcará el proceso actual. De lo contrario, este parámetro debe ser un identificador del objeto de sección SEC_IMAGE creado en el archivo EXE antes de llamar a ZwCreateProcess ().
Sin embargo, tenga en cuenta que Corinna Vinschen indica que Cygwin encontró que usar ZwCreateProcess () todavía no es confiable :
Iker Arizmendi escribió:
> 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
Este documento es bastante antiguo, más o menos 10 años. Si bien todavía estamos usando llamadas Win32 para emular fork, el método ha cambiado notablemente. Especialmente, ya no creamos el proceso hijo en el estado suspendido, a menos que los datos específicos necesiten un manejo especial en el padre antes de que se copien en el niño. En la versión actual 1.5.25, el único caso para un niño suspendido son los enchufes abiertos en el padre. La próxima versión 1.7.0 no se suspenderá en absoluto.
Una razón para no usar ZwCreateProcess fue que hasta la versión 1.5.25 todavía admitimos usuarios de Windows 9x. Sin embargo, dos intentos de usar ZwCreateProcess en sistemas basados en NT fallaron por una razón u otra.
Sería realmente bueno si este material fuera mejor o estuviera documentado, especialmente un par de estructuras de datos y cómo conectar un proceso a un subsistema. Si bien fork no es un concepto de Win32, no veo que sea malo hacer que fork sea más fácil de implementar.
fork
con inmediato exec
", entonces quizás CreateProcess sea un candidato. Pero fork
sin a exec
menudo es deseable y esto es lo que impulsa a las personas a pedir un real fork
.
Bueno, Windows realmente no tiene nada parecido. Especialmente porque fork puede usarse para crear conceptualmente un hilo o un proceso en * nix.
Entonces, tendría que decir:
CreateProcess()
/ /CreateProcessEx()
y
CreateThread()
(He escuchado que para aplicaciones C, _beginthreadex()
es mejor).
La gente ha intentado implementar fork en Windows. Esto es lo más cercano que puedo encontrar:
Tomado 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
bloquea, se bloquea el programa o el hilo simplemente se bloquea? Si se bloquea el programa, entonces esto no es realmente bifurcación. Solo por curiosidad, porque estoy buscando una solución real y espero que esta sea una alternativa decente.
Antes de que Microsoft presentara su nueva opción "Subsistema Linux para Windows", CreateProcess()
era lo más parecido a lo que Windows tenía que hacer fork()
, pero Windows requiere que especifique un ejecutable para ejecutarlo en ese proceso.
La creación del proceso UNIX es bastante diferente a la de Windows. Su fork()
llamada básicamente duplica el proceso actual casi en total, cada uno en su propio espacio de direcciones, y continúa ejecutándolos por separado. Si bien los procesos en sí son diferentes, todavía ejecutan el mismo programa. Vea aquí para una buena descripción del fork/exec
modelo.
Volviendo al otro lado, el equivalente de Windows CreateProcess()
es el fork()/exec()
par de funciones en UNIX.
Si estaba transfiriendo software a Windows y no le importaba una capa de traducción, Cygwin le brindó la capacidad que deseaba, pero era bastante burda.
Por supuesto, con el nuevo subsistema de Linux , lo más parecido a Windows tiene fork()
es en realidad fork()
:-)
fork
en una aplicación promedio que no sea WSL?
El siguiente documento proporciona información sobre el código de portabilidad de UNIX a Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Entre otras cosas, indica que el modelo de proceso es bastante diferente entre los dos sistemas y recomienda considerar CreateProcess y CreateThread donde se requiere un comportamiento similar a fork ().
"Tan pronto como desee acceder a los archivos o imprimir, entonces se rechazarán".
No puede tener su pastel y comérselo también ... en msvcrt.dll, printf () se basa en la API de la consola, que en sí misma utiliza lpc para comunicarse con el subsistema de la consola (csrss.exe). La conexión con csrss se inicia al inicio del proceso, lo que significa que cualquier proceso que comience su ejecución "en el medio" tendrá ese paso omitido. A menos que tenga acceso al código fuente del sistema operativo, entonces no tiene sentido intentar conectarse a csrss manualmente. En su lugar, debe crear su propio subsistema y, en consecuencia, evitar las funciones de la consola en aplicaciones que usan fork ().
una vez que haya implementado su propio subsistema, no olvide duplicar también todos los identificadores del padre para el proceso secundario ;-)
"Además, probablemente no deberías usar las funciones Zw * a menos que estés en modo kernel, probablemente deberías usar las funciones Nt * en su lugar".
ZwGetContextThread (NtCurrentThread (), & context);
la semántica fork () es necesaria cuando el niño necesita acceso al estado de memoria real del padre a partir del momento en que se llama fork (). Tengo un software que se basa en el mutex implícito de la copia de memoria a partir de la llamada fork instantánea (), lo que hace que los hilos sean imposibles de usar. (Esto se emula en las plataformas modernas * nix a través de la semántica copiar-escribir-escribir / actualizar-tabla-memoria).
Lo más cercano que existe en Windows como syscall es CreateProcess. Lo mejor que se puede hacer es que el padre congele todos los otros subprocesos durante el tiempo que está copiando la memoria en el espacio de memoria del nuevo proceso, luego descongelarlos. Ni la clase Cygwin frok [sic] ni el código Scilab que Eric des Courtis publicó hace el congelamiento de hilos, que puedo ver.
Además, probablemente no debería usar las funciones Zw * a menos que esté en modo kernel, probablemente debería usar las funciones Nt * en su lugar. Hay una rama adicional que verifica si está en modo kernel y, si no, realiza todas las verificaciones de límites y la verificación de parámetros que Nt * siempre hace. Por lo tanto, es un poco menos eficiente llamarlos desde el modo de usuario.
Sus mejores opciones son CreateProcess () o CreateThread () . Hay más información sobre portabilidad aquí .
No hay una manera fácil de emular fork () en Windows.
Te sugiero que uses hilos en su lugar.
fork
fue exactamente lo que hizo CygWin. Pero, si alguna vez leíste cómo lo hicieron, tu "no es una manera fácil" es un gran malentendido :-)
Lo más cerca que dices ... Déjame pensar ... Esto debe ser tenedor () supongo :)
Para obtener más información, consulte ¿Interix implementa fork ()?
Como han mencionado otras respuestas, NT (el núcleo subyacente a las versiones modernas de Windows) tiene un equivalente de Unix fork (). Ese no es el problema.
El problema es que clonar el estado completo de un proceso generalmente no es algo sensato. Esto es tan cierto en el mundo de Unix como lo es en Windows, pero en el mundo de Unix, fork () se usa todo el tiempo y las bibliotecas están diseñadas para manejarlo. Las bibliotecas de Windows no lo son.
Por ejemplo, las DLL del sistema kernel32.dll y user32.dll mantienen una conexión privada al proceso del servidor Win32 csrss.exe. Después de una bifurcación, hay dos procesos en el extremo del cliente de esa conexión, lo que causará problemas. El proceso secundario debe informar a csrss.exe de su existencia y establecer una nueva conexión, pero no hay una interfaz para hacerlo, porque estas bibliotecas no se diseñaron teniendo en cuenta fork ().
Entonces tienes dos opciones. Una es prohibir el uso de kernel32 y user32 y otras bibliotecas que no están diseñadas para ser bifurcadas, incluidas las bibliotecas que se vinculan directa o indirectamente con kernel32 o user32, que es prácticamente todas ellas. Esto significa que no puede interactuar con el escritorio de Windows en absoluto, y está atascado en su propio mundo de Unixy. Este es el enfoque adoptado por los diversos subsistemas de Unix para NT.
La otra opción es recurrir a algún tipo de truco horrible para tratar de hacer que las bibliotecas inconscientes funcionen con fork (). Eso es lo que hace Cygwin. Crea un nuevo proceso, le permite inicializarse (incluido el registro con csrss.exe), luego copia la mayor parte del estado dinámico del proceso anterior y espera lo mejor. Me sorprende que esto alguna vez funcione . Ciertamente no funciona de manera confiable, incluso si no falla aleatoriamente debido a un conflicto de espacio de direcciones, cualquier biblioteca que esté usando puede quedar silenciosamente en un estado roto. La afirmación de la respuesta actual aceptada de que Cygwin tiene un "tenedor con todas las funciones ()" es ... dudosa.
Resumen: en un entorno similar a Interix, puede bifurcar llamando a fork (). De lo contrario, intenta alejarte del deseo de hacerlo. Incluso si está apuntando a Cygwin, no use fork () a menos que sea absolutamente necesario.
Si solo le importa crear un subproceso y esperarlo, quizás _spawn * API's en proceso.h sean suficientes. Aquí hay más información sobre eso:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h