先看看libco例子中的example_cond.cpp的生产者
void* Producer(void* args)
{
co_enable_hook_sys();
stEnv_t* env= (stEnv_t*)args;
int id = 0;
while (true)
{
stTask_t* task = (stTask_t*)calloc(1, sizeof(stTask_t));
task->id = id++;
env->task_queue.push(task);
printf("%s:%d produce task %d\n", __func__, __LINE__, task->id);
co_cond_signal(env->cond);
poll(NULL, 0, 1000);
}
return NULL;
}
其中,第一个函数就要使能hook,而hook是一个什么东西呢?
hook说白了,就是比如本来你调用了socket(),这个函数,现在你使用一种方式,让程序运行时,先调用socket这个函数时,实际是调用了你写的另一个函数,看看是怎么实现的。
#define HOOK_SYS_FUNC(name)\
if( !g_sys_##name##_func ) \
{ \
g_sys_##name##_func = (name##_pfn_t)dlsym(RTLD_NEXT,#name); \
}
假设我们hook socket函数,其展开应该是如下
if (!g_sys_socket_func)
{
g_sys_socket_func = (socket_pfn_t)dlsym(RTLD_NEXT, "socket");
}
这就说明必须有定义了一个函数指针g_sys_socket_func,
typedef int (*socket_pfn_t)(int domain, int type, int protocol);
static socket_pfn_t g_sys_socket_func = (socket_pfn_t)dlsym(RTLD_NEXT,"socket");
上面实现了把系统的函数名称利用钩子的方式给“截获了”
socket函数hook实现
int socket(int domain, int type, int protocol)
{
HOOK_SYS_FUNC( socket );
if( !co_is_enable_sys_hook() )
{
return g_sys_socket_func( domain,type,protocol );
}
int fd = g_sys_socket_func(domain,type,protocol);
if( fd < 0 )
{
return fd;
}
rpchook_t *lp = alloc_by_fd( fd );
lp->domain = domain;
fcntl( fd, F_SETFL, g_sys_fcntl_func(fd, F_GETFL,0 ) );
return fd;
}
其中调用了fcntl( fd, F_SETFL, g_sys_fcntl_func(fd, F_GETFL,0 ) );
其中,也对了系统调用进行了捕获并重新,为什么要这么做呢?
从应用来说,我们使用了hook之后,我们hook函数里面会对阻塞IO设置为非阻塞的,如果应用要获取IO的状态,那这时候我们也应该告诉上层应用为阻塞IO,也就是钩子函数对于上层是透明的。
fcntl的一小部分hook实现
case F_GETFL:
{
ret = g_sys_fcntl_func( fildes,cmd );
if (lp && !(lp->user_flag & O_NONBLOCK)) {
ret = ret & (~O_NONBLOCK);
}
break;
}
case F_SETFL:
{
int param = va_arg(arg_list,int);
int flag = param;
if( co_is_enable_sys_hook() && lp )
{
flag |= O_NONBLOCK;
}
ret = g_sys_fcntl_func( fildes,cmd,flag );
if( 0 == ret && lp )
{
lp->user_flag = param;
}
break;
}
send的hook实现
ssize_t send(int socket, const void *buffer, size_t length, int flags)
{
HOOK_SYS_FUNC( send );
if( !co_is_enable_sys_hook() )
{
return g_sys_send_func( socket,buffer,length,flags );
}
rpchook_t *lp = get_by_fd( socket );
if( !lp || ( O_NONBLOCK & lp->user_flag ) )
{
return g_sys_send_func( socket,buffer,length,flags );
}
size_t wrotelen = 0;
int timeout = ( lp->write_timeout.tv_sec * 1000 )
+ ( lp->write_timeout.tv_usec / 1000 );
ssize_t writeret = g_sys_send_func( socket,buffer,length,flags );
if (writeret == 0)
{
return writeret;
}
if( writeret > 0 )
{
wrotelen += writeret;
}
while( wrotelen < length )
{
struct pollfd pf = { 0 };
pf.fd = socket;
pf.events = ( POLLOUT | POLLERR | POLLHUP );
poll( &pf,1,timeout );
writeret = g_sys_send_func( socket,(const char*)buffer + wrotelen, length - wrotelen,flags );
if( writeret <= 0 )
{
break;
}
wrotelen += writeret ;
}
if (writeret <= 0 && wrotelen == 0)
{
return writeret;
}
return wrotelen;
}
其主要的实现思路:
1、如果未启用了钩子函数功能或者本身就是非阻塞的IO,则直接使用系统send函数
2、writeret是写入的字节数,如果为0,则返回,否则判断是否写入完成,未完成,使用poll
而这个poll ,libco 重写了,我们来看看实现
poll的hook实现
int poll(struct pollfd fds[], nfds_t nfds, int timeout)
{
HOOK_SYS_FUNC( poll );
if (!co_is_enable_sys_hook() || timeout == 0) {
return g_sys_poll_func(fds, nfds, timeout);
}
pollfd *fds_merge = NULL;
nfds_t nfds_merge = 0;
std::map<int, int> m; // fd --> idx
std::map<int, int>::iterator it;
// 多个fd 合并到同一个fds_merge中
if (nfds > 1)
{
fds_merge = (pollfd *)malloc(sizeof(pollfd) * nfds);
for (size_t i = 0; i < nfds; i++)
{
if ((it = m.find(fds[i].fd)) == m.end())
{
fds_merge[nfds_merge] = fds[i];
m[fds[i].fd] = nfds_merge;
nfds_merge++;
}
else
{
int j = it->second;
fds_merge[j].events |= fds[i].events; // merge in j slot
}
}
}
int ret = 0;
if (nfds_merge == nfds || nfds == 1) // 使用入参fds
{
ret = co_poll_inner(co_get_epoll_ct(), fds, nfds, timeout, g_sys_poll_func);
}
else // 使用合并后的fds_merge
{
ret = co_poll_inner(co_get_epoll_ct(), fds_merge, nfds_merge, timeout, g_sys_poll_func);
if (ret > 0)
{
for (size_t i = 0; i < nfds; i++) {
it = m.find(fds[i].fd);
if (it != m.end()) {
int j = it->second;
fds[i].revents = fds_merge[j].revents & fds[i].events;
}
}
}
}
free(fds_merge);
return ret;
}
这段代码又长又臭,没有任何优雅可言,主要实现
1、目的是合并fds中相同socket的事件到一个events中,如果重复就要合并
2、根据是否重复,使用fds 或 fds_merge 作为事件的数组。
co_poll_inner的实现
co_poll_inner( stCoEpoll_t *ctx,struct pollfd fds[], nfds_t nfds, int timeout, poll_pfn_t pollfunc)
这个实现实在太长,不贴原代码,主要实现思路
其中有一长串是实现stPoll_t 这个结构体的构造,
struct stPollItem_t ;
struct stPoll_t : public stTimeoutItem_t
{
struct pollfd *fds; // poll 的fd集合
nfds_t nfds; // poll 事件个数
stPollItem_t *pPollItems; // 要加入epoll 事件
int iAllEventDetach; // 如果处理过该对象,赋值为1
int iEpollFd; // epoll fd句柄
int iRaiseCnt; // 此次触发的事件数
};
struct stPollItem_t : public stTimeoutItem_t
{
struct pollfd* pSelf; // 对应的poll结构
stPoll_t* pPoll; // 所属的stPoll_t
struct epoll_event stEvent; // epoll事件,poll转换过来的
};
stPoll_t arg 的结构
1、初始化arg的各个成员,包括epoll的事件pPollItems
2、转换事件并加入到epoll中,我一直有一个疑问为啥腾讯不一开始就一直使用epoll,要从poll转到epoll,这里是为啥,我后续看看有没有什么其他想法。我看有写协程库是完全使用epoll实现。
3、将对应的arg加入到超时队列中,添加成功后切换协程。
其中,超时的实现方式使用到时间轮实现,这个下一篇再来分享。