在上一章节中,我们了解了TopicManager节点启动背后的一些行为,本章我们将介绍同为控制节点的PollManager相关的一些源码行为。(本篇文章内容为作者观看源码后的主观理解,如若错误,敬请谅解)。
PollManager的源代码如下:
void PollManager::start()
{
shutting_down_ = false;
thread_ = boost::thread(&PollManager::threadFunc, this);
}
首先他将shutting_down_设置为false,这个变量设置为true意思为节点关闭。
接下来他会启动一个线程来执行threadFunc。
threadFunc函数的源代码如下:
void PollManager::threadFunc()
{
disableAllSignalsInThisThread();
while (!shutting_down_)
{
{
boost::recursive_mutex::scoped_lock lock(signal_mutex_);
poll_signal_();
}
if (shutting_down_)
{
return;
}
poll_set_.update(100);
}
}
首先他先屏蔽了所有信号,意味着这个线程不可中断。还记得我们在上一章讲的槽信号吗,在topic一端他将发布队列给到槽信号进行一个绑定,那么这里就是异步调用的另一端,他会调用槽信号函数,去处理发布队列。在调用一次之后他会判断节点是否关闭,没有关闭的话他会调用updata函数。我们再看一下PollSet::updata函数,它用于设置poll:
void PollSet::update(int poll_timeout)
{
createNativePollset();
// Poll across the sockets we're servicing
boost::shared_ptr<std::vector<socket_pollfd> > ofds = poll_sockets(epfd_, &ufds_.front(), ufds_.size(), poll_timeout);
if (!ofds)
{
if (last_socket_error() != EINTR)
{
ROS_ERROR_STREAM("poll failed with error " << last_socket_error_string());
}
}
else
{
for (std::vector<socket_pollfd>::iterator it = ofds->begin() ; it != ofds->end(); ++it)
{
int fd = it->fd;
int revents = it->revents;
SocketUpdateFunc func;
TransportPtr transport;
int events = 0;
if (revents == 0)
{
continue;
}
{
boost::mutex::scoped_lock lock(socket_info_mutex_);
M_SocketInfo::iterator it = socket_info_.find(fd);
// the socket has been entirely deleted
if (it == socket_info_.end())
{
continue;
}
const SocketInfo& info = it->second;
// Store off the function and transport in case the socket is deleted from another thread
func = info.func_;
transport = info.transport_;
events = info.events_;
}
// If these are registered events for this socket, OR the events are ERR/HUP/NVAL,
// call through to the registered function
if (func
&& ((events & revents)
|| (revents & POLLERR)
|| (revents & POLLHUP)
|| (revents & POLLNVAL)))
{
bool skip = false;
if (revents & (POLLNVAL|POLLERR|POLLHUP))
{
// If a socket was just closed and then the file descriptor immediately reused, we can
// get in here with what we think is a valid socket (since it was just re-added to our set)
// but which is actually referring to the previous fd with the same #. If this is the case,
// we ignore the first instance of one of these errors. If it's a real error we'll
// hit it again next time through.
boost::mutex::scoped_lock lock(just_deleted_mutex_);
if (std::find(just_deleted_.begin(), just_deleted_.end(), fd) != just_deleted_.end())
{
skip = true;
}
}
if (!skip)
{
func(revents & (events|POLLERR|POLLHUP|POLLNVAL));
}
}
}
}
他先调用createNativePollset函数用于设置轮询集合。此函数构建一个用于轮询操作的套接字结构列表。这个列表将被传递给 POLL 函数,以监控不同的套接字事件。需要说明的是虽然他叫poll_sockets,但是他内部调用的是epoll.createNativePollset的源代码如下:
void PollSet::createNativePollset()
{
boost::mutex::scoped_lock lock(socket_info_mutex_);
if (!sockets_changed_)
{
return;
}
// Build the list of structures to pass to poll for the sockets we're servicing
ufds_.resize(socket_info_.size());
M_SocketInfo::iterator sock_it = socket_info_.begin();
M_SocketInfo::iterator sock_end = socket_info_.end();
for (int i = 0; sock_it != sock_end; ++sock_it, ++i)
{
const SocketInfo& info = sock_it->second;
socket_pollfd& pfd = ufds_[i];
pfd.fd = info.fd_;
pfd.events = info.events_;
pfd.revents = 0;
}
sockets_changed_ = false;
}
他会在上锁之后查询套接字有没有改变,没有改变则返回。如果改变了,就更新信息,并且将sockets_changed_信号设置为false;
info
:包含套接字的详细信息(如文件描述符和事件类型)。ufds_
:存储pollfd
结构,准备传递给poll
系统调用。
ufds_
中的 pollfd
结构是从 info
中提取和格式化的,用于轮询和检测事件。createNativePollset
函数的主要任务是将 info
中的套接字信息转换为 pollfd
结构,并填充到 ufds_
中,以便在事件循环中进行处理。当套接字发生变化的时候,首先变化的是info,再由Info给实际事件轮询赋值
之后
boost::shared_ptr<std::vector<socket_pollfd> > ofds = poll_sockets(epfd_, &ufds_.front(), ufds_.size(), poll_timeout);
这句代码poll_sockets
函数执行实际的轮询操作。它会阻塞 poll_timeout
毫秒,也就是设置的100毫秒或直到事件发生。
对于之后的updata.
- 如果
poll_sockets
成功返回,它会返回一个包含发生事件的套接字列表的指针。通过遍历这些套接字,处理每个套接字的事件。 - 对于每个发生事件的套接字,首先获取文件描述符
fd
和事件revents
。 - 如果
revents
为 0,跳过处理。 - 使用互斥锁
socket_info_mutex_
确保线程安全地访问socket_info_
映射。查找该文件描述符对应的SocketInfo
对象。 - 如果找到对应的
SocketInfo
,提取其中的函数func
和传输对象transport
,以及注册的事件events
。 - 检查是否有注册事件或者是
POLLERR
、POLLHUP
、POLLNVAL
事件,若是则调用注册的函数func
处理事件。 - 在处理错误事件时,如果套接字刚被删除,则跳过这些错误。
总结:在pollmanager中他启动poll_manager的回调函数,运行槽函数,并设置了一个100ms的时延,去轮询的执行套接字的回调函数,这个func是什么我们下章再讲。