当前位置: 首页 > 开源项目 > 正文

获取时间CLOCK_MONOTONIC学习——顺记第一次与开源项目交互

前段时间接触horst项目,该项目是Linux下WIFI分析软件,可在界面上显示(使用ncurses库)。花了一些时间——断断续续也有几周吧,基本上大致掌握了它的流程和原理。后续再抽时间写一写其源码过程。

一、CLOCK_MONOTONIC

程序获致时间可以通过clock_gettime函数,该函数可以获取不同类型的时间,如CLOCK_REALTIME、CLOCK_MONOTONIC,其中CLOCK_REALTIME获取的是实时时间,而CLOCK_MONOTONIC获取的是相对时间。使用CLOCK_MONOTONIC得到的时间值是通过jiffies值来计算的,是不随系统时间变化而变化的,特别适合于时间连续性的应用(比如超时的应用)。如果使用CLOCK_REALTIME的话,当手动或NTP修改系统时间时,获取的值就会变化,无法保持“时间”的连续性。

二、horst分析

horst使用“节点”来保存WIFI数据包信息,比如SSID、帧类型、信号强度、MAC地址,等,每一帧的信息使用链表nodes来连接,这样在显示时就可遍历整个链表拿到节点信息了。

horst有一个超时功能,参数说明如下:

-t <sec>      Node timeout in seconds (60)

默认为60秒,即每隔一分钟会更新更新所有的节点,太旧的信息会被删除,以保证当前节点是最新的,其机制就是用CLOCK_MONOTONIC获取时间做超时判断依据。但在实际中发现并不如此,研究代码后,发现有一处地方有笔误而导致。于是向horst提了bug,不久这个bug就修改了,这是第一次使用github的issue功能,同时也算是真正意思上的第一次向开源项目做贡献吧。

下面看看更新节点的过程。首先看main函数处理过程:

int main(int argc, char** argv)
{
    // ....
    clock_gettime(CLOCK_MONOTONIC, &the_time); // 获取时间表
    // ....
    
    // 循环
    while (!conf.do_change_channel || conf.channel_scan_rounds != 0)
    {
        // // 接收帧并处理并在界面上显示
        receive_any(&waitmask);  // 注:此函数中在处理帧时会调用node_update加入nodes
        clock_gettime(CLOCK_MONOTONIC, &the_time); // 更新时间
        
        node_timeout(); // 检测节点是否超时,如果超时,则删除链表中对应的节点
        // ...
    }
}

receive_any是horst最复杂的函数,本文不展开。在这个函数中会将符合要求的帧进入到全局链表中,然后更新时间戳。最后检测节点是否超时,如超时则删除节点。

下面是node_timeout代码:

void node_timeout(void)
{
	struct node_info *n, *m, *n2, *m2;
	struct chan_node *cn, *cn2;

    // 如果时间还不到
	if ((the_time.tv_sec - last_nodetimeout.tv_sec) < conf.node_timeout )
		return;

    // 超时,则遍历节点链表,删除已经超时的节点
	list_for_each_safe(&nodes, n, m, list) {
        // 注:根据节点last_seen时间戳与超时时间对比
		if (n->last_seen < (the_time.tv_sec - conf.node_timeout)) {
			list_del(&n->list);
			if (n->essid != NULL)
				remove_node_from_essid(n);
			list_for_each_safe(&n->on_channels, cn, cn2, node_list) {
				list_del(&cn->node_list);
				list_del(&cn->chan_list);
				cn->chan->num_nodes--;
				free(cn);
			}
			/* remove AP pointers to this node */
			list_for_each_safe(&nodes, n2, m2, list) {
				if (n2->wlan_ap_node == n) {
					DEBUG("remove AP ref\n");
					n->wlan_ap_node = NULL;
				}
			}
			free(n);
		}
	}
	last_nodetimeout = the_time;
}

明显地,使用节点的last_seen字段做判断依据。这个字段在copy_nodeinfo函数中更新,代码如下:

static void copy_nodeinfo(struct node_info* n, struct packet_info* p)
{
	struct node_info* ap;

	memcpy(&(n->last_pkt), p, sizeof(struct packet_info));
	// update timestamp
	n->last_seen = time(NULL); // 这里使用time来获取上一次“存在”时间
	n->pkt_count++;
}

问题出现在这个函数中,last_seen得到的是实时时间,值十分大,不会匹配node_timeout的条件,故不会出现节点超时情况。这个last_seen实际应该是the_time,因为在主函数中更新the_time,而超时依据也是the_time,这样才能把时间刻度统一起来。如下:

static void copy_nodeinfo(struct node_info* n, struct packet_info* p)
{
	struct node_info* ap;

	memcpy(&(n->last_pkt), p, sizeof(struct packet_info));
	// update timestamp
	n->last_seen = the_time.tv_sec; // 使用the_time,在主函数中更新
	n->pkt_count++;
}

修改后,程序能如愿在超时时间后更新旧节点,显示新节点。
另外,保存文件也是使用the_time,这就有问题了,因为the_time值较小,得到的时间是1970年的。修正很简单,在此函数使用实时时间即可。

static void write_to_file(struct packet_info* p)
{
	char buf[40];
	int i;
    // bug 这里有问题,不应该拿the_time做时间转换的
	struct tm* ltm = localtime(&the_time.tv_sec);

	//timestamp, e.g. 2015-05-16 15:05:44.338806 +0300
	i = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ltm);
	i += snprintf(buf + i, sizeof(buf) - i, ".%06ld", (long)(the_time.tv_nsec / 1000));
	i += strftime(buf + i, sizeof(buf) - i, " %z", ltm);
	fprintf(DF, "%s, ", buf);
}

2016.10.14 周五 午休前 李迟

本文固定链接: http://www.latelee.org/open-project/clock_monotonic-bug-in-horst.html

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter