📄 fork.cc
字号:
/* fork.cc Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.This file is part of Cygwin.This software is a copyrighted work licensed under the terms of theCygwin license. Please consult the file "CYGWIN_LICENSE" fordetails. */#include "winsup.h"#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <stdarg.h>#include <errno.h>#include "security.h"#include "fhandler.h"#include "path.h"#include "dtable.h"#include "cygerrno.h"#include "sigproc.h"#include "pinfo.h"#include "cygheap.h"#include "child_info.h"#define NEED_VFORK#include "perthread.h"#include "perprocess.h"#include "dll_init.h"#include "sync.h"#include "shared_info.h"#include "cygmalloc.h"#include "cygthread.h"#ifdef DEBUGGINGstatic int npid;static int npid_max;static pid_t fork_pids[100];#endif/* Timeout to wait for child to start, parent to init child, etc. *//* FIXME: Once things stabilize, bump up to a few minutes. */#define FORK_WAIT_TIMEOUT (300 * 1000) /* 300 seconds */#define dll_data_start &_data_start__#define dll_data_end &_data_end__#define dll_bss_start &_bss_start__#define dll_bss_end &_bss_end__voidper_thread::set (void *s){ if (s == PER_THREAD_FORK_CLEAR) { tls = TlsAlloc (); s = NULL; } TlsSetValue (get_tls (), s);}static voidstack_base (child_info_fork &ch){ MEMORY_BASIC_INFORMATION m; memset (&m, 0, sizeof m); if (!VirtualQuery ((LPCVOID) &m, &m, sizeof m)) system_printf ("couldn't get memory info, %E"); ch.stacktop = m.AllocationBase; ch.stackbottom = (LPBYTE) m.BaseAddress + m.RegionSize; ch.stacksize = (DWORD) ch.stackbottom - (DWORD) &m; debug_printf ("bottom %p, top %p, stack %p, size %d, reserve %d", ch.stackbottom, ch.stacktop, &m, ch.stacksize, (DWORD) ch.stackbottom - (DWORD) ch.stacktop);}/* Copy memory from parent to child. The result is a boolean indicating success. */static intfork_copy (PROCESS_INFORMATION &pi, const char *what, ...){ va_list args; char *low; int pass = 0; va_start (args, what); while ((low = va_arg (args, char *))) { char *high = va_arg (args, char *); DWORD todo = wincap.chunksize () ?: high - low; char *here; for (here = low; here < high; here += todo) { DWORD done = 0; if (here + todo > high) todo = high - here; int res = WriteProcessMemory (pi.hProcess, here, here, todo, &done); debug_printf ("child handle %p, low %p, high %p, res %d", pi.hProcess, low, high, res); if (!res || todo != done) { if (!res) __seterrno (); /* If this happens then there is a bug in our fork implementation somewhere. */ system_printf ("%s pass %d failed, %p..%p, done %d, windows pid %u, %E", what, pass, low, high, done, pi.dwProcessId); goto err; } } pass++; } debug_printf ("done"); return 1; err: TerminateProcess (pi.hProcess, 1); set_errno (EAGAIN); return 0;}/* Wait for child to finish what it's doing and signal us. We don't want to wait forever here.If there's a problem somewhere it'll hang the entire system (since all forks are mutex'd). If we time out, set errno = EAGAIN and hope the app tries again. */static intsync_with_child (PROCESS_INFORMATION &pi, HANDLE subproc_ready, BOOL hang_child, const char *s){ /* We also add the child process handle to the wait. If the child fails to initialize (eg. because of a missing dll). Then this handle will become signalled. This stops a *looong* timeout wait. */ HANDLE w4[2]; debug_printf ("waiting for child. reason: %s, hang_child %d", s, hang_child); w4[1] = pi.hProcess; w4[0] = subproc_ready; DWORD rc = WaitForMultipleObjects (2, w4, FALSE, FORK_WAIT_TIMEOUT); if (rc == WAIT_OBJECT_0 || WaitForSingleObject (subproc_ready, 0) == WAIT_OBJECT_0) /* That's ok */; else if (rc == WAIT_FAILED || rc == WAIT_TIMEOUT) { if (rc != WAIT_FAILED) system_printf ("WaitForMultipleObjects timed out"); else system_printf ("WaitForMultipleObjects failed, %E"); set_errno (EAGAIN); syscall_printf ("-1 = fork(), WaitForMultipleObjects failed"); TerminateProcess (pi.hProcess, 1); return 0; } else { /* Child died. Clean up and exit. */ DWORD errcode; GetExitCodeProcess (pi.hProcess, &errcode); /* Fix me. This is not enough. The fork should not be considered * to have failed if the process was essentially killed by a signal. */ if (errcode != STATUS_CONTROL_C_EXIT) { system_printf ("child %d(%p) died before initialization with status code %p", pi.dwProcessId, pi.hProcess, errcode); system_printf ("*** child state %s", s);#ifdef DEBUGGING abort ();#endif } set_errno (EAGAIN); syscall_printf ("Child died before subproc_ready signalled"); return 0; } debug_printf ("child signalled me"); return 1;}static intresume_child (PROCESS_INFORMATION &pi, HANDLE forker_finished){ SetEvent (forker_finished); debug_printf ("signalled child"); return 1;}/* Notify parent that it is time for the next step. Note that this has to be a macro since the parent may be messing with our stack. */static void __stdcallsync_with_parent (const char *s, bool hang_self){ debug_printf ("signalling parent: %s", s); /* Tell our parent we're waiting. */ if (!SetEvent (fork_info->subproc_ready)) api_fatal ("fork child - SetEvent for %s failed, %E", s); if (hang_self) { HANDLE h = fork_info->forker_finished; /* Wait for the parent to fill in our stack and heap. Don't wait forever here. If our parent dies we don't want to clog the system. If the wait fails, we really can't continue so exit. */ DWORD psync_rc = WaitForSingleObject (h, FORK_WAIT_TIMEOUT); debug_printf ("awake"); switch (psync_rc) { case WAIT_TIMEOUT: api_fatal ("WFSO timed out for %s", s); break; case WAIT_FAILED: if (GetLastError () == ERROR_INVALID_HANDLE && WaitForSingleObject (fork_info->forker_finished, 1) != WAIT_FAILED) break; api_fatal ("WFSO failed for %s, fork_finished %p, %E", s, fork_info->forker_finished); break; default: debug_printf ("no problems"); break; } }}static int __stdcallfork_child (HANDLE& hParent, dll *&first_dll, bool& load_dlls){ debug_printf ("child is running. pid %d, ppid %d, stack here %p", myself->pid, myself->ppid, __builtin_frame_address (0)); /* Restore the inheritance state as in parent Don't call setuid here! The flags are already set. */ if (cygheap->user.impersonated) { debug_printf ("Impersonation of child, token: %d", cygheap->user.token); if (cygheap->user.token == INVALID_HANDLE_VALUE) RevertToSelf (); // probably not needed else if (!ImpersonateLoggedOnUser (cygheap->user.token)) system_printf ("Impersonate for forked child failed: %E"); } sync_with_parent ("after longjmp.", TRUE); sigproc_printf ("hParent %p, child 1 first_dll %p, load_dlls %d", hParent, first_dll, load_dlls);#ifdef DEBUGGING char c; if (GetEnvironmentVariable ("FORKDEBUG", &c, 1)) try_to_debug (); char buf[80]; /* This is useful for debugging fork problems. Use gdb to attach to the pid reported here. */ if (GetEnvironmentVariable ("CYGWIN_FORK_SLEEP", buf, sizeof (buf))) { small_printf ("Sleeping %d after fork, pid %u\n", atoi (buf), GetCurrentProcessId ()); Sleep (atoi (buf)); }#endif /* If we've played with the stack, stacksize != 0. That means that fork() was invoked from other than the main thread. Make sure that when the "main" thread exits it calls do_exit, like a normal process. Exit with a status code of 0. */ if (fork_info->stacksize) { ((DWORD *)fork_info->stackbottom)[-17] = (DWORD)do_exit; ((DWORD *)fork_info->stackbottom)[-15] = (DWORD)0; } set_file_api_mode (current_codepage); MALLOC_CHECK; if (fixup_mmaps_after_fork (hParent)) api_fatal ("recreate_mmaps_after_fork_failed"); MALLOC_CHECK; /* If we haven't dynamically loaded any dlls, just signal the parent. Otherwise, load all the dlls, tell the parent that we're done, and wait for the parent to fill in the. loaded dlls' data/bss. */ if (!load_dlls) { cygheap->fdtab.fixup_after_fork (hParent); ProtectHandleINH (hParent); sync_with_parent ("performed fork fixup.", FALSE); } else { dlls.load_after_fork (hParent, first_dll); cygheap->fdtab.fixup_after_fork (hParent); ProtectHandleINH (hParent); sync_with_parent ("loaded dlls", TRUE); } ForceCloseHandle (hParent); (void) ForceCloseHandle1 (fork_info->subproc_ready, subproc_ready); (void) ForceCloseHandle1 (fork_info->forker_finished, forker_finished); if (fixup_shms_after_fork ()) api_fatal ("recreate_shm areas after fork failed"); pinfo_fixup_after_fork (); signal_fixup_after_fork (); /* Set thread local stuff to zero. Under Windows 95/98 this is sometimes non-zero, for some reason. FIXME: There is a memory leak here after a fork. */ for (per_thread **t = threadstuff; *t; t++) if ((*t)->clear_on_fork ()) (*t)->set (); pthread::atforkchild (); wait_for_sigthread (); cygbench ("fork-child"); return 0;}static voidslow_pid_reuse (HANDLE h){ static NO_COPY HANDLE last_fork_procs[8] = {0}; static NO_COPY unsigned nfork_procs = 0; if (nfork_procs >= (sizeof (last_fork_procs) / sizeof (last_fork_procs [0]))) nfork_procs = 0; /* Keep a list of handles to forked processes sitting around to prevent Windows from reusing the same pid n times in a row. Having the same pids close in succesion confuses bash. Keeping a handle open will stop windows from reusing the same pid. */ if (last_fork_procs[nfork_procs]) ForceCloseHandle1 (last_fork_procs[nfork_procs], fork_stupidity); if (DuplicateHandle (hMainProc, h, hMainProc, &last_fork_procs[nfork_procs], 0, FALSE, DUPLICATE_SAME_ACCESS)) ProtectHandle1 (last_fork_procs[nfork_procs], fork_stupidity); else { last_fork_procs[nfork_procs] = NULL; system_printf ("couldn't create last_fork_proc, %E"); } nfork_procs++;}static int __stdcallfork_parent (HANDLE& hParent, dll *&first_dll, bool& load_dlls, void *stack_here, child_info_fork &ch){ HANDLE subproc_ready, forker_finished; DWORD rc; PROCESS_INFORMATION pi = {0, NULL, 0, 0}; pthread::atforkprepare (); subproc_init (); int c_flags = GetPriorityClass (hMainProc) /*| CREATE_NEW_PROCESS_GROUP*/; STARTUPINFO si = {0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}; /* If we don't have a console, then don't create a console for the child either. */ HANDLE console_handle = CreateFile ("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, &sec_none_nih, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (console_handle != INVALID_HANDLE_VALUE)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -