




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第九章LwIP及其网络编程应用实例,1,LwIP介绍,LwIP(LightWeightInternetProtocol)是瑞典计算机科学院(SwedishInstituteofComputerScience)的AdamDunkels等人开发的一套用于嵌入式系统的开源TCP/IP协议栈。LwIP的含义是轻型IP协议,其实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,这使得LwIP协议栈非常适合在小型嵌入式系统中使用。,2,LwIP介绍,LwIP的版本较多,较新的版本通常完善或增加了LwIP的功能。LwIP有如下特点:IP:支持多网络接口下的IP转发ARP:支持ARP协议ICMP:
2、支持ICMP协议UDP:支持UDP协议TCP:支持TCP协议,包括拥塞控制、RTT估算和快速恢复/快速重传RawAPI:提供专门的内部回调函数,以提高应用性能SocketAPI:可选的Berkeley-likesocketAPILwIP的较新版本还提供对以下功能或协议的支持:IPfragment:IP分片DNS:域名解析SNMP:简单网络管理协议DHCP:动态主机配置协议PPP:点对点协议IPv6,3,LwIP源码的文件组织,LwIP文件目录的组织结构如图所示,其源代码全部位于目录src下。src目录下一般有5个子目录LwIP提供的api子目录、core子目录、include子目录和netif
3、子目录需用户自己创建的arch目录。,4,LwIP源码的文件组织,每个子目录包含的某一类相关的文件,简要说明如下:api目录应用程序接口文件。arch目录与硬件和OS有关的文件,包括网络驱动、移植需要修改的文件。core目录LwIP的核心代码,包括ICMP、IP、UDP、TCP等协议的实现等。include目录LwIP的包含文件。netif目录ARP协议和LwIP网络设备驱动程序的模板,提供了网络接口驱动程序的基本框架。,5,LwIP的软件体系结构,LwIP的协议层次:LwIP也是以4层TCP/IP模型为参照来实现TCP/IP协议族的。每一个协议作为一个模块被实现,同时还提供了几个函数作为协议
4、的入口点。LwIP并没有严格地按照分层的方式实现协议族。实际上LwIP使用的是一种比较松散的通讯机制,通过共享内存的方式实现应用层与底层协议族之间的通讯。LwIP拥有独特的缓冲机制,使得各层次可以更加有效的重复使用缓冲区。LwIP尽量避免内存复制,避免了内存复制产生的性能损失。,6,LwIP的软件体系结构,与LwIP的协议层次相匹配,LwIP采用模块化设计的方法实现。TCP/IP协议的实现模块如ARP、IP、ICMP、UDP、TCP等许多相关支持模块。这些支持模块包括操作系统模拟层、缓冲与内存管理子系统、网络接口函数等。,7,LwIP的进程模型,TCP/IP协议族的进程模型指的是采用何种方法把
5、系统分成不同的进程。常见的进程模型有两种:每一个协议作为一个独立的进程协议栈作为一个内核只占据一个进程。第一种模型必须符合协议的每一层,协议层之间通过指定的方式进行通讯。优点较明显,即每一种协议都可以独立参与到系统运行中,其实现的代码也比较简单,整个协议栈的层次脉络清晰,便于理解和调试。缺点也是显而易见的,即数据跨层传递时不得不产生进程切换以及内存复制。这一缺点极大影响了系统的整体性能,尤其对于嵌入式系统来说更是不能忍受的。第二种模型将协议栈驻留在操作系统内核中,应用程序通过系统调用与协议栈进行通讯。这种设计可以使用交叉协议分层技术,各层协议不必严格划分。这种进程模型的缺点是层次不清,给理解增
6、加了难度。,8,LwIP的进程模型,LwIP则采用一种比较灵活的设计方法。它可以将所有的协议驻留在一个进程,以便独立于操作系统内核之外。应用程序既可以驻留在LwIP的进程中,也可以使用一个单独的进程。它也可以根据协议层次结构创建多个进程,但各个进程之间只传送尽可能少的必要信息,而没有引入额外的内存复制LwIP在协议层之间切换时,一般只传递数据缓冲区的地址,让需要处理数据的协议层自己去提取。,9,LwIP的函数调用关系,为了尽量避免不必要的内存复制,LwIP更多的是采用一种基于回调函数的设计方法。当数据需要处理或跨层传递时,通常是通过调用事先已定义好的回调函数来完成有关操作。优点是大大提高了Lw
7、IP的整体性能;缺点是使得LwIP的整个软件体系显得略微复杂,尤其是函数之间的调用关系更为繁琐。为了理清LwIP的函数调用关系,从两个不同的方向对这一问题进行分析:从不同的协议层出发,横向分析各个层次内的调用关系;从几种典型的协议模块出发,纵向分析各模块的跨层调用关系。,10,整体调用关系,图给出了LwIP的整体调用关系,基本上涵盖了LwIP的主要功能模块和绝大部分的函数调用。图中只标注了对LwIP的整个软件体系起着重要支撑作用的主干函数,11,协议层内的调用,TCP/IP协议栈是按功能层组织的,每一层都为上一层提供服务,并使用下一层提供的服务。在4层TCP/IP模型中,从下至上依次是网络接口
8、层、网际层、运输层和应用层。(1)网络接口层网络接口层是较高协议与局域网接口的地方。当主机通过查询或者中断方式得知网络芯片接收到数据帧时,LwIP协议栈对该数据帧进行解码,并判断数据帧的协议类型:如果是IP协议,则将该帧传递给上层(网际层)的ip_input()函数进行处理;如果是ARP协议,则直接传给本层的arp_input()函数,该函数根据需要决定是否调用arp_replay()进行ARP应答。当上层有数据需要通过网络接口层进行发送时,当前网络接口的输出函数netif-output()将会被调用,以完成真正的数据发送过程。,12,协议层内的调用,13,协议层内的调用,(2)网际层网际层负
9、责网间寻址(IP地址)、数据封装、路由选择、错误处理和诊断等典型协议有IP协议和ICMP协议。当从下层(网络接口层)接收到IP数据报时,调用ip_input()函数进行处理。根据IP数据报的协议字段,LwIP决定将该数据报传给上层(运输层)还是传给本层。如果IP净荷中承载的是ICMP协议,则本层的icmp_input()函数将会调用。当不论是上层还是本层有数据需要从网际层发送出去时LwIP将会调用ip_output()发送数据,或者先调用ip_route()找到一个合适的网络接口再调用ip_output_if()发送数据。实际上ip_output()也是通过先调用ip_route()再调用ip
10、_output_if()来实现的,14,协议层内的调用,15,协议层内的调用,(3)运输层运输层负责在网际设备之间运输数据,以可靠或不可靠的方式进行。TCP和UDP。当下层(网际层)有数据传给运输层时LwIP会根据数据类型的不同(是TCP还是UDP)调用该层的tcp_input()或者udp_input()。经过一定处理后,LwIP将数据由tcp_receive()或udp_input()提交给上层(应用层),一般会调用事先注册的接收函数。当上层需要发送数据时LwIP选择调用tcp_write()或者udp_send()对数据进行处理最后通过tcp_output()或udp_send()将数据
11、交给下层。,16,协议层内的调用,17,协议层内的调用,(4)应用层用户的应用运行在应用层,该层使用户可以根据自己的需要对数据进行处理。用户需要发送数据时由LwIP根据数据类型(TCP或UDP)调用下层(运输层)对应的发送函数。应用层并不需要直接关注数据是怎样发送出去的。用户接收的数据一般由LwIP调用下层的接收函数送达,此后用户可以根据实际情况实现应用程序。,18,典型模块的跨层调用,对于某一个协议来说它一般只隶属于某一个层次(ARP除外)。但往往会有其它层次调用该协议的有关函数而该协议一般也会主动调用其它层次的有关函数。(1)IP模块LwIP的较早期版本实现了IP层大部分的基本功能,能够发
12、送、接收以及转发信息包。接收信息包由网络设备驱动调用ip_input()函数开始处理。完成对IP版本字段及包头长度的初始完整性检查同时还要计算和验证包头校验和函数检查目的地址是否与网络接口的IP地址相符以确定信息包是否到达预定主机。如果一个到达的信息包被发现已经到达了目的主机,则由协议字段来决定信息包应该传送到哪一个上层协议。,19,典型模块的跨层调用,外发的信息包由ip_output()函数处理,该函数使用ip_route()函数查找适当的网络接口来传送信息包。当外发的网络接口确定后,信息包传给以外发网络接口为参数的ip_output_if()函数。所有的IP包头字段被填充,并且计算IP包头
13、校验和。IP信息包的源及目标地址作为参数被传递给ip_output_if()函数。传输层协议UDP与TCP在计算传输层校验和的时候需要拥有目标IP地址,因此一些传输层函数可能会直接直接调用ip_route()函数确定接口。这样这些函数在外发数据前就没有必要再对网络接口链表进行检索,而是直接调用ip_output_if()函数外发数据。,20,典型模块的跨层调用,如果没有网络接口的地址与到达的信息包的目标地址相同,信息包应该被转发。由ip_forward()函数完成。TTL字段值被减少,当减为0的时候,将会给IP信息包的最初发送者发送ICMP错误信息,并抛弃该信息包。因为IP包头被改变,因此需要
14、调整IP包头校验和。最后,信息包被转发到适当的网络接口。,21,典型模块的跨层调用,(2)ICMP模块ICMP信息包由ip_input()函数收到后,转交给icmp_input()函数对ICMP包头解码,然后进行适当的动作。如果需要对回送请求进行应答,则调用ip_output()函数发送应答报文。某些ICMP消息被传递给上层协议,由传输层的特定函数处理。ICMP目标不可到达消息可以由传输层发送,特别是UDP如udp_input()就可以调用icmp_dest_unreach()函数完成这项工作。icmp_dest_unreach()最后也会调用ip_output()发送ICMP报文。,22,典
15、型模块的跨层调用,23,典型模块的跨层调用,(3)UDP模块当一个UDP数据包到达时IP层调用udp_input()函数将数据包移交给udp_input()。如果需要的话,LwIP会在这里对UDP校验和进行检查。为了找到匹配的UDPPCB,LwIP会对UDPPCB全局链表进行线性搜索。如果当前链表中存在匹配的UDPPCB,则其recv函数会被调用。发送数据的过程由应用程序调用udp_send()函数发起。为了计算校验和,该函数会调用ip_route()确定网络接口,因为该接口地址将在校验和的计算过程中用到。最后,信息包被移交给ip_output_if()函数传送。,24,典型模块的跨层调用,2
16、5,典型模块的跨层调用,(4)TCP模块TCP处理比UDP处理要复杂得多与TCP输入相关的函数tcp_input()tcp_process()tcp_receive()与TCP输出有关的函数tcp_write()tcp_enqueue()tcp_output(),26,典型模块的跨层调用,27,典型模块的跨层调用,TCP数据的发送过程一般是由应用层发起。应用层调用tcp_write(),而tcp_write()再调用tcp_enqueue()。tcp_enqueue()函数会在必要时将数据分割成适当大小的TCP段,然后把这些TCP段放到所属连接的传输队列中。这时tcp_output()函数会判
17、断接收器窗口是否拥有足够大的空间,阻塞窗口是否也足够大,如果条件满足,就调用ip_route()找到一个合适的接口,再调用ip_output_if()完成发送过程。即使当时不能发送也不要紧,这是因为LwIP设置了定时器函数tcp_tmr(),该函数每隔固定时间就会被调用一次。tcp_tmr()会对当前连接的传输队列进行分析,并根据需要调用tcp_output()执行数据发送操作。,28,典型模块的跨层调用,TCP数据的接收过程由网络接口层发起。网络接口层将数据包传递给ip_input()函数,该函数验证IP头后移交TCP段给tcp_input()函数。tcp_input()函数主要完成两项工作
18、初始完整性检查(也就是校验和验证与TCP选项解析判定这个TCP段属于哪个TCP连接。接着,这个TCP段到达tcp_process()函数。tcp_process()函数实现了TCP状态机,任何必要的状态转换都在这里实现。当该TCP所属的连接正处于接受网络数据的状态时,tcp_receive()函数将被调用。最后,tcp_receive()函数将数据传给上层的应用程序,完成接收过程。如果收到一个ACK应答确认数据,表明接收器同意接收更多的数据,此时tcp_output()函数将会被调用。,29,LwIP的内存管理,LwIP的包缓冲区pbufpbuf是LwIP信息包的内部表示。pbuf结构既支持动
19、态内存分配以保存信息包内容,又支持让信息包数据驻留在静态存储区。多个pbuf可以通过一个链表结构链接成一个pbuf链,从而使一个信息包穿越多个pbuf。pbuf的内部结构定义为structpbufstructpbuf*next;/指向下一个pbufvoid*payload;/指向实际的数据负载u16_ttot_len;/pbuf链的数据负载总长度u16_tlen;/该pbuf的数据负载长度u16_tflags;/pbuf的类型标志u16_tref;/pbuf被引用的次数;,30,LwIP的内存管理,pbuf结构包括两个指针,两个长度字段,一个标志字段和一个引用计数字段。next指针指向pbuf
20、链中下一个pbuf的位置;payload指针指向pbuf中数据负载的开始位置len字段包含pbuf中数据内容的长度;tot_len字段包含当前pbuf的长度与在这个pbuf链中随后的所有pbuf的len字段之和flags字段标识pbuf的类型;ref字段指出pbuf被引用的次数。pbuf有四种类型PBUF_RAMPBUF_ROMPBUF_REFPBUF_POOL,31,PBUF_RAM类型的pbuf,PBUF_RAM在事先划分好的内存堆栈中分配,用于存放应用程序动态产生的数据。图示的是一个PBUF_RAM类型的pbuf实例,其实际的数据负载存放在由协议栈管理的存储区中。既然PBUF_RAM类型
21、的pbuf用于应用程序发送的数据被动态生成的情况,那么在这种情况下pbuf系统不仅为应用数据分配内存,还应给为这些数据预置的包头分配内存。pbuf系统不可能预先知道为这些数据预置什么样的包头,因而考虑最坏的情况。,32,PBUF_ROM/PBUF_REF类型的pbuf,PBUF_ROM类型的pbuf的payload指针指向不由协议栈管理的外部存储区如应用程序管理的存储器为用户数据分配的缓存。由于由应用程序交付的数据不能被改动因此就需要动态地分配一个PBUF_RAM来装载协议的首部然后将PBUF_RAM(首部)添加到PBUF_ROM(数据)的前面。这样就构成了一个完整的数据分组(pbuf链),3
22、3,PBUF_ROM/PBUF_REF类型的pbuf,34,PBUF_ROM/PBUF_REF类型的pbuf,图中的PBUF_ROM还可以是PBUF_REF,二者的特性非常相似,都可以实现数据的零拷贝,但是当发送数据需要排队时就表现出PBUF_REF的特性了。例如待发送的分组需要在ARP队列中排队,假如这些分组中有PBUF_ROM类型的pbuf,则直到分组被处理之前,被引用的应用程序的这块存储区域都不能另作它用。但如果是PBUF_REF类型的pbuf,LwIP则会在数据分组排队时为PBUF_REF类型的pbuf分配缓存(PBUF_POOL或PBUF_RAM),并将引用的应用程序的数据拷贝到分配
23、的缓存中。这样应用程序中被引用数据的存储区域就能被释放。,35,PBUF_POOL类型的pbuf,PBUF_POOL是具有固定容量的pbuf,其容量大小通过宏定义来指定。在协议栈管理的内存中初始化了一个pbuf池,具有相同尺寸的pbuf都是从这个pbuf池中分配得到。一般使用多个PBUF_POOL链接成一个链表,用于存储数据分组,36,PBUF_POOL类型的pbuf,37,PBUF_POOL类型的pbuf,PBU_POOL主要用于网络设备驱动层由于分配一个pbuf的操作可以快速完成,所以PBUF_POOL非常适合用于中断处理。一般来说,收到的pbuf是PBUF_POOL类型,发送出的pbuf
24、是PBUF_ROM或PBUF_RAM类型。不同类型的pbuf拥有各自的特点和不同的使用目的,因此只有正确选用,才能最好地发挥LwIP的特性。,38,LwIP的内存管理,LwIP的内存区域主要用于装载待接收和发送的网络数据分组。当接收到分组或者有分组要发送时,LwIP协议栈为这些分组分配缓存;在接收到的分组交付给应用程序或者分组己经发送完毕后,LwIP协议栈对分配的缓存进行回收利用。协议栈分配的缓存必须能容纳各种大小的报文例如从仅仅几个字节的ICMP应答报文到几百个字节的TCP分段报文。,39,PBUF_RAM的内存管理,LwIP协议栈首先从系统内存中开辟一块连续的静态存储区域该区域的大小可以事
25、先通过宏定义指定。协议栈将该区域作为PBUF_RAM的专用区域所有与PBUF_RAM有关的内存操作都被限制在该区域内,从而确保了协议栈不会因非法访问系统内存的其它区域而扰乱其它程序的正常运行。,40,PBUF_RAM的内存管理,为了方便内存管理,协议栈定义了一个比较小的结构体mem,并将该结构体置于内存分配块的顶部来保存内存分配记录。该结构体拥有三个成员变量,分别为两个“指针”和一个标志,其中next与prev分别指向内存的下一个和上一个分配块,used标志标示该内存块是否已被分配。next和prev并不是真正的指针,它们本质上是数组的下标,并没有直接指向真正的地址。,41,PBUF_RAM的
26、内存管理,(1)初始化使用PBUF_RAM内存之前,需对PBUF_RAM的专用内存区域进行初始化工作,即对该区域进行一定的格式设置。LwIP协议栈在该区域的头部和尾部各设置了一个mem结构以协助管理内存,如图所示。在头部的mem结构中,next指向尾部mem,prev指向该区域的起始处,used(=0)表明该mem结构后面的区域尚未使用;在尾部的mem结构中,next和prev均指向该mem自身,used(=1)表明该mem结构后面无可用的内存。在该区域的末尾处,协议栈预留了对齐空间,其主要目的是防止操作过程中因对齐而导致的对该专用区域之外的存储空间的越界访问。,42,PBUF_RAM的内存管
27、理,43,PBUF_RAM的内存管理,(2)分配PBUF_RAM内存块分配内存时首先根据所申请分配的大小来搜索所有未被使用的内存分配块搜索到的最先满足条件的内存块将分配给申请者。第一次分配时只要申请分配的大小没有超出限制,便会在PBUF_RAM专用存储区域的开头分配所需要的内存,44,PBUF_RAM的内存管理,经过多次的内存分配和释放操作后,PBUF_RAM存储区中会存在多个大小不一的未使用块。此时如果需要分配一个新的内存块,有可能搜索到的第一个空闲内存块空间不够。在这种情况下,内存管理机制会继续往后搜索未使用块,直到搜索到足够大的空闲内存块或搜索到存储区的末尾。,45,PBUF_RAM的内
28、存管理,(3)释放PBUF_RAM内存块对不再利用的内存块,需要进行回收,以便下次需要分配内存块时重新使用。回收内存块时,管理该内存块的mem结构的used标志将被清零,以表明该内存块已不再被使用,可以重新对其进行分配,46,PBUF_RAM的内存管理,为了防止内存碎片的产生,每回收一个内存块后,其上一个与下一个分配块的used标志将会被检查:如果它们中的任何一个还未被使用(used=0),则这个内存块将被合并到一个更大的未使用内存块中。只有经过了以上操作后,释放内存的工作才算是已经完成。,47,PBUF_RAM的内存管理,合并相邻空闲块的一个示例。,48,PBUF_RAM的内存管理,(4)调
29、整PBUF_RAM内存块大小在内存的使用过程中,有时希望调整已分配的内存块的大小。根据调整的方向(减小/增大)不同,处理的机制也不一样。调整前先对腾出的内存块大小进行预算:如果该值小于mem结构的长度加内存块的最小长度(即无法另行分配一个最小长度的内存块),则调整不被执行。调整时将在腾出的内存块开头置以一个新的mem结构以对其进行管理,该mem结构的used标志为0,表示可以对其进行分配使用。同释放内存时一样,与新的内存块相邻的内存块的used标志同样会被检查,以防止碎片产生。,49,PBUF_RAM的内存管理,给出了减小既定内存块大小的示意图。,50,PBUF_RAM的内存管理,增大既定内存
30、块的大小时所采取的机制与上述操作大为不同。如果希望将某内存块的大小调整为newsize,则处理过程是先分配一块大小为newsize的空闲内存块,然后将原内存块的内容复制到新内存块中,最后再释放原内存块。,51,PBUF_ROM/PBUR_REF的内存管理,对于PBUF_ROM/PBUF_REF,LwIP协议栈同样为其开辟了一块连续的存储区域。协议栈定义了结构体memp以协助PBUF_ROM/PBUF_RAM的内存管理。该结构体只有一个成员next,为指向下一个相同结构的存储区的指针。PBUF_ROM/PBUF_REF类型存储区域初始化后的结构示意图,多个pbuf在内存中以链表的形式存在。该种类
31、型pbuf的操作方法可以参考链表的一般操作方法。,52,PBUF_POOL的内存管理,PBUF_POOL类型的pbuf同样拥有自己专用的存储区域,该区域通过预先从系统内存中分配而得。区域大小由PBUF_POOL类型的pbuf个数和每个pbuf的缓冲区大小等参数共同决定,这些参数都可以事先通过宏定义指定。对PBUF_POOL类型的内存管理,LwIP协议栈并没有额外引入类似PBUF_RAM的mem结构或是PBUF_ROM的memp结构,而是直接采用pbuf结构对其进行管理。,53,PBUF_POOL的内存管理,PBUF_POOL存储区域的结构如图所示,对该类型pbuf的操作与普通链表的操作并无不同
32、。每个PBUF_POOL的数据缓冲区都紧跟在pbuf结构后面,并且大小相同。这点与PBUF_ROM不同,因为PBUF_ROM的数据缓冲区不在LwIP协议栈管理的区域,并且大小不尽相同。,54,LwIP移植,无RTOS时的移植LwIP既可以在无RTOS(RealTimeOperatingSystem,实时操作系统)的环境下运行,也可以很方便地移植到RTOS之上。移植过程中对于LwIP核心模块没必要也不建议进行修改,而真正的工作是结合实际的软硬件环境,针对与移植密切相关的相关文件与相关函数进行定制。,55,LwIP移植,移植函数为了将LwIP移植到特定的开发平台上,需要完成与网络接口有关的底层函数
33、。这些底层函数集中在srcnetifethernetif.c文件中。建议将“ethernet”替换成能更好地描述所选网络接口的词汇,如华中科技大学瑞萨高级嵌入式控制器实验室自行开发的RenesasM16C/62P嵌入式开发平台采用的网络芯片是CS8900A,该文件便用cs8900if.c文件进行了替代。文件中凡是用ethernet命名的函数,也一律用cs8900进行了替代。LwIP提供的ethernetif.c文件给出了网络接口驱动的整体框架,用户需要自己完成的函数主要有3个,分别是底层初始化函数low_level_init()底层输入函数low_level_input()底层输出函数low_
34、level_output()。,56,无RTOS时的移植,(1)底层初始化函数low_level_init()原型为staticvoidlow_level_init(structnetif*netif);该函数用来对网络接口进行初始化,任何与初始化网络接口有关的操作都可以在该函数内实现。如对网络接口有关参数进行配置,或是完成网络芯片硬件上所需的初始化操作等。(2)底层输入函数low_level_input()函数原型为staticstructpbuf*low_level_input(structnetif*netif);该函数为到达的数据包分配pbuf(通常是一个pbuf链),并将数据包从网络
35、接口传入至pbuf链中。数据具体接收过程的实现与网络接口硬件有关。将数据装载至pbbuf时,需对pbuf结构的各字段进行正确填充,使其形成逻辑上的pbuf链,57,无RTOS时的移植,通常,为收到的数据分配的pbuf是PBUF_POOL类型,因为分配一个PBUF_POOL可以很快完成。(3)底层输出函数low_level_output()函数原型为:staticerr_tlow_level_output(structnetif*netif,structpbuf*p);该函数实现真正的的数据包发送过程当需要发送数据包时,数据包装载在事先已分配好的pbuf(链)中。LwIP将pbuf作为参数传入给
36、该函数,由该函数负责将数据包发送至指定的网络接口中。数据具体发送过程的实现同样与网络接口硬件有关。,58,无RTOS时的移植,几个定时器函数在LwIP的移植过程中,注意有几个定时器函数必须每隔固定时间就调用一次。具体采用什么机制实现这一操作并无限制etharp_tmr()tcp_fasttmr()tcp_slowtmr()这些函数的运行间隔周期可以通过宏定义指定,各函数的具体定义可在LwIP提供的源码中找到。,59,LwIP在uC/OS-II下的移植,为了方便LwIP在RTOS下的移植,属于操作系统的函数调用及数据结构并没有在代码中直接使用,而是用操作系统模拟层来代替对这些函数的使用。操作系统
37、模拟层使用统一的接口提供定时器、进程同步及消息传递机制等诸如此类的系统服务原则上,移植LwIP只需针对目标操作系统修改模拟层实现即可。,60,LwIP在uC/OS-II下的移植,模拟层主要实现以下4大功能:定时与超时处理LwIP可以为某一线程注册若干个超时处理函数,当超时时限溢出时便会调用一个已注册的函数。进程同步进程同步机制为多个进程之间的同步操作提供支持,一般可以用信号量来实现。如果选用的RTOS不支持信号量,则可以使如条件变量等其它基本的同步方式来模拟。消息传递消息传递机制可通过一种称作邮箱的抽象方法来实现。邮箱有两种基本操作:向邮箱投递(post)一则消息和从邮箱中提取(fetch)一
38、则消息线程管理对LwIP协议栈的线程进行管理和维护,主要指创建线程。,61,移植相关文件与函数,移植过程中需要创建或修改的源文件和头文件位于目录src/arch之下目录组织结构如图所示:,62,移植相关文件与函数,头文件主要是一些宏定义,包括数据类型的定义和有关结构的封装等;而主要的功能函数均在sys_arch.c源文件中实现。此外,sys.c和sys.h两个文件虽无需作任何修改,但与以上文件(尤其是sys_arch.c)关联紧密,有助于更好地理解LwIP在RTOS下的移植实现。LwIP在设计时就考虑到了将来的RTOS移植问题。为了适应不同的操作系统,LwIP并没有在代码中使用针对某个特定RT
39、OS的系统调用和数据结构,而是提供操作系统模拟层作为LwIP和RTOS的一个接口。为了理解LwIP在RTOS下的移植实现过程,选用嵌入式实时操作系统uC/OS-II为例,对LwIP在uC/OS-II下的移植进行说明。uC/OS-II是专为嵌入式应用设计的实时内核,关于其详细信息可参考其官网,63,移植相关文件与函数,根据LwIP源码提供的sys_arch.txt文件,需要实现的函数有:voidsys_init(void)被调用来初始化操作系统模拟层。sys_sem_tsys_sem_new(u8_tcount)创建并返回一个新的信号量,参数count指定信号量的初始状态。voidsys_sem
40、_free(sys_sem_tsem)删除一个信号量。voidsys_sem_signal(sys_sem_tsem)发出一个信号量。u32_tsys_arch_sem_wait(sys_sem_tsem,u32_ttimeout)等待一个信号量,该操作会阻塞调用该函数的线程。sys_mbox_tsys_mbox_new(void)创建一个空的邮箱。voidsys_mbox_free(sys_mbox_tmbox)删除一个邮箱。,64,移植相关文件与函数,voidsys_mbox_post(sys_mbox_tmbox,void*msg)向指定的邮箱发送一则消息。u32_tsys_arch_m
41、box_fetch(sys_mbox_tmbox,void*msg,u32_ttimeout)从邮箱中提取一则消息,该操作同样会阻塞调用该函数的线程。structsys_timeouts*sys_arch_timeouts(void)返回指向当前线程的sys_timeouts结构的指针。LwIP的每个线程都有自己的超时等待属性,每个线程都分配了一个超时等待的数据结构sys_timeout,并把这个数据结构存放于链表sys_timeouts中。该函数的作用是通过查询来获得一个指向当前线程使用的sys_timeouts结构的指针。sys_thread_tsys_thread_new(void(*t
42、hread)(void*arg),void*arg,intprio)创建一个新的LwIP线程。,65,超时处理的实现,如前所述,LwIP的每个线程都有自己的超时等待属性。为了顺利理解LwIP的这一机制,先引入与之相关的几种数据结构。sys_timeout是线程的超时等待数据结构,其内部结构定义如下:structsys_timeoutstructsys_timeout*next;/指向链表中的下一个sys_timeoutu32_ttime;/超时时限(ms)sys_timeout_handlerh;/超时处理函数void*arg;/超时处理函数的参数;其中sys_timeout_handler是
43、指向超时处理函数的指针,其定义为typedefvoid(*sys_timeout_handler)(void*arg);多个sys_timeout可以链接成一个链表,如图所示:,66,超时处理的实现,sys_timeouts是sys_timeout链表的表头,它只包含一个元素,即指向sys_timeout结构的指针,其定义如下:structsys_timeoutsstructsys_timeout*next;/指向链表第一个sys_timeout;加上sys_timeouts结构后,LwIP线程的超时等待链表结构如图所示:,67,超时处理的实现,68,超时处理的实现,timeoutlist将一
44、个sys_timeouts结构和优先级联系在一起,这样便于根据当前优先级查找对应的sys_timeouts链表,其定义如下:structtimeoutliststructsys_timeoutstimeouts;/超时等待链表INT8Uprio;/优先级;每个线程都有一个对以的timeoutlist结构,通过该结构的timeouts元素可以定位超时等待列表的表头,从而确定该线程的所有超时处理函数,,69,超时处理的实现,LwIP在RTOS上的移植过程中需要实现的与超时处理有关的函数是sys_arch_timeouts()。(1)移植函数sys_arch_timeouts()sys_arch_t
45、imeouts()函数的作用是通过查询机制,获取指向当前线程的sys_timeouts结构的指针,相当于定位超时等待链表的表头。函数的原型如下:structsys_timeouts*sys_arch_timeouts(void);该函数表面上没有参数,但实际上调用该函数的线程有自己的优先级,因此可以利用当前线程的优先级充当函数的隐含参数。这样处理带来的限制是一个线程不能通过调用该函数来获取另一个线程的sys_timeouts结构,但这一般不会引起什么问题。,70,超时处理的实现,sys_timeouts结构和当前线程的优先级一起封装在timeoutlist结构中。为了存储线程的timeoutl
46、ist结构,在sys_arch.c文件中定义了一个timeoutlist数组:staticstructtimeoutlisttimeoutlistLWIP_MAX_TASKS;LWIP_MAX_TASKS是最大的LwIP线程数,可以事先进行配置。每次调用sys_thread_new()创建一个新的线程时,都会依序取出一个数组元素,用当前线程的优先级对数组元素的prio字段进行填充。sys_arch_timeouts()函数通过线性搜索的方法对数组元素进行遍历,直到发现某个数组元素的prio字段与当前优先级相同为止,而该数组元素的timeouts字段正是我们需要的目标。,71,超时处理的实现,7
47、2,超时处理的实现,(2)相关函数sys_timeout()sys_timeout()函数用以向当前线程增加一个超时处理函数,其原型如下:voidsys_timeout(u32_tmsecs,sys_timeout_handlerh,void*arg);sys_timeouts()函数首先从内存中申请一块空间,以存放一个sys_timeout结构。如申请成功则利用函数的实参对结构的各字段进行填充。要向当前线程注册一个超时处理函数,sys_timeouts()会通过sys_arch_timeouts()函数获取当前线程的sys_timeouts结构。,73,超时处理的实现,第一次调用sys_ti
48、meout()注册一个超时处理函数时,直接将sys_timeout结构链接在当前线程的sys_timeouts结构即可,如图所示:,74,超时处理的实现,应用程序可能会多次注册超时处理函数或删除超时处理函数,这样处理后一个线程的sys_timeouts链表中可能会同时存在多个sys_timeout结构。在这种情况下,向线程添加一个超时处理函数略微复杂,因为sys_timeout结构必须插入到链表的恰当位置。实际上如果一个线程有多个超时处理函数,LwIP会按照链表的逻辑顺序依次结算。这里所谓恰当的位置,就是比较当前链表节点的超时时限和待插入节点的超时时限,保证插入该节点后不会影响原有任一节点的超
49、时等待属性。,75,超时处理的实现,如图所示,假设当前线程已注册3个超时处理函数,对应有3个sys_timeout结构,其超时时限分别是time1=100,time2=40,time3=80。现要注册一个超时时限为time4=160的超时处理函数。为了确定恰当的插入位置,可以沿着链表逐次推算超时时限,分析过程如下:1time1time4next4在next3之前经以上步骤,next4的位置已经确定,即位于next2和next3之间。注意插入next4节点后,next4节点及紧挨在next4后面的next3节点的time属性值需做对应调整,调整后的结果如图所示:,76,超时处理的实现,77,超时
50、处理的实现,如果新节点next4的超时时限time4取其它值,则可能会出现一些特殊情况。time4time1,即新节点的time值比第一个节点还小。这种情况直接将next4节点插入到next1节点之前,并调整time1=time1-time4即可。,78,超时处理的实现,time4=time1+time2,即新节点的time值等于前面若干节点他time之和。这种情况next4将插入到next2的后面。next4节点的time值调整为0,而与next4相邻的next2节点和next3节点则无需调整time属性。线程在依序处理超时等待函数过程中,一旦执行完next2节点的超时处理函数h2(arg2
51、),会立即执行next4节点的超时处理函数h4(arg4)。time4time1+time2+time3,即新节点的time值大于现有所有节点time之和。这种情况next4节点将插入到最后,并调整time4=time4-time1-time2-time3,next4=NULL。,79,超时处理的实现,(3)相关函数sys_untimeout()sys_untimeout()函数与sys_timeout()函数的作用恰好相反,用以删除当前线程某一指定的超时处理函数。函数原型如下:voidsys_untimeout(sys_timeout_handlerh,void*arg);与sys_time
52、out()相比,sys_untimeout()函数同样会调用sys_arch_timeouts()获取当前线程的sys_timeouts链表结构。函数通过一种简单的线性搜索的方法,从表头开始遍历,直到找到一个超时处理函数h和参数arg均符合的sys_timeout结构。将该结构所在的节点从链表中删除,并调整紧挨其后的节点(如果有的话)的超时时限属性,最后释放该结构占用的内存。,80,超时处理的实现,假设某一线程的超时处理函数链表如图所示:,81,超时处理的实现,如果线程希望删除h2处理函数,即执行sys_untimeout(h2,arg2);next2节点将从原链表中删除,同时next3节点的
53、time3将会调整为time3=time3+time2。调整后的状态为:,82,超时处理的实现,但如果线程不是要删除h2处理函数,而是要删除最后一个超时处理函数,即执行sys_untimeout(h3,arg3);这种情况next3节点从链表中删除后,没有后续节点需要调整超时时限属性。结果如下:,83,超时处理的实现,(4)超时处理函数的使用在等待信号量或等待消息的过程中,LwIP会对超时等待链表中的超时处理函数进行处理。对一个线程来讲,要么通过调用sys_sem_wait()等待一个信号量,要么通过调用sys_mbox_fetch()等待一则消息,至少要采取一种方法阻塞当前线程,否则注册的超
54、时处理函数将无法正常执行。鉴于邮箱结构比信号量结构占用更多的资源,因此通常通过永久等待一个信号量来实现线程阻塞。如需每隔一定周期就执行一次某函数,则必须在超时处理函数中重新注册自己。例如需要每隔250ms就执行一次tcp_tmr(),通常可以采用下面的方式实现:/向当前线程注册一个超时处理函数sys_timeout(u32_t)OS_TICKS_PER_SEC/4,(sys_timeout_handler)TCP_Timer,NULL);/如需周期执行tcp_tmr(),则需在TCP_Timer()中重新注册自己voidTCP_Timer(void*p_arg)tcp_tmr();/每隔250
55、ms执行一次sys_timeout(u32_t)OS_TICKS_PER_SEC/4,(sys_timeout_handler)TCP_Timer,NULL);,84,进程同步的实现,进程同步机制是任务之间通信的一种重要方式,通常可以由信号量实现。uC/OS-II对信号量有较全面的支持,因此移植过程中比较方便实现。uC/OS-II实现了信号量和互斥型信号量,这里采用uC/OS-II的信号量实现。由于uC/OS-II支持信号量的各种操作,并且可以满足LwIP对信号量的要求,因此只需对相关结构和函数进行重新封装即可。,85,进程同步的实现,函数sys_sem_wait()该函数是由LwIP应用程序
56、调用的用来等待一个信号量的函数,但它会在等待信号量的过程中对当前线程的超时等待函数进行处理。函数原型如下:voidsys_sem_wait(sys_sem_tsem)该函数首先调用sys_arch_timeouts()获取当前线程的超时等待函数链表,以对超时等待链表中的超时处理函数依次结算。真正实现等待一个信号量的过程由sys_arch_sem_wait()完成。现仍以下面的超时等待链表为例,分析sys_sem_wait()在等待信号量过程中对超时处理函数的处理。,86,进程同步的实现,1执行sys_arch_sem_wait(sem,time1)。如超时溢出则表明在time1时间内一直未成功
57、等到信号量,此时执行超时处理函数h1(arg1),同时将next1节点从链表中删除,并转到步骤2。如在time1时间内成功等到信号量,则不论实际消耗的等待时间是多少,LwIP一律认为消耗时间为1ms,并调整time1=time1-1,此时转到步骤4。2执行sys_arch_sem_wait(sem,time2)。只有在time1时限耗尽的情况下,才会执行sys_arch_sem_wait(sem,time2)。与1类似,如未等到信号量则执行h2(arg2)并转到步骤3,否则调整time2=time2-1并转到步骤4。,87,进程同步的实现,3调用sys_arch_sem_wait(sem,ti
58、me3)。只有在next3节点前面的所有节点的超时时限均已耗尽的情况下,才会执行sys_arch_sem_wait(sem,time3)。由于next3已经是最后一个节点,因此不论成功等到信号量与否,均会转到步骤4。4sys_sem_wait()函数返回或作永久等待。如果成功等到了信号量,sys_sem_wait()函数返回。但如果超时等待链表中所有超时时限均已耗尽且所有超时处理函数均已执行后,仍未等到信号量,则sys_sem_wait()会调用sys_arch_sem_wait(0)一直等到信号量有效为止。,88,进程同步的实现,一种特殊情况是当前线程超时等待链表为空,也就是没有超时等待函数
59、。sys_sem_wait()会直接调用sys_arch_sem_wait(0)做永久等待。另一种情况是在等待信号量的过程中发现某一节点的超时时限time=0。表明该节点的超时时限已经耗尽,需立即执行节点对应的超时处理函数h(arg)。注意time=0与sys_timeouts链表为空是截然不同的:time=0只是表明该节点的超时时限已耗尽sys_timeouts为空则意味着当前线程没有任何函数需要做超时等待。,89,消息传递的实现,消息传递是任务之间通信的另一种重要方式,通常使用一种称为邮箱的抽象方法来实现。邮箱有两种基本的操作:邮递(post)(发送一则消息)操作不会阻塞进程提取(fetch)(等待一则消息)操作可能会阻塞进程。uC/OS-II提供了消息邮箱和消息队列两种机制,区别是消息邮箱一次只能处理一则消息,而消息队列可以存储多则消息。为了使LwIP更好地运作,采用uC/OS-II的
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 【正版授权】 ISO/IEC 23090-8:2025 EN Information technology - Coded representation of immersive media - Part 8: Network based media processing
- 【正版授权】 IEC 63119-1:2025 FR Information exchange for electric vehicle charging roaming service - Part 1: General
- 卒中相关知识课件
- 河南小升初鹤壁数学试卷
- 健康素养66项培训课件
- 贵阳市一模高三数学试卷
- 广西钦州市初二数学试卷
- 2025届广东省惠州市惠东县燕岭学校高一物理第二学期期末质量检测试题含解析
- 健康科普课件要求
- 2025年上海市华东师范大学二附中物理高二第二学期期末质量跟踪监视模拟试题含解析
- 2025年北京市中考招生考试数学真题试卷(真题+答案)
- 原创领袖的风采-易发久
- 沭阳如东中学教学工作十八条措施与有效教学的实施策略
- DB33∕642-2019 热电联产能效、能耗限额及计算方法
- 考试录用公务员笔试监考工作培训
- GM∕T 0036-2014 采用非接触卡的门禁系统密码应用指南
- 钱江杯优质工程检查表
- 内蒙古高中毕业生学籍表毕业生登记表学年评语表成绩单身体健康检查表完整版高中档案文件
- NMRV减速机说明
- 小升初火车过桥问题
- 动叶可调式轴流风机动叶调节原理图
评论
0/150
提交评论