Điều gần nhất mà Windows phải rẽ nhánh () là gì?


124

Tôi đoán câu hỏi nói lên tất cả.

Tôi muốn rẽ nhánh trên Windows. Hoạt động tương tự nhất là gì và làm thế nào để tôi sử dụng nó.

Câu trả lời:


86

Cygwin có đầy đủ tính năng fork () trên Windows. Do đó, nếu sử dụng Cygwin là chấp nhận được đối với bạn, thì vấn đề được giải quyết trong trường hợp thực hiện không phải là vấn đề.

Nếu không, bạn có thể xem Cygwin thực hiện fork (). Từ một tài liệu kiến trúc khá cũ của Cygwin :

5.6. Tạo quy trình Cuộc gọi rẽ nhánh trong Cygwin đặc biệt thú vị vì nó không ánh xạ tốt trên API Win32. Điều này làm cho nó rất khó để thực hiện chính xác. Hiện tại, ngã ba Cygwin là một triển khai không sao chép trên ghi tương tự như những gì đã có trong các hương vị ban đầu của UNIX.

Điều đầu tiên xảy ra khi một quá trình cha mẹ tạo ra một tiến trình con là cha mẹ khởi tạo một khoảng trắng trong bảng quy trình Cygwin cho con. Sau đó, nó tạo ra một tiến trình con bị treo bằng cách sử dụng lệnh gọi Win32 CreatProcess. Tiếp theo, tiến trình cha gọi setjmp để lưu bối cảnh của chính nó và đặt một con trỏ tới vùng này trong vùng nhớ chia sẻ Cygwin (được chia sẻ giữa tất cả các tác vụ Cygwin). Sau đó, nó điền vào các phần .data và .bss của trẻ bằng cách sao chép từ không gian địa chỉ của chính nó vào không gian địa chỉ của trẻ bị treo. Sau khi không gian địa chỉ của đứa trẻ được khởi tạo, đứa trẻ được chạy trong khi cha mẹ chờ đợi trên một mutex. Đứa trẻ phát hiện ra nó đã bị rẽ nhánh và nhảy dài bằng cách sử dụng bộ đệm nhảy đã lưu. Đứa trẻ sau đó thiết lập mutex mà cha mẹ đang chờ đợi và chặn trên một mutex khác. Đây là tín hiệu cho cha mẹ sao chép ngăn xếp của nó và heap vào đứa trẻ, sau đó nó giải phóng mutex mà đứa trẻ đang chờ đợi và trở về từ cuộc gọi ngã ba. Cuối cùng, đứa trẻ thức dậy khỏi việc chặn trên mutex cuối cùng, tạo lại bất kỳ khu vực được ánh xạ bộ nhớ nào được chuyển đến nó thông qua khu vực được chia sẻ và trở về từ chính ngã ba.

Mặc dù chúng tôi có một số ý tưởng về cách tăng tốc triển khai ngã ba của chúng tôi bằng cách giảm số lượng chuyển đổi ngữ cảnh giữa quy trình cha mẹ và con, gần như chắc chắn sẽ luôn không hiệu quả trong Win32. May mắn thay, trong hầu hết các trường hợp, các cuộc gọi sinh sản do Cygwin cung cấp có thể được thay thế cho một cặp fork / exec chỉ với một chút nỗ lực. Các cuộc gọi này được ánh xạ rõ ràng trên API Win32. Kết quả là, chúng hiệu quả hơn nhiều. Thay đổi chương trình trình điều khiển của trình biên dịch để gọi sinh sản thay vì ngã ba là một thay đổi nhỏ và tăng tốc độ biên dịch lên hai mươi đến ba mươi phần trăm trong các thử nghiệm của chúng tôi.

Tuy nhiên, sinh sản và thực hiện trình bày các khó khăn riêng của họ. Vì không có cách nào để thực hiện một thực thi thực tế theo Win32, Cygwin phải phát minh ra ID tiến trình (PID) của riêng mình. Kết quả là, khi một quá trình thực hiện nhiều lệnh thực thi, sẽ có nhiều Windows PID được liên kết với một Cygwin PID. Trong một số trường hợp, sơ khai của mỗi quy trình Win32 này có thể kéo dài, chờ quá trình Cygwin thực thi của chúng thoát ra.

