1、LWIP的結(jié)構(gòu)
lwip是瑞典計(jì)算機(jī)科學(xué)院(SICS)的Adam Dunkels 開發(fā)的一個(gè)小型開源的TCP/IP協(xié)議棧。實(shí)現(xiàn)的重點(diǎn)是在保持TCP協(xié)議主要功能的基礎(chǔ)上減少對(duì)RAM 的占用。
LWIP(Light weight internet protocol)的主要模塊包括:配置模塊、初始化模塊、NetIf模塊、mem(memp)模塊、netarp模塊、ip模塊、udp模塊、icmp 模塊、igmp模塊、dhcp模塊、tcp模塊、snmp模塊等。下面主要對(duì)我們需要關(guān)心的協(xié)議處理進(jìn)行說明和梳理。
配置模塊:
配置模塊通過各種宏定義的方式對(duì)系統(tǒng)、子模塊進(jìn)行了配置。比如,通過宏,配置了mem管理模塊的參數(shù)。該配置模塊還通過宏,配置了協(xié)議棧所支持的協(xié)議簇,通過宏定制的方式,決定了支持那些協(xié)議。主要的文件是opt.h。
初始化模塊:
初始化模塊入口的文件為tcpip.c,其初始化入口函數(shù)為: void tcpip_init(void (* initfunc)(void *), void *arg)
該入口通過調(diào)用lwip_init()函數(shù),初始化了所有的子模塊,并啟動(dòng)了協(xié)議棧管理進(jìn)程。同時(shí),該函數(shù)還帶有回調(diào)鉤子及其參數(shù)。可以在需要的地方進(jìn)行調(diào)用。
協(xié)議棧數(shù)據(jù)分發(fā)管理進(jìn)程負(fù)責(zé)了輸入報(bào)文的處理、超時(shí)處理、API函數(shù)以及回調(diào)的處理,原型如下:
static void tcpip_thread(void *arg)
NetIf模塊:
Netif模塊為協(xié)議棧與底層驅(qū)動(dòng)的接口模塊,其將底層的一個(gè)網(wǎng)口設(shè)備描述成協(xié)議棧的一個(gè)接口設(shè)備(net interface)。該模塊的主要文件為netif.c。其通過鏈表的方式描述了系統(tǒng)中的所有網(wǎng)口設(shè)備。
Netif的數(shù)據(jù)結(jié)構(gòu)描述了網(wǎng)口的參數(shù),包括IP地址、MAC地址、link狀態(tài)、網(wǎng)口號(hào)、收發(fā)函數(shù)等等參數(shù)。一個(gè)網(wǎng)口設(shè)備的數(shù)據(jù)收發(fā)主要通過該結(jié)構(gòu)進(jìn)行。
Mem(memp)模塊:
Mem模塊同一管理了協(xié)議棧使用的內(nèi)容緩沖區(qū),并管理pbuf結(jié)構(gòu)以及報(bào)文的字段處理。主要的文件包括mem.c、memp.c、pbuf.c。
netarp模塊:
netarp模塊是處理arp協(xié)議的模塊,主要源文件為etharp.c。其主要入口函數(shù)為: err_t ethernet_input(struct pbuf *p, struct netif *netif)
該入口函數(shù)通過判斷輸入報(bào)文p的協(xié)議類型來決定是按照arp協(xié)議進(jìn)行處理還是將該報(bào)文提交到IP協(xié)議。如果報(bào)文是arp報(bào)文,該接口則調(diào)用etharp_arp_input,進(jìn)行arp請(qǐng)求處理。
如果是ip報(bào)文,該接口就調(diào)用etharp_ip_input進(jìn)行arp更新,并調(diào)用ip_input接口,將報(bào)文提交給ip層。
在該模塊中,創(chuàng)建了設(shè)備的地址映射arp表,并提供地址映射關(guān)系查詢接口。同時(shí)還提供了arp報(bào)文的發(fā)送接口。如下
err_t etharp_output(struct netif *netif, struct pbuf *q, struct ip_addr *ipaddr)
該接口需要注冊(cè)到netif的output字段,ip層在輸出報(bào)文時(shí),通過該接口獲取目標(biāo)機(jī)的MAC地址,組合最終報(bào)文后,由該接口調(diào)用底層設(shè)備的驅(qū)動(dòng)接口發(fā)送數(shù)據(jù)。
在etharp_output接口中,判斷報(bào)文類型,如果是廣播包或者組播包,就調(diào)用etharp_send_ip(組裝目標(biāo)mac和源mac)接口,etharp_send_ip調(diào)用netif結(jié)構(gòu)中的設(shè)備驅(qū)動(dòng)注冊(cè)的linkoutput鉤子函數(shù)發(fā)送最終報(bào)文。如果是單播包,etharp_output接口就調(diào)用etharp_query進(jìn)行ip地址和MAC地址的映射,來獲取到目標(biāo)機(jī)的MAC地址。并在etharp_query中調(diào)用etharp_send_ip來發(fā)送最終組合報(bào)文。
ip模塊:
ip模塊實(shí)現(xiàn)了協(xié)議的ip層處理,主要文件為ip.c。其主要入口函數(shù)為: err_t ip_input(struct pbuf *p, struct netif *inp)
該接口通過判斷輸入報(bào)文的協(xié)議類型,將其輸入到相應(yīng)的上層協(xié)議模塊中去。比如,將udp報(bào)文送到udp_input。
該模塊另外一個(gè)接口是輸入函數(shù),原型如下:
err_t ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest, u8_t ttl, u8_t
tos, u8_t proto)
該接口通過路由表或者傳輸ip后,調(diào)用netif的output字段函數(shù)鉤子發(fā)送報(bào)文。
udp模塊:
udp模塊實(shí)現(xiàn)了udp協(xié)議層的協(xié)議處理,主要文件為udp.c。該模塊通過PCB控制塊將應(yīng)用端口跟應(yīng)用程序做了綁定。在接收到新報(bào)文時(shí),分析其對(duì)應(yīng)的PCB,找到對(duì)應(yīng)的處理鉤子,進(jìn)行應(yīng)用的處理。主要入口函數(shù)為:
void udp_input(struct pbuf *p, struct netif *inp) 該模塊負(fù)責(zé)輸出的接口如下:
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
該模塊負(fù)責(zé)將一個(gè)PCB跟一個(gè)本地端口進(jìn)行綁定的接口如下:
err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port) 該模塊負(fù)責(zé)將一個(gè)PCB跟一個(gè)遠(yuǎn)端端口綁定的接口如下:
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
igmp模塊:
igmp模塊負(fù)責(zé)分組管理。其主要的接口函數(shù)如下:
void igmp_input(struct pbuf *p, struct netif *inp, struct ip_addr *dest) 該接口負(fù)責(zé)IGMP協(xié)議報(bào)文的處理,比如分析當(dāng)前報(bào)文是請(qǐng)求還是應(yīng)答。 err_t igmp_joingroup(struct ip_addr *ifaddr, struct ip_addr *groupaddr) 該接口將一個(gè)網(wǎng)口加入一個(gè)組。
err_t igmp_leavegroup(struct ip_addr *ifaddr, struct ip_addr *groupaddr) 該接口將一個(gè)網(wǎng)口從一個(gè)組中移出。
dhcp模塊:
dhcp模塊用于獲取設(shè)備ip地址的相關(guān)信息。其處理入口主要有這么幾個(gè):dpch的啟動(dòng)、dpch的接收?qǐng)?bào)文處理以及定時(shí)器模塊的處理。
主要的接口原型如下:
err_t dhcp_start(struct netif *netif)
該接口用于設(shè)備啟動(dòng)dhcp模塊,主要是客戶端的功能。該模塊實(shí)現(xiàn)設(shè)備dhcp描述結(jié)構(gòu)生成,并將dhcp的端口綁定到udp協(xié)議中,以及將本dhcp模塊跟遠(yuǎn)端服務(wù)器端口進(jìn)行綁定。最后啟動(dòng)dhcp申請(qǐng)。
static void dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port)
該接口為一個(gè)注冊(cè)接口,用于dhcp報(bào)文接收。在start dhcp時(shí),該接口通過dhcp的udp pcb注冊(cè)到udp協(xié)議層。Udp進(jìn)行報(bào)文處理后,根據(jù)端口調(diào)用該注冊(cè)接口。該接口中,實(shí)現(xiàn)dhcp報(bào)文的協(xié)議處理。
Void dhcp_fine_tmr() Void dhcp_coarse_tmr()
這兩個(gè)函數(shù)接口實(shí)現(xiàn)了dhcp的相關(guān)超時(shí)處理監(jiān)控。上面一個(gè)用于請(qǐng)求應(yīng)答超時(shí)處理。下面一個(gè)用于地址租用情況的到期處理。
從源碼分析看,上述的接口在應(yīng)用lwip的協(xié)議棧時(shí),需要重點(diǎn)關(guān)注。對(duì)于小內(nèi)存應(yīng)用的場合,該協(xié)議棧的內(nèi)存管理以及pbuf應(yīng)用部分需要自行改寫。
2、lwip特征
?。?)支持多網(wǎng)絡(luò)接口下的IP轉(zhuǎn)發(fā);
?。?)支持ICMP協(xié)議;
?。?)包括實(shí)驗(yàn)性擴(kuò)展的UDP(用戶數(shù)據(jù)報(bào)協(xié)議);
?。?)包括阻塞控制、RTT 估算、快速恢復(fù)和快速轉(zhuǎn)發(fā)的TCP(傳輸控制協(xié)議);
?。?)提供專門的內(nèi)部回調(diào)接口(Raw API),用于提高應(yīng)用程序性能;
?。?)可選擇的Berkeley接口API (在多線程情況下使用) 。
(7)在最新的版本中支持ppp
?。?) 新版本中增加了的IP fragment的支持。
?。?) 支持DHCP協(xié)議,動(dòng)態(tài)分配ip地址.
3、LWIP的協(xié)議流程
下面這張圖比較清楚的描述了lwip的報(bào)文處理流程,呵呵,借用一下。不過,其對(duì)netif-》output描述不夠。從代碼看,該output實(shí)際是arp層的輸出,最后通過arp層調(diào)用netif中的底層輸出接口發(fā)送報(bào)文。
四、LwIP的API的實(shí)現(xiàn)
LwIP的API的實(shí)現(xiàn)主要有兩部分組成:一部分駐留在用戶進(jìn)程中,一部分駐留在TCP/IP協(xié)議棧進(jìn)程中。這兩個(gè)部分間通過操作系統(tǒng)模擬層提供的進(jìn)程通信機(jī)制(IPC)進(jìn)行通信,從而完成用戶進(jìn)程與協(xié)議棧間的通信,IPC包括共享內(nèi)存、消息傳遞和信號(hào)量。通常盡可能多的工作在用戶進(jìn)程內(nèi)的API部分實(shí)現(xiàn),例如運(yùn)算量及時(shí)間開銷大的工作;TCP/IP協(xié)議棧進(jìn)程中的API部分只完成少量的工作,主要是完成進(jìn)程間的通訊工作。兩部分API之間通過共享內(nèi)存?zhèn)鬟f數(shù)據(jù),對(duì)于共享內(nèi)存區(qū)的描述是采用和pbuf類似的結(jié)構(gòu)來實(shí)現(xiàn)。綜上,可以用簡單的一段話來描述這種API實(shí)現(xiàn)的機(jī)制:API 函數(shù)庫中處理網(wǎng)絡(luò)連接的函數(shù)駐留在 TCP/IP 進(jìn)程中。位于應(yīng)用程序進(jìn)程中的API函數(shù)使用郵箱這種通訊協(xié)議向駐留在TCP/IP 進(jìn)程中的API函數(shù)傳遞消息。這個(gè)消息包括需要協(xié)議棧執(zhí)行的操作類型及相關(guān)參數(shù)。駐留在 TCP/IP 進(jìn)程中的API函數(shù)執(zhí)行這個(gè)操作并通過消息傳遞向應(yīng)用程序返回操作結(jié)果。
很早以前描述過結(jié)構(gòu)pbuf,它是協(xié)議棧內(nèi)部用來描述數(shù)據(jù)包的一種方式。這里介紹數(shù)據(jù)結(jié)構(gòu)netbuf,它是API用來描述數(shù)據(jù)的一種方式。netbuf是基于pbuf來實(shí)現(xiàn)的,其結(jié)構(gòu)如下所示,很明顯,它就是包含了幾個(gè)典型結(jié)構(gòu)的指針。
struct netbuf {
struct pbuf *p, *ptr;
struct ip_addr *addr;
u16_t port;
};
注意,這里的netbuf只是相當(dāng)于一個(gè)數(shù)據(jù)頭,而真正保存數(shù)據(jù)的還是字段p指向的pbuf鏈表。字段ptr也是指向該netbuf的pbuf鏈表,但與p的區(qū)別在于:p一直指向pbuf鏈表中的第一個(gè)pbuf結(jié)構(gòu),而ptr則不然,它可能指向鏈表中的其他位置,源文檔里面把它描述為fragment pionter,與該指針調(diào)整密切相關(guān)的函數(shù)是netbuf_next和netbuf_first。還有兩個(gè)字段addr和port分別表示發(fā)出netbuf數(shù)據(jù)端的IP地址和端口號(hào),這兩個(gè)字段實(shí)際用處似乎不大,API定義了宏netbuf_fromaddr和netbuf_fromport分別用于返回某個(gè)netbuf結(jié)構(gòu)中這兩個(gè)字段的值。
與netbuf相關(guān)的處理函數(shù)很多,在源文檔15.2節(jié)中對(duì)每個(gè)函數(shù)也有了詳細(xì)的說明,這里說說比較重要的幾個(gè)。netbuf_new用于分配一個(gè)新的netbuf結(jié)構(gòu),注意這里只是一個(gè)頭部結(jié)構(gòu),而真正需要的存儲(chǔ)數(shù)據(jù)區(qū)域是在函數(shù)netbuf_alloc中分配的,同理函數(shù)netbuf_delete用于刪除一個(gè)netbuf結(jié)構(gòu),同時(shí)函數(shù)pbuf_free會(huì)被調(diào)用,用以刪除數(shù)據(jù)區(qū)域的空間。以上這幾個(gè)函數(shù)使用的簡單例子如下:
struct netbuf *buf; // 申明指針
buf = netbuf_new(); // 申請(qǐng)新的netbuf
netbuf_alloc(buf, 200); // 為netbuf分配200 字節(jié)的空間
。。。。 // 使用buf做相關(guān)事情
netbuf_delete(buf); // 刪除buf
講過了API的內(nèi)存管理,再來講具體的API函數(shù)。與前面的對(duì)應(yīng),API函數(shù)由兩部分組成,分別在文件api_lib.c和api_msg.c中。前者包括了用戶程序直接調(diào)用的API接口函數(shù),后者包括了與協(xié)議棧進(jìn)程通信的API函數(shù),用戶程序不可直接調(diào)用。這兩部分API函數(shù)之間通過郵箱傳遞的消息進(jìn)行通信。這里又要涉及到兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu):API應(yīng)用接口部分提供給上層應(yīng)用以描述一個(gè)網(wǎng)絡(luò)連接的數(shù)據(jù)結(jié)構(gòu)netconn,以及描述兩部分API函數(shù)間傳遞的消息結(jié)構(gòu)的api_msg。
應(yīng)用程序要使用API函數(shù)建立一個(gè)連接連接,首先應(yīng)該建立一個(gè)netconn的結(jié)構(gòu)來描述這個(gè)連接的各種屬性。netconn結(jié)構(gòu)如下,其中去掉了不必要的編譯選項(xiàng)和注釋。
struct netconn {
enum netconn_type type; // 連接的類型,包括TCP, UDP等
enum netconn_state state; // 連接的狀態(tài)
union { // 共用體,內(nèi)核用來描述某個(gè)連接
struct ip_pcb *ip;
struct tcp_pcb *tcp;
struct udp_pcb *udp;
struct raw_pcb *raw;
} pcb;
err_t err; // 該連接最近一次發(fā)生錯(cuò)誤的編碼
sys_sem_t op_completed; // 用于兩部分API間同步的信號(hào)量
sys_mbox_t recvmbox; // 接收數(shù)據(jù)的郵箱
sys_mbox_t acceptmbox; // 服務(wù)器用來接受外部連接的郵箱
int socket; // 該字段只在socket實(shí)現(xiàn)中使用
u16_t recv_avail; //
struct api_msg_msg *write_msg; // 對(duì)數(shù)據(jù)不能正常處理時(shí),保存信息
int write_offset; // 同上,表示已經(jīng)處理數(shù)據(jù)的多少
netconn_callback callback; // 回調(diào)函數(shù),在發(fā)生與該netconn相關(guān)的事件時(shí)可以調(diào)用
};
write_msg是api_msg_msg類型的指針,該結(jié)構(gòu)后續(xù)講到;netconn_callback是API定義的一種函數(shù)指針類型,其定義為:
typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
關(guān)鍵字typedef的功能是定義新的類型。這里它用于定義一種netconn_callback的類型,這種類型為指向某種函數(shù)的指針,且這種函數(shù)有三個(gè)類型的輸入?yún)?shù),返回類型為void。在這定義以后就可以像使用int,char一樣使用netconn_callback,也即它也表示一種數(shù)據(jù)類型了。所以上面的callback字段表示一個(gè)函數(shù)指針,當(dāng)把某個(gè)函數(shù)名賦給該字段后,該字段就記錄了這個(gè)函數(shù)的起始地址。
netconn的其他字段后面用到時(shí)再詳解。接下來看看兩部分API函數(shù)間傳遞的消息結(jié)構(gòu)的api_msg:
struct api_msg {
void (* function)(struct api_msg_msg *msg); // 函數(shù)指針
struct api_msg_msg msg; // 函數(shù)執(zhí)行時(shí)需要的參數(shù)
}
字段function是一個(gè)函數(shù)指針,通常當(dāng)消息被構(gòu)造時(shí),該字段被填充為api_msg.c中的某個(gè)與協(xié)議棧接口的API函數(shù),然后該消息被投遞到協(xié)議棧進(jìn)程中進(jìn)行處理,協(xié)議棧進(jìn)程解析出并執(zhí)行該函數(shù),這樣應(yīng)用程序與協(xié)議棧之間的通信就完成了。字段msg中包含了這個(gè)函數(shù)執(zhí)行時(shí)需要的所有參數(shù),api_msg_msg是個(gè)枚舉類型,所以對(duì)于不同的function,msg有著不同的結(jié)構(gòu)。api_msg_msg結(jié)構(gòu)如下所示:
struct api_msg_msg {
struct netconn *conn; // 與消息相關(guān)的某個(gè)連接
union {
struct netbuf *b; // 函數(shù)do_send的參數(shù)
struct { // 函數(shù)do_newconn的參數(shù)
u8_t proto;
} n;
struct { // 函數(shù)do_bind和do_connect的參數(shù)
struct ip_addr *ipaddr;
u16_t port;
} bc;
struct { // 函數(shù)do_getaddr的參數(shù)
struct ip_addr *ipaddr;
u16_t *port;
u8_t local;
} ad;
struct { // 函數(shù)do_write的參數(shù)
const void *dataptr;
int len;
u8_t apiflags;
} w;
struct { // 函數(shù)do_recv的參數(shù)
u16_t len;
} r;
} msg;
};
這個(gè)結(jié)構(gòu)體只包含了兩個(gè)字段:描述連接信息的conn和枚舉類型msg。在api_msg_msg中保存conn字段是必須的,因?yàn)閏onn結(jié)構(gòu)中包含了與該連接相關(guān)的信箱和信號(hào)量等信息,協(xié)議棧進(jìn)程要用這些信息來完成與應(yīng)用進(jìn)程間的同步與通信。枚舉類型msg的各個(gè)成員與調(diào)用它的函數(shù)密切相關(guān)。因此在構(gòu)建一個(gè)消息時(shí),API應(yīng)首先填充消息的function字段,并針對(duì)特定的function種類往api_msg_msg的枚舉字段寫入?yún)?shù),函數(shù)function被協(xié)議棧解析執(zhí)行時(shí),直接按照自己已定的格式取參數(shù)。這意味著,對(duì)于構(gòu)造消息的API函數(shù)和解析消息的function函數(shù),它們對(duì)參數(shù)個(gè)數(shù)、結(jié)構(gòu)的認(rèn)識(shí)是預(yù)先約定的,不能有其他變數(shù)。這一點(diǎn)體現(xiàn)出LwIP呆板僵硬的一面。
評(píng)論
查看更多