前言
本篇学习Fastsocket内核模块fastsocket.so
,作为用户态libfsocket.so
的内核态的支持,处理ioctl
传递到/dev/fastsocket
的数据,非常核心和基础。嗯,还是先翻译,随后挟带些点评进来。
模块介绍
Fastsocket内核模块 (fastsocket.ko
) 提供若干特性,并各自具有开启和关闭等丰富选项可配置。
VFS 优化
CentOS 6.5带来的内核锁竞争处处可见,导致无论如何优化TCP/IP网络堆栈都不能够带来很好的性能扩展。比较严重锁竞争例子,inode_lock
和dcache_lock
,针对套接字文件系统sockfs而言,并不是必须。fastsocket通过在VFS初始化结构时提供fastpath快速路径用以解决此项问题,已经向代号为香草(vanilla)的内核提交了两处修改:
a209dfc vfs: dont chain pipe/anon/socket on superblock s_inodes list
4b93688 fs: improve scalability of pseudo filesystems
此项修改没有提供选项可供配置,因此所有fastsocket创建的套接字sockets都会强制经由fastpath传输。
内核模块参数
enable_listen_spawn
fastsocket为每个CPU创建了一个本地socket监听表(local listen table),应用程序可以决定在一个特定CPU内核上处理某个新的连接,具体就是通过拷贝原始监听套接字socket,然后插入到本地套接字socket监听表中。当新建连接在某CPU处理时,系统内核尝试匹配本地socket监听表,匹配成功会插入到本地accept队列中。稍后,CPU会从本地accept队列中获取进行处理。
这种方式每一个网络软中断都会有隶属于自己本地套接字队列当新的连接进来时可以压入,每一个进程从本地队列中弹出连接进行处理。当进程和CPU进行绑定,一旦有网卡接口决定投递到某个CPU内核上,那么包括硬中断、软中断、系统调用以及用户进程,都会有这个CPU全程负责。好处就是客户端请求连接在没有锁的竞争环境下分散到各个CPU上被动处理本地连接。
本特性更适合以下情况:
- 尽可能多的网卡Rx接收队列和CPU核数
- 应用程序工作进程被静态绑定到每一个CPU上
第一种情况下,RPS可以在网卡接收队列小于CPU核数时被使用。第二种方案可以满足两个方面:
- 应用程序在启动时自己绑定工作进程和CPU亲和性
- 允许fastsocket自动为工作进程绑定CPU亲和性
因此,enable_listen_spawn
具有三个值可供配置:
- enable_listen_spawn=0: 彻底禁止
- enable_listen_spawn=1: 启用,但要求应用程序自己绑定CPU
- enable_listen_spawn=2 (默认值): 启用此特性,允许fastsocket为每一个工作进程绑定到CPU上
enable_fast_epoll
一旦开启,需要为文件结构额外添加一字段用以保存文件与epitem的映射关系,这样可省去在epoll_ctl
方法被调用时从epoll红黑树查找epitem的开销。
虽然此项优化有所修改epoll语义,但带来了套接字性能提升。开启的前提是一个套接字只允许添加到一个epoll实例中,但不包括监听套接字。默认值为true可以适用于绝大多数应用程序,若你的程序不满足条件就得需要禁用了。
enable_fast_epoll 为布尔型boolean选项:
- enable_fast_epoll=0: 禁用fast-epoll
- enable_fast_epoll=1 (默认值): 开启fast-epoll
enable_receive_flow_deliver
RFD(Receive Flow Deliver)会把为新建连接分配的CPU ID封装到其连接的端口号中,而不是随机选择新创建的主动连接的源端口进行分配到CPU上。
当应用从活动连接收到数据包RFD解码时,会从目的地端口上解析出对应的CPU内核ID,继而转发给对应的CPU内核。再加上listen_spawn,保证了一个连接CPU处理的完全本地化。
enable_receive_flow是一个布尔型选项:
- enable_receive_flow=0 (默认值): 禁用RFD
- enable_receive_flow=1: 启用RFD
注意事项:
- 当启用时,在当前的实现,RFD完全覆盖RPS策略,并使得RPS无效。若使用RPS,请禁用此特性
- 由于RFD只会对诸如代理应用程序有利,我们建议在Web服务器上禁用此特性
以上,翻译完毕。
源码简单梳理
fastsocket的内核模块相对路径为fastsocket/module/,除了README.md外,就是两个软连接文件了:
- fastsocket.c ../kernel/net/fastsocket/fastsocket.c 真实环境下不存在这个文件,可能是程序BUG
- fastsocket.h ../kernel/net/fastsocket/fastsocket.h 有对应头文件存在
换种说法,fastsocket内核模块真正路径为fastsocket/kernel/net/fastsocket
,具体文件列表为:
- Kconfig
- Makefile
- fastsocket.h 定义内核模块所使用到变量和方法
- fastsocket_core.c 负责方法实现,供fastsocket_api.c调用
- fastsocket_api.c 内核模块加载/卸载等操作,处理前端动态链接库经由ioctl传递的数据
fastsocket_api.c实现内核模块接口,在源码里面注册了好多文档暂时没有公开的可配置项目:
int enable_fastsocket_debug = 3;
int enable_listen_spawn = 2;
int enable_receive_flow_deliver;
int enable_fast_epoll = 1;
int enable_skb_pool;
int enable_rps_framework;
int enable_receive_cpu_selection = 0;
int enable_direct_tcp = 0;
int enable_socket_pool_size = 0;
module_param(enable_fastsocket_debug,int, 0);
module_param(enable_listen_spawn, int, 0);
module_param(enable_receive_flow_deliver, int, 0);
module_param(enable_fast_epoll, int, 0);
module_param(enable_direct_tcp, int, 0);
module_param(enable_skb_pool, int, 0);
module_param(enable_receive_cpu_selection, int, 0);
module_param(enable_socket_pool_size, int, 0);
MODULE_PARM_DESC(enable_fastsocket_debug, " Debug level [Default: 3]" );
MODULE_PARM_DESC(enable_listen_spawn, " Control Listen-Spawn: 0 = Disabled, 1 = Process affinity required, 2 = Autoset process affinity[Default]");
MODULE_PARM_DESC(enable_receive_flow_deliver, " Control Receive-Flow-Deliver: 0 = Disabled[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_fast_epoll, " Control Fast-Epoll: 0 = Disabled, 1 = Enabled[Default]");
MODULE_PARM_DESC(enable_direct_tcp, " Control Direct-TCP: 0 = Disbale[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_skb_pool, " Control Skb-Pool: 0 = Disbale[Default], 1 = Receive skb pool, 2 = Send skb pool, 3 = Both skb pool");
MODULE_PARM_DESC(enable_receive_cpu_selection, " Control RCS: 0 = Disabled[Default], 1 = Enabled");
MODULE_PARM_DESC(enable_socket_pool_size, "Control socket pool size: 0 = Disabled[Default], other are the pool size");
接收用户态的libfsocket.so通过ioctl传递过来的数据,根据命令进行数据分发:
static long fastsocket_ioctl(struct file *filp, unsigned int cmd, unsigned long __user u_arg)
{
struct fsocket_ioctl_arg k_arg;
if (copy_from_user(&k_arg, (struct fsocket_ioctl_arg *)u_arg, sizeof(k_arg))) {
EPRINTK_LIMIT(ERR, "copy ioctl parameter from user space to kernel failed\n");
return -EFAULT;
}
switch (cmd) {
case FSOCKET_IOC_SOCKET:
return fastsocket_socket(&k_arg);
case FSOCKET_IOC_LISTEN:
return fastsocket_listen(&k_arg);
case FSOCKET_IOC_SPAWN_LISTEN:
return fastsocket_spawn_listen(&k_arg);
case FSOCKET_IOC_ACCEPT:
return fastsocket_accept(&k_arg);
case FSOCKET_IOC_CLOSE:
return fastsocket_close(&k_arg);
case FSOCKET_IOC_SHUTDOWN_LISTEN:
return fastsocket_shutdown_listen(&k_arg);
default:
EPRINTK_LIMIT(ERR, "ioctl [%d] operation not support\n", cmd);
break;
}
return -EINVAL;
}
fastsocket/library/libsocket.h
头文件定义的FSOCKET_IOC_*
操作状态码就能够一一对应的上。 ioctl
传输数据从用户态->内核态,需要经过一次拷贝过程(copy_from_user
),然后根据cmd命令进行功能路由。
libfsocket.so如何与fastsocket内核模块交互
通过指定的设备通道/dev/fastsocket进行交互:
- fastsocket内核模块注册要监听的通道设备名称为
/dev/fastsocket
- libfsocket打开
/dev/fastsocket
设备获得文件句柄,开始ioctl
数据传递
小结
简单梳理了fastsocket内核模块,但一样有很多的点没有涉及,后面可能会在Fastsocket内核篇中再次梳理一下。