nng协议nni_posix_pollq_create(nni_posix_pollq *pq) 初始化

1.基本概念

        epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,适用于监视多个文件描述符,以便在这些文件描述符上发生 I/O 事件时能够立即得到通知。epoll 的工作方式比传统的 select 和 poll 更加高效,尤其适合处理大量并发连接的场景。

        epoll实例是由epoll_create 或 epoll_create1 创建的,它本质上是一个内核对象,用来跟踪和管理一组文件描述符的事件。创建epoll 实例时,会返回一个文件描述符,这个文件描述符用与后续的epoll操作。

2. 文件描述符(file descriptors)

在epoll中,文件描述符可以是任何支持非阻塞I/O的文件描述符,如套接字、管道、文件等。

可以通过  epoll_ctl  函数将这些文件描述符添加到epoll实例中,以监视它们上的事件。

3.事件:

        epoll 支持监视多种事件,如 读事件(EPOLLIN)、写事件(EPOLLOUT)、错误事件等。

事件通过 structure epoll_event 结构体来描述。

4. epoll 的基本使用流程

        4.1 创建epoll 实例

使用epoll_create  或 epoll_create1 创建一个epoll实例。返回的文件描述符用于标识这个实例。

int  epfd = epoll_create1(EPOLL_CLOEXEC);
if(epfd < 0){
    perror("epoll_create1");
    return -1;
}

        4.2 添加、修改或删除文件描述符:

使用epoll_ctl 函数添加(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)或 删除(EPOLL_CTL_DEL)要监视的文件描述符。

struct epoll_event ev;
ev.events = EPOLLIN;  // 监视读事件
ev.data.fd = fd;      // 需要监视的文件描述符

if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0){
    perror("epoll_ctl");
    return -1;
}

        4.3 等待事件

使用epoll_wait 来等待文件描述符上的事件。该函数会阻塞直到一个或多个事件发生,或者超时。

struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // epoll 等待超时,也就是没有等到集合中文件描述符上发生指定的事件,将返回 0;
if(nfds < 0){
    perror("epoll_wait");
    return -1;
}

for(int i=0; i < nfds; ++i){
    if(events[i].events & EPOLLIN){
        //处理读事件
    }

    if(events[i].events & EPOLLOUT){
        //处理写事件
    }

    //  处理其他事件,或许有错误发生
}

5. 代码分析【正文】

//初始化函数定义
int 
nni_init(void)
{
    int rv;
    if((rv = nni_plat_init(nni_init_halper)) != 0){
        nng_log_err("NNG-INIT", "NNG library initialization failed: %s", nng_strreror(rv));
    } 
    return rv;
}
//本文说的函数在nni_plat_init函数调用:
//Line 396
if((rv = nni_posix_pollq_sysinit()) != 0){
    //...
}

在nni_plat_init中,设置了   nni_cvattr 的时钟属性 ,参考这里(了解即可):nng协议分析记录--pthread_condattr_setclock 设置时钟属性-优快云博客    

5.1 函数说明

static int nni_posix_pollq_create(nni_posix_pollq *pq)

        这段代码的功能是创建一个文件描述符,并将其添加到 epoll 实例中,以便在程序需要自我唤醒时,能够使用该文件描述符触发  epoll  事件。 主要步骤包括创建事件文件描述符,设置文件描述符属性、 配置  epoll 事件并将其添加到  epoll  实例中。详细信息如下:

