...
hook实际就是把系统提供的api再进行一层封装,以便于在执行真正的系统调用之前进行一些操作。hook的目的是把socket io相关的api都转成异步,以便于提高性能。hook和io调度是密切相关的,如果不使用IO协程调度器,那hook没有任何意义。
考虑IOManager要调度以下协程:协程1:sleep(2) 睡眠两秒后返回协程2:在scoket fd1上send 100k数据协程3:在socket fd2上recv直到数据接收成功在未hook的情况下,IOManager要调度上面的协程,流程是下面这样的:
1. 调度协程1,协程阻塞在sleep上,等2秒后返回,这两秒内调度器是被协程1占用的,其他协程无法被调度。
2. 调度协徎2,协程阻塞send 100k数据上,这个操作一般问题不大,因为send数据无论如何都要占用时间,但如果fd迟迟不可写,那send会阻塞直到套接字可写,同样,在阻塞期间,调度器也无法调度其他协程。
3. 调度协程3,协程阻塞在recv上,这个操作要直到recv超时或是有数据时才返回,期间调度器也无法调度其他协程
上面的调度流程最终总结起来就是,协程只能按顺序调度,一旦有一个协程阻塞住了,那整个调度器也就阻塞住了,其他的协程都无法执行。像这种一条路走到黑的方式其实并不是完全不可避免,以sleep为例,调度器完全可以在检测到协程sleep后,将协程yield以让出执行权,同时设置一个定时器,2秒后再将协程重新resume,这样,调度器就可以在这2秒期间调度其他的任务,同时还可以顺利的实现sleep 2秒后再执行的效果。send/recv与此类似,在完全实现hook后,IOManager的执行流程将变成下面的方式:
1. 调度协程1,检测到协程sleep,那么先添加一个2秒的定时器,回调函数是在调度器上继续调度本协程,接着协程yield,等定时器超时。
2. 因为上一步协程1已经yield了,所以协徎2并不需要等2秒后才可以执行,而是立刻可以执行。同样,调度器检测到协程send,由于不知道fd是不是马上可写,所以先在IOManager上给fd注册一个写事件,回调函数是让当前协程resume并执行实际的send操作,然后当前协程yield,等可写事件发生。
3. 上一步协徎2也yield了,可以马上调度协程3。协程3与协程2类似,也是给fd注册一个读事件,回调函数是让当前协程resume并继续recv,然后本协程yield,等事件发生。
4. 等2秒超时后,执行定时器回调函数,将协程1 resume以便继续执行。
5. 等协程2的fd可写,一旦可写,调用写事件回调函数将协程2 resume以便继续执行send。
6. 等协程3的fd可读,一旦可读,调用回调函数将协程3 resume以便继续执行recv。
...
...
关于宏的骚操作
#define HOOK_FUN(XX)
XX(sleep) \
XX(usleep) \
#define XX(name)
HOOK_FUN(XX)
#undef XX
#define XX(name)
HOOK_FUN(XX)
#undef XX
hook的这种行为和在子类中重载父类方法有些类似,如果子类重载父类方法时,通常会先调用父类的同名方法执行父类的操作,再实现自己的操作,比如:
...