Ответы:
Cygwin имеет полнофункциональную fork () в Windows. Таким образом, если использование Cygwin для вас приемлемо, тогда проблема решается, если производительность не является проблемой.
В противном случае вы можете посмотреть, как Cygwin реализует fork (). Из довольно старого архитектурного документа Cygwin :
5.6. Создание процесса. Вызов fork в Cygwin особенно интересен, потому что он плохо отображается поверх Win32 API. Это очень затрудняет правильную реализацию. В настоящее время вилка Cygwin - это реализация без копирования при записи, аналогичная той, что присутствовала в ранних версиях UNIX.
Первое, что происходит, когда родительский процесс разветвляет дочерний процесс, - это то, что родитель инициализирует пространство в таблице процессов Cygwin для дочернего процесса. Затем он создает приостановленный дочерний процесс с помощью вызова Win32 CreateProcess. Затем родительский процесс вызывает setjmp для сохранения своего собственного контекста и устанавливает указатель на него в общей области памяти Cygwin (совместно используемой всеми задачами Cygwin). Затем он заполняет дочерние разделы .data и .bss, копируя из своего собственного адресного пространства в приостановленное дочернее адресное пространство. После инициализации адресного пространства дочернего элемента дочерний элемент запускается, пока родитель ждет мьютекса. Ребенок обнаруживает, что он был разветвлен, и прыгает в длину, используя сохраненный буфер перехода. Затем дочерний элемент устанавливает мьютекс, которого ожидает родитель, и блокирует другой мьютекс. Это сигнал для родителя скопировать свой стек и кучу в дочерний, после чего он освобождает мьютекс, которого ожидает дочерний элемент, и возвращается из вызова fork. Наконец, дочерний элемент просыпается от блокировки на последнем мьютексе, воссоздает все отображенные в память области, переданные ему через общую область, и возвращается из самого fork.
Хотя у нас есть некоторые идеи относительно того, как ускорить реализацию нашей вилки за счет уменьшения количества переключений контекста между родительским и дочерним процессом, вилка почти всегда будет неэффективной в Win32. К счастью, в большинстве случаев семейство вызовов spawn, предоставляемое Cygwin, может быть заменено парой fork / exec с небольшим усилием. Эти вызовы четко отображаются поверх Win32 API. В результате они намного эффективнее. Изменение программы драйвера компилятора для вызова spawn вместо fork было тривиальным изменением и увеличило скорость компиляции на двадцать-тридцать процентов в наших тестах.
Однако spawn и exec представляют свой набор трудностей. Поскольку в Win32 невозможно выполнить реальный запуск, Cygwin должен изобретать свои собственные идентификаторы процессов (PID). В результате, когда процесс выполняет несколько вызовов exec, будет несколько идентификаторов Windows PID, связанных с одним Cygwin PID. В некоторых случаях заглушки каждого из этих процессов Win32 могут задерживаться, ожидая завершения их процесса exec'd Cygwin.
Похоже, много работы, не так ли? И да, это медленно.
РЕДАКТИРОВАТЬ: документ устарел, см. Этот отличный ответ для обновления
fork
но достигает этого с помощью ненадежного решения, и вы должны быть готовы к неожиданным ситуациям.
Я, конечно, не знаю подробностей об этом, потому что я никогда этого не делал, но собственный NT API имеет возможность разветвлять процесс (подсистема POSIX в Windows нуждается в этой возможности - я не уверен, что подсистема POSIX даже не поддерживается).
Поиск по ZwCreateProcess () должен дать вам более подробную информацию - например, эту информацию от Максима Шацких :
Самый важный параметр здесь - SectionHandle. Если этот параметр равен NULL, ядро будет форкнуть текущий процесс. В противном случае этот параметр должен быть дескриптором объекта раздела SEC_IMAGE, созданного в EXE-файле перед вызовом ZwCreateProcess ().
Однако обратите внимание, что Коринна Виншен указывает, что Cygwin, обнаруженный с помощью ZwCreateProcess (), все еще ненадежен :
Икер Арисменди писал:
> 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
Этот документ довольно старый, лет 10 или около того. Хотя мы все еще используем вызовы Win32 для имитации вилки, метод заметно изменился. В частности, мы больше не создаем дочерний процесс в приостановленном состоянии, если только определенные данные не нуждаются в особой обработке в родительском процессе, прежде чем они будут скопированы в дочерний. В текущем выпуске 1.5.25 единственным случаем для приостановленного дочернего элемента являются открытые сокеты в родительском. Предстоящий выпуск 1.7.0 вообще не будет приостановлен.
Одна из причин не использовать ZwCreateProcess заключалась в том, что до выпуска 1.5.25 мы все еще поддерживали пользователей Windows 9x. Однако две попытки использовать ZwCreateProcess в системах на базе NT по той или иной причине не удались.
Было бы действительно хорошо, если бы этот материал был лучше или вообще задокументирован, особенно пара структур данных и способы подключения процесса к подсистеме. Хотя вилка не является концепцией Win32, я не думаю, что было бы плохо упростить реализацию вилки.
fork
с немедленным exec
», то, возможно, подойдет CreateProcess. Но fork
без этого exec
часто желательно, и именно это заставляет людей просить настоящих fork
.
Что ж, в windows действительно нет ничего похожего. Тем более, что fork можно использовать для концептуального создания потока или процесса в * nix.
Итак, я должен сказать:
CreateProcess()
/CreateProcessEx()
и
CreateThread()
(Я слышал, что для приложений C _beginthreadex()
лучше).
Люди пытались реализовать форк в Windows. Это самое близкое к нему, что я могу найти:
Взято с: 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
сбой, произойдет сбой программы или просто произойдет сбой потока? Если это приводит к сбою программы, то на самом деле это не разветвление. Просто любопытно, потому что я ищу реальное решение и надеюсь, что это может быть достойной альтернативой.
До того, как Microsoft представила свою новую опцию «Подсистема Linux для Windows», это CreateProcess()
было ближе всего к Windows.fork()
, но Windows требует, чтобы вы указали исполняемый файл для запуска в этом процессе.
Процесс создания UNIX сильно отличается от Windows. Его fork()
вызов практически полностью дублирует текущий процесс, каждый в своем адресном пространстве, и продолжает их запускать отдельно. Хотя сами процессы разные, они по-прежнему выполняют одну и ту же программу. См. Здесь хороший обзор fork/exec
модели.
Возвращаясь назад, эквивалент Windows CreateProcess()
- это fork()/exec()
пара функций в UNIX.
Если вы переносили программное обеспечение на Windows и не возражаете против уровня перевода, Cygwin предоставил вам нужные возможности, но это было довольно беспорядочно.
Конечно, с новой подсистемой Linux самое близкое к Windows fork()
- это fork()
:-)
fork
в обычном приложении, отличном от WSL?
В следующем документе представлена некоторая информация о переносе кода с UNIX на Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx
Среди прочего, он указывает на то, что модель процесса в двух системах сильно различается, и рекомендует рассмотреть CreateProcess и CreateThread, где требуется поведение, подобное fork ().
"как только вы захотите получить доступ к файлу или printf, тогда io будет отказано"
Вы не можете съесть свой торт и съесть его ... в msvcrt.dll printf () основан на API консоли, который сам по себе использует lpc для связи с подсистемой консоли (csrss.exe). Соединение с csrss инициируется при запуске процесса, что означает, что любой процесс, который начинает свое выполнение «посередине», будет пропускать этот шаг. Если у вас нет доступа к исходному коду операционной системы, нет смысла пытаться подключиться к csrss вручную. Вместо этого вы должны создать свою собственную подсистему и, соответственно, избегать функций консоли в приложениях, использующих fork ().
как только вы реализовали свою собственную подсистему, не забудьте также продублировать все родительские дескрипторы для дочернего процесса ;-)
«Кроме того, вам, вероятно, не следует использовать функции Zw *, если вы не находитесь в режиме ядра, вместо этого вам, вероятно, следует использовать функции Nt *».
ZwGetContextThread (NtCurrentThread (), & контекст);
Семантика fork () необходима там, где дочернему элементу требуется доступ к фактическому состоянию памяти родительского объекта на момент вызова fork (). У меня есть программа, которая полагается на неявный мьютекс копирования памяти при вызове функции fork (), что делает невозможным использование потоков. (Это эмулируется на современных платформах * nix с помощью семантики таблицы копирования при записи / обновления в памяти.)
Ближайшим из существующих в Windows системных вызовов является CreateProcess. Лучшее, что можно сделать, - это чтобы родительский объект заморозил все другие потоки на время, когда он копирует память в пространство памяти нового процесса, а затем разморозил их. Я вижу, что ни класс Cygwin frok, ни код Scilab, опубликованный Эриком де Курти, не останавливают потоки.
Кроме того, вам, вероятно, не следует использовать функции Zw *, если вы не находитесь в режиме ядра, вместо этого вам, вероятно, следует использовать функции Nt *. Есть дополнительная ветка, которая проверяет, находитесь ли вы в режиме ядра, и, если нет, выполняет все проверки границ и параметров, которые всегда выполняет Nt *. Таким образом, вызывать их из пользовательского режима немного менее эффективно.
Лучшие варианты - CreateProcess () или CreateThread () . Существует более подробная информация о переносе здесь .
Нет простого способа эмулировать fork () в Windows.
Я предлагаю вам вместо этого использовать потоки.
fork
была именно тем , что сделала CygWin. Но, если вы когда-нибудь читали о том, как они это сделали, ваш «нелегкий путь» будет большим недоразумением :-)
Самое близкое, что вы скажете ... Дайте подумать ... Наверное, это fork () :)
Подробнее см. Реализует ли Interix fork ()?
Как уже упоминалось в других ответах, NT (ядро, лежащее в основе современных версий Windows) имеет эквивалент Unix fork (). Проблема не в этом.
Проблема в том, что клонирование всего состояния процесса, как правило, неразумно. Это так же верно в мире Unix, как и в Windows, но в мире Unix fork () используется постоянно, и библиотеки предназначены для работы с этим. Библиотеки Windows - нет.
Например, системные библиотеки DLL kernel32.dll и user32.dll поддерживают частное соединение с серверным процессом Win32 csrss.exe. После вилки на клиентской стороне этого соединения есть два процесса, которые могут вызвать проблемы. Дочерний процесс должен сообщить csrss.exe о своем существовании и установить новое соединение, но для этого нет интерфейса, поскольку эти библиотеки не были разработаны с учетом fork ().
Итак, у вас есть два варианта. Один из них - запретить использование kernel32 и user32 и других библиотек, которые не предназначены для разветвления, включая любые библиотеки, которые прямо или косвенно ссылаются на kernel32 или user32, то есть практически все из них. Это означает, что вы вообще не можете взаимодействовать с рабочим столом Windows и застряли в своем собственном отдельном мире Unixy. Это подход, используемый различными подсистемами Unix для NT.
Другой вариант - прибегнуть к какой-то ужасной хитрости, чтобы попытаться заставить неосведомленные библиотеки работать с fork (). Это то, что делает Cygwin. Он создает новый процесс, позволяет ему инициализироваться (включая регистрацию с помощью csrss.exe), затем копирует большую часть динамического состояния из старого процесса и надеется на лучшее. Меня поражает, что это когда-либо работает. Он определенно не работает надежно - даже если он не выходит из строя случайным образом из-за конфликта адресного пространства, любая используемая вами библиотека может оставаться в неработающем состоянии. Утверждение текущего принятого ответа о том, что Cygwin имеет «полнофункциональную вилку ()» ... сомнительно.
Резюме: В среде, подобной Interix, вы можете выполнить вилку, вызвав fork (). В противном случае попробуйте отучить себя от этого желания. Даже если вы нацелены на Cygwin, не используйте fork (), если в этом нет крайней необходимости.
Если вас интересует только создание подпроцесса и его ожидание, возможно, достаточно _spawn * API в process.h. Подробнее об этом:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/process-and-environment-control https://en.wikipedia.org/wiki/Process.h