typedef struct uv_process_s uv_process_t;
struct uv_process_s {
UV_HANDLE_FIELDS//uv_handle_t的成员,此处不再展开
uv_exit_cb exit_cb;//回调函数
int pid;//进程id
//UV_PROCESS_PRIVATE_FIELDS宏展开:
struct uv_process_exit_s {
UV_REQ_FIELDS//uv_req_t的成员,此处不再展开,用来发送调用关闭回调的请求
} exit_req;
BYTE* child_stdio_buffer;//要发送给子进程的文件描述符
int exit_signal;//退出信号
HANDLE wait_handle;//监控子进程是否关闭的句柄,不需要closehandle
HANDLE process_handle;//进程句柄
volatile char exit_cb_pending;//进程关闭监控回调是否调用的标记
};
进程配置结构体:
typedef struct uv_process_options_s {
uv_exit_cb exit_cb; //进程退出后的回调
const char* file;//进程路径 utf8编码
//命令行参数utf8编码。 args[0]应该是进程路径。windows平台下调用CreateProcess函数,并将args参数
//转换为字符串,这可能会导致一些奇怪的问题,参考windows_verbatim_arguments
char** args;
//设置子进程环境变量 utf8编码
char** env;
//工作目录 utf8编码
const char* cwd;
//控制uv_spawn函数的标记量
unsigned int flags;
//`stdio`成员指向一个uv_stdio_container_t数组,uv_stdio_container_t里面存放将会传递给子进
//程的文件描述符。一般来说,stdio[0]指向stdin, fd 1是stdout, fd 2 是 stderr.
//在windows平台下,只有当子进程使用MSVCRT运行时环境时才能支持超过2个的文件描述符
int stdio_count;
uv_stdio_container_t* stdio;
//windows不支持
uv_uid_t uid;
uv_gid_t gid;
} uv_process_options_t;
初始化,内部函数,在uv_spawn中调用
static void uv_process_init(uv_loop_t* loop, uv_process_t* handle) {
uv__handle_init(loop, (uv_handle_t*) handle, UV_PROCESS);
handle->exit_cb = NULL;
handle->pid = 0;
handle->exit_signal = 0;
handle->wait_handle = INVALID_HANDLE_VALUE;
handle->process_handle = INVALID_HANDLE_VALUE;
handle->child_stdio_buffer = NULL;
handle->exit_cb_pending = 0;
uv_req_init(loop, (uv_req_t*)&handle->exit_req);//初始化请求,类型为UV_PROCESS_EXIT
handle->exit_req.type = UV_PROCESS_EXIT;
handle->exit_req.data = handle;
}
生成子进程
int uv_spawn(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options_t* options)
{
int i;
int err = 0;
WCHAR* path = NULL, *alloc_path = NULL;
BOOL result;
WCHAR* application_path = NULL, *application = NULL, *arguments = NULL,
*env = NULL, *cwd = NULL;
STARTUPINFOW startup;
PROCESS_INFORMATION info;
DWORD process_flags;
uv_process_init(loop, process);//初始化uv_process_t
process->exit_cb = options->exit_cb;//进程关闭时的回调函数
if (options->flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) {
return UV_ENOTSUP;//windows不支持
}
if (options->file == NULL || options->args == NULL) {
return UV_EINVAL;//可执行文件路径或者命令行参数为空,直接返回错误
}
assert(options->file != NULL);
assert(!(options->flags & ~(UV_PROCESS_DETACHED |
UV_PROCESS_SETGID |
UV_PROCESS_SETUID |
UV_PROCESS_WINDOWS_HIDE |
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
//将utf8字符串转换为utf16
err = uv_utf8_to_utf16_alloc(options->file, &application);
if (err)
goto done;
//构建命令行参数,UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS表示命令行参数不要用“”
err = make_program_args(
options->args,
options->flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS,
&arguments);
if (err)
goto done;
//构建环境变量参数
if (options->env) {
err = make_program_env(options->env, &env);
if (err)
goto done;
}
//构建工作目录参数
if (options->cwd) {
/* Explicit cwd */
err = uv_utf8_to_utf16_alloc(options->cwd, &cwd);
if (err)
goto done;
} else {//没有工作目录,就用当前工作目录
/* Inherit cwd */
DWORD cwd_len, r;
cwd_len = GetCurrentDirectoryW(0, NULL);
if (!cwd_len) {
err = GetLastError();
goto done;
}
cwd = (WCHAR*) uv__malloc(cwd_len * sizeof(WCHAR));
if (cwd == NULL) {
err = ERROR_OUTOFMEMORY;
goto done;
}
r = GetCurrentDirectoryW(cwd_len, cwd);
if (r == 0 || r >= cwd_len) {
err = GetLastError();
goto done;
}
}
//获取环境变量中的PATH
path = find_path(env);
if (path == NULL) {
DWORD path_len, r;
path_len = GetEnvironmentVariableW(L"PATH", NULL, 0);
if (path_len == 0) {
err = GetLastError();
goto done;
}
alloc_path = (WCHAR*) uv__malloc(path_len * sizeof(WCHAR));
if (alloc_path == NULL) {
err = ERROR_OUTOFMEMORY;
goto done;
}
path = alloc_path;
r = GetEnvironmentVariableW(L"PATH", path, path_len);
if (r == 0 || r >= path_len) {
err = GetLastError();
goto done;
}
}
//通过options中的stdio数组构建process->child_stdio_buffer
//child_stdio_buffer中至少有3个,最多255,如果option中少于3个,那么child_stdio_buffer中对应
//的多余的文件描述符标记为UV_IGNORE(忽略)。根据options->stdio的类型,构建对应的传递给子进程的文
//件描述符
err = uv__stdio_create(loop, options, &process->child_stdio_buffer);
if (err)
goto done;
//获取全路径(用户传入的可能是相对路径)
application_path = search_path(application,
cwd,
path);
if (application_path == NULL) {
/* Not found. */
err = ERROR_FILE_NOT_FOUND;
goto done;
}
//构建startup(STARTUPINFOW)结构体
startup.cb = sizeof(startup);
startup.lpReserved = NULL;
startup.lpDesktop = NULL;
startup.lpTitle = NULL;
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup.cbReserved2 = uv__stdio_size(process->child_stdio_buffer);
startup.lpReserved2 = (BYTE*) process->child_stdio_buffer;
//输入输出从定向(如果有的话)
startup.hStdInput = uv__stdio_handle(process->child_stdio_buffer, 0);
startup.hStdOutput = uv__stdio_handle(process->child_stdio_buffer, 1);
startup.hStdError = uv__stdio_handle(process->child_stdio_buffer, 2);
if (options->flags & UV_PROCESS_WINDOWS_HIDE) {
/* Use SW_HIDE to avoid any potential process window. */
startup.wShowWindow = SW_HIDE;
} else {
startup.wShowWindow = SW_SHOWDEFAULT;
}
process_flags = CREATE_UNICODE_ENVIRONMENT;
if (options->flags & UV_PROCESS_DETACHED) {
process_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
}
//创建进程
if (!CreateProcessW(application_path,
arguments,
NULL,
NULL,
1,
process_flags,
env,
cwd,
&startup,
&info)) {
/* CreateProcessW failed. */
err = GetLastError();
goto done;
}
//获取进程句柄与进程id
process->process_handle = info.hProcess;
process->pid = info.dwProcessId;
//如果子进程是非独立模式,将其分配给全局job对象,这样父进程关闭时也会关闭子进程
if (!(options->flags & UV_PROCESS_DETACHED)) {
//uv__init_global_job_handle函数只会调用一次,创建一个作业uv__init_global_job_handle
uv_once(&uv_global_job_handle_init_guard_, uv__init_global_job_handle);
//将子进程放入作业中
if (!AssignProcessToJobObject(uv_global_job_handle_, info.hProcess)) {
DWORD err = GetLastError();
if (err != ERROR_ACCESS_DENIED)
uv_fatal_error(err, "AssignProcessToJobObject");
}
}
//设置所有的命名管道进程间通信的进程id
for (i = 0; i < options->stdio_count; i++) {
const uv_stdio_container_t* fdopt = &options->stdio[i];
if (fdopt->flags & UV_CREATE_PIPE &&
fdopt->data.stream->type == UV_NAMED_PIPE &&
((uv_pipe_t*) fdopt->data.stream)->ipc) {
((uv_pipe_t*) fdopt->data.stream)->pipe.conn.ipc_pid = info.dwProcessId;
}
}
//开始对于进程句柄的监控,进程关闭后,系统会将其进程句柄设为有信号状态
result = RegisterWaitForSingleObject(&process->wait_handle,
process->process_handle, exit_wait_callback, (void*)process, INFINITE,
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE);
if (!result) {
uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject");
}
//关闭不再使用的进程句柄
CloseHandle(info.hThread);
assert(!err);
//开始uv_process_t
uv__handle_start(process);
//清理资源
done:
uv__free(application);
uv__free(application_path);
uv__free(arguments);
uv__free(cwd);
uv__free(env);
uv__free(alloc_path);
//清理之前生成的传递给子进程的文件描述符
if (process->child_stdio_buffer != NULL) {
/* Clean up child stdio handles. */
uv__stdio_destroy(process->child_stdio_buffer);
process->child_stdio_buffer = NULL;
}
return uv_translate_sys_error(err);
}
监听到子线程关闭之后的回调函数exit_wait_callback,将会在windows的线程池中调用
static void CALLBACK exit_wait_callback(void* data, BOOLEAN didTimeout)
{
uv_process_t* process = (uv_process_t*) data;
uv_loop_t* loop = process->loop;
assert(didTimeout == FALSE);
assert(process);
assert(!process->exit_cb_pending);
process->exit_cb_pending = 1;
//向iocp发送信号
POST_COMPLETION_FOR_REQ(loop, &process->exit_req);
}
uv_run中会在收到线程退出信息后,会调用uv_process_reqs处理请求,最终调用uv_process_proc_exit
void uv_process_proc_exit(uv_loop_t* loop, uv_process_t* handle)
{
int64_t exit_code;
DWORD status;
assert(handle->exit_cb_pending);
handle->exit_cb_pending = 0;
//如果handle是正在关闭状态,直接关闭handle。比如在监控关闭回调调用未完成时调用了uv_close关闭
//handle
/* callback now. */
if (handle->flags & UV__HANDLE_CLOSING) {
uv_want_endgame(loop, (uv_handle_t*) handle);
return;
}
//去掉监视
if (handle->wait_handle != INVALID_HANDLE_VALUE) {
UnregisterWait(handle->wait_handle);
handle->wait_handle = INVALID_HANDLE_VALUE;
}
//停止handle
uv__handle_stop(handle);
if (GetExitCodeProcess(handle->process_handle, &status)) {
exit_code = status;
} else {
/* Unable to to obtain the exit code. This should never happen. */
exit_code = uv_translate_sys_error(GetLastError());
}
//调用回调
if (handle->exit_cb) {
handle->exit_cb(handle, exit_code, handle->exit_signal);
}
}
通过uv_close关闭uv_process_t,最终会调用uv_process_close
void uv_process_close(uv_loop_t* loop, uv_process_t* handle)
{
uv__handle_closing(handle);//状态变为UV_HANDLE_CLOSING
if (handle->wait_handle != INVALID_HANDLE_VALUE) {
//注销监视
BOOL r = UnregisterWaitEx(handle->wait_handle, INVALID_HANDLE_VALUE);
if (!r) {
/* This should never happen, and if it happens, we can't recover... */
uv_fatal_error(GetLastError(), "UnregisterWaitEx");
}
handle->wait_handle = INVALID_HANDLE_VALUE;
}
//监控进程关闭的回调函数exit_wait_callback还未调用,直接关闭handle,否则需要等到loop处理关闭回调
//请求的时候再关闭handle
if (!handle->exit_cb_pending) {
uv_want_endgame(loop, (uv_handle_t*)handle);
}
}
使用libuv创建子进程,可以设定输出、输入重定向,或使用命名管道来进行进程间通信,这部分内容与之后的uv_pipe_t以及uv_stream_t等内容相关。