libuv学习笔记(10)

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值