lwIP(TCP/IP)協議棧移植(不包括網卡驅動)
一、lwIP 概述
lwIP是瑞士計算機科學院(Swedish Institute of Computer Science)的Adam Dunkels等開發的一套用於嵌入式系統的開放源代碼TCP/IP協議棧。Lwip既可以移植到操作系統上,又可以在無操作系統的情況下獨立運行.
LwIP的特性如下:
二、移植介紹
整個移植過程主要參考網路上關於移植到ucos 的說明和源碼。
1. 目錄及文件介紹
原版的lwIP1.1.0包含兩個目錄src 和 doc
移植后增加如下文件和目錄
[Arch]
Lib_arch.c本系統沒用,系統中沒有實現的C庫函數可以寫到這裡
Sys_arch.c 移植的主要工作在這裡,關於信號量、消息隊列、任務創建
[RX4000] 項目目錄
[Include]
[Arch]
cc.h 類型定義 大小端設置 PACK定義等
init.h
lib.h 跟Lib_arch.c對應 函數聲明
perf.h 沒用
sys_arch.h 跟Sys_arch.c對應的一些類型定義和宏定義
[Netif]
Dm9000a.h
Ne2kif.h
[Netif]
Dm_netif.c 網卡驅動與系統關聯的抽象層
Dm9000a.c網卡的硬體操作函數
Ne2kif.c 沒用
[Init]
Lwip.c 協議棧初始化和DHCP初始化
Lwipopts.h 協議棧相關參數設置
[Dns]
Dns.c 增加域名解析函數 gethostbyname (非可重入函數)
Dns.h
2. 移植相關函數介紹
1) sys_init
這個很簡單,就是一些全局量的初始化
2) sys_thread_new sys_arch_timeouts
相關的三個全局變數如下
struct sys_timeouts lwip_timeouts[LWIP_TASK_MAX];
為每一個由sys_thread_new創建的任務分配一個存放信號量超時信息的列表
struct sys_timeouts null_timeouts;
為一個超過任務上限數的任務和不是由sys_thread_new創建的任務取超時列表時返回使用。
MMAC_RTOS_TASK_ID LWIP_TASKS[LWIP_TASK_MAX];
任務id存放順序與lwip_timeouts相對應
sys_thread_new用來創建一個新的任務,保存任務ID。sys_arch_timeouts
就是通過取得任務ID返回任務對應的timeouts結構,從而可以添加、刪除和判斷超時的功能
3) sys_sem_new sys_sem_free sys_sem_signal sys_arch_sem_wait
sys_sem_new創建一個信號燈並初始化燈的數量返回sys_sem_t 類型的變數,定義是這樣的typedef MMAC_RTOS_SEMAPHORE *sys_sem_t; 由於返回失敗要返回NULL值所以就定義了系統信號量的指針為抽象信號量類型。因此在sys_sem_new和 sys_sem_free 分別要進行內存申請和釋放的工作。
sys_sem_signal釋放一個燈,sys_arch_sem_wait 等待信號,其中參數timeout是以ms為單位的,若wei零則表示永遠等待一直到信號的來臨。
在這個信號系統中本人還存在一個疑問,具體在5”存在的問題”中進行說明
4) sys_mbox_new sys_mbox_free sys_mbox_post sys_arch_mbox_fetch
同上原因在類型的定義成指針的。那sys_mbox_new 和sys_mbox_free同樣要進行內存的申請和釋放。在系統中消息隊列發送和接收的都是指向數據的指針,因為在發送前所有的數據都已經存放在一個全局的用來管理內存的變數中。所以發送的內容就是四個位元組。發送是還要判斷發送msg是否為NULL。因為發送的是msg的指針,而不是內容還要取一下地址,NULL明顯不能取址,所以有一個專門的static int *msg_null=NULL (這裡的=NULL 並不重要可以使任何值 * 也可以不要,因為要的是變數的地址在內存中的唯一性)用來發送“NULL”信息,使msg = &msg_null再發送。接收到后也要進行 *msg ==&msg_null的判斷。接收時也要進行msg NULL的判斷,若msg為NULL就需要零時申請一個空間進行接收。還要注意發送和接收時msg的類型,發送是void* 的 ,接收是void **,要做好相應的處理。
3. 移植中相關配置的介紹
1) SYS_LIGHTWEIGHT_PROT
我的理解應該是是否使用系統臨界區變數,由於本系統沒有單獨的臨界區變數,所以就設置成 0 ,那就用信號燈來完成該任務。且sys_arch.h中的最後三個宏也要定義成空。
2) 累加和
關閉所有的累加檢查,因為硬體已有該功能了
#define CHECKSUM_GEN_IP 0
#define CHECKSUM_GEN_UDP 0
#define CHECKSUM_GEN_TCP 0
#define CHECKSUM_CHECK_IP 0
#define CHECKSUM_CHECK_UDP 0
#define CHECKSUM_CHECK_TCP 0
3) LWIP_HAVE_LOOPIF
是否開啟迴環,在還沒有網卡驅動的時候,可以設置為 1 添加loop設備進行調試運行。
4) 內存分配設定
在協議棧中很多內存都是事先申請的,有協議棧自己進行管理。據我了解有三大塊內存PBUF MEMP MEM。在lwipopts.h中的Memory options中定義了各塊內存的種類及各種類的數量。這部分的設置要仔細斟酌。具體就不再詳述了。
5) TCP_SND_BUF
該設置對網路傳輸的速度由很大的影響。Ucos+lwIP的源碼中的默認設置是256,用socket進行速度測試時卻只有區區的1KB/S左右的速度。最後改成8192后速度達到 600KB/S。
6) LWIP_DHCP
本系統需要DHCP支持因此需要設置為 1。在他下面有一個DHCP_DOES_ARP_CHECK的宏設置為 0。 開啟后出現錯誤。原因不明。
4. 移植中碰到的問題總結
1) 同時支持UDP及TCP及DHCP的支持
不再詳述,看初始化代碼
void Task_lwip_init(void * pParam)
{
struct ip_addr ipaddr, netmask, gw;
sys_sem_t sem;
err_t result ;
int icount = 0;
int idhcpre=0;
#if LWIP_STATS
stats_init();
#endif
// initial lwIP stack
sys_init();
mem_init();
memp_init();
pbuf_init();
netif_init();
printf("LWIP:TCP/IP initializing...\n");
sem = sys_sem_new(0);
tcpip_init(tcpip_init_done_ok, &sem);
sys_sem_wait(sem);
sys_sem_free(sem);
printf("LWIP:TCP/IP initialized.\n");
/*
//add loop interface //set local loop-interface 127.0.0.1
IP4_ADDR(&gw, 127,0,0,1);
IP4_ADDR(&ipaddr, 127,0,0,1);
IP4_ADDR(&netmask, 255,0,0,0);
netif_add(&loop_if, &ipaddr, &netmask, &gw, NULL, loopif_init, tcpip_input);
netif_set_default(&loop_if);
netif_set_up(&loop_if);
//*/
#if 0
IP4_ADDR(&gw, 192,168,0,2);
IP4_ADDR(&ipaddr, 192,168,0,186);
IP4_ADDR(&netmask, 255,255,255,0);
netif_add(&dm9if_if, &ipaddr, &netmask, &gw, NULL, dm9_netif_init, tcpip_input);
netif_set_default(&dm9if_if);
netif_set_up(&dm9if_if);
#else
IP4_ADDR(&gw, 0,0,0,0);
IP4_ADDR(&ipaddr, 0,0,0,0);
IP4_ADDR(&netmask, 0,0,0,0);
netif_add(&dm9if_if, &ipaddr, &netmask, &gw, NULL, dm9_netif_init, udp_input);//添加udp支持
printf("LWIP:waiting for neif init \n");
MMAC_RTOS_Sleep( 3500);
for(idhcpre = 0; idhcpre<4; idhcpre++ )//dhcp最多重試4遍
{
printf("LWIP:start dhcp request \n");
result = dhcp_start(&dm9if_if);//廣播dhcp請求
IP4_ADDR(&ipaddr, 0,0,0,0);
for(icount = 0; (icount < 10) && (ipaddr.addr == 0); icount ++ )
{
ipaddr.addr = dm9if_if.ip_addr.addr;
MMAC_RTOS_Sleep( 1000);
} // if failed ipaddr = 0.0.0.0 ;timeout = 10 * 1000 ms
//等待dhcp是否接受到IP了
// add dns server ip
dns_add(0,&dm9if_if.dhcp->offered_dns_addr[0]);
dns_add(1,&dm9if_if.dhcp->offered_dns_addr[1]);
//不需要dns的去掉上面兩句
dhcp_stop(&dm9if_if); //一次dhcp結束
if (ipaddr.addr != 0)
break;
}
gw.addr = dm9if_if.gw.addr;
ipaddr.addr = dm9if_if.ip_addr.addr;
netmask.addr = dm9if_if.netmask.addr;
//netif_remove(&dm9if_if);
netif_add(&dm9if_if_tcp, &ipaddr, &netmask, &gw, NULL, dm9_netif_init, tcpip_input);//添加tcp支持
netif_set_up(&dm9if_if_tcp);
netif_set_up(&dm9if_if);
netif_set_default(&dm9if_if_tcp);
#endif
sprintf(STRIPADDR,"%d.%d.%d.%d",ip4_addr1(&ipaddr), ip4_addr2(&ipaddr),ip4_addr3(&ipaddr),ip4_addr4(&ipaddr));
printf("LWIP:IPADDR = %s\n",STRIPADDR);
if (ipaddr.addr != 0)
{
//------------------------------------------------------------
//
// http thread, a web page can be browsed
// sys_thread_new(httpd_init, (void*)"httpd",TCPIP_THREAD_PRIO);
//------------------------------------------------------------
sys_thread_new(test_net, NULL, TCPIP_THREAD_PRIO);
}
/* Block for ever. */
sem = sys_sem_new(0);
sys_sem_wait(sem);
}
2)對齊問題
PBUF_LINK_HLEN 16
static u8_t ip_reassbitmap[MEM_ALIGN_SIZE(IP_REASS_BUFSIZE / (8 * 8))];
在調試的時候經常碰到內存訪問錯誤的異常,最後查得原因是內存的起始地址不再4的倍數上,導致不能訪問。因為內存申請時有位元組數來的,有時要強制轉換為某種結構。為了保證地址不錯,PBUF_LINK_HLEN 定義為16,ip_reassbitmap的大小也變成4的倍數。因為它的大小不是4的倍數,就導致附近的內存分配起始不是4的倍數。這個解決辦法由點不好,但是沒有辦法,我用 align 等聲明沒有作用。
3)大包ping問題
原因是乙太網絡中,最大允許的包大小為1514位元組,若用pc機ping –l 2000 ip地址 測試,pc會把ip包分解成多個發送,lwIP接受後會把他數據合成方在pbuf中,並直接發送出去,可惜程序中不會把包分解發送。導致發送網路不允許的包。這樣不但pc接受不到包,而且lwIP也出現問題。
解決方法,在發送的地方,若包大於1514就不給發送。雖然解決不了大包ping不通問題,但至少lwIP不會死。
5. 存在的問題
操作系統中本身帶有的函數就已經有timeout參數了,用它多此一舉。但想想是不是為了沒有timeout的操作系統準備的呢。但它在運行過程中又沒有使用到,也沒有找到什麼代碼來確定某個sem已經超時,而僅僅使用了我使用嵌入式操作系統的timeout。
三、上層開發介面
1.Socket 介面 sockets.h
#define accept(a,b,c) lwip_accept(a,b,c)
#define bind(a,b,c) lwip_bind(a,b,c)
#define shutdown(a,b) lwip_shutdown(a,b)
#define close(s) lwip_close(s)
#define connect(a,b,c) lwip_connect(a,b,c)
#define getsockname(a,b,c) lwip_getsockname(a,b,c)
#define getpeername(a,b,c) lwip_getpeername(a,b,c)
#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)
#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)
#define listen(a,b) lwip_listen(a,b)
#define recv(a,b,c,d) lwip_recv(a,b,c,d)
#define read(a,b,c) lwip_read(a,b,c)
#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)
#define send(a,b,c,d) lwip_send(a,b,c,d)
#define sendto(a,b,c,d,e,f) lwip_sendto(a,b,c,d,e,f)
#define socket(a,b,c) lwip_socket(a,b,c)
#define write(a,b,c) lwip_write(a,b,c)
#define select(a,b,c,d,e) lwip_select(a,b,c,d,e)
#define ioctlsocket(a,b,c) lwip_ioctl(a,b,c)
2.Dns 客戶端 dns.h
struct hostent *gethostbyname(const char *name);
[admin via 研發互助社區 ] lwIP(TCP/IP)協議棧移植已經有8292次圍觀
http://cocdig.com/docs/show-post-42524.html