前言
本篇为fastsocket的动态链接库学习笔记,对应源码目录为 fastsocket/library,先翻译README.md文件内容,后面添加上个人学习心得。
介绍
动态链接库libfsocket.so
,为已有应用程序提供加速服务,具有可维护性和兼容性。
- 可维护性:Fastsocket优化在于重新实现套接字的系统调用从而达到Linux内核网络堆栈效率的提高。而应用程序是不用修改这些系统调用,借助于Fastsocket就可以达到加速的目的。Fastsocket在内核模块提供了一个新的ioctl接口,供上层应用程序调用。
- 兼容性:若让应用程序必须修改其代码以适应新的系统调用接口,在现实世界中这很麻烦也不可行。借助于libfsocket拦截系统调用并提供新的接口进行替换系统调用,同时Fastsocket提供了与BSD socket完全兼容的调用接口,这使得应用程序在无需更改任何代码的情况下,可直接使用Fastsocket,获得网络加速的效果。
编译
很简单,进入目录之后,执行make
命令编译即可:
cd fastsocket/library
make
最后在当前目录下生成libfsocket.so
文件。
用法
很简单的说,借助于LD_PRELOAD
加载libfsocket.so
,启动应用程序,以nginx为例:
LD_PRELOAD=/your_path/fastsocket/library/libfsocket.so nginx
若回滚,就简单了,直接启动nginx就行:
nginx
注意事项:
- 确保
fastsocket.ko
内核模块已经加载成功
- 只对启动时以预加载
libfsocket.so
的上层应用程序有效果
内部构件
Fastsocket拦截网络套接字的常规系统调用,并使用ioctl接口取代之。
若不依赖于libfsocket.so
,上层应用程序要想使用Fastsocket Percore-Listen-Table的特点,应用程序需要在父流程forking之后,以及提前做事件循环(event loop)处理,应用工作进程需要手动调用listen_spawn
函数,复制全局的监听套接字并插入到本地监听表中。
libfsocket.so
为上层应用程序做了listien_spawn
的工作,用以保持应用程序的代码不变,方法如下:
libfsocket.so
跟踪所有需要监听的套接字文件句柄
libfsocket.so
拦截了epoll_ctl
系统调用
- 当监听到应用程序调用
epoll_ctl
添加监听套接字文件句柄到epoll时,libfsocket.so
会调用listen_spawn
方法
不是所有应用程序都适合本方案,但nginx、haproxy、lighttpd与之配合就工作得相当不错。因此当你在其他应用程序中想使用Percore-Listen-Table特性时,请务必小心测试了,确保是否合适。
OK,翻译完毕。
源码一览
fastsocket/library用于构建libfsocket.so
动态链接库,主要组成:
- Makefile 编译脚本
- libsocket.h 头文件,定义变量、结构等
- libsocket.c 动态链接库实现
libsocket.h
定义了ioctl
(为Input/Output ConTroL缩写)函数和伪设备(/dev/fastsocket
)交换数据所使用到的几个命令:
#define IOC_ID 0xf5
#define FSOCKET_IOC_SOCKET _IO(IOC_ID, 0x01)
#define FSOCKET_IOC_LISTEN _IO(IOC_ID, 0x02)
#define FSOCKET_IOC_ACCEPT _IO(IOC_ID, 0x03)
#define FSOCKET_IOC_CLOSE _IO(IOC_ID, 0x04)
#define FSOCKET_IOC_SPAWN_LISTEN _IO(IOC_ID, 0x06)
#define FSOCKET_IOC_SHUTDOWN_LISTEN _IO(IOC_ID, 0x07)
紧接着定义了需要在用户态和内核态通过ioctl
进行交互的结构:
struct fsocket_ioctl_arg {
u32 fd;
u32 backlog;
union ops_arg {
struct socket_accept_op_t {
void *sockaddr;
int *sockaddr_len;
int flags;
}accept_op;
struct spawn_op_t {
int cpu;
}spawn_op;
struct io_op_t {
char *buf;
u32 buf_len;
}io_op;
struct socket_op_t {
u32 family;
u32 type;
u32 protocol;
}socket_op;
struct shutdown_op_t {
int how;
}shutdown_op;
struct epoll_op_t {
u32 epoll_fd;
u32 size;
u32 ep_ctl_cmd;
u32 time_out;
struct epoll_event *ev;
}epoll_op;
}op;
};
这样看来,ioctl
函数原型调用为:
ioctl(/dev/fastsocket设备文件句柄, FSOCKET_IOC_具体宏命令, fsocket_ioctl_arg结构指针)
现在大致能够弄清楚了内核态和用户态之间通过ioctl
传递结构化的数据的方式了。
libsocket.c 简要分析
连接内核模块已经注册好的设备管道/dev/fastsocket
,获取到文件描述符,同时做些CPU进程绑定的工作
#define INIT_FDSET_NUM 65536
......
__attribute__((constructor))
void fastsocket_init(void)
{
int ret = 0;
int i;
cpu_set_t cmask;
ret = open("/dev/fastsocket", O_RDONLY);
if (ret < 0) {
FSOCKET_ERR("Open fastsocket channel failed, please CHECK\n");
exit(-1);
}
fsocket_channel_fd = ret;
fsocket_fd_set = calloc(INIT_FDSET_NUM, sizeof(int));
if (!fsocket_fd_set) {
FSOCKET_ERR("Allocate memory for listen fd set failed\n");
exit(-1);
}
fsocket_fd_num = INIT_FDSET_NUM;
CPU_ZERO(&cmask);
for (i = 0; i < get_cpus(); i++)
CPU_SET(i, &cmask);
ret = sched_setaffinity(0, get_cpus(), &cmask);
if (ret < 0) {
FSOCKET_ERR("Clear process CPU affinity failed\n");
exit(-1);
}
return;
}
主观上,仅仅是为了短连接而设置的,定义的fastsocket文件句柄数组大小为65535,针对类似于WEB Server、HTTP API等环境足够了,针对百万级别的长连接服务器环境就不适合了。
socket/listen/accept/close/shutdown/epoll_ctl等函数,通过dlsym
方式替换已有套接字系统函数等,具体的交互过程使用ioctl
替代一些系统调用。
除了重写socket/listen/accept/close/shutdown等套接字接口,同时也对epoll_ctl
方法动了手术(江湖传言CPU多核多进程的epoll服务器存在惊群现象),更好利用多核:
int epoll_ctl(int efd, int cmd, int fd, struct epoll_event *ev)
{
static int (*real_epoll_ctl)(int, int, int, struct epoll_event *) = NULL;
int ret;
struct fsocket_ioctl_arg arg;
if (fsocket_channel_fd >= 0) {
arg.fd = fd;
arg.op.spawn_op.cpu = -1;
if (fsocket_fd_set[fd] && cmd == EPOLL_CTL_ADD) {
ret = ioctl(fsocket_channel_fd, FSOCKET_IOC_SPAWN_LISTEN, &arg);
if (ret < 0) {
FSOCKET_ERR("FSOCKET: spawn failed!\n");
}
}
}
if (!real_epoll_ctl)
real_epoll_ctl = dlsym(RTLD_NEXT, "epoll_ctl");
ret = real_epoll_ctl(efd, cmd, fd, ev);
return ret;
}
因为定义了作用于内部的静态变量real_epoll_ctl
,只有在第一次加载的时候才会被赋值,real_epoll_ctl = dlsym(RTLD_NEXT, "epoll_ctl")
,后面调用时通过ioctl
把fsocket_ioctl_arg传递到内核模块中去。
其它socket/listen/accept/close/shutdown等套接字接口,流程类似。
小结
以上简单翻译、粗略分析用户态fastsocket动态链接库大致情况,若要起作用,需要和内核态Fastsocket进行交互、传递数据才能够作用的很好。