Какое самое близкое, что есть в Windows к fork ()? - PullRequest
113 голосов
/ 12 июня 2009

Я думаю, вопрос говорит сам за себя.

Я хочу раскошелиться на Windows. Какая операция наиболее похожа и как ее использовать.

Ответы [ 12 ]

78 голосов
/ 12 июня 2009

Cygwin имеет полнофункциональную функцию fork () в Windows. Таким образом, если использование Cygwin приемлемо для вас, тогда проблема решается, если производительность не является проблемой.

В противном случае вы можете посмотреть, как Cygwin реализует fork (). Из довольно старой архитектуры Cygwin doc :

5,6. Создание процесса Форк-колл в Cygwin особенно интересен потому что он не отображается хорошо на вершине Win32 API. Это делает это очень трудно реализовать правильно. В настоящее время вилка Cygwin является реализация без копирования при записи похоже на то, что присутствовало в начале вкусы UNIX.

Первое, что происходит, когда родительский процесс разветвляет дочерний процесс является то, что родитель инициализирует пробел в таблице процессов Cygwin для ребенок. Затем он создает приостановленный дочерний процесс с использованием Win32 Вызов CreateProcess. Далее родитель процесс вызывает setjmp, чтобы сохранить его контекст и устанавливает указатель на это в область общей памяти Cygwin (общая среди всех задач Cygwin). Затем заполняет в разделах .data и .bss ребенка копируя из собственного адресного пространства в адрес отстраненного ребенка пространство. После адресного пространства ребенка инициализируется, ребенок запускается в то время как родитель ждет мьютекса. Ребенок обнаруживает, что он был раздвоен и longjumps используя сохраненный буфер перехода. Затем ребенок устанавливает мьютекс родитель ждет и блокирует другой мьютекс. Это сигнал для родитель, чтобы скопировать свой стек и кучу в ребенка, после чего это освобождает мьютекс ребенка жду и возвращается с развилки вызов. Наконец, ребенок просыпается от блокировка на последнем мьютексе, воссоздает любые области памяти, переданные ему через общую область и возвращается из сама вилка.

Хотя у нас есть некоторые идеи относительно того, как ускорить реализацию нашего форка уменьшение количества контекста переключается между родителем и ребенком процесс, вилка будет почти наверняка всегда быть неэффективным под Win32. К счастью, в большинстве случаев порождение семейства звонков, предоставляемых Cygwin может быть заменен пара вилка / exec только с небольшим усилия. Эти звонки отображаются сверху Win32 API. В результате они гораздо эффективнее. Изменение программа драйвера компилятора для вызова икру вместо форка было тривиально изменить и увеличить сборник скорость от двадцати до тридцати процентов в наши тесты.

Однако, spawn и exec представляют свои собственный набор трудностей. Потому что там нет никакого способа сделать фактический исполнитель под Win32, Cygwin должен изобрести свой собственный Идентификаторы процесса (PID). В результате, когда процесс выполняет несколько exec звонки, будет несколько окон PID, связанные с одним Cygwin PID. В некоторых случаях заглушки каждого из эти процессы Win32 могут затянуться, в ожидании их exec'd Cygwin процесс для выхода.

Звучит как большая работа, не так ли? И да, это неопрятно.

РЕДАКТИРОВАТЬ: документ устарел, пожалуйста, посмотрите этот отличный ответ для обновления

57 голосов
/ 12 июня 2009

Я, конечно, не знаю подробностей об этом, потому что я никогда не делал этого, но нативный 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, я не понять, что было бы плохо сделать реализацию проще в реализации.

35 голосов
/ 12 июня 2009

Ну, на самом деле окна не имеют ничего подобного. Тем более что fork можно использовать для концептуального создания потока или процесса в * nix.

Итак, я бы сказал:

CreateProcess() / CreateProcessEx()

и

CreateThread() (я слышал, что для приложений на C _beginthreadex() лучше).

15 голосов
/ 01 февраля 2013

Люди пытались реализовать форк на 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;
}
6 голосов
/ 12 июня 2009

До того, как Microsoft представила свою новую опцию "Подсистема Linux для Windows", CreateProcess() была самой близкой Windows * к fork(), но Windows требует, чтобы вы указали исполняемый файл для запуска в этом процессе.

