基于libco的c++协程实现4(hook实现)

先看看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加入到超时队列中,添加成功后切换协程。

其中,超时的实现方式使用到时间轮实现,这个下一篇再来分享。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值