libuv学习笔记(10)

来源:互联网 发布:老外吃中国菜知乎 编辑:程序博客网 时间:2024/03/29 23:12

libuv学习笔记(10)

uv_process_t数据结构与相关函数

数据结构

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;

相关函数

1.生成子进程。导出函数,在uv.h中声明,process.c中定义

初始化,内部函数,在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等内容相关。

0 0
原创粉丝点击