Создание процесса UNIX сильно отличается от Windows. Его вызов fork() практически полностью дублирует текущий процесс, каждый в своем собственном адресном пространстве, и продолжает их запускать отдельно. Хотя сами процессы отличаются, они все еще работают с одной и той же программой. См. здесь для хорошего обзора модели fork/exec.

Возвращаясь к другому пути, эквивалент Windows CreateProcess() представляет собой fork()/exec() пару функций в UNIX.

Если вы портировали программное обеспечение на Windows и не возражали против уровня перевода, Cygwin предоставил необходимую вам возможность, но она была довольно хитрой.

Конечно, с новой Linux-подсистемой , самая близкая вещь, которую Windows имеет к fork(), на самом деле fork(): -)

6 голосов
/ 12 июня 2009

В следующем документе содержится некоторая информация о переносе кода из UNIX в Win32: https://msdn.microsoft.com/en-us/library/y23kc048.aspx

Среди прочего, это указывает на то, что модель процесса в этих двух системах сильно различается, и рекомендует рассмотреть CreateProcess и CreateThread, где требуется поведение, подобное fork ().

3 голосов
/ 06 апреля 2014

"как только вы захотите получить доступ к файлу или printf, то io будут отклонены"

  • Вы не можете съесть свой торт и съесть его тоже ... в msvcrt.dll printf () основан на API консоли, который сам по себе использует lpc для взаимодействия с консольной подсистемой (csrss.exe). Соединение с csrss инициируется при запуске процесса, что означает, что любой процесс, который начинает свое выполнение «в середине», пропустит этот шаг. Если у вас нет доступа к исходному коду операционной системы, нет смысла пытаться подключиться к csrss вручную. Вместо этого вам следует создать собственную подсистему и, соответственно, избегать консольных функций в приложениях, использующих fork ().

  • после того, как вы внедрили свою собственную подсистему, не забудьте также продублировать все дескрипторы родителя для дочернего процесса; -)

"Кроме того, вам, вероятно, не следует использовать функции Zw *, если вы не находитесь в режиме ядра, вам, вероятно, следует использовать вместо этого функции Nt *."

  • Это неверно. При доступе в пользовательском режиме нет абсолютно никакой разницы между Zw *** Nt ***; это просто два разных (ntdll.dll) экспортированных имени, которые ссылаются на один и тот же (относительный) виртуальный адрес.

ZwGetContextThread (NtCurrentThread (), & context);

  • получение контекста текущего (запущенного) потока путем вызова ZwGetContextThread неверно, может привести к сбою и (из-за дополнительного системного вызова) также не является самым быстрым способом выполнения задачи.
3 голосов
/ 06 февраля 2014
Семантика

fork () необходима там, где дочернему элементу необходим доступ к фактическому состоянию памяти родителя, поскольку моментальный вызов fork () вызывается. У меня есть часть программного обеспечения, которая полагается на неявный мьютекс копирования памяти при вызове функции fork (), что делает невозможным использование потоков. (Это эмулируется на современных * nix платформах с помощью семантики копирования при записи / обновления таблицы памяти.)

Самым близким к системному вызову в Windows является CreateProcess. Лучшее, что можно сделать, - это чтобы родитель заморозил все другие потоки во время копирования памяти в область памяти нового процесса, а затем оттаил их. Ни класс Cygwin frok [sic], ни код Scilab, которые разместил Эрик де Кортис, не делает замораживание потоков, которое я вижу.

Кроме того, вам, вероятно, не следует использовать функции Zw *, если вы не находитесь в режиме ядра, вы, вероятно, должны использовать вместо этого функции Nt *. Есть дополнительная ветка, которая проверяет, находитесь ли вы в режиме ядра, и, если нет, выполняет все проверки границ и проверку параметров, которые всегда выполняет Nt *. Таким образом, вызывать их из пользовательского режима гораздо менее эффективно.

2 голосов
/ 21 марта 2011

Самое близкое, что вы говорите ... Дайте мне подумать ... Это должно быть fork (), я думаю:)

Подробнее см. В Interix реализована функция fork ()?

2 голосов
/ 12 июня 2009

В Windows нет простого способа эмулировать fork ().

Я предлагаю вам использовать темы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...