📄 21ic ucos+lwip应用心得[社区].htm
字号:
prio)//创建一个新进程<BR> sys_mbox_t
sys_mbox_new(void)//创建一个邮箱<BR> void sys_mbox_free(sys_mbox_t
mbox)//释放并删除一个邮箱<BR> void sys_mbox_post(sys_mbox_t
mbox, void *data) //发送一个消息到邮箱<BR> void
sys_mbox_fetch(sys_mbox_t mbox, void
**msg)//等待邮箱中的消息<BR> sys_sem_t sys_sem_new(u8_t
count)//创建一个信号量<BR>void sys_sem_free(sys_sem_t
sem)//释放并删除一个信号量<BR>void sys_sem_signal(sys_sem_t
sem)//发送一个信号量<BR>void sys_sem_wait(sys_sem_t
sem)//等待一个信号量<BR> void sys_timeout(u32_t msecs,
sys_timeout_handler h, void *arg)//设置一个超时事件<BR> void
sys_untimeout(sys_timeout_handler h, void
*arg)//删除一个超时事件<BR> …<BR>关于操作系统封装层的信息可以阅读lwip的doc目录下面的sys_arch.txt.文件.<BR><BR>2.2
Lwip在ucos上的移植.<BR><BR>2.2.1
系统初始化<BR><BR> sys_int必须在tcpip协议栈任务tcpip_thread创建前被调用.<BR> #define
MAX_QUEUES 20<BR>#define
MAX_QUEUE_ENTRIES 20<BR>typedef struct
{<BR> OS_EVENT* pQ;//ucos中指向事件控制块的指针<BR> void* pvQEntries[MAX_QUEUE_ENTRIES];//消息队列<BR>//MAX_QUEUE_ENTRIES消息队列中最多消息数<BR>}
TQ_DESCR, *PQ_DESCR;<BR>typedef
PQ_DESCR sys_mbox_t;//可见lwip中的mbox其实是ucos的消息队列<BR>static
char pcQueueMemoryPool[MAX_QUEUES * sizeof(TQ_DESCR)
];<BR> void
sys_init(void)<BR>{<BR> u8_t
i;<BR> s8_t ucErr; <BR> pQueueMem
= OSMemCreate( (void*)pcQueueMemoryPool, MAX_QUEUES,
sizeof(TQ_DESCR), &ucErr
);//为消息队列创建内存分区<BR> //init lwip task prio
offset<BR> curr_prio_offset =
0;<BR> //init lwip_timeouts for every lwip
task<BR> //初始化lwip定时事件表,具体实现参考下面章节<BR> for(i=0;i<LWIP_TASK_MAX;i++){<BR> lwip_timeouts[i].next
= NULL;<BR> }<BR>}<BR>2.2.2
创建一个和tcp/ip相关新进程:<BR>lwip中的进程就是ucos中的任务,创建一个新进程的代码如下:<BR>#define
LWIP_STK_SIZE 10*1024//和tcp/ip相关任务的堆栈大小.可以根据情况自<BR>//己设置,44b0开发板上有8M的sdram,所以设大<BR>//一点也没有关系:)<BR>//max
number of lwip tasks<BR>#define
LWIP_TASK_MAX 5 //和tcp/ip相关的任务最多数目<BR>//first
prio of lwip tasks<BR>#define LWIP_START_PRIO 5
//和tcp/ip相关任务的起始优先级,在本例中优先级可<BR>//以从(5-9).注意tcpip_thread在所有tcp/ip相关进程中//应该是优先级最高的.在本例中就是优先级5
<BR>//如果用户需要创建和tcp/ip无关任务,如uart任务等,<BR>//不要使用5-9的优先级<BR> OS_STK
LWIP_TASK_STK[LWIP_TASK_MAX][LWIP_STK_SIZE];//和tcp/ip相关进程<BR>//的堆栈区<BR> u8_t
curr_prio_offset ;<BR> sys_thread_t
sys_thread_new(void (* function)(void *arg), void *arg,int
prio)<BR>{<BR> if(curr_prio_offset <
LWIP_TASK_MAX){ <BR> OSTaskCreate(function,(void*)0x1111,
&LWIP_TASK_STK[curr_prio_offset][LWIP_STK_SIZE-1],<BR>LWIP_START_PRIO+curr_prio_offset
);<BR> curr_prio_offset++;
<BR> return 1;<BR> } else
{<BR> // PRINT(" lwip task prio out of range
! error!
");<BR> }<BR>}<BR>从代码中可以看出tcpip_thread应该是最先创建的.<BR> <BR>2.2.3
Lwip中的定时事件<BR><BR>
在tcp/ip协议中很多时候都要用到定时,定时的实现也是tcp/ip协议栈中一个重要的部分.lwip中定时事件的数据结构如下.
<BR>struct sys_timeout {<BR> struct sys_timeout
*next;//指向下一个定时结构<BR> u32_t
time;//定时时间<BR> sys_timeout_handler
h;//定时时间到后执行的函数<BR> void
*arg;//定时时间到后执行函数的参数.<BR>};<BR>struct sys_timeouts
{<BR> struct sys_timeout *next;<BR>};<BR>struct
sys_timeouts
lwip_timeouts[LWIP_TASK_MAX];<BR>Lwip中的定时事件表的结构如下图,每个和tcp/ip相关的任务的一系列定时事件组成一个单向链表.每个链表的起始指针存在lwip_timeouts的对应表项中.<BR> <BR><IMG
src="21IC ucos+lwip应用心得[社区].files/200312221355480.jpg" border=0></P>
<P>函数sys_arch_timeouts返回对应于当前任务的指向定时事件链表的起始指针.该指针存在lwip_timeouts[MAX_LWIP_TASKS]中.<BR>struct
sys_timeouts null_timeouts;<BR>struct sys_timeouts *
sys_arch_timeouts(void)<BR>{<BR> u8_t
curr_prio;<BR> s16_t err,offset;<BR>OS_TCB
curr_task_pcb;<BR> null_timeouts.next =
NULL;<BR> //获取当前任务的优先级<BR> err =
OSTaskQuery(OS_PRIO_SELF,&curr_task_pcb);<BR> curr_prio
= curr_task_pcb.OSTCBPrio; <BR> offset =
curr_prio -
LWIP_START_PRIO;<BR> //判断当前任务优先级是不是tcp/ip相关任务,优先级5-9<BR> if(offset
< 0 || offset >=
LWIP_TASK_MAX)<BR> {<BR> return
&null_timeouts;<BR> }<BR> return
&lwip_timeouts[offset];<BR>}</P>
<P>
注意:杨晔大侠移植的代码在本函数有一个bug.杨晔大侠的移植把上面函数中的OS_TCB
curr_task_tcb定义成了全局变量,使本函数成为了一个不可重入函数.我也是在进行如下测试时发现了这个bug.我的开发板上设置的ip地址是192.168.1.95.我在windows的dos窗口内运行</P>
<P> ping 192.168.1.95 –l 2000
–t,不间断用长度为2000的数据报进行ping测试,同时使用tftp客户端软件给192.168.1.95下载一个十几兆程序,同时再使用telnet连接192.168.1.95端口7(echo端口),往该端口写数测试echo功能.</P>
<P>在运行一段时间以后,开发板进入不再响应.我当时也是经过长时间的分析才发现是因为在低优先级任务运行ys_arch_timeouts()时被高优先级任务打断改写了curr_task_tcb的值,从而使sys_arch_timeouts返回的指针错误,进而导致系统死锁.函数sys_timeout给当前任务增加一个定时事件:<BR>void
sys_timeout(u32_t msecs, sys_timeout_handler h, void
*arg)<BR>{<BR> struct sys_timeouts
*timeouts;<BR> struct sys_timeout *timeout,
*t;<BR> timeout =
memp_malloc(MEMP_SYS_TIMEOUT);//为定时事件分配内存<BR> if (timeout
== NULL)
{<BR> return;<BR> }<BR> timeout->next
= NULL;<BR> timeout->h =
h;<BR> timeout->arg =
arg;<BR> timeout->time =
msecs;<BR> timeouts =
sys_arch_timeouts();//返回当前任务定时事件链表起始指针<BR> if
(timeouts->next == NULL)
{//如果链表为空直接增加该定时事件<BR> timeouts->next =
timeout;<BR> return;<BR> }<BR> //如果链表不为空,对定时事件进行排序.注意定时事件中的time存储的是本事件<BR>//时间相对于前一事件的时间的差值<BR> if
(timeouts->next->time > msecs)
{ <BR>timeouts->next->time -=
msecs;<BR> timeout->next =
timeouts->next;<BR> timeouts->next =
timeout;<BR> } else {<BR> for(t =
timeouts->next; t != NULL; t = t->next)
{<BR> timeout->time -=
t->time;<BR> if (t->next ==
NULL ||<BR> t->next->time >
timeout->time) {<BR> if (t->next != NULL)
{<BR> t->next->time -=
timeout->time;<BR> }<BR> timeout->next =
t->next;<BR> t->next =
timeout;<BR> break;<BR> }<BR> }<BR> }<BR>}<BR>函数sys_untimeout从当前任务定时事件链表中删除一个定时事件<BR>void
sys_untimeout(sys_timeout_handler h, void
*arg)<BR>{<BR> struct sys_timeouts
*timeouts;<BR> struct sys_timeout *prev_t,
*t;<BR> timeouts =
sys_arch_timeouts();//返回当前任务定时事件链表起始指针<BR> if
(timeouts->next ==
NULL)//如果链表为空直接返回<BR> {<BR> return;<BR> }<BR> //查找对应定时事件并从链表中删除.<BR> for
(t = timeouts->next, prev_t = NULL; t != NULL; prev_t = t, t =
t->next)<BR> {<BR> if
((t->h == h) && (t->arg ==
arg))<BR> {<BR> /*
We have a match
*/<BR> /*
Unlink from previous in list
*/<BR> if
(prev_t ==
NULL)<BR> timeouts->next
=
t->next;<BR> else<BR> prev_t->next
=
t->next;<BR> /*
If not the last one, add time of this one back to next
*/<BR> if
(t->next !=
NULL)<BR> t->next->time
+=
t->time;<BR> memp_free(MEMP_SYS_TIMEOUT,
t);<BR> return;<BR> }<BR> }<BR> return;<BR>}<BR>2.2.3 “mbox”的实现:</P>
<P> (1)mbox的创建<BR> sys_mbox_t
sys_mbox_new(void)<BR>{<BR> u8_t ucErr;<BR> PQ_DESCR pQDesc; <BR>//从消息队列内存分区中得到一个内存块<BR> pQDesc
= OSMemGet( pQueueMem, &ucErr
); <BR> if( ucErr ==
OS_NO_ERR )
{ <BR> //创建一个消息队列<BR> pQDesc->pQ=OSQCreate(&(pQDesc->pvQEntries[0]),
MAX_QUEUE_ENTRIES
); <BR> if(
pQDesc->pQ != NULL )
{<BR> return
pQDesc;<BR> }<BR> }
<BR> return SYS_MBOX_NULL;<BR>} </P>
<P> (2)发一条消息给”mbox”<BR> const void * const
pvNullPointer = 0xffffffff;<BR>void sys_mbox_post(sys_mbox_t mbox,
void *data)<BR>{<BR> INT8U
err;<BR> if( !data )
<BR> data =
(void*)&pvNullPointer;<BR> err= OSQPost(
mbox->pQ, data);<BR>}<BR>在ucos中,如果OSQPost (OS_EVENT *pevent, void
*msg)中的msg==NULL
会返回一条OS_ERR_POST_NULL_PTR错误.而在lwip中会调用sys_mbox_post(mbox,NULL)发送一条空消息,我们在本函数中把NULL变成一个常量指针0xffffffff.</P>
<P>(3)从”mbox”中读取一条消息<BR>#define SYS_ARCH_TIMEOUT 0xffffffff<BR>void
sys_mbox_fetch(sys_mbox_t mbox, void
**msg)<BR>{<BR> u32_t time;<BR> struct
sys_timeouts *timeouts;<BR> struct sys_timeout
*tmptimeout;<BR> sys_timeout_handler
h;<BR> void *arg;<BR>again:<BR> timeouts =
sys_arch_timeouts();////返回当前任务定时事件链表起始指针<BR> if
(!timeouts || !timeouts->next)
{//如果定时事件链表为空<BR> sys_arch_mbox_fetch(mbox,
msg, 0);//无超时等待消息<BR> } else
{<BR> if (timeouts->next->time > 0)
{<BR> //如果超时事件链表不为空,而且第一个超时事件的time
!=0<BR>//带超时等待消息队列,超时时间等于超时事件链表中第一个超时事件的time,<BR> time
= sys_arch_mbox_fetch(mbox, msg,
timeouts->next->time);<BR> //在后面分析中可以看到sys_arch_mbox_fetch调用了ucos中的OSQPend系统调<BR>//用从消息队列中读取消息.<BR>//如果”mbox”消息队列不为空,任务立刻返回,否则任务进入阻塞态.<BR>//需要重点说明的是sys_arch_mbox_fetch的返回值time:如果sys_arch_mbox_fetch<BR>//因为超时返回,time=SYS_ARCH_TIMEOUT,<BR>//如果sys_arch_mbox_fetch因为收到消息而返回,<BR>//time
=
收到消息时刻的时间-执行sys_arch_mbox_fetch时刻的时间,单位是毫秒<BR>//由于在ucos中任务调用OSQPend系统调用进入阻塞态,到收到消息重新开始执行<BR>//这段时间没有记录下来,所以我们要简单修改ucos的源代码.(后面我们会看到).<BR> }
else
{<BR> //如果定时事件链表不为空,而且第一个定时事件的time
==0,表示该事件的定时<BR>//时间到<BR> time =
SYS_ARCH_TIMEOUT;<BR> }<BR> if
(time == SYS_ARCH_TIMEOUT)
{<BR> //一个定时事件的定时时间到<BR> tmptimeout
=
timeouts->next;<BR> timeouts->next
= tmptimeout->next;<BR> h =
tmptimeout->h;<BR> arg =
tmptimeout->arg;<BR> memp_free(MEMP_SYS_TIMEOUT,
tmptimeout);<BR> //从内存中释放该定时事件,并执行该定时事件中的函数<BR> if
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -