我是靠谱客的博主 震动黄豆,最近开发中收集的这篇文章主要介绍[源码阅读]——Sylar服务器框架:Hook模块,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Hook模块

    • Hook模块概述
    • Hook模块实现
    • 总结

Hook模块概述

  关于Hook模块,本人真的属于无能为力了,甚至自己很多地方都没读懂,建议大家看路强大佬的笔记Hook模块
  我所理解的hook在sylar中的应用主要就是为了对系统调用API进行一次封装,可以由用户自己选择是直接调用底层的接口,还是调用我们自己重新实现的接口。比如sleep(1),在系统实现中,就是使其睡眠1秒钟,但是我们可以选择调用我们自己实现的sleep(1),其会设定1秒钟的定时器,然后swapout到其他任务,等定时器达到了,在返回。这样的话就可以在原本只能同步进行的协程任务中(虽然协程可以swapout和swapin,但是在同一线程中其只能顺序进行),表现出异步的性能。

Hook模块实现

  sylar中的Hook模块是支持用户自己控制线程是否使用Hook,在默认情况下,线程调度会在run()函数中通过set_hook_enable(true);开启Hook,此时则系统调用会进入到hook后的API中,也就是自己编写的模块中。sylar主要针对文件读写、sleep、socket等内容进行了Hook,以及一个connect_with_timeout用于实现带超时的connect。
  除了本身的Hook的API外,sylar还实现了一个fd_manager用于管理文件句柄,FdMananger类和FdCtxl类用来记录、管理fd的上下文信息。其中,FdCtx类在用户态记录了fd的读写超时和非阻塞信息,用户可以自行设置非阻塞,也可以通过hook内部设置非阻塞。

#ifndef __FD_MANAGER_H__
#define __FD_MANAGER_H__

#include <memory>
#include <vector>
#include "thread.h"
#include "singleton.h"

namespace sylar{

// 文件句柄上下文类,管理文件句柄类型 
// FdCtx类在用户态记录了fd的读写超时和非阻塞信息
// 其中非阻塞包括用户显式设置的非阻塞和hook内部设置的非阻塞
// 区分这两种非阻塞可以有效应对用户对fd设置/获取NONBLOCK模式的情形。
class FdCtx : public std::enable_shared_from_this<FdCtx>{
public:
    typedef std::shared_ptr<FdCtx> ptr;

    // 通过文件句柄构造FdCtx
    FdCtx(int fd);
    // 析构函数
    ~FdCtx();

    // 是否初始化完成
    bool isInit() const { return m_isInit; }
    // 是否socket
    bool isSocket() const { return m_isSocket; }
    // 是否关闭
    bool isClose() const { return m_isClose; }
    // 设置用户主动设置非阻塞
    void setUserNonblock(bool v) { m_userNonblock = v; }
    // 获取是否用户设置的非阻塞
    bool getUserNonblock() const { return m_userNonblock; }
    // 设置系统非阻塞
    void setSysNonblock(bool v) { m_sysNonblock = v; }
    // 获取系统非阻塞
    bool getSysNonblock() const { return m_sysNonblock; }
    // 设置超时时间
    void setTimeout(int type, uint64_t v);
    // 获取超时事件
    uint64_t getTimeout(int type);

private:
    // 初始化
    bool init();

private:
    // 是否初始化
    bool m_isInit: 1;
    // 是否socket
    bool m_isSocket: 1;
    // 是否hook非阻塞
    bool m_sysNonblock: 1;
    // 是否用户主动设置非阻塞
    bool m_userNonblock: 1;
    // 是否关闭
    bool m_isClose: 1;
    // 文件句柄
    int m_fd;
    // 读超时时间毫秒
    uint64_t m_recvTimeout;
    // 写超时时间毫秒
    uint64_t m_sendTimeout;
};

// 文件句柄管理类
class FdManager{
public:
    typedef RWMutex RWMutexType;

    // 无参构造
    FdManager();
    
    // 获取、创建文件句柄类
    FdCtx::ptr get(int fd, bool auto_creat = false);
    // 删除文件句柄类
    void del(int fd);

private:
    // 读写锁
    RWMutex m_mutex;
    // 文件句柄合计
    std::vector<FdCtx::ptr> m_datas;
};

// 文件句柄单例
typedef Singleton<FdManager> FdMgr;

}

#endif

  但是本人现在还没有理清楚这一部分的主要目的,感觉还没有理解其实际功能,这一点可能还需要多了解测试看看。
  关于hook的实现,其主要使用do_io模板来实现,其代码如下:

template<typename OriginFun, typename... Args>
static ssize_t do_io(int fd, OriginFun fun, const char* hook_fun_name,
        uint32_t event, int timeout_so, Args&&... args) {
    if(!sylar::t_hook_enable) {
        return fun(fd, std::forward<Args>(args)...);
    }

    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
    if(!ctx) {
        return fun(fd, std::forward<Args>(args)...);
    }

    if(ctx->isClose()) {
        errno = EBADF;
        return -1;
    }

    if(!ctx->isSocket() || ctx->getUserNonblock()) {
        return fun(fd, std::forward<Args>(args)...);
    }

    uint64_t to = ctx->getTimeout(timeout_so);
    std::shared_ptr<timer_info> tinfo(new timer_info);

retry:
    ssize_t n = fun(fd, std::forward<Args>(args)...);
    while(n == -1 && errno == EINTR) {
        n = fun(fd, std::forward<Args>(args)...);
    }
    if(n == -1 && errno == EAGAIN) {
        sylar::IOManager* iom = sylar::IOManager::GetThis();
        sylar::Timer::ptr timer;
        std::weak_ptr<timer_info> winfo(tinfo);

        if(to != (uint64_t)-1) {
            timer = iom->addConditionTimer(to, [winfo, fd, iom, event]() {
                auto t = winfo.lock();
                if(!t || t->cancelled) {
                    return;
                }
                t->cancelled = ETIMEDOUT;
                iom->cancelEvent(fd, (sylar::IOManager::Event)(event));
            }, winfo);
        }

        int rt = iom->addEvent(fd, (sylar::IOManager::Event)(event));
        if(SYLAR_UNLIKELY(rt)) {
            SYLAR_LOG_ERROR(g_logger) << hook_fun_name << " addEvent("
                << fd << ", " << event << ")";
            if(timer) {
                timer->cancel();
            }
            return -1;
        } else {
            sylar::Fiber::YieldToHold();
            if(timer) {
                timer->cancel();
            }
            if(tinfo->cancelled) {
                errno = tinfo->cancelled;
                return -1;
            }
            goto retry;
        }
    }
    
    return n;
}

  关于这一部分,其主要用到了条件定时器,在retry之前都是一些基础的判断,其重点在于if(n == -1 && errno == EAGAIN)之后,其中ssize_t n = fun(fd, std::forward<Args>(args)...);,当fun本来是非阻塞的情况,但是没有数据可操作时,则会返回EAGAIN,即errno == EAGAIN。此时进入if条件中,这个时候可以对这一部分进行一个简化来看这部分代码:

retry:
    ssize_t n = fun(fd, std::forward<Args>(args)...);
    while(n == -1 && errno == EINTR) {
        n = fun(fd, std::forward<Args>(args)...);
    }
    if(n == -1 && errno == EAGAIN) {
        sylar::IOManager* iom = sylar::IOManager::GetThis();
        sylar::Timer::ptr timer;
        std::weak_ptr<timer_info> winfo(tinfo);

        if(to != (uint64_t)-1) {
            // 添加条件定时器
            timer = iom->addConditionTimer(to, [winfo, fd, iom, event]() {
                auto t = winfo.lock();
                if(!t || t->cancelled) {
                    return;
                }
                // 在定时时间到后通过t->cancelled设置超时标志并触发一次WRITE事件
                t->cancelled = ETIMEDOUT;
                // 取消事件时会触发事件
                iom->cancelEvent(fd, (sylar::IOManager::Event)(event));
            }, winfo);
        }
        // 先将自己加入到io调度中
        int rt = iom->addEvent(fd, (sylar::IOManager::Event)(event));
        // 让出执行权,设置为hold
        sylar::Fiber::YieldToHold();
        if(timer) {
            timer->cancel();
        }
        if(tinfo->cancelled) {
            errno = tinfo->cancelled;
            return -1;
        }
        goto retry;
    }
    return n;

  这里而do_io和connect_with_timeout都运用到了条件定时器,两者是大同小异的,但是具体实现了什么可能还是要细究一下代码,可能还有很多细节错过了。
  接下来是sleep这些函数的hook,

unsigned int sleep(unsigned int seconds) {
    if(!sylar::t_hook_enable) {
        return sleep_f(seconds);
    }

    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManager* iom = sylar::IOManager::GetThis();
    iom->addTimer(seconds * 1000, std::bind((void(sylar::Scheduler::*)
            (sylar::Fiber::ptr, int thread))&sylar::IOManager::schedule
            ,iom, fiber, -1));
    sylar::Fiber::YieldToHold();
    return 0;
}

  这里的代码相对较简单,其实就是添加了一个定时器,随后让出执行权,当定时器时间到时,在返回,其他几个函数都是类似的思路,可以直接参考。
  那么实现了这些函数后,如何将其替代系统的标准函数,或者如何选择调用系统的函数还是用户自己实现的函数呢?这里要看一下sylar的宏定义的部分:

void hook_init() {
    static bool is_inited = false;
    if(is_inited) {
        return;
    }
#define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT, #name);
    HOOK_FUN(XX);
#undef XX
}

  这里是使用了Linux中的dslym方法,具体的底层实现和理论知识相信百度要比本人理解的更透彻~

总结

  这里真的是能力有限了,只能说知道这部分在做什么、怎么用,但具体实现还是需要多阅读多了解,而且网上关于这部分的资料感觉自己查到的也不是很多,还是没有吃透这部分,大家有什么好的笔记或者对这部分的想法也请多带带我谢谢~

最后

以上就是震动黄豆为你收集整理的[源码阅读]——Sylar服务器框架:Hook模块的全部内容,希望文章能够帮你解决[源码阅读]——Sylar服务器框架:Hook模块所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部