PInvoke

Разработка на C# под linux
 
https://github.com/dotnet/corefx/blob/4221edbaa7f73562765b2732d965084a6684b589/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L226-L239

// Invoke the shim fork/execve routine. It will create pipes for all requested
// redirects, fork a child process, map the pipe ends onto the appropriate stdin/stdout/stderr
// descriptors, and execve to execute the requested process. The shim implementation
// is used to fork/execve as executing managed code in a forked process is not safe (only
// the calling thread will transfer, thread IDs aren't stable across the fork, etc.)
int childPid, stdinFd, stdoutFd, stderrFd;
if (Interop.Sys.ForkAndExecProcess(
filename, argv, envp, cwd,
startInfo.RedirectStandardInput, startInfo.RedirectStandardOutput, startInfo.RedirectStandardError,
out childPid,
out stdinFd, out stdoutFd, out stderrFd) != 0)
{
throw new Win32Exception();
}

что такое shim routine?
shim = тонкая прокладка
Зачем выполнять "maps pipe ends"
Обычно при форке все нити клонируются?
One important thing that differs the child process from the parent is that the child has only one thread.
https://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them
if you choose to use threading, you can fork(2) a new process, with the following hints from the fork(2) manpage:
The child process is created with a single thread — the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.
fork() only copies the calling thread, and any mutexes held in child threads will be forever locked in the forked child.
http://stackoverflow.com/a/6079669/6017919
malloc mutexes might be left locked
It's safe to fork in a multithreaded program as long as you are very careful about the code between fork and exec. You can make only re-enterant (aka asynchronous-safe) system calls in that span. In theory, you are not allowed to malloc or free there, although in practice the default Linux allocator is safe, and Linux libraries came to rely on it End result is that you must use the default allocator.
if the threads in different processes use the same shared memory to comunicate, you have to devise a synchronization mechanism.
Как сделано, что остаётся только одна нить?

https://github.com/terassy/xferoc/blob/3c8519cf21ebe3b3b8930f49240a70ef3c946548/src/Common/src/Interop/Unix/libcoreclr/Interop.ForkAndExecProcess.cs#L35-L39
[DllImport("libcoreclr", SetLastError = true)]
private static extern unsafe int ForkAndExecProcess(
string filename, byte** argv, byte** envp, string cwd,
int redirectStdin, int redirectStdout, int redirectStderr,
out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd);
Что такое libcoreclr ?
это вот этот проект:
https://github.com/dotnet/coreclr

extern "C" {
int ForkAndExecProcess(
const char* filename,
char* const argv[],
char* const envp[],
const char* cwd,
int redirectStdin,
int redirectStdout,
int redirectStderr,
int* childPid,
int* stdinFd,
int* stdoutFd,
int* stderrFd
);
}
Вероятно в старом mono это сделано в другом месте? (может быть в Mono.Posix?)

Что такое cwd?
Рабочая директория
path passed to chdir in child process

Есть ещё какой-то код:
https://github.com/mono/mono/blob/c153626bdf923ef47b48eaf119423b39dfb8184a/mono/io-layer/processes.c#L561-L567

Shared memory alternative to pipes

the share-nothing philosophy of forked processes is good for reducing shared-data bugs but does mean you either need to create pipes to move data between processes or use shared memory (shmget(2) or shm_open(3)).

Как запускать процесс?

Рекомендуют дожидаться завершения процесса таким способом:
process.WaitForExit();
process.Dispose() ;
process = null;
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
То есть, получается, после форка нить продолжает выполнение в родительском процессе и может начать ждать завершения.
Кто-то ещё должен читать/писать в потоки которые соединены с дочерним процессом.

Как закрыть все дескрипторы

В дочернем процессе перед exec надо закрыть все лишние дескрипторы кроме трёх стандартных.
Windows сразу так и делает, а в linux нужно делать это вручную.

http://stackoverflow.com/a/1644590/6017919
You simply need to be careful and set close-on-exec on all file descriptors you care about.
#include <fcntl.h>
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);

#include <unistd.h>
/* please don't do this */
for (i = getdtablesize(); i --> 3;) {
if ((flags = fcntl(i, F_GETFD)) != -1)
fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
}

Как проверить, что дескрипторы освобождаются?

sounds like your process is running out of file descriptors. If you are using stdin/stdout and stderr for each child you will use up 3 per process. If you don't clean up dead processes I can imagine you hitting limits quite easily

надо что-нибудь такое потестить:
1. Просто вызов C-функций из C# кода.
это позволит проверить как работает мэппинг имён dll-ок на имена .so-шек, а так же поиск .so-шек через LD_LIBRARY_PATH

2. Вызов делегатов из C-кода
сначала мы как-то попанаем в управляемый код, потом вызываем функции из C, а они долго работают и всё это время вызывают делегаты из C#
то есть на примере с freeciv - из C# кода запускам главную функцию сервера (надо переоформить код exe-шника в .dll, а плагин реализующий ИИ будет вызывать C# делегаты).
откуда плагин узнает нужные значения указателей на делегаты?
плагин может быть слинкован с mono-рантаймом и вызывать его функции.
Загрузка рантайма произойдёт динамически (то есть выяснится, что рантайм уже загружен)

Т.е. общая схема mono-code.exe вызывает c-code.engine, вызывает c-code-plugin, вызывает mono-engine

Здесь мне не ясно, как библиотека вызовет код mono.

Что-нибудь мешает загрузить mono-code.exe из c-code.engine.exe (пропатчив его)?

http://www.mono-project.com/docs/advanced/embedding/

done by linking `libmono’

в принципе, и из клиента ведь можно создать домен? Потом функция main через PInvoke затянет данные, выставит коллбеки и можно будет реагировать...

Парсинг строки аргументов для execve

https://github.com/dotnet/corefx/blob/4221edbaa7f73562765b2732d965084a6684b589/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L428
Что интересно, этот код написан как попало (а не преобразованием грамматика -> парсер
И он не обрабатывает всякие фичи bash (вроде подставноки переменных окружения и обработки одиночных кавычек)
насчет кавычек наверное можно придумать ситуацию где это сработает неожиданно для пользователя (оставляется читателю в качестве упражнения)

man execve

http://linux.die.net/man/2/execve
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);

AllocNullTerminatedArray

private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arrPtr)

https://github.com/terassy/xferoc/blob/3c8519cf21ebe3b3b8930f49240a70ef3c946548/src/Common/src/Interop/Unix/libcoreclr/Interop.ForkAndExecProcess.cs#L41-L70

Допустим, что память выделяется до форка, потом процесс форкается, а потом освобождать память не надо.
Malloc можно просто обернуть. Почему в других местах реализовано по-другому? (и как именно?)