首页 > 系统相关 >libuv进程

libuv进程

时间:2024-05-21 09:31:47浏览次数:16  
标签:stdio uv worker child 进程 libuv loop

1、创建进程

  调用uv_spawn()来启动一个进程:

#include <stdio.h>
#include <uv.h>

uv_loop_t* loop;
uv_process_t child_req;
uv_process_options_t options; //全局变量会自动初始化int类型为0,改为局部变量的话需要将所有没用的域设为0
char worker_path[500] = {0};

void on_exit(uv_process_t* req, int64_t exit_status/*main()返回值*/, int term_signal/*导致进程exit的信号*/) {
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);

    uv_close((uv_handle_t*)req, NULL); //进程关闭后回收资源
}

int main() {  
    loop = uv_default_loop();

    //通过uv_process_options_t设置启动子进程的选项
    options.exit_cb = on_exit; //进程结束后的回调

    char* args[2];
    strcpy_s(worker_path, sizeof(worker_path), "D:\\Study\\C++\\libuv\\test\\x64\\Debug\\worker");
    args[0] = worker_path;
    args[1] = NULL; //遵从惯例:实际传入参数的数目要比需要的参数多一个,因为最后一个参数会被设为NULL
    options.file = args[0]; //要执行的程序
    options.args = args; //参数

    options.env = nullptr; //设置环境变量,格式是以null为结尾的字符串数组,其中每一个字符串的形式都是VAR=VALUE,nullptr表示继承父进程的环境变量
    //options.cwd; //更改目录
    //options.flags; //定义子进程的行为,多个行为的话使用或运算。UV_PROCESS_DETACHED表示启动守护进程,即父进程的退出不会影响子进程(在Windows上使用此标志的话发现启动的子进程不能继承父进程的文件描述符)

    //下面对uv_process_options_t的设置将父进程的标准输出和标准出错分享给了子进程,这样子进程中的标准输出和标准出错就继承了父进程的
    options.stdio_count = 3; //文件描述符的个数
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE; //不打算使用
    child_stdio[1].flags = UV_INHERIT_FD; //使用一个已经存在的文件描述符
    child_stdio[1].data.fd = 1; //标准输出
    child_stdio[2].flags = UV_INHERIT_FD;
    child_stdio[2].data.fd = 2; //标准错误
    options.stdio = child_stdio; //设置子进程的文件描述符

    int r;
    if ((r = uv_spawn(loop, &child_req, &options))) { //启动进程
        fprintf(stderr, "%s\n", uv_strerror(r));
        return 1;
    }

    fprintf(stderr, "Launched process with ID %d\n", child_req.pid);
   
    return uv_run(loop, UV_RUN_DEFAULT);
}

2、继承文件描述符

  一个新产生的进程都有自己的一套文件描述符映射表,例如0,1,2分别对应stdin,stdout和stderr。父进程想要将自己的文件描述符映射表分享给子进程的话,可以像上面那样使用,使用uv_process_options_t::stdio域设置子进程的文件描述符,这样子进程就继承了父进程的stdout和stderr,子进程对标准输出和标准出错写就是对父进程的标准输出和标准出错进行写入。

  也可以通过uv_process_options_t::stdio来设置流的重定向,如下所示我们在TCP服务收到连接并accept后,调用invoke_cgi_script()来开启一个CGI程序,uv_process_options_t::stdio中的相关设置就可以将连接socket流重定向到CGI的stdio中,即在CGI程序中向标准输出写的话就相当于是向socket写(CGI 程序接收信息有三种途径:环境变量、命令行和标准输入):

//连接回调
void on_new_connection(uv_stream_t* server, int status) {
    ......

    if (uv_accept(server, (uv_stream_t*)client) == 0) {
        invoke_cgi_script(client);
    }

    ......
}

//打开CGI程序
void invoke_cgi_script(uv_tcp_t* client) {
    ......
    
    options.stdio_count = 3;
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE;
    child_stdio[1].flags = UV_INHERIT_STREAM;
    child_stdio[1].data.stream = (uv_stream_t*)client;
    child_stdio[2].flags = UV_IGNORE;
    options.stdio = child_stdio;

    options.exit_cb = cleanup_handles;
    options.file = args[0];
    options.args = args;

    // Set this so we can close the socket after the child process exits.
    child_req.data = (void*)client;
    int r = uv_spawn(loop, &child_req, &options);

    ......
}


//CGI程序:
include <stdio.h>
#include <unistd.h>

int main() {
    for (int i = 0; i < 10; i++) {
        printf("tick\n");
        fflush(stdout);
        sleep(1);
    }
    printf("BOOM!\n");

    return 0;
}

 

