版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第九章LwIP及其網(wǎng)絡(luò)編程應(yīng)用實例第九章LwIP及其網(wǎng)絡(luò)編程應(yīng)用實例LwIP介紹LwIP(LightWeightInternetProtocol)是瑞典計算機科學(xué)院(SwedishInstituteofComputerScience)的AdamDunkels等人開發(fā)的一套用于嵌入式系統(tǒng)的開源TCP/IP協(xié)議棧。LwIP的含義是輕型IP協(xié)議,其實現(xiàn)的重點是在保持TCP協(xié)議主要功能的基礎(chǔ)上減少對RAM的占用,這使得LwIP協(xié)議棧非常適合在小型嵌入式系統(tǒng)中使用。LwIP介紹LwIP(LightWeightInternLwIP介紹LwIP的版本較多,較新的版本通常完善或增加了LwIP的功能。LwIP有如下特點:IP:支持多網(wǎng)絡(luò)接口下的IP轉(zhuǎn)發(fā)ARP:支持ARP協(xié)議ICMP:支持ICMP協(xié)議UDP:支持UDP協(xié)議TCP:支持TCP協(xié)議,包括擁塞控制、RTT估算和快速恢復(fù)/快速重傳RawAPI:提供專門的內(nèi)部回調(diào)函數(shù),以提高應(yīng)用性能SocketAPI:可選的Berkeley-likesocketAPILwIP的較新版本還提供對以下功能或協(xié)議的支持:IPfragment:IP分片DNS:域名解析SNMP:簡單網(wǎng)絡(luò)管理協(xié)議DHCP:動態(tài)主機配置協(xié)議PPP:點對點協(xié)議IPv6LwIP介紹LwIP的版本較多,較新的版本通常完善或增加了LLwIP源碼的文件組織LwIP文件目錄的組織結(jié)構(gòu)如圖所示,其源代碼全部位于目錄src下。src目錄下一般有5個子目錄LwIP提供的api子目錄、core子目錄、include子目錄和netif子目錄需用戶自己創(chuàng)建的arch目錄。LwIP源碼的文件組織LwIP文件目錄的組織結(jié)構(gòu)如圖所示,其LwIP源碼的文件組織每個子目錄包含的某一類相關(guān)的文件,簡要說明如下:api目錄應(yīng)用程序接口文件。arch目錄與硬件和OS有關(guān)的文件,包括網(wǎng)絡(luò)驅(qū)動、移植需要修改的文件。core目錄LwIP的核心代碼,包括ICMP、IP、UDP、TCP等協(xié)議的實現(xiàn)等。include目錄LwIP的包含文件。netif目錄ARP協(xié)議和LwIP網(wǎng)絡(luò)設(shè)備驅(qū)動程序的模板,提供了網(wǎng)絡(luò)接口驅(qū)動程序的基本框架。LwIP源碼的文件組織每個子目錄包含的某一類相關(guān)的文件,簡要LwIP的軟件體系結(jié)構(gòu)LwIP的協(xié)議層次:LwIP也是以4層TCP/IP模型為參照來實現(xiàn)TCP/IP協(xié)議族的。每一個協(xié)議作為一個模塊被實現(xiàn),同時還提供了幾個函數(shù)作為協(xié)議的入口點。LwIP并沒有嚴格地按照分層的方式實現(xiàn)協(xié)議族。實際上LwIP使用的是一種比較松散的通訊機制,通過共享內(nèi)存的方式實現(xiàn)應(yīng)用層與底層協(xié)議族之間的通訊。LwIP擁有獨特的緩沖機制,使得各層次可以更加有效的重復(fù)使用緩沖區(qū)。LwIP盡量避免內(nèi)存復(fù)制,避免了內(nèi)存復(fù)制產(chǎn)生的性能損失。LwIP的軟件體系結(jié)構(gòu)LwIP的協(xié)議層次:LwIP的軟件體系結(jié)構(gòu)與LwIP的協(xié)議層次相匹配,LwIP采用模塊化設(shè)計的方法實現(xiàn)。TCP/IP協(xié)議的實現(xiàn)模塊如ARP、IP、ICMP、UDP、TCP等許多相關(guān)支持模塊。這些支持模塊包括操作系統(tǒng)模擬層、緩沖與內(nèi)存管理子系統(tǒng)、網(wǎng)絡(luò)接口函數(shù)等。LwIP的軟件體系結(jié)構(gòu)與LwIP的協(xié)議層次相匹配,LwIP采LwIP的進程模型TCP/IP協(xié)議族的進程模型指的是采用何種方法把系統(tǒng)分成不同的進程。常見的進程模型有兩種:每一個協(xié)議作為一個獨立的進程協(xié)議棧作為一個內(nèi)核只占據(jù)一個進程。第一種模型必須符合協(xié)議的每一層,協(xié)議層之間通過指定的方式進行通訊。優(yōu)點較明顯,即每一種協(xié)議都可以獨立參與到系統(tǒng)運行中,其實現(xiàn)的代碼也比較簡單,整個協(xié)議棧的層次脈絡(luò)清晰,便于理解和調(diào)試。缺點也是顯而易見的,即數(shù)據(jù)跨層傳遞時不得不產(chǎn)生進程切換以及內(nèi)存復(fù)制。這一缺點極大影響了系統(tǒng)的整體性能,尤其對于嵌入式系統(tǒng)來說更是不能忍受的。第二種模型將協(xié)議棧駐留在操作系統(tǒng)內(nèi)核中,應(yīng)用程序通過系統(tǒng)調(diào)用與協(xié)議棧進行通訊。這種設(shè)計可以使用交叉協(xié)議分層技術(shù),各層協(xié)議不必嚴格劃分。這種進程模型的缺點是層次不清,給理解增加了難度。LwIP的進程模型TCP/IP協(xié)議族的進程模型指的是采用何種LwIP的進程模型LwIP則采用一種比較靈活的設(shè)計方法。它可以將所有的協(xié)議駐留在一個進程,以便獨立于操作系統(tǒng)內(nèi)核之外。應(yīng)用程序既可以駐留在LwIP的進程中,也可以使用一個單獨的進程。它也可以根據(jù)協(xié)議層次結(jié)構(gòu)創(chuàng)建多個進程,但各個進程之間只傳送盡可能少的必要信息,而沒有引入額外的內(nèi)存復(fù)制LwIP在協(xié)議層之間切換時,一般只傳遞數(shù)據(jù)緩沖區(qū)的地址,讓需要處理數(shù)據(jù)的協(xié)議層自己去提取。LwIP的進程模型LwIP則采用一種比較靈活的設(shè)計方法。LwIP的函數(shù)調(diào)用關(guān)系為了盡量避免不必要的內(nèi)存復(fù)制,LwIP更多的是采用一種基于回調(diào)函數(shù)的設(shè)計方法。當(dāng)數(shù)據(jù)需要處理或跨層傳遞時,通常是通過調(diào)用事先已定義好的回調(diào)函數(shù)來完成有關(guān)操作。優(yōu)點是大大提高了LwIP的整體性能;缺點是使得LwIP的整個軟件體系顯得略微復(fù)雜,尤其是函數(shù)之間的調(diào)用關(guān)系更為繁瑣。為了理清LwIP的函數(shù)調(diào)用關(guān)系,從兩個不同的方向?qū)@一問題進行分析:從不同的協(xié)議層出發(fā),橫向分析各個層次內(nèi)的調(diào)用關(guān)系;從幾種典型的協(xié)議模塊出發(fā),縱向分析各模塊的跨層調(diào)用關(guān)系。LwIP的函數(shù)調(diào)用關(guān)系為了盡量避免不必要的內(nèi)存復(fù)制,LwIP整體調(diào)用關(guān)系圖給出了LwIP的整體調(diào)用關(guān)系,基本上涵蓋了LwIP的主要功能模塊和絕大部分的函數(shù)調(diào)用。圖中只標注了對LwIP的整個軟件體系起著重要支撐作用的主干函數(shù)整體調(diào)用關(guān)系圖給出了LwIP的整體調(diào)用關(guān)系,基本上涵蓋了Lw協(xié)議層內(nèi)的調(diào)用TCP/IP協(xié)議棧是按功能層組織的,每一層都為上一層提供服務(wù),并使用下一層提供的服務(wù)。在4層TCP/IP模型中,從下至上依次是網(wǎng)絡(luò)接口層、網(wǎng)際層、運輸層和應(yīng)用層。(1)網(wǎng)絡(luò)接口層網(wǎng)絡(luò)接口層是較高協(xié)議與局域網(wǎng)接口的地方。當(dāng)主機通過查詢或者中斷方式得知網(wǎng)絡(luò)芯片接收到數(shù)據(jù)幀時,LwIP協(xié)議棧對該數(shù)據(jù)幀進行解碼,并判斷數(shù)據(jù)幀的協(xié)議類型:如果是IP協(xié)議,則將該幀傳遞給上層(網(wǎng)際層)的ip_input()函數(shù)進行處理;如果是ARP協(xié)議,則直接傳給本層的arp_input()函數(shù),該函數(shù)根據(jù)需要決定是否調(diào)用arp_replay()進行ARP應(yīng)答。當(dāng)上層有數(shù)據(jù)需要通過網(wǎng)絡(luò)接口層進行發(fā)送時,當(dāng)前網(wǎng)絡(luò)接口的輸出函數(shù)netif->output()將會被調(diào)用,以完成真正的數(shù)據(jù)發(fā)送過程。協(xié)議層內(nèi)的調(diào)用TCP/IP協(xié)議棧是按功能層組織的,每一層都為協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用(2)網(wǎng)際層網(wǎng)際層負責(zé)網(wǎng)間尋址(IP地址)、數(shù)據(jù)封裝、路由選擇、錯誤處理和診斷等典型協(xié)議有IP協(xié)議和ICMP協(xié)議。當(dāng)從下層(網(wǎng)絡(luò)接口層)接收到IP數(shù)據(jù)報時,調(diào)用ip_input()函數(shù)進行處理。根據(jù)IP數(shù)據(jù)報的協(xié)議字段,LwIP決定將該數(shù)據(jù)報傳給上層(運輸層)還是傳給本層。如果IP凈荷中承載的是ICMP協(xié)議,則本層的icmp_input()函數(shù)將會調(diào)用。當(dāng)不論是上層還是本層有數(shù)據(jù)需要從網(wǎng)際層發(fā)送出去時LwIP將會調(diào)用ip_output()發(fā)送數(shù)據(jù),或者先調(diào)用ip_route()找到一個合適的網(wǎng)絡(luò)接口再調(diào)用ip_output_if()發(fā)送數(shù)據(jù)。實際上ip_output()也是通過先調(diào)用ip_route()再調(diào)用ip_output_if()來實現(xiàn)的協(xié)議層內(nèi)的調(diào)用(2)網(wǎng)際層協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用(3)運輸層運輸層負責(zé)在網(wǎng)際設(shè)備之間運輸數(shù)據(jù),以可靠或不可靠的方式進行。TCP和UDP。當(dāng)下層(網(wǎng)際層)有數(shù)據(jù)傳給運輸層時LwIP會根據(jù)數(shù)據(jù)類型的不同(是TCP還是UDP)調(diào)用該層的tcp_input()或者udp_input()。經(jīng)過一定處理后,LwIP將數(shù)據(jù)由tcp_receive()或udp_input()提交給上層(應(yīng)用層),一般會調(diào)用事先注冊的接收函數(shù)。當(dāng)上層需要發(fā)送數(shù)據(jù)時LwIP選擇調(diào)用tcp_write()或者udp_send()對數(shù)據(jù)進行處理最后通過tcp_output()或udp_send()將數(shù)據(jù)交給下層。協(xié)議層內(nèi)的調(diào)用(3)運輸層協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用協(xié)議層內(nèi)的調(diào)用(4)應(yīng)用層用戶的應(yīng)用運行在應(yīng)用層,該層使用戶可以根據(jù)自己的需要對數(shù)據(jù)進行處理。用戶需要發(fā)送數(shù)據(jù)時由LwIP根據(jù)數(shù)據(jù)類型(TCP或UDP)調(diào)用下層(運輸層)對應(yīng)的發(fā)送函數(shù)。應(yīng)用層并不需要直接關(guān)注數(shù)據(jù)是怎樣發(fā)送出去的。用戶接收的數(shù)據(jù)一般由LwIP調(diào)用下層的接收函數(shù)送達,此后用戶可以根據(jù)實際情況實現(xiàn)應(yīng)用程序。協(xié)議層內(nèi)的調(diào)用(4)應(yīng)用層典型模塊的跨層調(diào)用對于某一個協(xié)議來說它一般只隸屬于某一個層次(ARP除外)。但往往會有其它層次調(diào)用該協(xié)議的有關(guān)函數(shù)而該協(xié)議一般也會主動調(diào)用其它層次的有關(guān)函數(shù)。
(1)IP模塊LwIP的較早期版本實現(xiàn)了IP層大部分的基本功能,能夠發(fā)送、接收以及轉(zhuǎn)發(fā)信息包。接收信息包由網(wǎng)絡(luò)設(shè)備驅(qū)動調(diào)用ip_input()函數(shù)開始處理。完成對IP版本字段及包頭長度的初始完整性檢查同時還要計算和驗證包頭校驗和函數(shù)檢查目的地址是否與網(wǎng)絡(luò)接口的IP地址相符以確定信息包是否到達預(yù)定主機。如果一個到達的信息包被發(fā)現(xiàn)已經(jīng)到達了目的主機,則由協(xié)議字段來決定信息包應(yīng)該傳送到哪一個上層協(xié)議。典型模塊的跨層調(diào)用對于某一個協(xié)議來說典型模塊的跨層調(diào)用外發(fā)的信息包由ip_output()函數(shù)處理,該函數(shù)使用ip_route()函數(shù)查找適當(dāng)?shù)木W(wǎng)絡(luò)接口來傳送信息包。當(dāng)外發(fā)的網(wǎng)絡(luò)接口確定后,信息包傳給以外發(fā)網(wǎng)絡(luò)接口為參數(shù)的ip_output_if()函數(shù)。所有的IP包頭字段被填充,并且計算IP包頭校驗和。IP信息包的源及目標地址作為參數(shù)被傳遞給ip_output_if()函數(shù)。傳輸層協(xié)議UDP與TCP在計算傳輸層校驗和的時候需要擁有目標IP地址,因此一些傳輸層函數(shù)可能會直接直接調(diào)用ip_route()函數(shù)確定接口。這樣這些函數(shù)在外發(fā)數(shù)據(jù)前就沒有必要再對網(wǎng)絡(luò)接口鏈表進行檢索,而是直接調(diào)用ip_output_if()函數(shù)外發(fā)數(shù)據(jù)。典型模塊的跨層調(diào)用外發(fā)的信息包由ip_output()函數(shù)典型模塊的跨層調(diào)用如果沒有網(wǎng)絡(luò)接口的地址與到達的信息包的目標地址相同,信息包應(yīng)該被轉(zhuǎn)發(fā)。由ip_forward()函數(shù)完成。TTL字段值被減少,當(dāng)減為0的時候,將會給IP信息包的最初發(fā)送者發(fā)送ICMP錯誤信息,并拋棄該信息包。因為IP包頭被改變,因此需要調(diào)整IP包頭校驗和。最后,信息包被轉(zhuǎn)發(fā)到適當(dāng)?shù)木W(wǎng)絡(luò)接口。典型模塊的跨層調(diào)用如果沒有網(wǎng)絡(luò)接口的地址與到達的信息包的目標典型模塊的跨層調(diào)用(2)ICMP模塊ICMP信息包由ip_input()函數(shù)收到后,轉(zhuǎn)交給icmp_input()函數(shù)對ICMP包頭解碼,然后進行適當(dāng)?shù)膭幼?。如果需要對回送請求進行應(yīng)答,則調(diào)用ip_output()函數(shù)發(fā)送應(yīng)答報文。某些ICMP消息被傳遞給上層協(xié)議,由傳輸層的特定函數(shù)處理。ICMP目標不可到達消息可以由傳輸層發(fā)送,特別是UDP如udp_input()就可以調(diào)用icmp_dest_unreach()函數(shù)完成這項工作。icmp_dest_unreach()最后也會調(diào)用ip_output()發(fā)送ICMP報文。典型模塊的跨層調(diào)用(2)ICMP模塊典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用(3)UDP模塊當(dāng)一個UDP數(shù)據(jù)包到達時IP層調(diào)用udp_input()函數(shù)將數(shù)據(jù)包移交給udp_input()。如果需要的話,LwIP會在這里對UDP校驗和進行檢查。為了找到匹配的UDPPCB,LwIP會對UDPPCB全局鏈表進行線性搜索。如果當(dāng)前鏈表中存在匹配的UDPPCB,則其recv函數(shù)會被調(diào)用。發(fā)送數(shù)據(jù)的過程由應(yīng)用程序調(diào)用udp_send()函數(shù)發(fā)起。為了計算校驗和,該函數(shù)會調(diào)用ip_route()確定網(wǎng)絡(luò)接口,因為該接口地址將在校驗和的計算過程中用到。最后,信息包被移交給ip_output_if()函數(shù)傳送。典型模塊的跨層調(diào)用(3)UDP模塊典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用(4)TCP模塊TCP處理比UDP處理要復(fù)雜得多與TCP輸入相關(guān)的函數(shù)tcp_input()tcp_process()tcp_receive()與TCP輸出有關(guān)的函數(shù)tcp_write()tcp_enqueue()tcp_output()典型模塊的跨層調(diào)用(4)TCP模塊典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用典型模塊的跨層調(diào)用TCP數(shù)據(jù)的發(fā)送過程一般是由應(yīng)用層發(fā)起。應(yīng)用層調(diào)用tcp_write(),而tcp_write()再調(diào)用tcp_enqueue()。tcp_enqueue()函數(shù)會在必要時將數(shù)據(jù)分割成適當(dāng)大小的TCP段,然后把這些TCP段放到所屬連接的傳輸隊列中。這時tcp_output()函數(shù)會判斷接收器窗口是否擁有足夠大的空間,阻塞窗口是否也足夠大,如果條件滿足,就調(diào)用ip_route()找到一個合適的接口,再調(diào)用ip_output_if()完成發(fā)送過程。即使當(dāng)時不能發(fā)送也不要緊,這是因為LwIP設(shè)置了定時器函數(shù)tcp_tmr(),該函數(shù)每隔固定時間就會被調(diào)用一次。tcp_tmr()會對當(dāng)前連接的傳輸隊列進行分析,并根據(jù)需要調(diào)用tcp_output()執(zhí)行數(shù)據(jù)發(fā)送操作。典型模塊的跨層調(diào)用TCP數(shù)據(jù)的發(fā)送過程一般是由應(yīng)用層發(fā)起。典型模塊的跨層調(diào)用TCP數(shù)據(jù)的接收過程由網(wǎng)絡(luò)接口層發(fā)起。網(wǎng)絡(luò)接口層將數(shù)據(jù)包傳遞給ip_input()函數(shù),該函數(shù)驗證IP頭后移交TCP段給tcp_input()函數(shù)。tcp_input()函數(shù)主要完成兩項工作初始完整性檢查(也就是校驗和驗證與TCP選項解析判定這個TCP段屬于哪個TCP連接。接著,這個TCP段到達tcp_process()函數(shù)。tcp_process()函數(shù)實現(xiàn)了TCP狀態(tài)機,任何必要的狀態(tài)轉(zhuǎn)換都在這里實現(xiàn)。當(dāng)該TCP所屬的連接正處于接受網(wǎng)絡(luò)數(shù)據(jù)的狀態(tài)時,tcp_receive()函數(shù)將被調(diào)用。最后,tcp_receive()函數(shù)將數(shù)據(jù)傳給上層的應(yīng)用程序,完成接收過程。如果收到一個ACK應(yīng)答確認數(shù)據(jù),表明接收器同意接收更多的數(shù)據(jù),此時tcp_output()函數(shù)將會被調(diào)用。典型模塊的跨層調(diào)用TCP數(shù)據(jù)的接收過程由網(wǎng)絡(luò)接口層發(fā)起。LwIP的內(nèi)存管理LwIP的包緩沖區(qū)pbufpbuf是LwIP信息包的內(nèi)部表示。pbuf結(jié)構(gòu)既支持動態(tài)內(nèi)存分配以保存信息包內(nèi)容,又支持讓信息包數(shù)據(jù)駐留在靜態(tài)存儲區(qū)。多個pbuf可以通過一個鏈表結(jié)構(gòu)鏈接成一個pbuf鏈,從而使一個信息包穿越多個pbuf。pbuf的內(nèi)部結(jié)構(gòu)定義為structpbuf{structpbuf*next; //指向下一個pbufvoid*payload; //指向?qū)嶋H的數(shù)據(jù)負載u16_ttot_len; //pbuf鏈的數(shù)據(jù)負載總長度u16_tlen; //該pbuf的數(shù)據(jù)負載長度u16_tflags; //pbuf的類型標志u16_tref; //pbuf被引用的次數(shù)};LwIP的內(nèi)存管理LwIP的包緩沖區(qū)pbufLwIP的內(nèi)存管理pbuf結(jié)構(gòu)包括兩個指針,兩個長度字段,一個標志字段和一個引用計數(shù)字段。next指針指向pbuf鏈中下一個pbuf的位置;payload指針指向pbuf中數(shù)據(jù)負載的開始位置len字段包含pbuf中數(shù)據(jù)內(nèi)容的長度;tot_len字段包含當(dāng)前pbuf的長度與在這個pbuf鏈中隨后的所有pbuf的len字段之和flags字段標識pbuf的類型;ref字段指出pbuf被引用的次數(shù)。pbuf有四種類型PBUF_RAMPBUF_ROMPBUF_REFPBUF_POOLLwIP的內(nèi)存管理pbuf結(jié)構(gòu)包括兩個指針,兩個長度字段,一PBUF_RAM類型的pbufPBUF_RAM在事先劃分好的內(nèi)存堆棧中分配,用于存放應(yīng)用程序動態(tài)產(chǎn)生的數(shù)據(jù)。圖示的是一個PBUF_RAM類型的pbuf實例,其實際的數(shù)據(jù)負載存放在由協(xié)議棧管理的存儲區(qū)中。既然PBUF_RAM類型的pbuf用于應(yīng)用程序發(fā)送的數(shù)據(jù)被動態(tài)生成的情況,那么在這種情況下pbuf系統(tǒng)不僅為應(yīng)用數(shù)據(jù)分配內(nèi)存,還應(yīng)給為這些數(shù)據(jù)預(yù)置的包頭分配內(nèi)存。pbuf系統(tǒng)不可能預(yù)先知道為這些數(shù)據(jù)預(yù)置什么樣的包頭,因而考慮最壞的情況。PBUF_RAM類型的pbufPBUF_RAM在事先劃分好的PBUF_ROM/PBUF_REF類型的pbufPBUF_ROM類型的pbuf的payload指針指向不由協(xié)議棧管理的外部存儲區(qū)如應(yīng)用程序管理的存儲器為用戶數(shù)據(jù)分配的緩存。由于由應(yīng)用程序交付的數(shù)據(jù)不能被改動因此就需要動態(tài)地分配一個PBUF_RAM來裝載協(xié)議的首部然后將PBUF_RAM(首部)添加到PBUF_ROM(數(shù)據(jù))的前面。這樣就構(gòu)成了一個完整的數(shù)據(jù)分組(pbuf鏈)PBUF_ROM/PBUF_REF類型的pbufPBUF_RPBUF_ROM/PBUF_REF類型的pbufPBUF_ROM/PBUF_REF類型的pbufPBUF_ROM/PBUF_REF類型的pbuf圖中的PBUF_ROM還可以是PBUF_REF,二者的特性非常相似,都可以實現(xiàn)數(shù)據(jù)的零拷貝,但是當(dāng)發(fā)送數(shù)據(jù)需要排隊時就表現(xiàn)出PBUF_REF的特性了。例如待發(fā)送的分組需要在ARP隊列中排隊,假如這些分組中有PBUF_ROM類型的pbuf,則直到分組被處理之前,被引用的應(yīng)用程序的這塊存儲區(qū)域都不能另作它用。但如果是PBUF_REF類型的pbuf,LwIP則會在數(shù)據(jù)分組排隊時為PBUF_REF類型的pbuf分配緩存(PBUF_POOL或PBUF_RAM),并將引用的應(yīng)用程序的數(shù)據(jù)拷貝到分配的緩存中。這樣應(yīng)用程序中被引用數(shù)據(jù)的存儲區(qū)域就能被釋放。PBUF_ROM/PBUF_REF類型的pbuf圖中的PBUPBUF_POOL類型的pbufPBUF_POOL是具有固定容量的pbuf,其容量大小通過宏定義來指定。在協(xié)議棧管理的內(nèi)存中初始化了一個pbuf池,具有相同尺寸的pbuf都是從這個pbuf池中分配得到。一般使用多個PBUF_POOL鏈接成一個鏈表,用于存儲數(shù)據(jù)分組PBUF_POOL類型的pbufPBUF_POOL是具有固定PBUF_POOL類型的pbufPBUF_POOL類型的pbufPBUF_POOL類型的pbufPBU_POOL主要用于網(wǎng)絡(luò)設(shè)備驅(qū)動層由于分配一個pbuf的操作可以快速完成,所以PBUF_POOL非常適合用于中斷處理。一般來說,收到的pbuf是PBUF_POOL類型,發(fā)送出的pbuf是PBUF_ROM或PBUF_RAM類型。不同類型的pbuf擁有各自的特點和不同的使用目的,因此只有正確選用,才能最好地發(fā)揮LwIP的特性。PBUF_POOL類型的pbufPBU_POOL主要用于網(wǎng)絡(luò)LwIP的內(nèi)存管理LwIP的內(nèi)存區(qū)域主要用于裝載待接收和發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)分組。當(dāng)接收到分組或者有分組要發(fā)送時,LwIP協(xié)議棧為這些分組分配緩存;在接收到的分組交付給應(yīng)用程序或者分組己經(jīng)發(fā)送完畢后,LwIP協(xié)議棧對分配的緩存進行回收利用。協(xié)議棧分配的緩存必須能容納各種大小的報文例如從僅僅幾個字節(jié)的ICMP應(yīng)答報文到幾百個字節(jié)的TCP分段報文。LwIP的內(nèi)存管理LwIP的內(nèi)存區(qū)域主要用于裝載待接收和發(fā)送PBUF_RAM的內(nèi)存管理LwIP協(xié)議棧首先從系統(tǒng)內(nèi)存中開辟一塊連續(xù)的靜態(tài)存儲區(qū)域該區(qū)域的大小可以事先通過宏定義指定。協(xié)議棧將該區(qū)域作為PBUF_RAM的專用區(qū)域所有與PBUF_RAM有關(guān)的內(nèi)存操作都被限制在該區(qū)域內(nèi),從而確保了協(xié)議棧不會因非法訪問系統(tǒng)內(nèi)存的其它區(qū)域而擾亂其它程序的正常運行。PBUF_RAM的內(nèi)存管理LwIP協(xié)議棧首先從系統(tǒng)內(nèi)存中開辟PBUF_RAM的內(nèi)存管理為了方便內(nèi)存管理,協(xié)議棧定義了一個比較小的結(jié)構(gòu)體mem,并將該結(jié)構(gòu)體置于內(nèi)存分配塊的頂部來保存內(nèi)存分配記錄。該結(jié)構(gòu)體擁有三個成員變量,分別為兩個“指針”和一個標志,其中next與prev分別指向內(nèi)存的下一個和上一個分配塊,used標志標示該內(nèi)存塊是否已被分配。next和prev并不是真正的指針,它們本質(zhì)上是數(shù)組的下標,并沒有直接指向真正的地址。PBUF_RAM的內(nèi)存管理為了方便內(nèi)存管理,協(xié)議棧定義了一個PBUF_RAM的內(nèi)存管理(1)初始化使用PBUF_RAM內(nèi)存之前,需對PBUF_RAM的專用內(nèi)存區(qū)域進行初始化工作,即對該區(qū)域進行一定的格式設(shè)置。LwIP協(xié)議棧在該區(qū)域的頭部和尾部各設(shè)置了一個mem結(jié)構(gòu)以協(xié)助管理內(nèi)存,如圖所示。在頭部的mem結(jié)構(gòu)中,next指向尾部mem,prev指向該區(qū)域的起始處,used(=0)表明該mem結(jié)構(gòu)后面的區(qū)域尚未使用;在尾部的mem結(jié)構(gòu)中,next和prev均指向該mem自身,used(=1)表明該mem結(jié)構(gòu)后面無可用的內(nèi)存。在該區(qū)域的末尾處,協(xié)議棧預(yù)留了對齊空間,其主要目的是防止操作過程中因?qū)R而導(dǎo)致的對該專用區(qū)域之外的存儲空間的越界訪問。PBUF_RAM的內(nèi)存管理(1)初始化PBUF_RAM的內(nèi)存管理PBUF_RAM的內(nèi)存管理PBUF_RAM的內(nèi)存管理(2)分配PBUF_RAM內(nèi)存塊分配內(nèi)存時首先根據(jù)所申請分配的大小來搜索所有未被使用的內(nèi)存分配塊搜索到的最先滿足條件的內(nèi)存塊將分配給申請者。第一次分配時只要申請分配的大小沒有超出限制,便會在PBUF_RAM專用存儲區(qū)域的開頭分配所需要的內(nèi)存PBUF_RAM的內(nèi)存管理(2)分配PBUF_RAM內(nèi)存塊PBUF_RAM的內(nèi)存管理經(jīng)過多次的內(nèi)存分配和釋放操作后,PBUF_RAM存儲區(qū)中會存在多個大小不一的未使用塊。此時如果需要分配一個新的內(nèi)存塊,有可能搜索到的第一個空閑內(nèi)存塊空間不夠。在這種情況下,內(nèi)存管理機制會繼續(xù)往后搜索未使用塊,直到搜索到足夠大的空閑內(nèi)存塊或搜索到存儲區(qū)的末尾。PBUF_RAM的內(nèi)存管理經(jīng)過多次的內(nèi)存分配和釋放操作后,PPBUF_RAM的內(nèi)存管理(3)釋放PBUF_RAM內(nèi)存塊對不再利用的內(nèi)存塊,需要進行回收,以便下次需要分配內(nèi)存塊時重新使用。回收內(nèi)存塊時,管理該內(nèi)存塊的mem結(jié)構(gòu)的used標志將被清零,以表明該內(nèi)存塊已不再被使用,可以重新對其進行分配PBUF_RAM的內(nèi)存管理(3)釋放PBUF_RAM內(nèi)存塊PBUF_RAM的內(nèi)存管理為了防止內(nèi)存碎片的產(chǎn)生,每回收一個內(nèi)存塊后,其上一個與下一個分配塊的used標志將會被檢查:如果它們中的任何一個還未被使用(used=0),則這個內(nèi)存塊將被合并到一個更大的未使用內(nèi)存塊中。只有經(jīng)過了以上操作后,釋放內(nèi)存的工作才算是已經(jīng)完成。PBUF_RAM的內(nèi)存管理為了防止內(nèi)存碎片的產(chǎn)生,每回收一個PBUF_RAM的內(nèi)存管理合并相鄰空閑塊的一個示例。PBUF_RAM的內(nèi)存管理合并相鄰空閑塊的一個示例。PBUF_RAM的內(nèi)存管理(4)調(diào)整PBUF_RAM內(nèi)存塊大小在內(nèi)存的使用過程中,有時希望調(diào)整已分配的內(nèi)存塊的大小。根據(jù)調(diào)整的方向(減小/增大)不同,處理的機制也不一樣。調(diào)整前先對騰出的內(nèi)存塊大小進行預(yù)算:如果該值小于mem結(jié)構(gòu)的長度加內(nèi)存塊的最小長度(即無法另行分配一個最小長度的內(nèi)存塊),則調(diào)整不被執(zhí)行。調(diào)整時將在騰出的內(nèi)存塊開頭置以一個新的mem結(jié)構(gòu)以對其進行管理,該mem結(jié)構(gòu)的used標志為0,表示可以對其進行分配使用。同釋放內(nèi)存時一樣,與新的內(nèi)存塊相鄰的內(nèi)存塊的used標志同樣會被檢查,以防止碎片產(chǎn)生。PBUF_RAM的內(nèi)存管理(4)調(diào)整PBUF_RAM內(nèi)存塊大PBUF_RAM的內(nèi)存管理給出了減小既定內(nèi)存塊大小的示意圖。PBUF_RAM的內(nèi)存管理給出了減小既定內(nèi)存塊大小的示意圖。PBUF_RAM的內(nèi)存管理增大既定內(nèi)存塊的大小時所采取的機制與上述操作大為不同。如果希望將某內(nèi)存塊的大小調(diào)整為newsize,則處理過程是先分配一塊大小為newsize的空閑內(nèi)存塊,然后將原內(nèi)存塊的內(nèi)容復(fù)制到新內(nèi)存塊中,最后再釋放原內(nèi)存塊。PBUF_RAM的內(nèi)存管理增大既定內(nèi)存塊的大小時所采取的機制PBUF_ROM/PBUR_REF的內(nèi)存管理對于PBUF_ROM/PBUF_REF,LwIP協(xié)議棧同樣為其開辟了一塊連續(xù)的存儲區(qū)域。協(xié)議棧定義了結(jié)構(gòu)體memp以協(xié)助PBUF_ROM/PBUF_RAM的內(nèi)存管理。該結(jié)構(gòu)體只有一個成員next,為指向下一個相同結(jié)構(gòu)的存儲區(qū)的指針。PBUF_ROM/PBUF_REF類型存儲區(qū)域初始化后的結(jié)構(gòu)示意圖,多個pbuf在內(nèi)存中以鏈表的形式存在。該種類型pbuf的操作方法可以參考鏈表的一般操作方法。PBUF_ROM/PBUR_REF的內(nèi)存管理對于PBUF_RPBUF_POOL的內(nèi)存管理PBUF_POOL類型的pbuf同樣擁有自己專用的存儲區(qū)域,該區(qū)域通過預(yù)先從系統(tǒng)內(nèi)存中分配而得。區(qū)域大小由PBUF_POOL類型的pbuf個數(shù)和每個pbuf的緩沖區(qū)大小等參數(shù)共同決定,這些參數(shù)都可以事先通過宏定義指定。對PBUF_POOL類型的內(nèi)存管理,LwIP協(xié)議棧并沒有額外引入類似PBUF_RAM的mem結(jié)構(gòu)或是PBUF_ROM的memp結(jié)構(gòu),而是直接采用pbuf結(jié)構(gòu)對其進行管理。PBUF_POOL的內(nèi)存管理PBUF_POOL類型的pbufPBUF_POOL的內(nèi)存管理PBUF_POOL存儲區(qū)域的結(jié)構(gòu)如圖所示,對該類型pbuf的操作與普通鏈表的操作并無不同。每個PBUF_POOL的數(shù)據(jù)緩沖區(qū)都緊跟在pbuf結(jié)構(gòu)后面,并且大小相同。這點與PBUF_ROM不同,因為PBUF_ROM的數(shù)據(jù)緩沖區(qū)不在LwIP協(xié)議棧管理的區(qū)域,并且大小不盡相同。PBUF_POOL的內(nèi)存管理PBUF_POOL存儲區(qū)域的結(jié)構(gòu)LwIP移植無RTOS時的移植LwIP既可以在無RTOS(RealTimeOperatingSystem,實時操作系統(tǒng))的環(huán)境下運行,也可以很方便地移植到RTOS之上。移植過程中對于LwIP核心模塊沒必要也不建議進行修改,而真正的工作是結(jié)合實際的軟硬件環(huán)境,針對與移植密切相關(guān)的相關(guān)文件與相關(guān)函數(shù)進行定制。LwIP移植無RTOS時的移植LwIP移植移植函數(shù)為了將LwIP移植到特定的開發(fā)平臺上,需要完成與網(wǎng)絡(luò)接口有關(guān)的底層函數(shù)。這些底層函數(shù)集中在\src\netif\ethernetif.c文件中。建議將“ethernet”替換成能更好地描述所選網(wǎng)絡(luò)接口的詞匯,如華中科技大學(xué)瑞薩高級嵌入式控制器實驗室自行開發(fā)的RenesasM16C/62P嵌入式開發(fā)平臺采用的網(wǎng)絡(luò)芯片是CS8900A,該文件便用cs8900if.c文件進行了替代。文件中凡是用ethernet命名的函數(shù),也一律用cs8900進行了替代。LwIP提供的ethernetif.c文件給出了網(wǎng)絡(luò)接口驅(qū)動的整體框架,用戶需要自己完成的函數(shù)主要有3個,分別是底層初始化函數(shù)low_level_init()底層輸入函數(shù)low_level_input()底層輸出函數(shù)low_level_output()。LwIP移植移植函數(shù)無RTOS時的移植(1)底層初始化函數(shù)low_level_init()原型為staticvoidlow_level_init(structnetif*netif);該函數(shù)用來對網(wǎng)絡(luò)接口進行初始化,任何與初始化網(wǎng)絡(luò)接口有關(guān)的操作都可以在該函數(shù)內(nèi)實現(xiàn)。如對網(wǎng)絡(luò)接口有關(guān)參數(shù)進行配置,或是完成網(wǎng)絡(luò)芯片硬件上所需的初始化操作等。(2)底層輸入函數(shù)low_level_input()函數(shù)原型為staticstructpbuf*low_level_input(structnetif*netif);該函數(shù)為到達的數(shù)據(jù)包分配pbuf(通常是一個pbuf鏈),并將數(shù)據(jù)包從網(wǎng)絡(luò)接口傳入至pbuf鏈中。數(shù)據(jù)具體接收過程的實現(xiàn)與網(wǎng)絡(luò)接口硬件有關(guān)。將數(shù)據(jù)裝載至pbbuf時,需對pbuf結(jié)構(gòu)的各字段進行正確填充,使其形成邏輯上的pbuf鏈無RTOS時的移植(1)底層初始化函數(shù)low_level_i無RTOS時的移植通常,為收到的數(shù)據(jù)分配的pbuf是PBUF_POOL類型,因為分配一個PBUF_POOL可以很快完成。(3)底層輸出函數(shù)low_level_output()函數(shù)原型為:staticerr_tlow_level_output(structnetif*netif,structpbuf*p);該函數(shù)實現(xiàn)真正的的數(shù)據(jù)包發(fā)送過程當(dāng)需要發(fā)送數(shù)據(jù)包時,數(shù)據(jù)包裝載在事先已分配好的pbuf(鏈)中。LwIP將pbuf作為參數(shù)傳入給該函數(shù),由該函數(shù)負責(zé)將數(shù)據(jù)包發(fā)送至指定的網(wǎng)絡(luò)接口中。數(shù)據(jù)具體發(fā)送過程的實現(xiàn)同樣與網(wǎng)絡(luò)接口硬件有關(guān)。無RTOS時的移植通常,為收到的數(shù)據(jù)分配的pbuf是PBUF無RTOS時的移植幾個定時器函數(shù)在LwIP的移植過程中,注意有幾個定時器函數(shù)必須每隔固定時間就調(diào)用一次。具體采用什么機制實現(xiàn)這一操作并無限制etharp_tmr()tcp_fasttmr()tcp_slowtmr()這些函數(shù)的運行間隔周期可以通過宏定義指定,各函數(shù)的具體定義可在LwIP提供的源碼中找到。無RTOS時的移植幾個定時器函數(shù)LwIP在uC/OS-II下的移植為了方便LwIP在RTOS下的移植,屬于操作系統(tǒng)的函數(shù)調(diào)用及數(shù)據(jù)結(jié)構(gòu)并沒有在代碼中直接使用,而是用操作系統(tǒng)模擬層來代替對這些函數(shù)的使用。操作系統(tǒng)模擬層使用統(tǒng)一的接口提供定時器、進程同步及消息傳遞機制等諸如此類的系統(tǒng)服務(wù)原則上,移植LwIP只需針對目標操作系統(tǒng)修改模擬層實現(xiàn)即可。LwIP在uC/OS-II下的移植為了方便LwIP在RTOSLwIP在uC/OS-II下的移植模擬層主要實現(xiàn)以下4大功能:定時與超時處理LwIP可以為某一線程注冊若干個超時處理函數(shù),當(dāng)超時時限溢出時便會調(diào)用一個已注冊的函數(shù)。進程同步進程同步機制為多個進程之間的同步操作提供支持,一般可以用信號量來實現(xiàn)。如果選用的RTOS不支持信號量,則可以使如條件變量等其它基本的同步方式來模擬。消息傳遞消息傳遞機制可通過一種稱作郵箱的抽象方法來實現(xiàn)。郵箱有兩種基本操作:向郵箱投遞(post)一則消息和從郵箱中提取(fetch)一則消息線程管理對LwIP協(xié)議棧的線程進行管理和維護,主要指創(chuàng)建線程。LwIP在uC/OS-II下的移植模擬層主要實現(xiàn)以下4大功能移植相關(guān)文件與函數(shù)移植過程中需要創(chuàng)建或修改的源文件和頭文件位于目錄src/arch之下目錄組織結(jié)構(gòu)如圖所示:移植相關(guān)文件與函數(shù)移植過程中需要創(chuàng)建或修改的源文件和頭文件位移植相關(guān)文件與函數(shù)頭文件主要是一些宏定義,包括數(shù)據(jù)類型的定義和有關(guān)結(jié)構(gòu)的封裝等;而主要的功能函數(shù)均在sys_arch.c源文件中實現(xiàn)。此外,sys.c和sys.h兩個文件雖無需作任何修改,但與以上文件(尤其是sys_arch.c)關(guān)聯(lián)緊密,有助于更好地理解LwIP在RTOS下的移植實現(xiàn)。LwIP在設(shè)計時就考慮到了將來的RTOS移植問題。為了適應(yīng)不同的操作系統(tǒng),LwIP并沒有在代碼中使用針對某個特定RTOS的系統(tǒng)調(diào)用和數(shù)據(jù)結(jié)構(gòu),而是提供操作系統(tǒng)模擬層作為LwIP和RTOS的一個接口。為了理解LwIP在RTOS下的移植實現(xiàn)過程,選用嵌入式實時操作系統(tǒng)uC/OS-II為例,對LwIP在uC/OS-II下的移植進行說明。uC/OS-II是專為嵌入式應(yīng)用設(shè)計的實時內(nèi)核,關(guān)于其詳細信息可參考其官網(wǎng)/page/home。實現(xiàn)LwIP在uC/OS-II下的移植,其主要工作就是結(jié)合LwIP和uC/OS-II的特點,對操作系統(tǒng)模擬層進行修改和定制,使LwIP和uC/OS-II無縫連接。移植相關(guān)文件與函數(shù)頭文件主要是一些宏定義,包括數(shù)據(jù)類型的定義移植相關(guān)文件與函數(shù)根據(jù)LwIP源碼提供的sys_arch.txt文件,需要實現(xiàn)的函數(shù)有:voidsys_init(void)被調(diào)用來初始化操作系統(tǒng)模擬層。sys_sem_tsys_sem_new(u8_tcount)創(chuàng)建并返回一個新的信號量,參數(shù)count指定信號量的初始狀態(tài)。voidsys_sem_free(sys_sem_tsem)刪除一個信號量。voidsys_sem_signal(sys_sem_tsem)發(fā)出一個信號量。u32_tsys_arch_sem_wait(sys_sem_tsem,u32_ttimeout)等待一個信號量,該操作會阻塞調(diào)用該函數(shù)的線程。sys_mbox_tsys_mbox_new(void)創(chuàng)建一個空的郵箱。voidsys_mbox_free(sys_mbox_tmbox)刪除一個郵箱。移植相關(guān)文件與函數(shù)根據(jù)LwIP源碼提供的sys_arch.t移植相關(guān)文件與函數(shù)voidsys_mbox_post(sys_mbox_tmbox,void*msg)向指定的郵箱發(fā)送一則消息。u32_tsys_arch_mbox_fetch(sys_mbox_tmbox,void**msg,u32_ttimeout)從郵箱中提取一則消息,該操作同樣會阻塞調(diào)用該函數(shù)的線程。structsys_timeouts*sys_arch_timeouts(void)返回指向當(dāng)前線程的sys_timeouts結(jié)構(gòu)的指針。LwIP的每個線程都有自己的超時等待屬性,每個線程都分配了一個超時等待的數(shù)據(jù)結(jié)構(gòu)sys_timeout,并把這個數(shù)據(jù)結(jié)構(gòu)存放于鏈表sys_timeouts中。該函數(shù)的作用是通過查詢來獲得一個指向當(dāng)前線程使用的sys_timeouts結(jié)構(gòu)的指針。sys_thread_tsys_thread_new(void(*thread)(void*arg),void*arg,intprio)創(chuàng)建一個新的LwIP線程。移植相關(guān)文件與函數(shù)voidsys_mbox_post(sy超時處理的實現(xiàn)如前所述,LwIP的每個線程都有自己的超時等待屬性。為了順利理解LwIP的這一機制,先引入與之相關(guān)的幾種數(shù)據(jù)結(jié)構(gòu)。sys_timeout是線程的超時等待數(shù)據(jù)結(jié)構(gòu),其內(nèi)部結(jié)構(gòu)定義如下:structsys_timeout{structsys_timeout*next; //指向鏈表中的下一個sys_timeoutu32_ttime; //超時時限(ms)sys_timeout_handlerh; //超時處理函數(shù)void*arg; //超時處理函數(shù)的參數(shù)};其中sys_timeout_handler是指向超時處理函數(shù)的指針,其定義為typedefvoid(*sys_timeout_handler)(void*arg);多個sys_timeout可以鏈接成一個鏈表,如圖所示:超時處理的實現(xiàn)如前所述,LwIP的每個線程都有自己的超時等待超時處理的實現(xiàn)sys_timeouts是sys_timeout鏈表的表頭,它只包含一個元素,即指向sys_timeout結(jié)構(gòu)的指針,其定義如下:structsys_timeouts{structsys_timeout*next; //指向鏈表第一個sys_timeout};加上sys_timeouts結(jié)構(gòu)后,LwIP線程的超時等待鏈表結(jié)構(gòu)如圖所示:超時處理的實現(xiàn)sys_timeouts是sys_timeou超時處理的實現(xiàn)超時處理的實現(xiàn)超時處理的實現(xiàn)timeoutlist將一個sys_timeouts結(jié)構(gòu)和優(yōu)先級聯(lián)系在一起,這樣便于根據(jù)當(dāng)前優(yōu)先級查找對應(yīng)的sys_timeouts鏈表,其定義如下:structtimeoutlist{structsys_timeoutstimeouts; //超時等待鏈表INT8Uprio; //優(yōu)先級};每個線程都有一個對以的timeoutlist結(jié)構(gòu),通過該結(jié)構(gòu)的timeouts元素可以定位超時等待列表的表頭,從而確定該線程的所有超時處理函數(shù),超時處理的實現(xiàn)timeoutlist將一個sys_timeo超時處理的實現(xiàn)LwIP在RTOS上的移植過程中需要實現(xiàn)的與超時處理有關(guān)的函數(shù)是sys_arch_timeouts()。(1)移植函數(shù)sys_arch_timeouts()sys_arch_timeouts()函數(shù)的作用是通過查詢機制,獲取指向當(dāng)前線程的sys_timeouts結(jié)構(gòu)的指針,相當(dāng)于定位超時等待鏈表的表頭。函數(shù)的原型如下:structsys_timeouts*sys_arch_timeouts(void);該函數(shù)表面上沒有參數(shù),但實際上調(diào)用該函數(shù)的線程有自己的優(yōu)先級,因此可以利用當(dāng)前線程的優(yōu)先級充當(dāng)函數(shù)的隱含參數(shù)。這樣處理帶來的限制是一個線程不能通過調(diào)用該函數(shù)來獲取另一個線程的sys_timeouts結(jié)構(gòu),但這一般不會引起什么問題。超時處理的實現(xiàn)LwIP在RTOS上的移植過程中需要實現(xiàn)的與超超時處理的實現(xiàn)sys_timeouts結(jié)構(gòu)和當(dāng)前線程的優(yōu)先級一起封裝在timeoutlist結(jié)構(gòu)中。為了存儲線程的timeoutlist結(jié)構(gòu),在sys_arch.c文件中定義了一個timeoutlist數(shù)組:staticstructtimeoutlisttimeoutlist[LWIP_MAX_TASKS];LWIP_MAX_TASKS是最大的LwIP線程數(shù),可以事先進行配置。每次調(diào)用sys_thread_new()創(chuàng)建一個新的線程時,都會依序取出一個數(shù)組元素,用當(dāng)前線程的優(yōu)先級對數(shù)組元素的prio字段進行填充。sys_arch_timeouts()函數(shù)通過線性搜索的方法對數(shù)組元素進行遍歷,直到發(fā)現(xiàn)某個數(shù)組元素的prio字段與當(dāng)前優(yōu)先級相同為止,而該數(shù)組元素的timeouts字段正是我們需要的目標。超時處理的實現(xiàn)sys_timeouts結(jié)構(gòu)和當(dāng)前線程的優(yōu)先級超時處理的實現(xiàn)超時處理的實現(xiàn)超時處理的實現(xiàn)(2)相關(guān)函數(shù)sys_timeout()sys_timeout()函數(shù)用以向當(dāng)前線程增加一個超時處理函數(shù),其原型如下:voidsys_timeout(u32_tmsecs,sys_timeout_handlerh,void*arg);sys_timeouts()函數(shù)首先從內(nèi)存中申請一塊空間,以存放一個sys_timeout結(jié)構(gòu)。如申請成功則利用函數(shù)的實參對結(jié)構(gòu)的各字段進行填充。要向當(dāng)前線程注冊一個超時處理函數(shù),sys_timeouts()會通過sys_arch_timeouts()函數(shù)獲取當(dāng)前線程的sys_timeouts結(jié)構(gòu)。超時處理的實現(xiàn)(2)相關(guān)函數(shù)sys_timeout()超時處理的實現(xiàn)第一次調(diào)用sys_timeout()注冊一個超時處理函數(shù)時,直接將sys_timeout結(jié)構(gòu)鏈接在當(dāng)前線程的sys_timeouts結(jié)構(gòu)即可,如圖所示:超時處理的實現(xiàn)第一次調(diào)用sys_timeout()注冊一個超時處理的實現(xiàn)應(yīng)用程序可能會多次注冊超時處理函數(shù)或刪除超時處理函數(shù),這樣處理后一個線程的sys_timeouts鏈表中可能會同時存在多個sys_timeout結(jié)構(gòu)。在這種情況下,向線程添加一個超時處理函數(shù)略微復(fù)雜,因為sys_timeout結(jié)構(gòu)必須插入到鏈表的恰當(dāng)位置。實際上如果一個線程有多個超時處理函數(shù),LwIP會按照鏈表的邏輯順序依次結(jié)算。這里所謂恰當(dāng)?shù)奈恢?,就是比較當(dāng)前鏈表節(jié)點的超時時限和待插入節(jié)點的超時時限,保證插入該節(jié)點后不會影響原有任一節(jié)點的超時等待屬性。超時處理的實現(xiàn)應(yīng)用程序可能會多次注冊超時處理函數(shù)或刪除超時處超時處理的實現(xiàn)如圖所示,假設(shè)當(dāng)前線程已注冊3個超時處理函數(shù),對應(yīng)有3個sys_timeout結(jié)構(gòu),其超時時限分別是time1=100,time2=40,time3=80?,F(xiàn)要注冊一個超時時限為time4=160的超時處理函數(shù)。為了確定恰當(dāng)?shù)牟迦胛恢?,可以沿著鏈表逐次推算超時時限,分析過程如下:1time1<time4→next4在next1之后2time1+time2<time4→next4在next2之后3time1+time2+time3>time4→next4在next3之前經(jīng)以上步驟,next4的位置已經(jīng)確定,即位于next2和next3之間。注意插入next4節(jié)點后,next4節(jié)點及緊挨在next4后面的next3節(jié)點的time屬性值需做對應(yīng)調(diào)整,調(diào)整后的結(jié)果如圖所示:超時處理的實現(xiàn)如圖所示,假設(shè)當(dāng)前線程已注冊3個超時處理函數(shù),超時處理的實現(xiàn)超時處理的實現(xiàn)超時處理的實現(xiàn)如果新節(jié)點next4的超時時限time4取其它值,則可能會出現(xiàn)一些特殊情況。time4<time1,即新節(jié)點的time值比第一個節(jié)點還小。這種情況直接將next4節(jié)點插入到next1節(jié)點之前,并調(diào)整time1=time1-time4即可。超時處理的實現(xiàn)如果新節(jié)點next4的超時時限time4取其它超時處理的實現(xiàn)time4=time1+time2,即新節(jié)點的time值等于前面若干節(jié)點他time之和。這種情況next4將插入到next2的后面。next4節(jié)點的time值調(diào)整為0,而與next4相鄰的next2節(jié)點和next3節(jié)點則無需調(diào)整time屬性。線程在依序處理超時等待函數(shù)過程中,一旦執(zhí)行完next2節(jié)點的超時處理函數(shù)h2(arg2),會立即執(zhí)行next4節(jié)點的超時處理函數(shù)h4(arg4)。time4>time1+time2+time3,即新節(jié)點的time值大于現(xiàn)有所有節(jié)點time之和。這種情況next4節(jié)點將插入到最后,并調(diào)整time4=time4-time1-time2-time3,next4=NULL。超時處理的實現(xiàn)time4=time1+time2,即新節(jié)點的超時處理的實現(xiàn)(3)相關(guān)函數(shù)sys_untimeout()sys_untimeout()函數(shù)與sys_timeout()函數(shù)的作用恰好相反,用以刪除當(dāng)前線程某一指定的超時處理函數(shù)。函數(shù)原型如下:voidsys_untimeout(sys_timeout_handlerh,void*arg);與sys_timeout()相比,sys_untimeout()函數(shù)同樣會調(diào)用sys_arch_timeouts()獲取當(dāng)前線程的sys_timeouts鏈表結(jié)構(gòu)。函數(shù)通過一種簡單的線性搜索的方法,從表頭開始遍歷,直到找到一個超時處理函數(shù)h和參數(shù)arg均符合的sys_timeout結(jié)構(gòu)。將該結(jié)構(gòu)所在的節(jié)點從鏈表中刪除,并調(diào)整緊挨其后的節(jié)點(如果有的話)的超時時限屬性,最后釋放該結(jié)構(gòu)占用的內(nèi)存。超時處理的實現(xiàn)(3)相關(guān)函數(shù)sys_untimeout()超時處理的實現(xiàn)假設(shè)某一線程的超時處理函數(shù)鏈表如圖所示:超時處理的實現(xiàn)假設(shè)某一線程的超時處理函數(shù)鏈表如圖所示:超時處理的實現(xiàn)如果線程希望刪除h2處理函數(shù),即執(zhí)行sys_untimeout(h2,arg2);next2節(jié)點將從原鏈表中刪除,同時next3節(jié)點的time3將會調(diào)整為time3=time3+time2。調(diào)整后的狀態(tài)為:超時處理的實現(xiàn)如果線程希望刪除h2處理函數(shù),即執(zhí)行sys_u超時處理的實現(xiàn)但如果線程不是要刪除h2處理函數(shù),而是要刪除最后一個超時處理函數(shù),即執(zhí)行sys_untimeout(h3,arg3);這種情況next3節(jié)點從鏈表中刪除后,沒有后續(xù)節(jié)點需要調(diào)整超時時限屬性。結(jié)果如下:超時處理的實現(xiàn)但如果線程不是要刪除h2處理函數(shù),而是要刪除最超時處理的實現(xiàn)(4)超時處理函數(shù)的使用在等待信號量或等待消息的過程中,LwIP會對超時等待鏈表中的超時處理函數(shù)進行處理。對一個線程來講,要么通過調(diào)用sys_sem_wait()等待一個信號量,要么通過調(diào)用sys_mbox_fetch()等待一則消息,至少要采取一種方法阻塞當(dāng)前線程,否則注冊的超時處理函數(shù)將無法正常執(zhí)行。鑒于郵箱結(jié)構(gòu)比信號量結(jié)構(gòu)占用更多的資源,因此通常通過永久等待一個信號量來實現(xiàn)線程阻塞。如需每隔一定周期就執(zhí)行一次某函數(shù),則必須在超時處理函數(shù)中重新注冊自己。例如需要每隔250ms就執(zhí)行一次tcp_tmr(),通??梢圆捎孟旅娴姆绞綄崿F(xiàn)://向當(dāng)前線程注冊一個超時處理函數(shù)sys_timeout((u32_t)OS_TICKS_PER_SEC/4,(sys_timeout_handler)TCP_Timer,NULL);……//如需周期執(zhí)行tcp_tmr(),則需在TCP_Timer()中重新注冊自己voidTCP_Timer(void*p_arg){ tcp_tmr();//每隔250ms執(zhí)行一次 sys_timeout((u32_t)OS_TICKS_PER_SEC/4,(sys_timeout_handler)TCP_Timer,NULL);}超時處理的實現(xiàn)(4)超時處理函數(shù)的使用進程同步的實現(xiàn)進程同步機制是任務(wù)之間通信的一種重要方式,通常可以由信號量實現(xiàn)。uC/OS-II對信號量有較全面的支持,因此移植過程中比較方便實現(xiàn)。uC/OS-II實現(xiàn)了信號量和互斥型信號量,這里采用uC/OS-II的信號量實現(xiàn)。由于uC/OS-II支持信號量的各種操作,并且可以滿足LwIP對信號量的要求,因此只需對相關(guān)結(jié)構(gòu)和函數(shù)進行重新封裝即可。進程同步的實現(xiàn)進程同步機制是任務(wù)之間通信的一種重要方式,通常進程同步的實現(xiàn)函數(shù)sys_sem_wait()
該函數(shù)是由LwIP應(yīng)用程序調(diào)用的用來等待一個信號量的函數(shù),但它會在等待信號量的過程中對當(dāng)前線程的超時等待函數(shù)進行處理。函數(shù)原型如下:
voidsys_sem_wait(sys_sem_tsem)該函數(shù)首先調(diào)用sys_arch_timeouts()獲取當(dāng)前線程的超時等待函數(shù)鏈表,以對超時等待鏈表中的超時處理函數(shù)依次結(jié)算。真正實現(xiàn)等待一個信號量的過程由sys_arch_sem_wait()完成?,F(xiàn)仍以下面的超時等待鏈表為例,分析sys_sem_wait()在等待信號量過程中對超時處理函數(shù)的處理。進程同步的實現(xiàn)函數(shù)sys_sem_wait()進程同步的實現(xiàn)1執(zhí)行sys_arch_sem_wait(sem,time1)。如超時溢出則表明在time1時間內(nèi)一直未成功等到信號量,此時執(zhí)行超時處理函數(shù)h1(arg1),同時將next1節(jié)點從鏈表中刪除,并轉(zhuǎn)到步驟2。如在time1時間內(nèi)成功等到信號量,則不論實際消耗的等待時間是多少,LwIP一律認為消耗時間為1ms,并調(diào)整time1=time1-1,此時轉(zhuǎn)到步驟4。2執(zhí)行sys_arch_sem_wait(sem,time2)。只有在time1時限耗盡的情況下,才會執(zhí)行sys_arch_sem_wait(sem,time2)。與1類似,如未等到信號量則執(zhí)行h2(arg2)并轉(zhuǎn)到步驟3,否則調(diào)整time2=time2-1并轉(zhuǎn)到步驟4。進程同步的實現(xiàn)1執(zhí)行sys_arch_sem_wait(se進程同步的實現(xiàn)3調(diào)用sys_arch_sem_wait(sem,time3)。只有在next3節(jié)點前面的所有節(jié)點的超時時限均已耗盡的情況下,才會執(zhí)行sys_arch_sem_wait(sem,time3)。由于next3已經(jīng)是最后一個節(jié)點,因此不論成功等到信號量與否,均會轉(zhuǎn)到步驟4。4sys_sem_wait()函數(shù)返回或作永久等待。如果成功等到了信號量,sys_sem_wait()函數(shù)返回。但如果超時等待鏈表中所有超時時限均已耗盡且所有超時處理函數(shù)均已執(zhí)行后,仍未等到信號量,則sys_sem_wait()會調(diào)用sys_arch_sem_wait(0)一直等到信號量有效為止。進程同步的實現(xiàn)3調(diào)用sys_arch_sem_wait(se進程同步的實現(xiàn)一種特殊情況是當(dāng)前線程超時等待鏈表為空,也就是沒有超時等待函數(shù)。sys_sem_wait()會直接調(diào)用sys_arch_sem_wait(0)做永久等待。另一種情況是在等待信號量的過程中發(fā)現(xiàn)某一節(jié)點的超時時限time=0。表明該節(jié)點的超時時限已經(jīng)耗盡,需立即執(zhí)行節(jié)點對應(yīng)的超時處理函數(shù)h(arg)。注意time=0與sys_timeouts鏈表為空是截然不同的:time=0只是表明該節(jié)點的超時時限已耗盡sys_timeouts為空則意味著當(dāng)前線程沒有任何函數(shù)需要做超時等待。進程同步的實現(xiàn)一種特殊情況是當(dāng)前線程超時等待鏈表為空,也就是消息傳遞的實現(xiàn)消息傳遞是任務(wù)之間通信的另一種重要方式,通常使用一種稱為郵箱的抽象方法來實現(xiàn)。郵箱有兩種基本的操作:郵遞(post)(發(fā)送一則消息)操作不會阻塞進程提取(fetch)(等待一則消息)操作可能會阻塞進程。uC/OS-II提供了消息郵箱和消息隊列兩種機制,區(qū)別是消息郵箱一次只能處理一則消息,而消息隊列可以存儲多則消息。為了使LwIP更好地運作,采用uC/OS-II的消息隊列實現(xiàn)LwIP所需的消息傳遞機制。消息傳遞的實現(xiàn)消息傳遞是任務(wù)之間通信的另一種重要方式,通常使線程管理的實現(xiàn)在線程管理方面,LwIP只提供了創(chuàng)建線程的操作。由于uC/OS-II沒有采用“線程”這一概念,而是采用“任務(wù)”的概念,LwIP的線程管理實際上是通過uC/OS-II的任務(wù)管理機制實現(xiàn)的。每個線程都有自己的超時等待屬性。為了區(qū)別不同線程的超時等待屬性,在創(chuàng)建線程的過程中會將優(yōu)先級prio填入到一個timeoutlist結(jié)構(gòu)的prio成員中,如圖所示:線程管理的實現(xiàn)在線程管理方面,LwIP只提供了創(chuàng)建線程的操作線程管理的實現(xiàn)由于uC/OS-II中每個任務(wù)都具有唯一的優(yōu)先級,因此prio可以作為LwIP線程的一個標識,以區(qū)分不同的線程。實際上sys_arch_timeouts()正是通過這一標識來定位當(dāng)前線程的超時等待鏈表的。線程管理的實現(xiàn)由于uC/OS-II中每個任務(wù)都具有唯一的優(yōu)先LwIP網(wǎng)絡(luò)編程應(yīng)用實例為了對LwIP的移植和應(yīng)用進行測試和驗證,以一個具體的應(yīng)用實例來說明LwIP網(wǎng)絡(luò)編程的一般方法。目的是設(shè)計和實現(xiàn)一個簡單的嵌入式WEB服務(wù)器,該WEB服務(wù)器可以響應(yīng)來自瀏覽器的HTTPGET請求,并在發(fā)送請求的瀏覽器上顯示一個小型頁面。LwIP網(wǎng)絡(luò)編程應(yīng)用實例為了對LwIP的移植和應(yīng)用進行測試和實驗平臺準備硬件平臺的一個最基本要求是提供對以太網(wǎng)接口的支持。選用的是華中科技大學(xué)瑞薩高級嵌入式控制器實驗室自主研發(fā)制作的RenesasM16C/62P嵌入式開發(fā)平臺。該平臺采用瑞薩科技(RENESAS)的M16C/62P單片機作為主控制器,通過集成一塊CS8900A網(wǎng)絡(luò)芯片來實現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)收發(fā)功能。實驗平臺準備硬件平臺的一個最基本要求是提供對以太網(wǎng)接口的支持實驗平臺準備軟件平臺最重要的部分是開發(fā)環(huán)境。采用Renesas的High-performanceEmbeddedWorkshop進行編程開發(fā)。為了方便調(diào)試,華中科技大學(xué)瑞薩高級嵌入式控制器實驗室開發(fā)了專門針對RenesasM16C/62P單片機的監(jiān)控程序。通過該監(jiān)控程序,將用戶應(yīng)用程序下載至ROM或者RAM,可以實現(xiàn)調(diào)試功能實驗平臺準備軟件平臺最重要的部分是開發(fā)環(huán)境。嵌入式WEB服務(wù)器的設(shè)計瀏覽器訪問WEB服務(wù)器所使用的是HTTP協(xié)議??蛻簦╓EB服務(wù)器)通過HTTP協(xié)議向服務(wù)器發(fā)送請求服務(wù)器根據(jù)HTTP協(xié)議對客戶端發(fā)來的請求進行解碼,并對其作出應(yīng)答。HTTP使用TCP作為運輸層,客戶在向服務(wù)器發(fā)送請求之前,要先與服務(wù)器的IP地址在端口80(HTTP的知名端口)建立一個連接。服務(wù)器在端口80偵聽進入的連接,并接受和處理客戶的請求。由于只是對LwIP協(xié)議棧進行測試和驗證,因此設(shè)計的WEB服務(wù)器只對客戶端的GET請求作出應(yīng)答,而對其它請求一概不予理會。嵌入式WEB服務(wù)器的設(shè)計瀏覽器訪問WEB服務(wù)器所使用的是HT嵌入式WEB服務(wù)器的設(shè)計瀏覽器與WEB服務(wù)器交互的示意圖。圖中瀏覽器運行在PC機(或支持瀏覽器的其它設(shè)備)上,WEB服務(wù)器運行在RenesasM16C/62P嵌入式開發(fā)平臺上,二者通過網(wǎng)絡(luò)進行連接。所有的數(shù)據(jù)交互過程受TCP/IP協(xié)議族的制約。嵌入式WEB服務(wù)器的設(shè)計瀏覽器與WEB服務(wù)器交互的示意圖。嵌入式WEB服務(wù)器的實現(xiàn)在進行了必要的初始化工作后,打開TCP的80端口并對該端口進行偵聽。一旦客戶發(fā)起請求,則服務(wù)器接受并解析該請求。如果請求正確,則服務(wù)器將相應(yīng)的頁面內(nèi)容發(fā)送給客戶端,而客戶端(通常是瀏覽器)將該頁面直觀地顯示出來。至此,一次完整的交互過程已完成。服務(wù)器或者繼續(xù)偵聽更多的請求,或者主動關(guān)閉。嵌入式WEB服務(wù)器的實現(xiàn)在進行了必要的初始化工作后,打開TC主流程圖主流程圖主要程序的實現(xiàn)(1)HTTP主線程HTTP的主線程首先注冊一個新的TCP連接,并將其綁定到80端口。隨后該連接進入偵聽的狀態(tài),一旦接受到客戶發(fā)起的連接請求,則調(diào)用process_connection()函數(shù)進行處理。主要程序代碼如下:staticvoidhttpd_thread(void*arg){ structnetconn*conn,*newconn;
/*CreateanewTCPconnectionhandle.*/ conn=netconn_new(NETCONN_TCP);主要程序的實現(xiàn)(1)HTTP主線程主要程序的實現(xiàn)
/*Bindtheconnectiontoport80onany localIPaddress.*/ netconn_bind(conn,NULL,80);
/*PuttheconnectionintoLISTENstate.*/ netconn_listen(conn);
/*Loopforever.*/ while(1) { /*Acceptanewconnection.*/ newconn=netconn_accept(conn); if(newconn!=NULL) { /*Processtheincommingconnection.*/ process_connection(newconn); /*Deallocateconnectionhandle.*/ netconn_delete(newconn); }OSTimeDly(2); }}主要程序的實現(xiàn)
主要程序的實現(xiàn)(2)處理連接的函數(shù)實際的處理連接過程由process_connection()函數(shù)完成。該函數(shù)對客戶請求進行解碼:如為GET請求,則將網(wǎng)頁內(nèi)容發(fā)送給請求客戶端;否則不予理會。網(wǎng)頁內(nèi)容如下:/*Thisisthedatafortheactualwebpage.*/staticcharindexdata[]="<html><head><title>M16C-uC/OS-IIWebservertestpage.</title>\</head>\<bodybgcolor=\"darkGray\"text=\"black\"link=\"blue\">\<h1align=center>Welcometothistestpage.</h1>\<p><h3>ThiswebsiteisrunningLwIPonaM16C16-bitmicrocontroller,usinguC/OS-II\RealTimeOperatingSystem.<br>Thecontrollerhas31KBRAMand384KBROM.\</h3></p><hr>\<h3align=center>Numberofpageviews:[0]</h3>\<hr>\<p>This\"server\"showssome\"dynamic\"content...\Refreshthepageandwatchthecounterincrement!\"Wow!!!11\"</p>\</body>\</html>";主要程序的實現(xiàn)(2)處理連接的函數(shù)主要程序的實現(xiàn)
staticcharhttp_html_hdr[]="HTTP/1.1200OK\r\n\Content-Type:text/html\r\n\r\n\n";函數(shù)中設(shè)計了一個簡單的計數(shù)器,對客戶請求的次數(shù)進行計數(shù),并將計數(shù)值反饋到網(wǎng)頁內(nèi)容中。主要的程序代碼如下:/*Thisfunctionprocessesanincommingconnection.*/staticvoidprocess_connection(structnetconn*conn){ structnetbuf*inbuf; char*rq; u16_tlen; inti; staticintNumber=0;
if(++Number==10){ Number=0; }
i=0; while(indexdata[i]) { if(indexdata[i]=='[') { i++; indexdata[i]='0'+Number; } i++; }主要程序的實現(xiàn)
staticcharhttp_html_主要程序的實現(xiàn) /*Readdatafromtheconnectionintothenetbufinbuf. Weassumethatthefullrequestisinthenetbuf.*/ if((inbuf=netconn_recv(conn))==NULL) { return; } netbuf_data(inbuf,(void**)&rq,&len); /*deletedatabuffer*/ netbuf_delete(inbuf);
/*CheckiftherequestwasanHTTP"GET/\r\n".*/ if(rq[0]=='G'&&rq[1]=='E'&&
rq[2]=='T'&&rq[3]==''&& rq[4]=='/')
{ /*Sendtheheader.*/ netconn_write(conn,(void*)http_html_hdr,sizeof(http_html_hdr),NETCONN_NOCOPY); /*Sendtheactualwebpage.*/ netconn_write(conn,(void*)indexdata,sizeof(indexdata),NETCONN_COPY); }}主要程序的實現(xiàn) /*Readdatafromthe實現(xiàn)結(jié)果嵌入式WEB服務(wù)器在RenesasM16C/62P上運行后,通過瀏覽器對其進行訪問,訪問結(jié)果如圖所示。實現(xiàn)結(jié)果嵌入式WEB服務(wù)器在RenesasM16C/62P嵌入式WEB服務(wù)器功能擴展由于只是對LwIP進行簡單的驗證,因此設(shè)計的嵌入式WEB服務(wù)器只是簡單地顯示一個頁面。實際上可以結(jié)合應(yīng)用需求,對嵌入式WEB服務(wù)器作出各種各樣的功能擴展。如可以將嵌入式WEB服務(wù)器與移動機器人結(jié)合起來。在移動機器人上搭建嵌入式WEB服務(wù)器,使得客戶可以非常方便地通過WEB瀏覽器對移動機器人進行遠程監(jiān)控,如圖像采集、機器人狀態(tài)信息查詢、機器人運動控制等。嵌入式WEB服務(wù)器在移動機器人上搭建成功以后,原則上世界上任何一個地方都可以通過WEB瀏覽器獲取該服務(wù)器發(fā)布的監(jiān)控系統(tǒng)實時信息,進而進行遠程控制、調(diào)節(jié)和維護等。嵌入式WEB服務(wù)器功能擴展由于只是對LwIP進行簡單的驗證,第九章LwIP及其網(wǎng)絡(luò)編程應(yīng)用實例第九章LwIP及
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 福建師范大學(xué)《社會學(xué)概論》2021-2022學(xué)年第一學(xué)期期末試卷
- 福建師范大學(xué)《環(huán)境生態(tài)工程》2021-2022學(xué)年第一學(xué)期期末試卷
- 福建師范大學(xué)《地理語言學(xué)》2023-2024學(xué)年第一學(xué)期期末試卷
- 第14章 因子分析1統(tǒng)計學(xué)原理課件
- 中國高血壓防治指南關(guān)于高血壓急癥的解讀
- 深度報文檢測技術(shù)Comware V7 DPI
- 油田動土作業(yè)安全管理實施細則
- 教研活動記錄(班級環(huán)創(chuàng)及擺布)
- 2024年太原客運駕駛員考試試題答案解析
- 2024年防城港A1客運從業(yè)資格證
- 劉潤年度演講2024
- 【核心素養(yǎng)目標】14.1熱機 教案 2023-2024學(xué)年人教版物理九年級上學(xué)期
- 2025屆高考語文復(fù)習(xí):文言實詞推斷方法 課件
- 醫(yī)院轉(zhuǎn)讓居間服務(wù)合同范本
- 遼寧省法院系統(tǒng)招聘聘用制書記員真題
- 2024全國各地區(qū)語文中考真題匯編《第二期》
- 6.18 美國的獨立 課件 2024-2025學(xué)年統(tǒng)編版九年級歷史上冊
- 3.1平移現(xiàn)象(課件)冀教版數(shù)學(xué)三年級上冊
- 備戰(zhàn)2025年高考語文易錯題(新高考專用)易錯題22 古代詩歌閱讀之形象題-不明詩歌形象內(nèi)涵含答案
- 中國共產(chǎn)主義青年團團員教育管理工作條例(試行)團課學(xué)習(xí)課件
- 二甲雙胍臨床應(yīng)用專家共識(2023年版)解讀
評論
0/150
提交評論