Fastsocket学习笔记之动态链接库篇

Fastsocket学习笔记之动态链接库篇

前言

本篇为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_EPOLL_CTL _IO(IOC_ID, 0x05)
#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); // 建立fastsocket通道
     if (ret < 0) {
          FSOCKET_ERR("Open fastsocket channel failed, please CHECK\n");
          /* Just exit for safty*/
          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; // 值为65535
     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;

          /* "Automatically" do the spawn */
          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进行交互、传递数据才能够作用的很好。

你可能感兴趣的:(Fastsocket学习笔记之动态链接库篇)