Відповіді:
Cygwin повністю розмістив fork () у Windows. Таким чином, якщо використання Cygwin є прийнятним для вас, тоді проблема вирішується, якщо продуктивність не є проблемою.
В іншому випадку ви можете подивитися, як Cygwin реалізує fork (). З досить давнього документа архітектури Сігвіна :
5.6. Створення процесу Виклик fork у Cygwin особливо цікавий тим, що він не відображається добре на API Win32. Це ускладнює правильне виконання. В даний час вилка Cygwin - це реалізація без копіювання під час запису, подібна до тієї, яка була присутня в ранніх ароматах UNIX.
Перше, що відбувається, коли батьківський процес розщеплює дочірній процес, це те, що батько ініціалізує простір у таблиці процесів Cygwin для дитини. Потім створюється призупинений дочірній процес за допомогою виклику Win32 CreateProcess. Далі батьківський процес викликає setjmp для збереження власного контексту і встановлює покажчик на це в області спільної пам’яті Cygwin (поділяється між усіма завданнями Cygwin). Потім він заповнює розділи .data та .bss дитини, копіюючи з власного адресного простору в призупинений адресний простір дитини. Після ініціалізації адресного простору дитини дитина запускається, поки батько чекає на мьютекс. Дитина виявляє, що він був роздвоєним і довгими, скориставшись збереженим буфером стрибків. Потім дитина встановлює мутекс, на який чекає батько, і блокує інший мютекс. Це сигнал для батька скопіювати його стек і купу в дитину, після чого він випустить мутекс, на який чекає дитина, і повертається з виклику вилки. Нарешті, дитина прокидається від блокування останнього мютексу, відтворює будь-які області, нанесені на пам'ять, передані йому через спільну область, і повертається з самої вилки.
Хоча у нас є деякі ідеї, як пришвидшити реалізацію виделки, зменшивши кількість контекстних перемикань між батьківським і дочірнім процесом, форк майже напевно завжди буде неефективним у Win32. На щастя, у більшості випадків сімейство дзвінків, що надається Cygwin, може замінити пару форк / exec лише з невеликими зусиллями. Ці дзвінки чітко відображаються поверх API Win32. Як результат, вони набагато ефективніші. Зміна програми драйверів компілятора на виклик spawn замість fork було тривіальною зміною та збільшило швидкість компіляції на двадцять до тридцяти відсотків у наших тестах.
Однак нерест і виконавець представляють власну сукупність труднощів. Оскільки в Win32 немає можливості зробити фактичний exec, Cygwin повинен винайти власні ідентифікатори процесу (PID). В результаті, коли процес виконує кілька викликів exec, у ньому буде декілька PID Windows, пов'язаних з одним PID Cygwin. У деяких випадках заглушки кожного з цих процесів Win32 можуть затримуватися, чекаючи завершення процесу виконання Cygwin.
Здається, багато роботи, чи не так? І так, це slooooow.
РЕДАКТИРОВАННЯ: документ є застарілим, дивіться цю відмінну відповідь для оновлення
fork
але це досягає пропускного рішення, і ви повинні бути готові до несподіваних ситуацій.
Я, звичайно, не знаю деталей щодо цього, тому що я ніколи цього не робив, але рідний API NT має можливість розблокувати процес (підсистема 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 не вдалися з тієї чи іншої причини.
Було б дуже приємно, якби цей матеріал був би кращим або взагалі був задокументований, особливо пару структур даних і як підключити процес до підсистеми. Хоча fork - це не концепція Win32, я не бачу, що було б погано зробити вилку легшою у застосуванні.
fork
негайно exec
", можливо, CreateProcess є кандидатом. Але fork
без exec
цього часто бажано, і це те, що водіїв люди вимагають реально fork
.
Ну, вікна насправді не мають нічого подібного. Тим більше, що вилка може використовуватися для концептуальної створення потоку або процесу в * nix.
Отже, я б сказав:
CreateProcess()
/CreateProcessEx()
і
CreateThread()
(Я чув, що для додатків С _beginthreadex()
краще).
Люди намагалися реалізувати fork у 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()
було найближчим завданням 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"
Ви не можете мати торт і їсти його теж ... in msvcrt.dll, printf () заснований на API консолі, який сам по собі використовує lpc для зв'язку з підсистемою консолі (csrss.exe). З'єднання з csrss ініціюється при запуску процесу, а це означає, що будь-який процес, який починає його виконання "посередині", цей крок буде пропущений. Якщо у вас немає доступу до вихідного коду операційної системи, немає сенсу намагатися підключитися до csrss вручну. Натомість слід створити власну підсистему і відповідно уникати функцій консолей у додатках, які використовують fork ().
як тільки ви реалізували власну підсистему, не забудьте також дублювати всі батьківські ручки для дочірнього процесу ;-)
"Крім того, ви, ймовірно, не повинні використовувати функції Zw *, якщо ви не перебуваєте в режимі ядра, ви, мабуть, замість цього повинні використовувати функції Nt *."
ZwGetContextThread (NtCurrentThread () та контекст);
Семантика fork () необхідна, коли дитині потрібен доступ до фактичного стану пам'яті батьків з моменту виклику миттєвого вилка (). У мене є програмне забезпечення, яке покладається на неявну копію копіювання пам'яті пам'яті, як називається миттєвий форк (), що робить потоки неможливими у використанні. (Це емулюється на сучасних платформах * nix за допомогою семантики копіювання-запису / оновлення-пам’яті таблиці.)
Найближче, що існує в Windows як систематичний виклик, - CreateProcess. Найкраще, що можна зробити, це те, щоб батьки заморозили всі інші потоки протягом часу, коли він копіює пам'ять у новий простір пам’яті, а потім відтають їх. Ані клас Cygwin frok [sic], ані код Scilab, який опублікував Ерік де Кортіс, не роблять нитки заморожування, що я бачу.
Крім того, ви, ймовірно, не повинні використовувати функції Zw *, якщо ви не знаходитесь в режимі ядра, ви, мабуть, замість цього повинні використовувати функції Nt *. Існує додаткова гілка, яка перевіряє, чи перебуваєте ви в режимі ядра, і, якщо ні, виконує перевірку всіх меж і перевірку параметрів, які завжди роблять Nt *. Таким чином, викликати їх з користувальницького режиму дуже малоефективно.
Ваші найкращі варіанти - CreateProcess () або CreateThread () . Більше інформації про перенесення тут .
Існує не простий спосіб емуляції fork () в Windows.
Я пропоную вам замість цього використовувати нитки.
fork
була саме тим , що зробив CygWin. Але якщо ви коли-небудь читаєте про те, як вони це зробили, ваш "непростий шлях" - це грубе неправильне
Найближчі ви кажете ... Дозвольте мені подумати ... Це повинно бути виделкою () я здогадуюсь :)
Детальніше див., Чи реалізує 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 (). Ось що робить Цигвін. Він створює новий процес, дозволяє ініціалізуватися (включаючи реєстрацію себе за допомогою csrss.exe), потім копіює більшість динамічного стану зі старого процесу та сподівається на найкраще. Мене дивує, що це коли-небудь спрацьовує. Це, безумовно, не працює надійно - навіть якщо це не випадково виходить з ладу через конфлікт адресного простору, будь-яка бібліотека, яку ви використовуєте, може бути мовчки залишена в розбитому стані. Твердження поточної прийнятої відповіді про те, що у Cygwin є "повнофункціональний fork ()" є ... сумнівним.
Підсумок: У середовищі, подібному до 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