概述
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模块所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复