Nghe có vẻ nhiều việc phải không? Và vâng, nó là slooooow.

EDIT: tài liệu đã lỗi thời, vui lòng xem câu trả lời tuyệt vời này để cập nhật


11
Đây là một câu trả lời tốt nếu bạn muốn viết một ứng dụng Cygwin trên windows. Nhưng nói chung nó không phải là điều tốt nhất để làm. Về cơ bản, các mô hình xử lý và luồng * nix và Windows khá khác nhau. CreatProcess () và CreateThread () là các API tương đương thường
Foredecker

2
Các nhà phát triển nên nhớ rằng đây là một cơ chế không được hỗ trợ và IIRC thực sự có xu hướng bị phá vỡ bất cứ khi nào một số quy trình khác trên hệ thống đang sử dụng mã tiêm.
Harry Johnston

1
Các liên kết thực hiện khác nhau không còn hiệu lực.
PythonNut

Đã chỉnh sửa để chỉ liên kết câu trả lời khác
Laurynas Biveinis

@Foredecker, Thật ra bạn không nên làm điều đó ngay cả khi bạn đang cố gắng viết một "ứng dụng cygwin". Nó cố gắng bắt chước Unix forknhưng nó hoàn thành việc này bằng một giải pháp rò rỉ và bạn phải chuẩn bị cho những tình huống bất ngờ.
Pacerier

66

Tôi chắc chắn không biết chi tiết về điều này bởi vì tôi chưa bao giờ thực hiện nó, nhưng API NT gốc có khả năng rẽ nhánh một quy trình (hệ thống con POSIX trên Windows cần khả năng này - Tôi không chắc hệ thống con POSIX thậm chí còn được hỗ trợ nữa).

Một tìm kiếm cho ZwCreateProcess () sẽ cung cấp cho bạn một số chi tiết khác - ví dụ như thông tin này từ Maxim Shatskih :

Tham số quan trọng nhất ở đây là PartHandle. Nếu tham số này là NULL, kernel sẽ rẽ nhánh tiến trình hiện tại. Mặt khác, tham số này phải là một điều khiển của đối tượng phần SEC_IMAGE được tạo trên tệp EXE trước khi gọi ZwCreateProcess ().

Mặc dù lưu ý rằng Corinna Vinschen chỉ ra rằng Cygwin được tìm thấy bằng ZwCreateProcess () vẫn không đáng tin cậy :

Iker Arizmendi đã viết:

> 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

Tài liệu này khá cũ, 10 năm hoặc lâu hơn. Mặc dù chúng tôi vẫn đang sử dụng các cuộc gọi Win32 để mô phỏng ngã ba, phương thức này đã thay đổi đáng chú ý. Đặc biệt, chúng tôi không tạo quy trình con ở trạng thái treo nữa, trừ khi các cơ sở dữ liệu cụ thể cần một xử lý đặc biệt ở cha mẹ trước khi chúng được sao chép sang con. Trong bản phát hành 1.5.25 hiện tại, trường hợp duy nhất cho một đứa trẻ bị đình chỉ là các ổ cắm mở trong cha mẹ. Bản phát hành 1.7.0 sắp tới sẽ không đình chỉ chút nào.

Một lý do để không sử dụng ZwCreateProcess là vì bản phát hành 1.5.25 chúng tôi vẫn hỗ trợ người dùng Windows 9x. Tuy nhiên, hai lần thử sử dụng ZwCreateProcess trên các hệ thống dựa trên NT đã thất bại vì lý do này hay lý do khác.

Sẽ thực sự tốt nếu công cụ này sẽ tốt hơn hoặc hoàn toàn được ghi lại, đặc biệt là một vài cơ sở dữ liệu và cách kết nối một quy trình với một hệ thống con. Mặc dù fork không phải là một khái niệm Win32, tôi không thấy rằng nó sẽ là một điều xấu khi làm cho fork dễ thực hiện hơn.


Đây là câu trả lời sai. CreatProcess () và CreateThread () là các tương đương chung.
Foredecker

