概述
在操作系统层TCP/IP 协议中很多时候都要用到定时,定时器的实现是 TCP/IP 协议栈中一个重要部分。LWIP 为每个与外界网络连接的任务都有设定了 timeout 属性,即等待超时时间。在具体实现上,每个任务都对应一个 sys_timeout 结构体,里面包括这个任务的 timeout 时间长度,以及超时后应调用的超时处理函数——该函数主要负责释放连接和资源回收。如若某一个任务的 sys_timeout 结构为空,说明对应的线程要进行永久的等待。LWIP 将这些结构体存放于链表 sys_timeouts 中,并使用每个任务的优先级作为该任务的标识符。通过查询来获得一个指向当前任务使用的 sys_timeout 结构体的指针,从而使用该指针访问相应的结构体,以获得相应任务的 timeout 属性。开发者需要实现查找函数sys_arch_timeouts(),其实现思想就是根据索引(即任务的优先级)进行链表查找,实现如下:
struct sys_timeouts *sys_arch_timeouts(void)
{
u8_t CurrPrio;
s16_t err, offset;
OS_TCB CurrTaskPcb;
NullTimeouts.next = NULL;
err = OSTaskQuery(OS_PRIO_SELF, &CurrTaskPcb);
CurrPrio = CurrTaskPcb.OSTCBPrio;
offset = CurrPrio - LWIP_TASK_START_PRIO;
if(offset<0 || offset >= LWIP_TASK_MAX){
return &NullTimeouts;
}
return &LwipTimeouts[offset];
}
1、timeout有关结构
struct sys_timeo {
struct sys_timeo *next;
u32_t time;
sys_timeout_handler h;
void *arg;
};
struct sys_timeouts {
struct sys_timeo *next;
};
2、定时链表
系统中所有定时事件都按照先后顺序组织在定时链表上,定时链表有一个固定的首部 sys_timeouts。
![这里写图片描述](https://img-blog.csdn.net/20150921164055252)
当lwip运行在操作系统下,用户应该为每个lwip线程分配一个sys_timouts定时器链表头。
向内核注册一个定时事件,即向定时链表添加一个定时结构
void sys_timeout(u32_t msecs, sys_timeout_handler h, void *arg)
timeouts会在以下情况处理
—sys_mbox_fetch()等待消息
—sys_sem_wait() 或 sys_sem_wait_timeout()等待信号量
—内置sys_msleep()睡眠
通过查看sys_sem_wait(sys_sem sem)代码了解timeout处理过程。
—获取当前任务的定时链表头
—查看是否有定时时间,若有,则无限期等待信号量。若无,转下一步
—获取定时结构中的定时时间作为时间参数调用sys_arch_sem_wait(sem , timeouts->next->time)。即信号量的等待时限为 timeouts->next->time. 并返回信号量实际等待时间time_needed。
—-如果在等待时限内获取到信号量,则将当前sys_timo->time = sys_timo->time - time_needed,sys_timo->time = 0;
—-如在等待时限内,未获取到信号量,即time_needed = SYS_ARCH_TIMEOUT,则执行当前定时块的handler函数,并 将该定时结构删除。然后继续获取信号量直至在等待时限内获取信号量,或定时链表无定时事件。
3、sys_timeout使用
sys_timeout()为向内核注册一个定时事件,在sys_arch.c文件中返回的sys_timeouts链表头指针是每个LWIP线程的头指针。
凡是应用层任务使用了LWIP内核,在消息机制中,都会使用到信号量,任务在等待信号量时,都会去查询该任务是否注册了定时事件。
在sys_arch.c中可以分配LWIP_TASK_MAX - LWIP_TASK_START_PRIO个内核定时链表头。再根据任务实际需求来注册相应的内核定时事件。
在lwip源码tcpip.c中tcpip_thread()中,函数根据宏开关可以创建如下定时器:
#if IP_REASSEMBLY
sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
#endif /* IP_REASSEMBLY */
#if LWIP_ARP
sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
#endif /* LWIP_ARP */
#if LWIP_DHCP
sys_timeout(DHCP_COARSE_TIMER_MSECS, dhcp_timer_coarse, NULL);
sys_timeout(DHCP_FINE_TIMER_MSECS, dhcp_timer_fine, NULL);
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP
sys_timeout(AUTOIP_TMR_INTERVAL, autoip_timer, NULL);
#endif /* LWIP_AUTOIP */
#if LWIP_IGMP
sys_timeout(IGMP_TMR_INTERVAL, igmp_timer, NULL);
#endif /* LWIP_IGMP */
#if LWIP_DNS
sys_timeout(DNS_TMR_INTERVAL, dns_timer, NULL);
#endif /* LWIP_DNS */
以ip_reass_timer()为例,看lwip内核定时器的使用
static void ip_reass_timer(void *arg)
{
LWIP_UNUSED_ARG(arg);
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip: ip_reass_tmr()n"));
ip_reass_tmr();
sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
}
其中ip_reass_tmr()完成重装的实质性工作。sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);重新注册内核timeout控制块。以便周而复始的进行下去。
而实际tmr是工作在sys_arch_sem_wait()和sys_arch_mbox_wait();而这两个函数都是LWIP线程在等待LWIP操作。
以tcpip_thread()为例,当线程在等待邮箱时调用sys_mbox_fetch()函数。sys_mbox_fetch()会按按上小节方式工作。来完成相应的定时任务以及内存释放。
4、关于定时器如何做到准确定时
实际,在线程模式下,定时器是做不到准确定时的。这依赖于使用该定时器任务的优先级。
以ip_reass_timer()为例。
—-在A线程创建时,创建ip_reass_timer()定时器。
—-假设第一次定时事件刚完成,那么ip_reass_timer()又第二次创建该定时器。
—-B线程优先级高,获得CPU。A线程挂起。B线程运行TB
—-B线程结束,C线程运行条件到且优先级比A高,C获得CPU。A继续挂起。C线程运行TC
—-C运行结束,A获得CPU。等待信号量或邮箱,定时器开始工作。那么因为优先级不足,A从创建定时器到定时器开始工作,实际系统已经走了TB+TC。这个时间实际 上定时器是没有工作的。
可见要实现准确定时,要将A优先级任务提到最高,实际情况可能不允许。所以求平衡。然而在非操作系统模式下,可以通过SysTick实现准确定时。
5、关于sys_sem_wait()函数
---若time_needed > TIME_ARCH_OUT 则令定时器timeouts->next->time = 0(timeouts为定时立链表头);
若其定时链表还有其他定时任务,则其余任务将永远得不到执行。因为定时time = 0将会一直存在timeouts链表头。而sys_sem_wait()对于timeouts->next->time = 0将采取无限期等待。不会对timeouts->next->time做任何操作。故在sys_arch.c中实现u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout_ret)时,需保证返回值<timeout_ret;
最后
以上就是务实灯泡为你收集整理的LWIP定时器的全部内容,希望文章能够帮你解决LWIP定时器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复