2、Signal

  可以调用uv_signal_init()和uv_signal_start()来将指定的信号与loop管理,进程收到该消息的话就会触发制定的回调,如下所示,我们向进程发送SIGUSR1的话,进程内所有线程都会收到该消息,signal_handler()方法会被调用两次:

#include <stdio.h>
#include <uv.h>

uv_loop_t* create_loop()
{
    uv_loop_t* loop = (uv_loop_t*)malloc(sizeof(uv_loop_t));
    if (loop) {
        uv_loop_init(loop);
    }
    return loop;
}

void signal_handler(uv_signal_t* handle, int signum)
{
    printf("Signal received: %d\n", signum);
    uv_signal_stop(handle); //停止监听
}

void thread1_worker(void* userp)
{
    uv_loop_t* loop = create_loop();

    uv_signal_t sig;
    uv_signal_init(loop, &sig); //初始化signal handle(uv_signal_t),将其关联到一个loop
    uv_signal_start(&sig, signal_handler, SIGUSR1); //监听指定的信号

    uv_run(loop, UV_RUN_DEFAULT);
}

void thread2_worker(void* userp)
{
    uv_loop_t* loop = create_loop();

    uv_signal_t sig;
    uv_signal_init(loop, &sig);
    uv_signal_start(&sig, signal_handler, SIGUSR1);

    uv_process_kill()
    uv_run(loop, UV_RUN_DEFAULT);
}


int main() {  
    uv_thread_t thread1, thread2;

    uv_thread_create(&thread1, thread1_worker, 0);
    uv_thread_create(&thread2, thread2_worker, 0);

    uv_thread_join(&thread1);
    uv_thread_join(&thread2);
}

  调用uv_kill(int pid, int signum)或者uv_process_kill(uv_process_t*, int signum)可以向进程发送消息,对于libuv启动的进程,可以使用uv_process_kill()来向其发送消息,因为其第一个参数为uv_process_t类型。SIGTERM,SIGINT和SIGKILL都会导致进程的中断,一个使用了多个event-loop的服务器程序,只要给每一个进程添加信号SIGINT监视器,就可以保证程序在中断退出前对数据进行安全的处理。

 3、IPC与multi-echo-server

  父子进程之间可以通过管道来进行通信,uv_pipe_t背后有unix本地socket或者windows实名管道的支持。进程间可以交换文件描述符,目前libuv只支持通过管道传输TCP sockets或者其他的pipes。如下所示,我们实现了一个多进程的服务端以利用多核计算机的优势,每个进程执行一个event-loop,主进程将连接socket分配到工作进程中去处理IO,父进程通过管道进行通信来将连接socket发送给子进程,工作进程的数量与CPU核心数相关:

/***************主进程***************/

#include <stdio.h>
#include <uv.h>

uv_loop_t* loop;

uv_buf_t dummy_buf;
char worker_path[500];

struct child_worker {
    uv_process_t req;
    uv_process_options_t options;
    uv_pipe_t pipe;
} *workers;

int round_robin_counter; //全局变量默认初始化为0
int child_worker_count;

void close_process_handle(uv_process_t* req, int64_t exit_status, int term_signal) {
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
    uv_close((uv_handle_t*)req, NULL);
}

void on_new_connection(uv_stream_t* server, int status) {
    if (status < 0) {
        fprintf(stderr, "New connection error: %s\n", uv_strerror(status));
        return;
    }

    uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
    uv_tcp_init(loop, client);
    if (uv_accept(server, (uv_stream_t*)client) == 0) {
        uv_write_t* write_req = (uv_write_t*)malloc(sizeof(uv_write_t));
        
        dummy_buf = uv_buf_init("a", 1); //uv_write2()需要一个不为空的buffer
        struct child_worker* worker = &workers[round_robin_counter];
        uv_write2(write_req, (uv_stream_t*)&worker->pipe, &dummy_buf, 1, (uv_stream_t*)client, NULL); //调用uv_write2()来向管道发送连接socket
        round_robin_counter = (round_robin_counter + 1) % child_worker_count; //轮流发送
    }
    uv_close((uv_handle_t*)client, NULL);
}

void setup_workers() {
    size_t path_size = 500;
    uv_exepath(worker_path, &path_size); //获得当前目录,格式范例:"D:\Study\C++\libuv\test\x64\Debug\multi-server.exe"
    strcpy_s(worker_path + (strlen(worker_path) - strlen("multi-server.exe")), strlen("multi-server.exe"), "worker.exe"); //"D:\Study\C++\libuv\test\x64\Debug\worker.exe"

    char* args[2];
    args[0] = worker_path;
    args[1] = NULL;

    uv_cpu_info_t* info;
    int cpu_count;
    uv_cpu_info(&info, &cpu_count); //获得CPU数量
    uv_free_cpu_info(info, cpu_count);
    child_worker_count = cpu_count; //工作进程数与CPU数量相同
    workers = (child_worker*)calloc(sizeof(struct child_worker), cpu_count); //工作进程相关信息

    while (cpu_count--) {
        struct child_worker* worker = &workers[cpu_count];
        uv_pipe_init(loop, &worker->pipe, 1/*该参数设为1表示管道将被用来IPC*/); //每个工作进程对应一个管道

        //工作进程继承父进程的管道来进行IPC
        uv_stdio_container_t child_stdio[3];
        child_stdio[0].flags = (uv_stdio_flags)(UV_CREATE_PIPE | UV_READABLE_PIPE); //子进程的标准输入是一个可读的pipe
        child_stdio[0].data.stream = (uv_stream_t*)&worker->pipe; //指定子进程的stdin是上面创建的pipe
        child_stdio[1].flags = UV_IGNORE;
        child_stdio[2].flags = UV_INHERIT_FD;
        child_stdio[2].data.fd = 2;

        worker->options.stdio = child_stdio;
        worker->options.stdio_count = 3;

        worker->options.exit_cb = close_process_handle;
        worker->options.file = args[0];
        worker->options.args = args;

        int r;
        if ((r = uv_spawn(loop, &worker->req, &worker->options))) { //启动工作进程
            fprintf(stderr, "%s\n", uv_strerror(r));
            return;
        }
        fprintf(stderr, "Started worker %d\n", worker->req.pid);
    }
}

int main() {  
    loop = uv_default_loop();

    setup_workers();//创建工作进程

    uv_tcp_t server;
    uv_tcp_init(loop, &server); 

    sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 6000, &addr);
    uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);

    int r = uv_listen((uv_stream_t*)&server, SOMAXCONN, on_new_connection);
    if (r) {
        fprintf(stderr, "Listen error: %s\n", uv_strerror(r));
        return 1;
    }

    return uv_run(loop, UV_RUN_DEFAULT);
}

  父进程将PIPE共享给工作进程并作为其标准输入stdin,所以工作进程从标准输入:

/***************工作进程***************/

#include <stdio.h>
#include <uv.h>

uv_loop_t* loop;
uv_pipe_t queue;

typedef struct {
    uv_write_t req;
    uv_buf_t buf;
} write_req_t;

void free_write_req(uv_write_t* req) {
    write_req_t* wr = (write_req_t*)req;
    free(wr->buf.base);
    free(wr);
}

void on_write(uv_write_t* req, int status) {
    if (status) {
        fprintf(stderr, "Write error %s\n", uv_err_name(status));
    }
    free_write_req(req);
}

void on_read(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) {
    if (nread > 0) {
        write_req_t* req = (write_req_t*)malloc(sizeof(write_req_t));
        req->buf = uv_buf_init(buf->base, nread);
        uv_write((uv_write_t*)req, client, &req->buf, 1, on_write); //向socket原样发回读取到的数据
        return;
    }

    if (nread < 0) {
        if (nread != UV_EOF)
            fprintf(stderr, "Read error %s\n", uv_err_name(nread));
        uv_close((uv_handle_t*)client, NULL);
    }

    free(buf->base);
}

void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
    buf->base = (char*)malloc(suggested_size);
    buf->len = suggested_size;
}

void on_new_connection(uv_stream_t* q, ssize_t nread, const uv_buf_t* buf) {
    if (nread < 0) {
        if (nread != UV_EOF)
            fprintf(stderr, "Read error %s\n", uv_err_name(nread));
        uv_close((uv_handle_t*)q, NULL);
        return;
    }

    uv_pipe_t* pipe = (uv_pipe_t*)q;
    if (!uv_pipe_pending_count(pipe)) { //无可读数据
        fprintf(stderr, "No pending count\n");
        return;
    }

    uv_handle_type pending = uv_pipe_pending_type(pipe); //获得句柄类型
    assert(pending == UV_TCP); //只支持TCP

    uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
    uv_tcp_init(loop, client);
    if (uv_accept(q, (uv_stream_t*)client) == 0) { //这里accept()的功能是从pipe中获取socket
        uv_os_fd_t fd;
        uv_fileno((const uv_handle_t*)client, &fd); //获取平台相关的文件描述符
        fprintf(stderr, "Worker %d: Accepted fd %d\n", getpid(), fd);

        uv_read_start((uv_stream_t*)client, alloc_buffer, on_read); //从socket中读取数据
    }
    else {
        uv_close((uv_handle_t*)client, NULL);
    }
}

int main() {
    loop = uv_default_loop();

    uv_pipe_init(loop, &queue, 1 /*该值为1表示pipe将被用来做IPC*/); //初始化pipe
    uv_pipe_open(&queue, 0/*关联stdin到pipe*/);
    uv_read_start((uv_stream_t*)&queue, alloc_buffer, on_new_connection); //从pipe读取数据

    return uv_run(loop, UV_RUN_DEFAULT);
}

 

标签:stdio,uv,worker,child,进程,libuv,loop
From: https://www.cnblogs.com/milanleon/p/18191878

相关文章

  • mit6.828笔记 - lab4 Part C:抢占式多任务和进程间通信(IPC)
    PartC:抢占式多任务和进程间通信(IPClab4到目前为止,我们能够启动多个CPU,让多个CPU同时处理多个进程。实现了中断处理,并且实现了用户级页面故障机制以及写时复制fork。但是,我们的进程调度不是抢占式的,现在每个进程只有在发生中断的时候,才会被调度(调用shed_yeild),这样就有可能会有......
  • 逆向 | 驱动挂靠进程直接读内存
    逆向|驱动挂靠进程直接读内存参考:https://cloud.tencent.com/developer/article/2358904https://github.com/Whitebird0/driver_read_and_write/blob/main/04-读写内存/ReadMemory.c代码如下:代码不长但是有坑,比如说ExAllocatePool2的参数就跟之前不一样了,这个点我调试了好......
  • Process对象补充,僵尸孤儿进程,守护进程
    ⅠProcess对象的其他方法或属性(了解)【一】查看当前进程的进程ID【1】进程ID指的是某个应用程序运行在系统之上的标志【2】查看所有进程Windows系统CMD命令行tasklist即可查看Mac系统/linux终端运行psaux即可查看【3】如何根据指定进程号查看进程......
  • process.poll() 检查子进程运行状态
    在Python的subprocess模块中,poll()方法是Popen类的实例方法,用于检查一个子进程是否已经结束,如果已经结束,它将返回子进程的退出状态码;如果子进程尚未结束,它将返回None。以下是poll()方法的一些关键点:检查子进程状态:poll()允许你检查一个子进程是否已经完成执行,而无需等待它实际......
  • 多线程和多进程 - 初窥
    一、说明在平常工作中,我们使用top命令查看一台linux服务器的cpu使用情况时,会发现某个进程的cpu使用率会超过100%,这是为什么?二、举例实验环境为CentOS7.6+Python2.71.多线程、多进程在操作系统中的表现形式我们首先看两个例子,test1.py和test2.py,都是执行死循环,test1.py两......
  • ptrace attach 修改进程内存
    #include<stdio.h>#include<stdlib.h>#include<sys/ptrace.h>#include<stdint.h>#include<errno.h>#include<sys/wait.h>intmain(intargc,char*argv[]){ void*addr1; void*addr2; pid_tattack_pid=-1; if(......
  • PHP的多样化执行方式(parallel PHP多线程实现,原生协程实现,多进程实现,ZTS、NTS、TS又是
    进程、线程、协程进程:应用程序的启动实例,运行起的代码叫进程,有独立的内存空间,类比工厂的P个(P=1单进程,P>1多进程)车间。线程:线程是CPU调度的最小单位,是进程内的执行单元,多个线程共享所属进程的资源。类比车间内的T个员工(T=1单线程,T>1多线程)车间。协程:类似线程,协程是用户态(CPU受......
  • KPCR进程概念
    1.KPCR进程概念KPCR介绍KPCR是CPU的控制结构FS段寄存器在R0(FS=0x30)的时候指向KPCR结构FS段寄存器在R3(FS=0x3b)的时候指向当前线程的TEB(线程)线程结构是运行在CPU上面,所以线程结构是放在CPU上的kd>dt_KPCRntdll!_KPCR+0x000NtTib:_NT_TIB+0x0......
  • 进程
    2.进程KPROCESS这里我们使用驱动管理程序kd>dt_kprocess86de0d20ntdll!_KPROCESS+0x000Header:_DISPATCHER_HEADER+0x010ProfileListHead:_LIST_ENTRY[0x86de0d30-0x86de0d30]//基本都是空的+0x018DirectoryTableBase:0xbeda71e0//......
  • 孤儿进程和僵尸进程
     孤儿进程和僵尸进程是操作系统中两种不同的进程状态,它们有着不同的特征和产生原因。1.孤儿进程:-孤儿进程是指父进程退出或意外终止后,子进程仍然在操作系统中运行的情况。由于子进程的父进程已经不存在,操作系统将其托付给init进程(在Unix/Linux系统中是进程号为1的init进程......