F-Stack 之 kqueue 封装为 epoll 介绍

F-Stack是一个全用户态的高性能的网络接入开发包,基于DPDK、FreeBSD协议栈、微线程接口等,适用于各种需要网络接入的业务,用户只需要关注业务逻辑,简单的接入F-Stack即可实现高性能的网络服务器。

F-Stack中使用的FreeBSD协议栈的高性能异步事件通知的API是kqueue,而Linux系统上则是我们熟悉的epoll,大量的Linux网络server都是基于epoll事件通知机制,为降低已有服务器接入F-Stack的修改难度,F-Stack协议栈实现了把kqueue封装为epoll接口,提供的API如下::

  • int ff_epoll_create(int size): 创建epoll fd,底层实际调用freebsd协议栈的kern_kqueue()接口
  • int ff_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event): epoll事件操作函数,添加关心的fd和事件到epoll fd中,底层实际调用了freebsd协议栈的kern_kevent()接口
  • int ff_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout):epoll事件操作通知函数,底层实际调用了freebsd协议栈的kern_kevent()接口
  • int ff_epoll_close(int epfd):epoll fd的关闭函数,底层调用kern_close()

比较核心的ff_epoll_ctl()ff_epoll_wait()代码实现如下:

int 
ff_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
	if (!event && op != EPOLL_CTL_DEL) {
        ff_os_errno(ff_EINVAL);
		return -1;
	}

	struct kevent kev[3];
	if (op == EPOLL_CTL_ADD){
		EV_SET(&kev[0], fd, EVFILT_READ,
			EV_ADD | (event->events & EPOLLIN ? 0 : EV_DISABLE), 0, 0, NULL);
		EV_SET(&kev[1], fd, EVFILT_WRITE,
			EV_ADD | (event->events & EPOLLOUT ? 0 : EV_DISABLE), 0, 0, NULL);
		EV_SET(&kev[2], fd, EVFILT_USER, EV_ADD,
			    event->events & EPOLLRDHUP ? 1 : 0, 0, NULL);		
	} else if (op == EPOLL_CTL_DEL) {
		EV_SET(&kev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
		EV_SET(&kev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
		EV_SET(&kev[2], fd, EVFILT_USER, EV_DELETE, 0, 0, NULL);
	} else if (op == EPOLL_CTL_MOD) {
		EV_SET(&kev[0], fd, EVFILT_READ,
		    event->events & EPOLLIN ? EV_ENABLE : EV_DISABLE, 0, 0, NULL);
		EV_SET(&kev[1], fd, EVFILT_WRITE,
		    event->events & EPOLLOUT ? EV_ENABLE : EV_DISABLE, 0, 0, NULL);
		EV_SET(&kev[2], fd, EVFILT_USER, 0,
		    NOTE_FFCOPY | (event->events & EPOLLRDHUP ? 1 : 0), 0, NULL);		
	} else {
		ff_os_errno(ff_EINVAL);
		return -1;
	}

	return ff_kevent(epfd, kev, 3, NULL, 0, NULL);
}

ff_epoll_ctl()核心是把Linux Epoll的事件EPOLLIN、EPOLLOUT(其他的暂未支持)转成成Freebsd的事件标EVFILT_READ、EVFILT_WRITE、EVFILT_USER。

int 
ff_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
	if (!events || maxevents < 1) {
		ff_os_errno(ff_EINVAL);
		return -1;
	}
	
	struct kevent *evlist = malloc(sizeof(struct kevent)*maxevents, M_DEVBUF, M_ZERO|M_NOWAIT);
	if(NULL == evlist){
		ff_os_errno(ff_EINVAL);
		return -1;		
	}
	memset(evlist, 0, sizeof(struct kevent)*maxevents);
	
	int ret = ff_kevent(epfd, NULL, 0, evlist, maxevents, NULL);
	if (ret == -1) {
		free(evlist, M_DEVBUF);
		return ret;
	}

	unsigned int event_one = 0;
	for (int i = 0; i < ret; ++i) {
		event_one = 0;
		if (evlist[i].filter & EVFILT_READ) {
			event_one |= EPOLLIN;
		} 
		if (evlist[i].filter & EVFILT_WRITE) {
			event_one |= EPOLLOUT;
		}

		if (evlist[i].flags & EV_ERROR) {
			event_one |= EPOLLERR;
		}

		if (evlist[i].flags & EV_EOF) {
			event_one |= EPOLLIN;		
		}
		events[i].events   = event_one;
		events[i].data.fd  = evlist[i].ident;
	}
	
	free(evlist, M_DEVBUF);
	return ret;
}

ff_epoll_wait()的核心就是struct kevent结构和struct epoll_event的转换,把kqueue返回的fd和事件都封装到struct epoll_event结构中,返回给调用者。

一个实际的Server DEMO代码如下:

int loop(void *arg)
{
    /* Wait for events to happen */

    int nevents = ff_epoll_wait(epfd,  events, MAX_EVENTS, 0);
    int i;

    for (i = 0; i < nevents; ++i) {	
        /* Handle new connect */
        if (events[i].data.fd == sockfd) {
            int nclientfd = ff_accept(sockfd, NULL, NULL);
            assert(nclientfd > 0);
            /* Add to event list */
    	    ev.data.fd = nclientfd;
    	    ev.events  = EPOLLIN;
    	    assert(ff_epoll_ctl(epfd, EPOLL_CTL_ADD, nclientfd, &ev) == 0);
            //fprintf(stderr, "A new client connected to the server..., fd:%d\n", nclientfd);
        } else { 
            if (events[i].events & EPOLLERR ) {
                /* Simply close socket */
        	ff_epoll_ctl(epfd, EPOLL_CTL_DEL,  events[i].data.fd, NULL);
                ff_close(events[i].data.fd);
                //fprintf(stderr, "A client has left the server...,fd:%d\n", events[i].data.fd);
            } else if (events[i].events & EPOLLIN) {
                char buf[256];
                size_t readlen = ff_read( events[i].data.fd, buf, sizeof(buf));
                //fprintf(stderr, "bytes are available to read..., readlen:%d, fd:%d\n", readlen,  events[i].data.fd);
        	if(readlen > 0){
                    ff_write( events[i].data.fd, html, sizeof(html));
        	} else {
        	    ff_epoll_ctl(epfd, EPOLL_CTL_DEL,  events[i].data.fd, NULL);
                    ff_close( events[i].data.fd);
                    //fprintf(stderr, "A client has left the server...,fd:%d\n", events[i].data.fd);		
        	}
            } else {
                fprintf(stderr, "unknown event: %8.8X\n", events[i].events);
            }
        }
    }
}

实际运行结果:

更多具体信息请访问F-Stack的github主页进行查看。

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏

使用splice实现高效的代理服务器

很多网络应用场景下, 当原设备与目标设备无法直接建立连接时,这时就需要一台代理服务器进行中转。代理服务器只需要将来自源设备的报文 原封不动的转发给目标设备,而并...

1869
来自专栏乐沙弥的世界

使用优化器性能视图获取SQL语句执行环境

    Oracle SQL语句的运行环境分为多个不同的层次,主要包括实例级别,会话级别,语句级别,其优先级依次递增。即语句级别的执行环境具 有最高的优先权,...

592
来自专栏Kubernetes

Linux kernel Namespace源码分析

学习一下linux kernel namespace的代码还是很有必要的,让你对docker容器的namespace隔离有更深的认识。我的源码分析,是基于Lin...

3808
来自专栏岑玉海

Hive Tunning(二)优化存储

接着上一章我们讲的hive的连接策略,现在我们讲一下hive的数据存储。 下面是hive支持的数据存储格式,有我们常见的文本,JSON,XML,这里我们主要...

3534
来自专栏数据和云

故障分析:一则library cache lock问题处理

编辑手记:library cache lock 大家都并不陌生,在MOS上对该阻塞的一般成因描述为:一般可以理解的是alter table或者alter pac...

3415
来自专栏大数据学习笔记

Hadoop基础教程-第11章 Hive:SQL on Hadoop(11.5 HQL:DDL数据定义)(草稿)

第11章 Hive:SQL on Hadoop 11.5 HQL:DDL数据定义 HQL中数据定义部分,也就是DDL,主要包括数据库定义和数据表的定义。 前面创...

2099
来自专栏杨建荣的学习笔记

一条关于swap争用的报警邮件分析(一)(r7笔记第28天)

最近这些天有一台服务器总是会收到剩余swap过低的告警。 邮件内容大体如下: ############ ZABBIX-监控系统: --------------...

3314
来自专栏后台及大数据开发

mysql 中select for update 锁表的范围备注

  实例:指定了锁定id=1的行且数据存在①,在更新1时lock wait超时②,但是更新id不为1的项目时可以直接更新③,释放锁后④,可以任意更新⑤

602
来自专栏数据库新发现

关于shared pool的深入探讨(五)

http://www.eygle.com/internal/shared_pool-5.htm

652
来自专栏杨建荣的学习笔记

关于dblink锁定带来的问题(r3笔记第20天)

可能在一些分布式环境中,有一些数据访问都需要用到db link。从某种程度上来说dblink是很方便,但是从性能上来说还是有一些的隐患。如果两个环境之间的网络情...

2635

扫码关注云+社区