IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    nieyong发表于 2015-02-02 06:16:00
    love 0

    前言

    本篇为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进行交互、传递数据才能够作用的很好。



    nieyong 2015-02-02 14:16 发表评论


沪ICP备19023445号-2号
友情链接