LWIP轻量级TCPIP协议栈的移植
http://blog.csdn.net/ygrx/article/details/8020516
好久没有做过技术工作了,前几天因为一些需要,要在ST的OS20平台上进行了LWIP的移植,有一些心得,写出来供大家参考。
LWIP的背景我就不介绍了,相信能看到这篇文章的人都对其背景有过了解了。
LWIP的模块化还是很强的,所以移植起来没有想象的那么多困难,一个协议栈在某个平台上移植,其实主要来说包括两个大的部分接口,注意一下,我使用的是最新的LWIP1.4.0。
1.与系统相关的接口,比如多线程,信号量,互斥锁,系统时间等,当然,LWIP还有一种无操作系统模式,那这些接口就不用实现了。
2.与硬件相关的接口,实际上就是网卡驱动啦,当然,如果你的物理层不是网卡而是别的什么,那就需要的是另外那个硬件设备的驱动啦,比如Docsis设备之类的。
好了,开始移植吧。
首先是操作系统部分:
1.opt.h
首先看这个文件,这个文件里面包含了LWIP的模块选项,可以在这里选择哪些模块需要编译,那些模块不编译,分成几个部分,mem,arp,icmp,igmp,ppp,dhcp等,这里可以根据自己的需要修改编译选项,如果是带操作系统的,还要修改栈空间,优先级之类的选项。
2.cc.h
这个文件是没有的,你需要建立一个目录arch,然后在下面添加cc.h,这里里面主要是一些定义,包括数据类型,大小头端之类的,我的cc.h比较简单
/*数据类型宏定义*/
typedef unsigned char u8_t;
typedef unsigned short u16_t;
typedef unsigned int u32_t;
typedef char s8_t;
typedef short s16_t;
typedef int s32_t;
typedef int mem_ptr_t;
typedef semaphore_t sys_sem_t;
typedef mutex_t sys_mutex_t;
typedef message_queue_t sys_mbox_t;
typedef u8_t sys_thread_t;
#define BYTE_ORDER LITTLE_ENDIAN //大小头端选择
/*调试信息宏定义*/
#define U16_F "hu"
#define S16_F "d"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
#define SZT_F "uz"
就这些。
3.arch.h
这个地方需要注意就是字节对齐,字节对齐在某些编译器上是用的#pragam pack(n)来实现的,那需要把相应的宏定义修改:
#ifndef PACK_STRUCT_BEGIN
#define PACK_STRUCT_BEGIN #pragma pack(1)
#endif /* PACK_STRUCT_BEGIN */
#ifndef PACK_STRUCT_END #pragma pack()
#define PACK_STRUCT_END //#
而ST的st2cc编译器是在编译的时候加了个参数来进行字节对齐的,所以这里不用修改
4.sys.h
这个文件就比较关键了,这是你移植过程中要写最多代码的地方,在LWIP中,如果是有操作系统的编译版本,那么需要实现线程和与线程通信的相关代码,其中包括:线程建立,互斥锁,信号量,消息队列(一般叫MailBox),系统当前时间,需要实现的东西都在sys.h中,一般情况下,我们为了文件清晰,会在arch目录下新建一个文件sys_arch.c,当然,你也可以直接在根目录下建立这个文件,注意的是要在该文件中include sys.h,然后你就开始挨个实现sys.h中的系统函数吧,实际上也挺简单,就是把系统的API调用封装成LWIP的模式。
线程部分:
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio) 线程建立
互斥锁:
err_t sys_mutex_new(sys_mutex_t *mutex) 新建锁
void sys_mutex_lock(sys_mutex_t *mutex) 获取锁
void sys_mutex_unlock(sys_mutex_t *mutex) 释放锁
void sys_mutex_free(sys_mutex_t *mutex) 删除锁
int sys_mutex_valid(sys_mutex_t *mutex) 查询锁是否可用
void sys_mutex_set_invalid(sys_mutex_t *mutex) 设置锁为失效
信号量:
err_t sys_sem_new(sys_sem_t *sem, u8_t count) 新建信号量
void sys_sem_signal(sys_sem_t *sem) 发送信号量
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) 等待信号量
void sys_sem_free(sys_sem_t *sem) 删除信号量
int sys_sem_valid(sys_sem_t *sem) 查询是否可用
void sys_sem_set_invalid(sys_sem_t *sem) 设置为失效
消息队列(MailBox):
这个东西稍微复杂一点,如果操作系统的API中没有现成的消息队列机制,那么你需要自己实现,其实无非就是个链表的带锁操作,在OS20中是自己带有MailBox的API的,所以我也没自己实现,直接使用了,需要实现的函数在sys.h中也都有,我就不列举了。
系统时间:
这个我返回的是系统调用的系统时间,就是系统启动后多少秒
u32_t sys_now(void)
好了,这几个文件都实现完了,那基本上操作系统部分就差不多了,我的建议是,在opt.h和cc.h修改完以后,你就开始编译,如果有错误几乎都应该是没有include某个文件造成的,及时修改错误,然后直到编译得没有错误了,再来添加sys_arch.c,这样可以减少一些麻烦。
总得来说就是,首先,你要修改opt.h,把你需要的模块添加上去,把栈空间,mailbox大小之类的定义好,然后编译,因为LWIP是标准的C写的,理论上应该很容易编译成功,我只是有两个地方没有.h文件,添加一下就编译过了。
当然,编译过了只是第一步,完了以后就要手写sys_arch.c了,sys.h里面的所有函数都要实现,当然,你要偷懒的话有些也可以直接返回。这个文件是和你系统API相关的,写好以后记得加入到Makefile里面,然后再编译,找错误,编译,直到成功。
其实还有临界保护,主要是
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev)
#define SYS_ARCH_UNPROTECT(lev)
这三个宏,定义成你的系统的临界保护调用就行了,我偷懒定义的空的,暂时没发现问题。
好了,操作系统部分大概就这么一些,接下来就是底层的硬件驱动了,驱动的具体实现我就不说了,各自的平台不一样,网卡硬件也不一样,但是基本的三个东西一定要有,1.初始化网卡,2.接收数据,3.发送数据。
在LWIP1.4.0中,其实已经跟我们写好驱动的框架,我们所需要做的就是填充这个框架。框架对应的文件是:ethernetif.c
打开这个文件后你会发现这个文件被#if 0包起来了,首先改成#if 1,这样,你会发现这个文件中有如下几个函数:
static void low_level_init(struct netif *netif) //底层硬件初始化
static err_t low_level_output(struct netif *netif, struct pbuf *p) //底层硬件发送数据函数
static struct pbuf * low_level_input(struct netif *netif) //底层硬件接收数据函数
err_t ethernetif_input(struct pbuf *pp,struct netif *netif) //硬件抽象层接收数据
err_t ethernetif_init(struct netif *netif) //硬件抽象层初始化
这些函数里面已经实现了一部分代码,主要是硬件抽象层的代码,我们需要把具体的驱动程序添加进去,我们按调用的顺序来说一下:
当LWIP协议栈启动的时候,协议栈本身会首先调用ethernetif_init,然后在ethernetif_init函数中会调用low_level_init,在low_level_init中实际上就是你的网卡的初始化程序了,当然,为了更加结构化,你可以另外写一个函数比如DM9003_init,在low_level_init调用他,总之low_level_init中就要实现你的网卡初始化,参数就是netif,用来告诉你这个设备需要绑定一个句柄。
当协议栈要有数据发送出去的时候,协议栈会自动调用low_level_output,参数包括netif,这是的句柄,用来区分应该从哪个网卡发走,p就是数据包内容。
当有数据进来的时候,这时就和你的实现有关了,如果你底层获取数据是中断形式,那么就是中断来了以后在中断中调用ethernetif_input,然后在ethernetif_input会调用low_level_input去硬件获取数据,然后把数据通过消息队列的形式发给LWIP协议栈,所以这一部分中,low_level_input是需要实现的实际的硬件接收数据驱动程序,并且,需要驱动在适当的时候调用ethernetif_input来把数据传给上层协议栈,参数还是netif,实际上这个netif一直是个全局变量,稍后我们会说在哪里声明他。
数据接收这一部分有很大的灵活性,总的原则就是当数据确实的到达以后,由驱动在适当的时候调用ethernetif_input,而low_level_input是具体的获取数据函数,完了ethernetif_input会简单的判断一下数据类型然后决定是否传到上层协议栈。
由于OS20的速度有限,在中断中直接调用ethernetif_input时间消耗很长,我的实现是,在初始化时启动了中断模式,并且在初始化时同时启动了一个轮询线程,中断来了数据以后直接把网卡的数据存到内存中去,然后给轮询的线程发一个信号量,轮询线程先阻塞在信号量上,收到信号以后,查询该内存块,有数据的话就在线程中调用ethernetif_input把数据发给上层协议栈,然后重新阻塞住。
好了,至此,底层部分结构也说完了,现在让我们看看初始化,LWIP是如何工作的,其实上面两部分移植好了以后,要使用LWIP很简单,只有两个步骤。
1.找到netif.c,在前面声明一个全局变量: struct netif eth_netif;
然后找到void netif_init(void)函数,在该函数最后面加上(静态IP):
ip_addr_t loop_ipaddr, loop_netmask, loop_gw;
IP4_ADDR(&loop_gw, 192,168,3,1); 设置网关的IP
IP4_ADDR(&loop_ipaddr, 192,168,3,100);设置本机IP
IP4_ADDR(&loop_netmask, 255,255,255,0);设置子网掩码
/*将IP绑定到eth_netif上,并且告诉协议栈,初始化网卡的函数是ethernetif_init,网卡接收到数据以后调用tcpip_input来处理数据*/
netif_add(ð_netif, &loop_ipaddr, &loop_netmask, &loop_gw, 0, ethernetif_init, tcpip_input);
netif_set_up(ð_netif); 启动该网络设备
这里的eth_netif就是上一部分中的netif参数,设置成一个全局变量,驱动就能用了,具体是怎么对接的仔细看看netif_add就明白了,tcpip_input函数是协议栈自带的,不用管,填上就行。
2.在你的main函数里调用tcpip_init();记得include "tcpip.h"。
OK,结束。
如果你按这个步骤一步一步来的话,并且驱动程序正确,那么这时候你应该可以ping通你的设备了,接下来你可以试试网络上传下载的速度啦,后面的东西你就自己微调吧,怎么样,够简单吧?
LWIP轻量级TCPIP协议栈的移植的更多相关文章
- LwIP学习笔记——STM32 ENC28J60移植与入门
0.前言 去年(2013年)的整理了LwIP相关代码,并在STM32上"裸奔"成功.一直没有时间深入整理,在这里借博文整理总结.LwIP的移植过程细节很多,博文也不可能一一 ...
- LWIP在STM32上的移植
本文做记录摘抄,加上自己的体会: 文章标题:STM32使用LWIP实现DHCP客户端 http://www.cnblogs.com/dengxiaojun/p/4379545.html 该文章介绍了几 ...
- LWIP network interface 即 LWIP 的 硬件 数据 接口 移植 首先 详解 STM32 以太网数据 到达 的第一站: ETH DMA 中断函数
要 运行 LWIP 不光 要实现 OS 的 一些 接口 ,还要 有 硬件 数据 接口 移植 ,即 网线上 来的 数据 怎么个形式 传递给 LWIP ,去解析 做出相应的 应答 ,2017 ...
- LWIP network interface 即 LWIP 的 硬件 数据 接口 移植 详解 STM32 以太网数据 到达 的第二站: void ethernetif_input( void * pvParameters )
根据 上一篇 文章 , ETH DMA 数据中断 会 发送 一个信号量 ,我使用 全局 搜索 这个信号量 s_xSemaphore 得到 一下 几个 值 根据 这个 分析 我们找到了 数据 的 ...
- 【lwip】04-网络数据包流向
目录 前言 4.1 TCPIP分层与lwip数据共享 4.2 协议栈线程模型 4.3 pbuf 结构体 4.3.1 pbuf的标志位flags 4.4 pbuf的类型 4.4.1 PBUF_RAM类型 ...
- lwip移植到stm32上-enc28j60,103mcu(2)
前面小玩了一下ucos和lwip,但是都还不是真正的网络多任务,真正的网络多任务应该是什么样子的呢?应该是有一个专门的任务负责网络的通讯,他负责将数据发送出去,将数据接收回来,而其他的需要用到网络的任 ...
- Lwip:原生态的Linux socket应用如何移植到Lwip上
lwIP - A Lightweight TCP/IP stack 在上一篇中,我们了解到在OpenFastPath上如何移植原生态的Linux Socket应用程序,那么,对于另外一个老牌的小型TC ...
- LWIP移植文件介绍
在介绍文件之前首先介绍一下DMA描述符 stm32以太网模块接收/发送FIFO和内存之间的以太网传输是通过以太网DMA使用DMA描述符完成的,一共有两个描述符列表:一个用于接收,一个用于发送, 两个列 ...
- LwIP应用开发笔记之一:LwIP无操作系统基本移植
现在,TCP/IP协议的应用无处不在.随着物联网的火爆,嵌入式领域使用TCP/IP协议进行通讯也越来越广泛.在我们的相关产品中,也都有应用,所以我们结合应用实际对相关应用作相应的总结. 1.技术准备 ...
随机推荐
- SLD 官方实例
基于xml标准的sld格式: <?xml version="1.0" encoding="UTF-8"?> <StyledLayerDescr ...
- curd 插件
1. Django项目启动 自动加载文件 制作启动文件 . 注册strak 在apps.py 类里面增加如下 def ready(self): from django.utils.module_loa ...
- Codefroces 919D Substring(拓扑排序+DP)
题目链接:http://codeforces.com/problemset/problem/919/D 题目大意:给你一张有向图,给你每个顶点上的字母和一些边,让你找出一条路径,路径上的相同字母数最多 ...
- HDU 2181 哈密顿绕行世界问题 (DFS)
题目链接:https://vjudge.net/contest/185350#problem/C 题目大意:一个规则的实心十二面体,它的 20个顶点标出世界著名的20个城市,你从一个城市出发经过每个城 ...
- 下划线css
/* <div class="text">header</div> */ .text { /* 作用元素 */ display: inline-block; ...
- 安卓使用WebView清除缓存
原文:https://blog.csdn.net/liwei123liwei123/article/details/52624826 Android 清除WebView缓存 最近项目中需要用WebVi ...
- [实战]MVC5+EF6+MySql企业网盘实战(24)——视频列表
写在前面 上篇文章实现了文档列表,所以实现视频列表就依葫芦画瓢就行了. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) ...
- jQuery 最简化实现
window.jQuery = (selector) => { let nodes = {}; if (typeof selector === 'string') { //是字符串 let te ...
- Python全栈开发之7、模块和几种常见模块以及format知识补充
一.模块的分类 Python流行的一个原因就是因为它的第三方模块数量巨大,我们编写代码不必从零开始重新造轮子,许多要用的功能都已经写好封装成库了,我们只要直接调用即可,模块分为内建模块.自定义的模块. ...
- loadrunner中Run-time-Setting设置