2
Interix có sẵn trong Windows Vista Enterprise / Ultimate dưới dạng "Hệ thống con cho các ứng dụng UNIX": en.wikipedia.org/wiki/Interix
bk1e

15
@Foredecker - đây có thể là một câu trả lời sai, nhưng CreatProcess () / CreateThread () cũng có thể sai. Nó phụ thuộc vào việc người ta đang tìm kiếm 'cách Win32 để làm mọi thứ' hay 'càng gần với ngữ nghĩa fork () càng tốt'. CreatProcess () hoạt động khác biệt đáng kể so với fork (), đó là lý do cygwin cần thực hiện nhiều công việc để hỗ trợ nó.
Michael Burr

1
@jon: Tôi đã cố gắng sửa chữa các liên kết và sao chép văn bản có liên quan vào câu trả lời (vì vậy các liên kết bị hỏng trong tương lai không phải là vấn đề). Tuy nhiên, câu trả lời này từ rất lâu trước đây mà tôi không chắc chắn 100% câu trích dẫn tôi tìm thấy hôm nay là những gì tôi đã đề cập đến trong năm 2009.
Michael Burr

4
Nếu mọi người muốn " forkngay lập tức exec", thì có lẽ CreatProcess là một ứng cử viên. Nhưng forkkhông execcó thường là mong muốn và đây là những gì trình điều khiển mọi người yêu cầu thực sự fork.
Aaron McDaid

37

Chà, windows không thực sự có thứ gì giống nó cả. Đặc biệt là ngã ba có thể được sử dụng để tạo khái niệm một luồng hoặc một quá trình trong * nix.

Vì vậy, tôi phải nói:

CreateProcess()/CreateProcessEx()

CreateThread()(Tôi đã nghe nói rằng đối với các ứng dụng C, _beginthreadex()tốt hơn).


17

Mọi người đã cố gắng thực hiện fork trên Windows. Đây là thứ gần nhất với nó tôi có thể tìm thấy:

Lấy từ: 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;
}

4
Lưu ý rằng hầu hết việc kiểm tra lỗi bị thiếu - ví dụ: ZwCreateThread trả về giá trị NTSTATUS có thể được kiểm tra bằng cách sử dụng macro SUCCEEDED và FAILED.
BCran

1
Điều gì xảy ra nếu sự forkcố, nó bị sập chương trình, hoặc luồng chỉ bị sập? Nếu nó bị hỏng chương trình, thì đây không thực sự là giả mạo. Chỉ tò mò, bởi vì tôi đang tìm kiếm một giải pháp thực sự, và hy vọng đây có thể là một giải pháp thay thế hợp lý.
leetNightshade

1
Tôi muốn lưu ý rằng có một lỗi trong mã được cung cấp. haveLoadedFiancesForFork là một hàm toàn cục trong tiêu đề, nhưng là một hàm tĩnh trong tệp c. Cả hai nên là toàn cầu. Và hiện đang gặp sự cố ngã ba, thêm kiểm tra lỗi ngay bây giờ.
leetNightshade

Trang web đã chết và tôi không biết làm thế nào tôi có thể biên dịch ví dụ trên hệ thống của riêng tôi. Tôi giả sử tôi đang thiếu một số tiêu đề hoặc bao gồm những cái sai phải không? (ví dụ không hiển thị chúng.)
Paul Stelian

6

Trước khi Microsoft giới thiệu tùy chọn "Hệ thống con Linux cho Windows" mới của họ, CreateProcess()là thứ gần nhất mà Windows phải có fork(), nhưng Windows yêu cầu bạn chỉ định một tệp thực thi để chạy trong quy trình đó.

Quá trình tạo UNIX khá khác với Windows. fork()Cuộc gọi của nó về cơ bản sao chép toàn bộ quá trình hiện tại, gần như toàn bộ trong không gian địa chỉ của riêng họ và tiếp tục chạy chúng một cách riêng biệt. Mặc dù các quy trình là khác nhau, nhưng chúng vẫn đang chạy cùng một chương trình. Xem ở đây để có một cái nhìn tổng quan tốt về fork/execmô hình.

