LWIP定时器

来源:互联网 发布:a卡优化的单机游戏 编辑:程序博客网 时间:2024/06/05 21:02

在操作系统层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。![这里写图片描述](http://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;
0 0