if((pq->epfd = epoll_create1(EPOLL_CLOEXEC) < 0){
    return (nni_plat_erron(errno))
}

这段代码的作用是创建一个epoll  实例并将文件描述符存储在 pq 结构体的 epfd  中。 参数EPOLL_CLOEXEC 的表示在执行exec系列函数时,自动关闭这个文件描述符。(请自行参考exec系列函数,exit...)。

        5.2  nni_posix_pollq_add_eventfd(pq)

        在nni_posix_pollq_create(nni_posix_pollq  *pq)中调用了 nni_posix_pollq_add_eventfd(pq)这个函数,函数操作如下。

        1.  定义epoll_event   ev

         2. 创建事件文件描述符, 使用eventfd 函数创建一个文件描述符  fd, 初始值为 0 , 标志为EFD_NONBLOCK (非阻塞)

if((fd = eventfd(0, EFD_NONBLOCK) < 0 ){
    return (nni_plat_errno(errno));
}

        3. 设置文件描述符属性

(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
(void) fcntl(fd, F_SETFL, O_NONBLOCK);

使用fcntl 设置文件描述符的标志,FD_CLOEXEC,使其在执行exec()系列函数后关闭。

再次使用fcntl 函数设置文件描述符为非阻塞模式。

        4. 设置 epoll 事件

ev.events = EPOLLIN;
ev.data.ptr = 0;

设置 epoll 事件唯   EPOLLIN,表示监听读事件。 将ev.data.ptr 设置为 0, 这个指针可以用来存储用户数据,此处设置为 0. 

        5. 向  epoll  实例中添加事件。

if(epoll_ctl(pq->epfd, EPOLL_CTL_ADD, fd, &ev) != 0){
    (void) close(fd);
    return (nni_plat_errno(errno);
}

         6. 保存文件描述符并返回成功

       将文件描述符  fd  保存到 pq 结构体的 evfd 字段中。并返回成功。

pq->evfd  = fd;
return (0);

总结一下就是:这段代码的功能是创建一个事件文件描述符,并将其添加到 epoll 实例中,以便在程序需要自我唤醒时能够使用该文件描述符触发 epoll 事件。主要步骤包括创建事件文件描述符、设置文件描述符属性、配置 epoll 事件并将其添加到 epoll 实例中。

6.  nni_thr_init(&pq->thr, nni_posix_poll_thr, pq) 初始化

这里先贴一下nni_posix_pollq结构体的定义。

struct nni_posix_pollq{
    nni_mtx  mtx;
    int      epfd;
    int      evfd;
    bool     close;
    nni_thr  thr;
    nni_list reqpq;
}
//成员变量nni_thr thr的定义如下:

struct nni_thr{
    nni_plat_thr   thr;
    nni_plat_mtx   mtx;
    nni_plat_cv    cv;
    nni_thr_func   fn;
    void *         arg;
    int            start;
    int            stop;
    int            done;
    int            init;
}

//这里又嵌套了一层nng_plat_thr
strcut nni_plat_thr{
    pthread_t  tid;
    void(*func)(void *);
    void *arg;
}

nni_thr_init 函数的参数是全局变量 nni_posix_global_pollq->thr  对应上面的结构体定义就是thr  这个成员变量。这个函数的作用就是对pq结构体成员thr进行初始化。

第一层 nni_thr_init 这里就是对pq->thr进行初始化,首先对基本成员赋值。

        第一个参数就是pq->thr

        第二个参数是函数指针(函数 nni_posix_poll_thr),

        第三个参数是pq.

thr->done  = 0;
thr->start = 0;
thr->stop  = 0;
thr->fn    = fn; 
thr->arg   = arg;

        第一层初始化后的值如下:

//nni_thr_init(&pq->thr, nni_posix_poll_thr, pq) 函数
pq->thr->done    = 0;
pq->thr->start   = 0;
pq->thr->stop    = 0;
pq->thr->func    =  nni_posix_poll_thr;  //对应thr->func = fn
pq->thr->arg     = pq; 

这里如果fn 为空则会返回 ,至此pq->thr  初始化完成,下面会pq->thr->thr进行初始化。

第二层初始化,在nni_thr_init中,对nni_thr 的成员变量  nni_plat_thr再次进行初始化。也就是对 pq->thr->thr 进行的初始化操作,执行函数 nni_plat_thr_init:  

        传入的三个参数:

        1. thr->thr  也就是pq->thr->thr  待初始化对象

        2. 函数指针 nni_thr_wrap

        3. thr 也就是  pq->thr

//nni_plat_thr_init(&thr->thr, nni_thr_wrap, thr)
//这里的赋值实际关系是:
thr->func = fn;    //---->  pq-thr-thr-func = nni_thr_wrap;
thr->arg  = arg;   //---->  pq-thr-thr-arg  = pq-thr;

总结一下,如果仅仅看赋值关系,在执行完 nni_plat_htr_init之后,如果函数指针参数均不为空,我们可以得到的pq->thr的成员变量值如下:

pq->thr->done  = 0;

pq->thr->start   = 0;

pq->thr->stop   = 0;

pq->thr->fn       = nni_posix_poll_thr;

pq->thr->arg     = pq;

pq->thr->thr->func = nni_thr_wrap;

pq->thr->thr->arg   = pq->thr;

        下面创建线程,线程属性是nni_init函数在前面设置的线程属性。线程ID  是pq->thr->thr->tid ,需要执行的线程函数是 nni_plat_thr_main, 函数参数是  pq->thr->thr .

在  nni_plat_thr_main中, 执行了 nni_plat_thr结构体的成员函数 thr->func(thr->arg) 

实际执行的就是  pq->thr->thr->fn 也就是  nni_thr_wrap  

参数是thr->arg   也就是 nni_plat_thr->arg 也就是 pq->thr。

这里贴出nni_thr_wrap 函数的定义:

//这里执行的函数是pq->thr->thr-fn() 
//参数是pq->thr, 这里可能有点迷惑,但是从下面函数的变量类型也可以推断出来
static void
nni_thr_wrap(void *arg)
{
	nni_thr *thr = arg;
	int      start;

	nni_plat_mtx_lock(&thr->mtx);
	while (((start = thr->start) == 0) && (thr->stop == 0)) {
		nni_plat_cv_wait(&thr->cv);
	}
	nni_plat_mtx_unlock(&thr->mtx);
	if ((start) && (thr->fn != NULL)) {
		thr->fn(thr->arg);
	}
	nni_plat_mtx_lock(&thr->mtx);
	thr->done = 1;
	nni_plat_cv_wake(&thr->cv);
	nni_plat_mtx_unlock(&thr->mtx);
}

从这里看,由于前面初始化的值thr->start == 0, thr->stop == 0 线程将等待在thr->cv这个条件变量上,除非while的条件不满足。

while (((start = thr->start) == 0) && (thr->stop == 0)) {
        nni_plat_cv_wait(&thr->cv);

}

7. 设置 pq->thr名称 

        略

8. 执行nni_thr_run函数

nni_thr_run函数里,设置thr->start = 1, 同时会唤醒等待在 pq->thr->cv 这个条件变量上的任务,条件满足,开始执行pq->thr->fn()  函数,也就是nni_posix_poll_thr,而参数就是  pq.系统就开始轮询在pq->epfd实例上的可读事件。初始化结束! 

这里也贴出  nni_posix_poll_thr 的函数定义:

//这里在轮询的就是最初创建的epoll实例 !!!
static void
nni_posix_poll_thr(void *arg)
{
	nni_posix_pollq *  pq = arg;
	struct epoll_event events[NNI_MAX_EPOLL_EVENTS];

	for (;;) {
		int  n;
		bool reap = false;

		n = epoll_wait(pq->epfd, events, NNI_MAX_EPOLL_EVENTS, -1);
		if ((n < 0) && (errno == EBADF)) {
			// Epoll fd closed, bail.
			return;
		}

		// dispatch events
		for (int i = 0; i < n; ++i) {
			const struct epoll_event *ev;

			ev = &events[i];
			// If the waker pipe was signaled, read from it.
			if ((ev->data.ptr == NULL) &&
			    (ev->events & (unsigned) POLLIN)) {
				uint64_t clear;
				if (read(pq->evfd, &clear, sizeof(clear)) !=
				    sizeof(clear)) {
					nni_panic("read from evfd incorrect!");
				}
				reap = true;
			} else {
				nni_posix_pfd *  pfd = ev->data.ptr;
				nni_posix_pfd_cb cb;
				void *           cbarg;
				unsigned         mask;

				mask = ev->events &
				    ((unsigned) EPOLLIN | (unsigned) EPOLLOUT |
				        (unsigned) EPOLLERR);

				nni_mtx_lock(&pfd->mtx);
				pfd->events &= ~mask;
				cb    = pfd->cb;
				cbarg = pfd->arg;
				nni_mtx_unlock(&pfd->mtx);

				// Execute the callback with lock released
				if (cb != NULL) {
					cb(pfd, mask, cbarg);
				}
			}
		}

		if (reap) {
			nni_posix_pollq_reap(pq);
			nni_mtx_lock(&pq->mtx);
			if (pq->close) {
				nni_mtx_unlock(&pq->mtx);
				return;
			}
			nni_mtx_unlock(&pq->mtx);
		}
	}
}

这里省略了部分实现细节,比如线程属性,信号的处理以及操作的加解锁过程。

pthread_cond_wait 条件变量等相关的用法,请参考这里:pthread_cond_broadcast和pthread_cond_wait使用_cond broadcast-优快云博客

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值