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内部设置非阻塞。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92#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模板来实现,其代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69template<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条件中,这个时候可以对这一部分进行一个简化来看这部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38retry: 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,
1
2
3
4
5
6
7
8
9
10
11
12
13
14unsigned 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的宏定义的部分:
1
2
3
4
5
6
7
8
9
10void 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服务器框架内容请搜索靠谱客的其他文章。
发表评论 取消回复