ucos+lwip应用心得
ucos+lwip应用心得
经过几天调试除掉几个bug以后,ucos+lwip在我的44b0+8019开发板上终于跑得比较稳定了.一只觉得lwip是一个不错的开放源码的tcp/ip 协议栈,想把自己对lwip的移植和理解写出来.但是由于最近比较忙,lwip的移植也是利用业余时间做的,今天写好了第一部分(lwip的 process model)先贴上来,如果大家有兴趣我再接着往下写.另外我的移植参看了skyeye扬晔大侠的代码,大家可以去看看扬晔大侠的lwip在ucos上移植的文章和代码.
lwip应用心得
lwIP是瑞士计算机科学院(Swedish Institute of Computer Science)的Adam Dunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈。Lwip既可以移植到操作系统上,又可以在无操作系统的情况下独立运行.
LwIP的特性如下:
(1)支持多网络接口下的IP转发
(2)支持ICMP协议
(3)包括实验性扩展的的UDP(用户数据报协议)
(4)包括阻塞控制,RTT估算和快速恢复和快速转发的TCP(传输控制协议)
(5)提供专门的内部回调接口(Raw API)用于提高应用程序性能
(6)可选择的Berkeley接口API(多线程情况下)
(7)在最新的版本中支持ppp
(8)新版本中增加了的IP fragment的支持.
(9)支持DHCP协议,动态分配ip地址.
现在网上最新的版本是V0.6.4
1.lwip的进程模型(process model)
tcp/ip协议栈的process model一般有几种方式.
1.tcp/ip协议的每一层是一个单独进程.链路层是一个进程,ip层是一个进程,tcp层是一个进程.这样的好处是网络协议的每一层都非常清晰,代码的调试和理解都非常容易.但是最大的坏处数据跨层传递时会引起上下文切换(context switch).
对于接收一个TCP segment要引起3次context switch(从网卡驱动程序到链路层进程,从链路层进程到ip层进程,从ip层进程到TCP进程).通常对于操作系统来说,任务切换是要浪费时间的.过频的context swich是不可取的.
2.另外一种方式是TCP/IP协议栈在操作系统内核当中.应用程序通过操作系统的系统调用(system call)和协议栈来进行通讯.这样TCP/IP的协议栈就限定于特定的操作系统内核了.如windows就是这种方式.
3.lwip的process model:所有tcp/ip协议栈都在一个进程当中,这样tcp/ip协议栈就和操作系统内核分开了.而应用层程序既可以是单独的进程也可以驻留在tcp/ip进程中.如果应用程序是单独的进程可以通过操作系统的邮箱,消息队列等和tcp/ip进程进行通讯.
如果应用层程序驻留tcp/ip进程中,那应用层程序就利用内部回调函数口(Raw API)和tcp/ip协议栈通讯.对于ucos来说进程就是一个系统任务.lwip的process model请参看下图.在图中可以看到整个tcp/ip协议栈都在同一个任务(tcpip_thread)中.应用层程序既可以是独立的任务(如图中的tftp_thread,tcpecho_thread),也可以在tcpip_thread中(如图左上角)中利用内部回调函数口(Raw API)和tcp/ip协议栈通讯
2 Port Lwip to uCos
在这个项目中我用的硬件平台是s3c44b0x+rtl8019.ucos在44b0上的移植在网上有很多大侠非常详尽的讲解和移植代码.我就不敢罗嗦了.需要说明的一点是lwip会为每个网络连接动态分配一些信号量(semaphone)和消息队列(Message Queue),当连接断开时会删掉这些semaphone和Queue.而Ucos-2.0不支持semaphone和Queue的删除,所以要选择一些较高版本的ucos.我用的是ucos-2.51.
2.1 Lwip的操作系统封装层(operating system.emulation layer)
Lwip为了适应不同的操作系统,在代码中没有使用和某一个操作系统相关的系统调用和数据结构.而是在lwip和操作系统之间增加了一个操作系统封装层.操作系统封装层为操作系统服务(定时,进程同步,消息传递)提供了一个统一的接口.在lwip中进程同步使用semaphone和消息传递采用”mbox”(其实在ucos的实现中我们使用的是Message Queue来实现lwip中的”mbox”,下面大家可以看到这一点)
Operating system emulation layer的原代码在…/lwip/src/core/sys.c中.而和具体的操作系统相关的代码在../lwip/src/arch/sys_arch.c中.
操作系统封装层的主要函数如下:
void sys_init(void)//系统初始化
sys_thread_t sys_thread_new(void (* function)(void *arg), void *arg,int prio)//创建一个新进程
sys_mbox_t sys_mbox_new(void)//创建一个邮箱
voidsys_mbox_free(sys_mbox_t mbox)//释放并删除一个邮箱
voidsys_mbox_post(sys_mbox_t mbox, void *data) //发送一个消息到邮箱
void sys_mbox_fetch(sys_mbox_t mbox, void **msg)//等待邮箱中的消息
sys_sem_t sys_sem_new(u8_t count)//创建一个信号量
void sys_sem_free(sys_sem_t sem)//释放并删除一个信号量
void sys_sem_signal(sys_sem_t sem)//发送一个信号量
void sys_sem_wait(sys_sem_t sem)//等待一个信号量
void sys_timeout(u32_t msecs, sys_timeout_handler h, void *arg)//设置一个超时事件
void sys_untimeout(sys_timeout_handler h, void *arg)//删除一个超时事件
…
关于操作系统封装层的信息可以阅读lwip的doc目录下面的sys_arch.txt.文件.
2.2 Lwip在ucos上的移植.
2.2.1 系统初始化
sys_int必须在tcpip协议栈任务tcpip_thread创建前被调用.
#define MAX_QUEUES20
#define MAX_QUEUE_ENTRIES20
typedef struct {
OS_EVENT*pQ;//ucos中指向事件控制块的指针
void*pvQEntries;//消息队列
//MAX_QUEUE_ENTRIES消息队列中最多消息数
} TQ_DESCR, *PQ_DESCR;
typedef PQ_DESCRsys_mbox_t;//可见lwip中的mbox其实是ucos的消息队列
static char pcQueueMemoryPool CR) ];
void sys_init(void)
{
u8_t i;
s8_tucErr;
pQueueMem = OSMemCreate( (void*)pcQueueMemoryPool, MAX_QUEUES, sizeof(TQ_DESCR), &ucErr );//为消息队列创建内存分区
//init lwip task prio offset
curr_prio_offset = 0;
//init lwip_timeouts for every lwip task
//初始化lwip定时事件表,具体实现参考下面章节
for(i=0;i<LWIP_TASK_MAX;i++){
lwip_timeouts.next = NULL;
}
}
2.2.2 创建一个和tcp/ip相关新进程:
lwip中的进程就是ucos中的任务,创建一个新进程的代码如下:
#define LWIP_STK_SIZE10*1024//和tcp/ip相关任务的堆栈大小.可以根据情况自
//己设置,44b0开发板上有8M的sdram,所以设大
//一点也没有关系:)
//max number of lwip tasks
#define LWIP_TASK_MAX5 //和tcp/ip相关的任务最多数目
//first prio of lwip tasks
#define LWIP_START_PRIO5 /
/和tcp/ip相关任务的起始优先级,在本例中优先级可
//以从(5-9).注意tcpip_thread在所有tcp/ip相关进程中//应该是优先级最高的.在本例中就是优先级5
//如果用户需要创建和tcp/ip无关任务,如uart任务等,
//不要使用5-9的优先级
OS_STK LWIP_TASK_STK;//和tcp/ip相关进程
//的堆栈区
u8_t curr_prio_offset ;
sys_thread_t sys_thread_new(void (* function)(void *arg), void *arg,int prio)
{
if(curr_prio_offset < LWIP_TASK_MAX){
OSTaskCreate(function,(void*)0x1111, &LWIP_TASK_STK,
LWIP_START_PRIO+curr_prio_offset );
curr_prio_offset++;
return 1;
} else {
// PRINT(" lwip task prio out of range ! error! ");
}
}
从代码中可以看出tcpip_thread应该是最先创建的.
2.2.3 Lwip中的定时事件
在tcp/ip协议中很多时候都要用到定时,定时的实现也是tcp/ip协议栈中一个重要的部分.lwip中定时事件的数据结构如下.
struct sys_timeout {
struct sys_timeout *next;//指向下一个定时结构
u32_t time;//定时时间
sys_timeout_handler h;//定时时间到后执行的函数
void *arg;//定时时间到后执行函数的参数.
};
struct sys
_timeouts {
struct sys_timeout *next;
};
struct sys_timeouts lwip_timeouts;
Lwip中的定时事件表的结构如下图,每个和tcp/ip相关的任务的一系列定时事件组成一个单向链表.每个链表的起始指针存在lwip_timeouts的对应表项中.
函数sys_arch_timeouts返回对应于当前任务的指向定时事件链表的起始指针.该指针存在lwip_timeouts中.
struct sys_timeouts null_timeouts;
struct sys_timeouts * sys_arch_timeouts(void)
{
u8_t curr_prio;
s16_t err,offset;
OS_TCB curr_task_pcb;
null_timeouts.next = NULL;
//获取当前任务的优先级
err = OSTaskQuery(OS_PRIO_SELF,&curr_task_pcb);
curr_prio = curr_task_pcb.OSTCBPrio;
offset = curr_prio - LWIP_START_PRIO;
//判断当前任务优先级是不是tcp/ip相关任务,优先级5-9
if(offset < 0 || offset >= LWIP_TASK_MAX)
{
return &null_timeouts;
}
return &lwip_timeouts;
}
注意:杨晔大侠移植的代码在本函数有一个bug.杨晔大侠的.移植把上面函数中的OS_TCB curr_task_tcb定义成了全局变量,使本函数成为了一个不可重入函数.我也是在进行如下测试时发现了这个bug.我的开发板上设置的ip地址是192.168.1.95.我在windows的dos窗口内运行
ping 192.168.1.95 –l 2000 –t,不间断用长度为2000的数据报进行ping测试,同时使用tftp客户端软件给192.168.1.95下载一个十几兆程序,同时再使用telnet连192.168.1.95端口7(echo端口),往该端口写数测试echo功能.
在运行一段时间以后,开发板进入不再响应.我当时也是经过长时间的分析才发现是因为在低优先级任务运行ys_arch_timeouts()时被高优先级任务打断改写了curr_task_tcb的值,从而使sys_arch_timeouts返回的指针错误,进而导致系统死锁.函数sys_timeout给当前任务增加一个定时事件:
void sys_timeout(u32_t msecs, sys_timeout_h
andler h, void *arg)
{
struct sys_timeouts *timeouts;
struct sys_timeout *timeout, *t;
timeout = memp_malloc(MEMP_SYS_TIMEOUT);//为定时事件分配内存
if (timeout == NULL) {
return;
}
timeout->next = NULL;
timeout->h = h;
timeout->arg = arg;
timeout->time = msecs;
timeouts = sys_arch_timeouts();//返回当前任务定时事件链表起始指针
if (timeouts->next == NULL) {//如果链表为空直接增加该定时事件
timeouts->next = timeout;
return;
}
//
如果链表不为空,对定时事件进行排序.注意定时事件中的time存储的是本事件
//时间相对于前一事件的时间的差值
if (timeouts->next->time > msecs) {
timeouts->next->time -= msecs;
timeout->next = timeouts->next;
timeouts->next = timeout;
} else {
for(t = timeouts->next; t != NULL; t = t->next) {
timeout->time -= t->time;
if (t->next == NULL ||
t->next->time > timeout->time) {
if (t->next != NULL) {
t->next->time -= timeout->time;
}
timeout->next = t->next;
t->next = timeout;
break;
}
}
}
}
函数sys_untimeout从当前任务定时事件链表中删除一个定时事件
void sys_untimeout(sys_timeout_handler h, void *arg)
{
struct sys_timeouts *timeouts;
struct sys_timeout *prev_t, *t;
timeouts = sys_arch_timeouts();//返回当前任务定时事件链表起始指针
if (timeouts->next == NULL)//如果链表为空直接返回
{
return;
}
//查找对应定时事件并从链表中删除.
for (t = timeouts->next, prev_t = NULL; t != NULL; prev_t = t, t = t->next)
{
if ((t->h == h) && (t->arg == arg))
{
/* We have a match */
/* Unlink from previous in list */
if (prev_t == NULL)
timeouts->next = t->next;
&nbs
p;else
&nbs
p;prev_t->next = t->next;
/* If not the last one, add time of this one back to next */
if (t->next != NULL)
t->next->time += t->time;
memp_free(MEMP_SYS_TIMEOUT, t);
return;
}
}
return;
}
2.2.3“mbox”的实现:
(1)mbox的创建
sys_mbox_t sys_mbox_new(void)
{
u8_tucErr;
PQ_DESCRpQDesc;
//从消息队列内存分区中得到一个内存块
pQDesc = OSMemGet( pQueueMem, &ucErr );
if( ucErr == OS_NO_ERR ) {
//创建一个消息队列
pQDesc->pQ=OSQCreate(&(pQDesc->pvQEntries), MAX_QUEUE_ENTRIES );
if( pQDesc->pQ != NULL ) {
return pQDesc;
}
}
return SYS_MBOX_NULL;
}
(2)发一条消息给”mbox”
const void * const pvNullPointer = 0xffffffff;
void sys_mbox_post(sys_mbox_t mbox, void *data)
{
INT8U err;
if( !data )
data = (void*)&pvNullPointer;
err= OSQPost( mbox->pQ, data);
}
在ucos中,如果OSQPost (OS_EVENT *pevent, void *msg)中的msg==NULL 会返回一条OS_ERR_POST_NULL_PTR错误.而在lwip中会调用sys_mbox_pos
t(mbox,NULL)发送一条空消息,我们在本函数中把NULL变成一个常量指针0xffffffff.
(3)从”mbox”中读取一条消息
#define SYS_ARCH_TIMEOUT 0xffffffff
void sys_mbox_fetch(sys_mbox_t mbox, void **msg)
{
u32_t time;
struct sys_timeouts *timeouts;
struct sys_timeout *tmptimeout;
sys_timeout_handler h;
void *arg;
again:
timeouts = sys_arch_timeouts();////返回当前任务定时事件链表起始指针
if (!timeouts || !timeouts->next) {//如果定时事件链表为空
&n
bsp;sys_arch_mbox_fetch(mbox, msg, 0);//无超时等待消息
} else {
if (timeouts->next->time > 0) {
//如果超时事件链表不为空,而且第一个超时事件的time !=0
//带超时等待消息队列,超时时间等于超时事件链表中第一个超时事件的time,
time = sys_arch_mbox_fetch(mbox, msg, timeouts->next->time);
//在后面分析中可以看到sys_arch_mbox_fetch调用了ucos中的OSQPend系统调
//用从消息队列中读取消息.
//如果”mbox”消息队列不为空,任务立刻返回,否则任务进入阻塞态.
//需要重点说明的是sys_arch_mbox_fetch的返回值time:如果sys_arch_mbox_fetch
//因为超时返回,time=SYS_ARCH_TIMEOUT,
//如果sys_arch_mbox_fetch因为收到消息而返回,
//time = 收到消息时刻的时间-执行sys_arch_mbox_fetch时刻的时间,单位是毫秒
//由于在ucos中任务调用OSQPend系统调用进入阻塞态,到收到消息重新开始执行
//这段时间没有记录下来,所以我们要简单修改ucos的源代码.(后面我们会看到).
} else {
//如果定时事件链表不为空,而且第一个定时事件的time ==0,表示该事件的定时
//时间到
time = SYS_ARCH_TIMEOUT;
}
if (time == SYS_ARCH_TIMEOUT) {
//一个定时事件的定时时间到
tmptimeout = timeouts->next;
timeouts->next = tmptimeout->next;
h = tmptimeout->h;
arg = tmptimeout->arg;
memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
//从内存中释放该定时事件,并执行该定时事件中的函数
if (h != NULL) {
h(arg);
}
//因为定时事件中的定时时间到或者是因为sys_arch_mbo_fetch超时到而执行到
//这里,返回本函数开头重新等待mbox的消息
goto again;
} else {
//如果sys_arch_mbox_fetch无超时收到消息返回
//则刷新定时事件链表中定时事件的time值.
if (time <= timeouts-="">next->time) {
timeouts->next->time -= time;
} else {
timeouts->next->time = 0;
}
}
}
}
u32_tsys_arch_mbox_fetch(sys_mbox_t mbox, void **data, u32_t timeout)
{
u32_tucErr;
u16_t ucos_timeout;
//在 lwip中 ,timeout的单位是ms
// 在ucosII ,timeout 的单位是timer tick
ucos_timeout = 0;
if(timeout != 0){
ucos_timeout = (timeout )*( OS_TICKS_PER_SEC/1000);
if(ucos_timeout < 1)
ucos_timeout = 1;
else if(ucos_timeout > 65535)
ucos_timeout = 65535;
}&nbs
p;
//如果data!=NULL就返回消息指针,
if(data != NULL){
*data = OSQPend( mbox->pQ, (u16_t)ucos_timeout, &ucErr );
}else{
OSQPend(mbox->pQ,(u16_t)ucos_timeout,&ucErr);
}
//这里修改了ucos中的OSQPend系统调用,
//原来的void*OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
// err的返回值只有两种:收到消息就返回OS_NO_ERR,超时则返回OS_TIMEOUT
//这里先将err从8位数据改变成了16位数据 OSQPend(*pevent,timeout, INT16U *err)
//重新定义了OS_TIMEOUT
//在ucos中原有#define OS_TIMEOUT 20
//改为 #defineOS_TIMEOUT-1
//err返回值的意义也改变了,如果超时返回OS_TIMEOUT
// 如果收到消息,则返回OSTCBCur->OSTCBDly修改部分代码如下
//if (msg != (void *)0) { /* Did we get a message?*/
// OSTCBCur->OSTCBMsg = (void *)0;
// OSTCBCur->OSTCBStat= OS_STAT_RDY;
// OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
// *err = OSTCBCur->OSTCBDly;// zhangzs @2003.12.12
//OS_EXIT_CRITICAL();
// return (msg);/* Return message received */
//&n
bsp;}
//关于ucos的OSTBCur->OSTCBDly的含义请查阅ucos的书籍
if( ucErr == OS_TIMEOUT ) {
timeout = SYS_ARCH_TIMEOUT;
} else {
if(*data == (void*)&pvNullPointer )
*data = NULL;
//单位转换,从ucos tick->ms
timeout = (ucos_timeout -ucErr)*(1000/ OS_TICKS_PER_SEC);
}
return timeout;
}
semaphone的实现和mbox类似,这里就不再重复了.
江西高考排名228170左右排位理科可以上哪些大学,具体能上什么大学
天津工业职业学院和武汉工程职业技术学院哪个好 附对比和区别排名
浙江高考排名250670左右排位综合可以上哪些大学,具体能上什么大学
江苏经贸职业技术学院在湖北高考招生计划人数专业代码(2024参考)
四川高考排名162360左右排位理科可以上哪些大学,具体能上什么大学
安徽高考排名68770左右排位文科可以上哪些大学,具体能上什么大学
湖南高考排名105480左右排位历史可以上哪些大学,具体能上什么大学
浙江高考排名115060左右排位综合可以上哪些大学,具体能上什么大学
湖南高考排名81440左右排位历史可以上哪些大学,具体能上什么大学
四川高考排名50780左右排位理科可以上哪些大学,具体能上什么大学
甘肃高考排名4380左右排位文科可以上哪些大学,具体能上什么大学
山西高考排名32220左右排位文科可以上哪些大学,具体能上什么大学
广东高考排名15500左右排位物理可以上哪些大学,具体能上什么大学
上海电子信息职业技术学院在云南高考历年录戎数线(2024届参考)
吉林高考排名5360左右排位理科可以上哪些大学,具体能上什么大学
中国人民解放军海军军医大学和中国社会科学院大学哪个好 附对比和区别排
新疆轻工职业技术学院的食品检验检测技术专业排名怎么样 附历年录戎数
辽宁高考排名40230左右排位历史可以上哪些大学,具体能上什么大学
考硅湖职业技术学院要多少分湖南考生 附2024录取名次和最低分
山东高考排名27810左右排位综合可以上哪些大学,具体能上什么大学
护士工作心得体会汇编十五篇)
幼儿园老师德育心得体会最新
安全生产反违章大反思大讨论心得体会
员工岗位技能培训心得
参观药厂的心得体会
安全学习心得范文
新教师培训心得精选
优秀员工培训会的心得
教育顶岗实习心得体会范文
线下团校培训心得体会
团学组织干部培训心得范文
班干部的2000字培训心得范文
防艾培训心得体会范文
乡医培训学习心得范文
最新学生会计实习心得体会1200字