Quay trở lại theo cách khác, tương đương với Windows CreateProcess()fork()/exec() cặp chức năng trong UNIX.

Nếu bạn đang chuyển phần mềm sang Windows và bạn không bận tâm đến một lớp dịch, Cygwin đã cung cấp khả năng mà bạn muốn nhưng nó khá đơn giản.

Tất nhiên, với hệ thống con Linux mới , thứ gần nhất mà Windows phải thực sựfork() là :-) fork()


2
Vì vậy, với WSL, tôi có thể sử dụng forktrong một ứng dụng WSL trung bình không?
Caesar


4

"ngay khi bạn muốn truy cập tập tin hoặc printf thì tôi sẽ bị từ chối"

  • Bạn không thể có bánh của mình và cũng ăn nó ... trong msvcrt.dll, printf () dựa trên API Console, bản thân nó sử dụng lpc để giao tiếp với hệ thống con giao diện điều khiển (csrss.exe). Kết nối với csrss được bắt đầu khi khởi động quá trình, điều đó có nghĩa là bất kỳ quá trình nào bắt đầu thực hiện "ở giữa" sẽ bị bỏ qua bước đó. Trừ khi bạn có quyền truy cập vào mã nguồn của hệ điều hành, thì không có lý do gì để cố gắng kết nối với csrss bằng tay. Thay vào đó, bạn nên tạo hệ thống con của riêng mình và theo đó tránh các chức năng của bàn điều khiển trong các ứng dụng sử dụng fork ().

  • khi bạn đã triển khai hệ thống con của riêng mình, đừng quên sao chép tất cả các thẻ điều khiển của cha mẹ cho quy trình con ;-)

"Ngoài ra, có lẽ bạn không nên sử dụng các chức năng Zw * trừ khi bạn ở chế độ kernel, có lẽ bạn nên sử dụng các chức năng Nt *."

  • Điều này là không đúng. Khi được truy cập trong chế độ người dùng, hoàn toàn không có sự khác biệt giữa Zw *** Nt ***; đây chỉ là hai tên được xuất (ntdll.dll) khác nhau đề cập đến cùng một địa chỉ ảo (tương đối).

ZwGetContextThread (NtCienThread (), & bối cảnh);

  • có được bối cảnh của luồng (đang chạy) hiện tại bằng cách gọi ZwGetContextThread là sai, có khả năng bị sập và (do cuộc gọi hệ thống phụ) cũng không phải là cách nhanh nhất để hoàn thành nhiệm vụ.

2
Điều này dường như không trả lời câu hỏi chính nhưng trả lời một vài câu trả lời khác nhau, và có lẽ sẽ trả lời trực tiếp tốt hơn cho từng câu hỏi cho rõ ràng và để dễ dàng theo dõi những gì đang diễn ra.
Leigh

bạn dường như đang phát hiện ra rằng printf luôn ghi vào bảng điều khiển.
Jasen

3

ngữ nghĩa của fork () là cần thiết khi đứa trẻ cần truy cập vào trạng thái bộ nhớ thực tế của cha mẹ kể từ khi fork () được gọi ngay lập tức. Tôi có một phần mềm dựa trên mutex ẩn của sao chép bộ nhớ kể từ khi fork () được gọi ngay lập tức, khiến cho các luồng không thể sử dụng. (Điều này được mô phỏng trên các nền tảng * nix hiện đại thông qua ngữ nghĩa sao chép trên ghi / cập nhật-bộ nhớ-bảng.)

Gần nhất tồn tại trên Windows dưới dạng một tòa nhà chọc trời là CreatProcess. Điều tốt nhất có thể được thực hiện là cha mẹ đóng băng tất cả các luồng khác trong thời gian nó sao chép bộ nhớ vào không gian bộ nhớ của quy trình mới, sau đó làm tan chúng. Cả lớp Cygwin frok [sic] cũng không phải mã Scilab mà Eric des Courtis đăng tải đều đóng băng, mà tôi có thể thấy.

Ngoài ra, có lẽ bạn không nên sử dụng các chức năng Zw * trừ khi bạn ở chế độ kernel, có lẽ bạn nên sử dụng các chức năng Nt *. Có thêm một nhánh kiểm tra xem bạn có ở chế độ kernel hay không, và nếu không, thực hiện tất cả các giới hạn kiểm tra và xác minh tham số mà Nt * luôn làm. Do đó, việc gọi họ từ chế độ người dùng sẽ rất kém hiệu quả.


Thông tin rất thú vị liên quan đến các biểu tượng xuất khẩu Zw *, cảm ơn bạn.
Andon M. Coleman

Xin lưu ý rằng các hàm Zw * từ không gian người dùng vẫn ánh xạ tới các hàm Nt * trong không gian kernel, để đảm bảo an toàn. Hoặc ít nhất họ nên.
Paul Stelian


2

Không có cách nào dễ dàng để giả lập fork () trên Windows.

Tôi đề nghị bạn sử dụng chủ đề thay thế.


Vâng, trong công bằng, thực hiện forkchính xác những gì Cygwin đã làm. Nhưng, nếu bạn đã từng đọc về cách họ đã làm điều đó, thì "không có cách nào dễ dàng" là một sự hiểu lầm thô thiển :-)
paxdiablo


2

Như các câu trả lời khác đã đề cập, NT (hạt nhân nằm dưới các phiên bản Windows hiện đại) có tương đương với Unix fork (). Đó không phải là vấn đề.

Vấn đề là nhân bản toàn bộ trạng thái của một quá trình nói chung không phải là một việc lành mạnh để làm. Điều này đúng trong thế giới Unix giống như trong Windows, nhưng trong thế giới Unix, fork () được sử dụng mọi lúc và các thư viện được thiết kế để đối phó với nó. Thư viện Windows không.

Ví dụ: DLL hệ thống kernel32.dll và user32.dll duy trì kết nối riêng với máy chủ Win32 xử lý csrss.exe. Sau một ngã ba, có hai quá trình ở đầu máy khách của kết nối đó, điều này sẽ gây ra vấn đề. Quá trình con nên thông báo cho csrss.exe về sự tồn tại của nó và tạo một kết nối mới - nhưng không có giao diện nào để thực hiện điều đó, vì các thư viện này không được thiết kế với fork ().

Vì vậy, bạn có hai lựa chọn. Một là cấm sử dụng kernel32 và user32 và các thư viện khác không được thiết kế để rẽ nhánh - bao gồm mọi thư viện liên kết trực tiếp hoặc gián tiếp với kernel32 hoặc user32, hầu như là tất cả chúng. Điều này có nghĩa là bạn hoàn toàn không thể tương tác với máy tính để bàn Windows và bị mắc kẹt trong thế giới Unixy riêng của bạn. Đây là cách tiếp cận được thực hiện bởi các hệ thống con Unix khác nhau cho NT.

Tùy chọn khác là sử dụng một số loại hack khủng khiếp để cố gắng để các thư viện không biết làm việc với fork (). Đó là những gì Cygwin làm. Nó tạo ra một quy trình mới, cho phép nó khởi tạo (bao gồm đăng ký chính nó với csrss.exe), sau đó sao chép hầu hết trạng thái động từ quy trình cũ và hy vọng điều tốt nhất. Điều làm tôi ngạc nhiên là điều này từng hoạt động. Nó chắc chắn không hoạt động đáng tin cậy - ngay cả khi nó không thất bại ngẫu nhiên do xung đột không gian địa chỉ, bất kỳ thư viện nào bạn đang sử dụng có thể bị bỏ lại trong tình trạng bị hỏng. Yêu cầu của câu trả lời được chấp nhận hiện tại rằng Cygwin có một "ngã ba đầy đủ tính năng ()" là ... đáng ngờ.

Tóm tắt: Trong môi trường giống như Interix, bạn có thể rẽ nhánh bằng cách gọi fork (). Nếu không, hãy cố gắng tự cai sữa khỏi mong muốn làm điều đó. Ngay cả khi bạn đang nhắm mục tiêu Cygwin, đừng sử dụng fork () trừ khi bạn thực sự phải làm.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.