試環(huán)境搭建與優(yōu)化)
做嵌入式開發(fā)的人應(yīng)該都經(jīng)歷過(guò)一種糾結(jié)明明可以抱著一塊開發(fā)板到處跑卻總得拖著一根網(wǎng)線才能干活。有的板子沒(méi)有有線網(wǎng)口有的板子放在某個(gè)現(xiàn)場(chǎng)角落拉線不現(xiàn)實(shí)還有些時(shí)候手里拿的是一臺(tái)手機(jī)改裝過(guò)的調(diào)試終端。這時(shí)候把NFS和TFTP這兩個(gè)調(diào)試?yán)骷艿絎iFi上就成了非?,F(xiàn)實(shí)的需求。這篇內(nèi)容我想把“WiFi模式下nfs與tftp的網(wǎng)絡(luò)環(huán)境搭建”這件事從頭到尾捋一遍。它不復(fù)雜但也不是直接把有線環(huán)境的配置搬過(guò)來(lái)就行——無(wú)線鏈路的延遲、丟包、IP漂移這些特性會(huì)實(shí)實(shí)在在影響NFS的掛載穩(wěn)定性和TFTP的傳輸成功率。這篇文章適合正在用開發(fā)板做根文件系統(tǒng)掛載調(diào)試的工程師適合想擺脫網(wǎng)線束縛的嵌入式愛好者也適合那些手里只有WiFi環(huán)境、卻要折騰uboot和內(nèi)核鏡像傳輸?shù)娜?。先給你交個(gè)底這套環(huán)境本身并不難難的是讓它穩(wěn)定、可復(fù)用。下面我會(huì)從整體設(shè)計(jì)思路說(shuō)起把服務(wù)端配置、客戶端優(yōu)化、WiFi場(chǎng)景下的驗(yàn)證和排錯(cuò)一路講完。我不喜歡寫那種“復(fù)制粘貼就能跑”的教程你想跑起來(lái)很容易想跑得穩(wěn)就得知道參數(shù)背后的原因。1. WiFi模式下NFS與TFTP環(huán)境的整體設(shè)計(jì)與核心思路1.1 為什么非要在WiFi下搭這套環(huán)境先把場(chǎng)景掰開。NFSNetwork File System解決的是開發(fā)板啟動(dòng)時(shí)掛載根文件系統(tǒng)的問(wèn)題TFTP解決的是uboot階段傳輸內(nèi)核鏡像、設(shè)備樹文件的問(wèn)題。這兩個(gè)服務(wù)在有線網(wǎng)絡(luò)下都非常成熟但WiFi場(chǎng)景會(huì)引入幾個(gè)有線環(huán)境幾乎不會(huì)遇到的不確定因素IP地址可能是動(dòng)態(tài)分配的、丟包率比網(wǎng)線高一個(gè)數(shù)量級(jí)、延遲抖動(dòng)明顯、路由器的AP隔離可能直接掐斷設(shè)備間的互訪。我碰到過(guò)的真實(shí)場(chǎng)景是某塊開發(fā)板自帶WiFi模塊但沒(méi)有以太網(wǎng)口項(xiàng)目現(xiàn)場(chǎng)又不能隨便布線。當(dāng)時(shí)手頭只有一臺(tái)筆記本電腦和一個(gè)家用路由器要在這種條件里完成內(nèi)核下載、文件系統(tǒng)掛載的整套調(diào)試不像聽起來(lái)那么輕松。特別是第一次用TFTP傳內(nèi)核時(shí)反復(fù)超時(shí)后來(lái)發(fā)現(xiàn)是WiFi基站下的丟包率偏高小文件沒(méi)感覺一傳到十幾MB就原形畢露。WiFi天然具有“移動(dòng)性”優(yōu)勢(shì)很多開發(fā)環(huán)境需要設(shè)備在實(shí)際擺放位置調(diào)試電腦可以就近放著但網(wǎng)線布不過(guò)去。把NFS和TFTP從有線遷到無(wú)線本質(zhì)上是把調(diào)試鏈路的傳輸介質(zhì)換掉了但服務(wù)架構(gòu)沒(méi)有變一臺(tái)開發(fā)機(jī)服務(wù)器提供共享目錄和啟動(dòng)文件開發(fā)板作為客戶端啟動(dòng)時(shí)通過(guò)網(wǎng)絡(luò)拉取資源。區(qū)別在于無(wú)線鏈路不允許你像有線那樣“隨便配配就能用”必須針對(duì)鏈路質(zhì)量做參數(shù)層面的適配。1.2 網(wǎng)絡(luò)拓?fù)渑c鏈路規(guī)劃這套環(huán)境里的角色劃分非常清晰開發(fā)機(jī)也被稱作宿主機(jī)安裝并運(yùn)行NFS服務(wù)端和TFTP服務(wù)端通常通過(guò)網(wǎng)線或WiFi接入同一個(gè)局域網(wǎng)段。開發(fā)板客戶端通過(guò)WiFi接入相同局域網(wǎng)啟動(dòng)過(guò)程中先從DHCP獲取IP或配置靜態(tài)IP然后依次通過(guò)TFTP下載內(nèi)核、通過(guò)NFS掛載根文件系統(tǒng)。無(wú)線模式下我強(qiáng)烈建議給開發(fā)板固定IP而不是完全依賴DHCP動(dòng)態(tài)分配。原因后面細(xì)說(shuō)簡(jiǎn)單講就是動(dòng)態(tài)IP在WiFi下重新關(guān)聯(lián)路由器時(shí)很容易變化而NFS掛載一旦斷掉再恢復(fù)就要重新定位服務(wù)器IP這會(huì)浪費(fèi)大量調(diào)試時(shí)間。實(shí)測(cè)中比較穩(wěn)的搭配是路由器開啟DHCP靜態(tài)綁定把開發(fā)板的MAC地址和某個(gè)固定IP綁在一起。這樣既不用在板子上寫死IP換網(wǎng)絡(luò)環(huán)境時(shí)還能自動(dòng)適配又能保證每次重新上電后IP不變。這是整套環(huán)境的鏈路示意圖路由器位于中間開發(fā)機(jī)通過(guò)WiFi/網(wǎng)線接入開發(fā)板通過(guò)WiFi接入兩者處于同一網(wǎng)段。TFTP走的是UDP 69端口NFS走的是TCP/UDP 2049端口外加一組動(dòng)態(tài)端口rpcbind。WiFi環(huán)境下如果路由器開啟了AP隔離客戶端隔離這兩個(gè)設(shè)備的二層通信會(huì)被直接切斷表現(xiàn)為互相ping不通——這個(gè)問(wèn)題我后文還會(huì)專門說(shuō)。1.3 方案選型為什么不直接用U盤或串口你可能問(wèn)既然WiFi不穩(wěn)定為什么不直接U盤拷貝、串口下載答案是效率和迭代速度。嵌入式調(diào)試是一個(gè)“改代碼、編內(nèi)核、拷過(guò)去、看現(xiàn)象”的循環(huán)串口傳一個(gè)大內(nèi)核要等幾分鐘U盤還得來(lái)回插拔。NFS和TFTP的價(jià)值在于把“傳輸”變成網(wǎng)絡(luò)上的常態(tài)操作每次改動(dòng)后重啟開發(fā)板就能加載最新內(nèi)容。WiFi雖然不如有線穩(wěn)定但勝在沒(méi)有物理束縛尤其適合移動(dòng)調(diào)試和現(xiàn)場(chǎng)聯(lián)調(diào)場(chǎng)景。這套方案解決的真實(shí)痛點(diǎn)其實(shí)就一句話在沒(méi)有有線條件的環(huán)境里保留完整的網(wǎng)絡(luò)調(diào)試能力。代價(jià)則是鏈路質(zhì)量下降所以整個(gè)配置過(guò)程不能照搬有線思路要在NFS協(xié)議版本、掛載超時(shí)參數(shù)、TFTP塊大小這些層面上做針對(duì)性調(diào)整。2. 服務(wù)端基礎(chǔ)環(huán)境搭建NFS與TFTP的安裝和啟動(dòng)2.1 確認(rèn)服務(wù)端網(wǎng)絡(luò)狀態(tài)與軟件源無(wú)論你用的是哪一套Linux發(fā)行版第一步都先確認(rèn)開發(fā)機(jī)自身的網(wǎng)絡(luò)狀態(tài)。這一步看起來(lái)多余但確實(shí)現(xiàn)場(chǎng)翻車過(guò)很多回有人裝好了NFS掛載時(shí)才發(fā)現(xiàn)服務(wù)端自己有兩個(gè)IP開發(fā)板訪問(wèn)的是那個(gè)被防火墻擋住的地址。# 查看網(wǎng)卡IP與連接狀態(tài) ip addr show ip route show # 確認(rèn)能與開發(fā)板互通先做一次基本ping ping 開發(fā)板IP我建議第一次搭建的時(shí)候用有線把開發(fā)機(jī)和路由器連接起來(lái)確認(rèn)整套NFSTFTP流程走通再切換到WiFi做鏈路優(yōu)化。這不是慫而是把變量拆開排查——有線都跑不通就上無(wú)線出了問(wèn)題都不知道怪誰(shuí)。軟件源方面Debian系和Red Hat系裝的服務(wù)名字不一樣但核心組件一樣發(fā)行版系NFS服務(wù)端包名TFTP服務(wù)端包名Debian/Ubuntunfs-kernel-servertftpd-hpaCentOS/RHEL/Rockynfs-utilstftp-server2.2 安裝NFS服務(wù)端以Debian系為例sudo apt update sudo apt install -y nfs-kernel-server裝完之后不要急著配先把服務(wù)狀態(tài)確認(rèn)一下sudo systemctl status nfs-server這個(gè)服務(wù)名在Debian系上是 nfs-server在部分版本上可能是 nfs-kernel-server。不管叫啥state為activerunning就行。Red Hat系的安裝命令是sudo dnf install -y nfs-utils sudo systemctl enable --now nfs-server這一步的作用是讓NFS在開機(jī)后自動(dòng)拉起避免每次手動(dòng)啟動(dòng)。后面你在開發(fā)板上配置開機(jī)自動(dòng)掛載時(shí)服務(wù)端的自啟動(dòng)就是前提條件。2.3 安裝TFTP服務(wù)端TFTP的陣營(yíng)選擇比較多我用的是 tftpd-hpa理由是它配置集中、支持systemd管理、參數(shù)語(yǔ)義清楚。Debian系安裝sudo apt install -y tftpd-hpaRed Hat系則要裝 tftp-server并通過(guò)socket激活sudo dnf install -y tftp-server sudo systemctl enable --now tftp.socket注意Red Hat系會(huì)拆成tftp.service和tftp.socket兩個(gè)單元實(shí)際監(jiān)聽68/69端口的是socket單元部署的時(shí)候要確認(rèn)socket是啟動(dòng)狀態(tài)。我見過(guò)有人在Red Hat上配了老半天配置文件結(jié)果發(fā)現(xiàn)tftp.service是獨(dú)立的、根本不是自己用的那個(gè)進(jìn)程。2.4 防火墻策略NFS和TFTP的端口開放防火墻是NFS環(huán)境搭建里翻車率最高的環(huán)節(jié)。NFS本身不止監(jiān)聽2049端口它依賴rpcbind動(dòng)態(tài)分配端口所以不能只放行2049還要放行rpcbind的111端口以及一系列動(dòng)態(tài)端口。TFTP則相對(duì)簡(jiǎn)單固定UDP 69端口。Debian系的ufw可以這樣操作sudo ufw allow 2049/tcp sudo ufw allow 2049/udp sudo ufw allow 111/tcp sudo ufw allow 111/udp sudo ufw allow 69/udpRed Hat系的firewalld更省事直接認(rèn)服務(wù)名sudo firewall-cmd --permanent --add-servicenfs sudo firewall-cmd --permanent --add-servicerpc-bind sudo firewall-cmd --permanent --add-servicetftp sudo firewall-cmd --reloadfirewalld能識(shí)別nfs這個(gè)服務(wù)名會(huì)自動(dòng)放行2049、111以及NFS動(dòng)態(tài)端口。但有一個(gè)問(wèn)題有些環(huán)境中firewalld的服務(wù)定義不包含全部動(dòng)態(tài)端口范圍配置完還是掛不上。保險(xiǎn)起見可以查看自己的NFS實(shí)際使用了哪些端口rpcinfo -p | grep nfs如果發(fā)現(xiàn)動(dòng)態(tài)端口不在防火墻放行范圍內(nèi)就得手動(dòng)加一條規(guī)則。有種更穩(wěn)的做法是固定NFS動(dòng)態(tài)端口范圍到某個(gè)區(qū)間再對(duì)區(qū)間放行具體做法是把下面這行寫進(jìn) /etc/nfs.conf 的[nfsd]段[nfsd] vers3y vers4y動(dòng)態(tài)端口固定的做法稍復(fù)雜但在部分企業(yè)路由器環(huán)境下確實(shí)更可靠。這是后話如果你只是個(gè)人開發(fā)環(huán)境放行服務(wù)名的方案通常就夠了。3. NFS核心配置與WiFi鏈路下的參數(shù)優(yōu)化3.1 導(dǎo)出目錄配置/etc/exports 的每一項(xiàng)都別糊弄NFS服務(wù)端配置核心就一個(gè)文件/etc/exports。它定義了“哪些目錄可以給誰(shuí)用、用什么權(quán)限”。我的習(xí)慣是單獨(dú)劃一個(gè)目錄做嵌入式調(diào)試共享區(qū)比如 /srv/nfs/rootfs里面放編譯好的根文件系統(tǒng)。一個(gè)典型的exports條目/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)四個(gè)參數(shù)里每個(gè)都有講究rw允許讀寫。調(diào)試階段你經(jīng)常要在開發(fā)板上寫文件只讀的話連日志都存不下來(lái)。sync服務(wù)端在響應(yīng)客戶端寫請(qǐng)求前必須將數(shù)據(jù)落盤。WiFi下鏈路本來(lái)就不穩(wěn)sync能最大限度避免數(shù)據(jù)不一致的問(wèn)題。有人為了性能用async但是在嵌入式調(diào)試場(chǎng)景我寧可慢一點(diǎn)也要數(shù)據(jù)安全。no_subtree_check關(guān)閉子目錄的權(quán)限檢查。這個(gè)參數(shù)能降低NFS在目錄重命名等場(chǎng)景下的延遲WiFi下尤其有價(jià)值。no_root_squash允許開發(fā)板上的root用戶以root權(quán)限訪問(wèn)共享目錄。沒(méi)有這個(gè)參數(shù)開發(fā)板上的root會(huì)被映射成nobody你連創(chuàng)建設(shè)備節(jié)點(diǎn)都做不了很多根文件系統(tǒng)掛載后會(huì)直接啟動(dòng)失敗。寫完exports后執(zhí)行sudo exportfs -ra sudo showmount -e localhostshowmount -e 輸出的內(nèi)容就是開發(fā)板側(cè)能看到的共享列表。如果這里沒(méi)有輸出你的共享目錄大概率是exports格式寫錯(cuò)了別急著去客戶端排查。3.2 WiFi場(chǎng)景下NFS掛載參數(shù)的選擇邏輯服務(wù)端配置搞定后真正能體現(xiàn)“WiFi優(yōu)化”的是客戶端掛載參數(shù)。有線環(huán)境下很多人直接 mount -t nfs 192.168.1.10:/srv/nfs/rootfs /mnt/nfs但無(wú)線環(huán)境的丟包與延遲不允許這么粗暴。這是我實(shí)測(cè)在WiFi場(chǎng)景下很好用的一組參數(shù)mount -t nfs -o nolock,tcp,rsize4096,wsize4096,timeo50,retrans5,soft \ 192.168.1.10:/srv/nfs/rootfs /mnt/nfs逐項(xiàng)說(shuō)明nolock關(guān)閉NFS的鎖管理。開發(fā)板側(cè)的NFS客戶端鎖機(jī)制在無(wú)線環(huán)境下容易觸發(fā)問(wèn)題關(guān)閉后能減少掛載失敗的概率。tcp強(qiáng)制走TCP而非UDP。WiFi本身丟包率不低TCP的重傳機(jī)制能保證數(shù)據(jù)傳輸完整性。有人擔(dān)心TCP比UDP慢但在NFS場(chǎng)景下TCP的可靠?jī)?yōu)勢(shì)遠(yuǎn)大于那點(diǎn)延遲差。rsize/wsize4096單次讀寫的塊大小。有線環(huán)境你完全可以設(shè)成32768但WiFi下大塊傳輸容易觸發(fā)底層重傳風(fēng)暴4096是實(shí)踐下來(lái)比較折中好用的值。timeo50超時(shí)時(shí)間單位是0.1秒也就是5秒。這個(gè)值比默認(rèn)的60060秒小很多。為什么不直接用默認(rèn)因?yàn)槟J(rèn)超時(shí)在無(wú)線鏈路上會(huì)讓你等很久才報(bào)錯(cuò)調(diào)試時(shí)卡在那里非常被動(dòng)。retrans5重傳次數(shù)。配合timeo約5次重試后還沒(méi)成功就報(bào)錯(cuò)。這兩個(gè)參數(shù)的意義是把“等待掛載結(jié)果”的時(shí)長(zhǎng)控制在可接受范圍內(nèi)。soft使用軟掛載即重試超時(shí)后返回錯(cuò)誤而不是無(wú)限阻塞。開發(fā)調(diào)試階段用soft因?yàn)槟憬?jīng)常改配置、調(diào)鏈路硬掛載一旦斷連會(huì)卡死整個(gè)系統(tǒng)只能重啟開發(fā)板。我之前在某塊板子上測(cè)試過(guò)同樣的NFS共享有線掛載開機(jī)到進(jìn)入shell大約10秒WiFi下用優(yōu)化參數(shù)大約14秒。多出的時(shí)間主要是無(wú)線鏈路的連接開銷和NFS握手延遲這個(gè)代價(jià)可以接受。但你如果用默認(rèn)的全部參數(shù)直接掛會(huì)出現(xiàn)一種情況掛載命令執(zhí)行后沒(méi)有任何反饋卡幾分鐘都不報(bào)錯(cuò)——那是硬掛載的等待重傳機(jī)制在空轉(zhuǎn)。3.3 客戶端掛載操作與開機(jī)自動(dòng)掛載手動(dòng)掛載確認(rèn)無(wú)誤后最好把掛載動(dòng)作固化到開發(fā)板的啟動(dòng)流程里。有兩種常見做法一種是把掛載命令寫進(jìn)啟動(dòng)腳本rc.local或systemd服務(wù)另一種是寫進(jìn) /etc/fstab。開發(fā)板上的文件系統(tǒng)如果本身在本地Flash里fstab是合適的192.168.1.10:/srv/nfs/rootfs /mnt/nfs nfs nolock,tcp,rsize4096,wsize4096,timeo50,retrans5,soft 0 0如果開發(fā)板根本沒(méi)有本地根文件系統(tǒng)整個(gè)過(guò)程是“uboot通過(guò)TFTP下載內(nèi)核 - 內(nèi)核通過(guò)NFS掛載根文件系統(tǒng)”那掛載參數(shù)要傳在內(nèi)核bootargs里。比如root/dev/nfs nfsroot192.168.1.10:/srv/nfs/rootfs,tcp,nolock,rsize4096,wsize4096 rw ipdhcp注意這里的nfsroot參數(shù)列表和服務(wù)端配置是配套的。別小看這個(gè)細(xì)節(jié)我見過(guò)有人uboot能起來(lái)、TFTP也能下內(nèi)核但根文件系統(tǒng)一直掛不上最后發(fā)現(xiàn)是bootargs里沒(méi)寫tcp、nolock內(nèi)核默認(rèn)用UDP掛載在WiFi下就是會(huì)莫名失敗。3.4 NFS權(quán)限問(wèn)題的排查思路Permission denied是NFS最常見的錯(cuò)誤之一而在WiFi環(huán)境中多了一層配IP的干擾讓問(wèn)題更難定位。排查順序是先確認(rèn)客戶端拿到的是否是192.168.1.0/24網(wǎng)段的IP再看exports里的網(wǎng)段是否覆蓋了這個(gè)IP再看no_root_squash是否加上了。小技巧在服務(wù)器端看日志能最快定位權(quán)限問(wèn)題。sudo tail -f /var/log/syslog | grep nfs日志會(huì)直接告訴你“nfsd: permission error”以及來(lái)源IP。我看到過(guò)不少人折騰半天防火墻結(jié)果全是網(wǎng)段寫錯(cuò)導(dǎo)致的權(quán)限拒絕。還有一個(gè)坑是SELinux。Red Hat系默認(rèn)SELinux強(qiáng)制模式會(huì)攔NFS流量表現(xiàn)為客戶端卡在掛載、服務(wù)端日志卻干干凈凈。臨時(shí)驗(yàn)證可以先執(zhí)行 setenforce 0如果掛載立刻成功說(shuō)明就要么關(guān)SELinux要么打SELinux布爾值放行NFS。4. TFTP服務(wù)配置啟動(dòng)文件的分發(fā)通道4.1 TFTP與NFS的職責(zé)邊界在整套啟動(dòng)流程里TFTP和NFS的分工是uboot階段用TFTP下載內(nèi)核鏡像和設(shè)備樹因?yàn)閡boot里的NFS客戶端太簡(jiǎn)陋性能和穩(wěn)定性都差內(nèi)核接管啟動(dòng)后再用NFS掛載根文件系統(tǒng)。這兩個(gè)服務(wù)各管一段但文件來(lái)源可以共享同一個(gè)目錄也可以分開。TFTP配置目錄建議放在 /srv/tftp 或者 /var/lib/tftpboot。里面放zImage、uImage、設(shè)備樹dtb文件。你可能會(huì)問(wèn)這些文件都不大為什么WiFi下還偶爾傳失敗因?yàn)門FTP走UDP本身沒(méi)有擁塞控制WiFi的隨機(jī)丟包一旦觸發(fā)了服務(wù)端的超時(shí)重傳重傳往往也一起丟——這就是TFTP在無(wú)線環(huán)境里“越傳越慢、最后失敗”的根因。4.2 tftpd-hpa 的配置細(xì)節(jié)tftpd-hpa的配置文件在 /etc/default/tftpd-hpa內(nèi)容一般長(zhǎng)這樣TFTP_USERNAMEtftp TFTP_DIRECTORY/srv/tftp TFTP_ADDRESS:69 TFTP_OPTIONS--secure --create每個(gè)配置項(xiàng)的作用TFTP_USERNAME運(yùn)行tftpd的進(jìn)程用戶建議專用低權(quán)限用戶tftp。防止TFTP被攻擊時(shí)拿到root權(quán)限。TFTP_DIRECTORYTFTP的根目錄。--secure模式下客戶端訪問(wèn)的所有文件都限制在這個(gè)目錄里相當(dāng)于chroot。TFTP_ADDRESS監(jiān)聽地址:69表示監(jiān)聽所有網(wǎng)卡的69端口。也可以指定開發(fā)機(jī)的IP比如192.168.1.10:69這樣更安全但如果開發(fā)機(jī)IP變化會(huì)直接失聯(lián)。TFTP_OPTIONS--secure是安全的chroot模式務(wù)必保留。--create允許客戶端創(chuàng)建文件這樣開發(fā)板也能把日志、臨時(shí)文件傳回服務(wù)端。如果只是為了啟動(dòng)鏡像下載不一定要加--create。改完配置后sudo systemctl restart tftpd-hpa sudo ss -ulpn | grep :69看到監(jiān)聽在69端口就說(shuō)明服務(wù)起來(lái)了。有些老教程會(huì)教你把TFTP托管給xinetd配置 /etc/xinetd.d/tftp。我不推薦新環(huán)境用xinetd因?yàn)樗嗔艘粚舆M(jìn)程管理的復(fù)雜度systemd已經(jīng)把它替代得差不多了。用tftpd-hpa的獨(dú)立daemon模式啟動(dòng)快、日志清晰、排查方便。4.3 WiFi鏈路下的TFTP實(shí)測(cè)驗(yàn)證搭建完服務(wù)端用命令行tftp客戶端做一次完整測(cè)試# 開發(fā)機(jī)上測(cè)試 tftp 192.168.1.10 tftp put testfile.txt tftp get zImage tftp quit建議傳一個(gè)稍微大一點(diǎn)的文件幾十MB級(jí)別來(lái)驗(yàn)證穩(wěn)定性。我第一次在WiFi下傳35MB的內(nèi)核壓縮包傳了三次兩次在校驗(yàn)階段失敗都是丟包導(dǎo)致的。TFTP協(xié)議有個(gè)天然限制單塊數(shù)據(jù)固定512字節(jié)超時(shí)時(shí)間默認(rèn)5秒。在無(wú)線鏈路下如果丟包率高于1%重傳會(huì)頻繁觸發(fā)整個(gè)傳輸過(guò)程會(huì)拖得很長(zhǎng)甚至超時(shí)終止。我實(shí)測(cè)的一個(gè)實(shí)用優(yōu)化是按數(shù)據(jù)包模式調(diào)整雖然TFTP協(xié)議本身規(guī)定了超時(shí)重傳但客戶端uboot往往沒(méi)給你調(diào)節(jié)超時(shí)的選項(xiàng)。所以更現(xiàn)實(shí)的辦法是能用NFS就用NFSTFTP只傳小文件。如果必須傳大文件優(yōu)先考慮從服務(wù)端角度降低鏈路丟包率——換5GHz頻段、靠近路由器、減少無(wú)線信道干擾比在TFTP參數(shù)上做文章有效得多。4.4 與uboot結(jié)合的啟動(dòng)流程如果你在uboot階段使用TFTP需要在uboot環(huán)境變量里指定服務(wù)端IP和文件名。大致流程如下setenv serverip 192.168.1.10 setenv ipaddr 192.168.1.20 setenv bootfile zImage # 下載內(nèi)核到內(nèi)存 tftp 0x81000000 ${bootfile} # 下載設(shè)備樹 tftp 0x82000000 ${devicetree}這里有一個(gè)經(jīng)常出錯(cuò)的地方uboot的ipaddr、serverip必須和開發(fā)板運(yùn)行內(nèi)核后使用的IP保持一致否則會(huì)出現(xiàn)“uboot下tftp成功內(nèi)核啟動(dòng)后網(wǎng)絡(luò)不通”的詭異狀況。WiFi環(huán)境下尤其要注意如果路由器給開發(fā)板分配的是動(dòng)態(tài)IPuboot階段的固定ipaddr和內(nèi)核階段的DHCP獲得的IP可能不一致導(dǎo)致服務(wù)器無(wú)法回包或者NFS掛載失敗。最省心的方案是前面說(shuō)過(guò)的路由器上綁定開發(fā)板MAC到固定IP同時(shí)uboot里把ipaddr設(shè)成同一個(gè)固定IPserverip設(shè)為開發(fā)機(jī)IP。然后把內(nèi)核bootargs改成 ip192.168.1.20::192.168.1.1:255.255.255.0::wlan0:off用靜態(tài)IP而不是dhcp。這樣整條鏈路從uboot到內(nèi)核IP一致不容易出幺蛾子。5. WiFi環(huán)境下的聯(lián)通性驗(yàn)證與穩(wěn)定性加固5.1 從物理層到應(yīng)用層的逐級(jí)驗(yàn)證很多人搭完環(huán)境掛載失敗第一反應(yīng)就是改NFS參數(shù)實(shí)際上問(wèn)題可能出在物理鏈路。無(wú)線環(huán)境下必須按這個(gè)順序排查每一步都通過(guò)再進(jìn)下一步先在開發(fā)板上確認(rèn)無(wú)線連接本身正常ip link show wlan0 iw dev wlan0 linkiw dev 的輸出能看到當(dāng)前連接的SSID和信號(hào)強(qiáng)度。信號(hào)強(qiáng)度低于-70dBm就要小心了這個(gè)閾值下丟包率會(huì)劇增NFS和TFTP都會(huì)表現(xiàn)得不穩(wěn)定。然后驗(yàn)證基本連通性ping -c 5 192.168.1.10不要只看“通不通”要關(guān)注延遲分布和丟包率。如果ping的丟包率超過(guò)1%后面的NFS、TFTP都不必談先解決鏈路問(wèn)題。第三步驗(yàn)證端口可達(dá)性# 開發(fā)板側(cè)測(cè)試NFS端口 nc -vz 192.168.1.10 2049 # 測(cè)試TFTP端口 nc -vzu 192.168.1.10 69nc測(cè)TCP端口穩(wěn)一點(diǎn)UDP端口測(cè)試不一定準(zhǔn)確nc的UDP模式不保證能反映服務(wù)端狀態(tài)但好在TFTP服務(wù)是否啟動(dòng)可以通過(guò)ss命令在服務(wù)端確認(rèn)兩端對(duì)照即可。最后驗(yàn)證NFS共享列表showmount -e 192.168.1.10如果能列出共享但掛載失敗問(wèn)題基本鎖定在掛載參數(shù)或權(quán)限而不是鏈路。5.2 AP隔離WiFi下最容易被忽略的攔路虎很多家用路由器默認(rèn)開啟“AP隔離”也叫客戶端隔離、無(wú)線隔離目的是防止同一WiFi下的設(shè)備互相訪問(wèn)。這個(gè)功能對(duì)普通家庭上網(wǎng)沒(méi)影響但對(duì)嵌入式調(diào)試是致命的開發(fā)板和開發(fā)機(jī)都能上網(wǎng)、都能ping通路由器但彼此無(wú)法通信。癥狀是開發(fā)板ping開發(fā)機(jī)能通因?yàn)樽叩氖锹酚善鬓D(zhuǎn)發(fā)AP隔離一般只隔離WLAN客戶端之間的二層直接通信不一定隔離三層轉(zhuǎn)發(fā)實(shí)際上多數(shù)家用路由的AP隔離會(huì)擋掉客戶端之間的所有訪問(wèn)包括局域網(wǎng)內(nèi)訪問(wèn)。我來(lái)說(shuō)一個(gè)清晰的判斷方法如果開發(fā)板和開發(fā)機(jī)都能ping通路由器但互相ping不通第一優(yōu)先懷疑就是AP隔離。處理方式是登錄路由器后臺(tái)關(guān)閉“AP隔離”或“無(wú)線AP隔離”設(shè)置位置通常在無(wú)線設(shè)置的高級(jí)選項(xiàng)里。有的固件把guest network默認(rèn)開啟并默認(rèn)啟用隔離如果你連接的是訪客網(wǎng)絡(luò)大概率就是這個(gè)原因。5.3 頻段選擇與信號(hào)質(zhì)量?jī)?yōu)化2.4GHz和5GHz在這個(gè)場(chǎng)景里我會(huì)毫不猶豫選5GHz。原因很直接2.4GHz頻段在家庭和辦公環(huán)境里干擾源太多藍(lán)牙、微波爐、USB 3.0設(shè)備、鄰居WiFi都在搶信道。實(shí)測(cè)在同一個(gè)位置2.4GHz下ping延遲的抖動(dòng)范圍可以達(dá)到幾十毫秒5GHz則穩(wěn)定在個(gè)位毫秒級(jí)。如果你的開發(fā)板支持5GHz一定切到5GHz如果不支持就得在2.4GHz下做更多鏈路層面的補(bǔ)償比如把路由器擺放得更近、選擇一個(gè)不那么擁擠的信道。另外有個(gè)WiFi省電模式的坑。很多開發(fā)板的無(wú)線網(wǎng)卡默認(rèn)開啟省電模式power save這會(huì)帶來(lái)明顯的延遲尖峰和偶發(fā)丟包。開發(fā)板連接WiFi后通過(guò)下面的命令關(guān)閉省電iw dev wlan0 set power_save off這條命令太實(shí)用了。我之前在某塊板子上遇到過(guò)NFS隨機(jī)卡死的情況查了一圈發(fā)現(xiàn)是無(wú)線網(wǎng)卡每隔幾秒進(jìn)入一次休眠再喚醒TCP連接沒(méi)斷但是傳輸停頓了一下NFS客戶端就等超時(shí)了。關(guān)掉省電模式以后問(wèn)題再?zèng)]出現(xiàn)過(guò)。5.4 動(dòng)態(tài)IP場(chǎng)景下的掛載自恢復(fù)即使做了靜態(tài)綁定WiFi斷開重連后開發(fā)板的IP可能依然會(huì)變或者網(wǎng)絡(luò)棧在重連過(guò)程中出現(xiàn)短暫不可用。這時(shí)候NFS掛載會(huì)卡住或報(bào)錯(cuò)。如果你沒(méi)有做靜態(tài)綁定那么在開發(fā)板上寫一個(gè)小的監(jiān)控腳本是必要的#!/bin/sh while true; do ip addr show wlan0 | grep 192.168.1.10 /dev/null 21 if [ $? -ne 0 ]; then IP_CHANGED1 fi mountpoint -q /mnt/nfs if [ $? -ne 0 ] [ $IP_CHANGED -eq 1 ]; then mount -t nfs -o nolock,tcp,rsize4096,wsize4096,timeo50,retrans5,soft \ 192.168.1.10:/srv/nfs/rootfs /mnt/nfs IP_CHANGED0 fi sleep 10 done這個(gè)腳本雖然是簡(jiǎn)化版本但是思路是對(duì)的檢測(cè)IP、檢測(cè)掛載狀態(tài)、重新掛載。實(shí)際項(xiàng)目里你可以把它弄成一個(gè)systemd服務(wù)。有這個(gè)腳本兜底WiFi重啟、路由器重啟后調(diào)試環(huán)境能自動(dòng)恢復(fù)省掉很多手動(dòng)操作。6. 常見故障排查與個(gè)人實(shí)操心得先把這段時(shí)間積攢下來(lái)的高頻問(wèn)題做成一個(gè)速查表遇到的問(wèn)題基本都能在里面找到入口現(xiàn)象可能原因排查方案開發(fā)板和開發(fā)機(jī)互ping不通AP隔離開啟 / 不在同一網(wǎng)段檢查路由器設(shè)置、檢查WiFi連接ping通但NFS掛載卡住防火墻未放行 / rpcbind未啟動(dòng)放行111和2049端口檢查rpcinfoNFS掛載報(bào)Permission deniedexports網(wǎng)段不對(duì) / 缺少no_root_squash查看服務(wù)端syslog、檢查exportfsNFS掛載偶爾斷開無(wú)線省電模式 / 丟包率偏高關(guān)閉power_save、換5GHz頻段TFTP傳小文件成功大文件失敗WiFi丟包觸發(fā)頻繁重傳換有線驗(yàn)證、換鏈路、減小文件uboot能tftp但內(nèi)核啟動(dòng)后網(wǎng)絡(luò)不通uboot固定IP與內(nèi)核IP不一致路由器靜態(tài)綁定MAC、bootargs設(shè)靜態(tài)IPSELinux攔截導(dǎo)致掛載無(wú)反應(yīng)Red Hat系默認(rèn)SELinux強(qiáng)制setenforce 0驗(yàn)證后配置布爾值6.1 掛載命令卡住不動(dòng)怎么快速脫身NFS掛載命令一旦卡住最常見的錯(cuò)誤操作是按CtrlC然后重試發(fā)現(xiàn)還是卡。原因是NFS cliente掛載進(jìn)程已經(jīng)進(jìn)入不可中斷的等待狀態(tài)D state普通信號(hào)殺不掉。我的處理方式# 先等軟掛載的timeo retrans周期走完 # 如果超過(guò)2分鐘還無(wú)響應(yīng)強(qiáng)制卸載 umount -lf /mnt/nfs使用 umount -lf懶惰卸載可以強(qiáng)制解除掛載點(diǎn)然后檢查鏈路和服務(wù)端狀態(tài)確認(rèn)沒(méi)問(wèn)題再重新掛載。加了這個(gè)惰性卸載參數(shù)之后你就不用為了恢復(fù)環(huán)境而重啟整個(gè)開發(fā)板了。但注意省事不代表可以亂用頻繁強(qiáng)制卸載容易造成文件系統(tǒng)緩存未落盤。調(diào)試階段沒(méi)問(wèn)題生產(chǎn)設(shè)備上不要這么干。6.2 服務(wù)端日志怎么高效利用NFS和TFTP的很多問(wèn)題只在服務(wù)端日志里有線索客戶端界面只能給你一個(gè)籠統(tǒng)的錯(cuò)誤碼。Debian系NFS在 /var/log/syslog 或 /var/log/messages 里Red Hat系在 /var/log/messages 里。每次出現(xiàn)問(wèn)題時(shí)第一時(shí)間查這兩個(gè)日志比反復(fù)重啟客戶端高效太多。我之前遇到過(guò)一次開發(fā)板掛載NFS失敗排查了很久沒(méi)頭緒最后打開服務(wù)端日志看到一行 nfsd: request from unprivileged port才知道問(wèn)題出在開發(fā)板把源端口映射到了高端口而服務(wù)端配置了安全端口限制。這個(gè)錯(cuò)誤信息在客戶端完全不顯示不查日志根本無(wú)從入手。還有一點(diǎn)很多教程不會(huì)提TFTP上傳失敗時(shí)在/tftpboot目錄下查找同名零字節(jié)文件如果存在說(shuō)明上次傳輸失敗了但殘留了空文件會(huì)影響下次傳輸或誤判文件完整性。我的習(xí)慣是每次做TFTP傳輸測(cè)試前先清空一下TFTP目錄里同名目標(biāo)文件。6.3 TFTP傳輸大文件容易失敗的另類解法WiFi下TFTP傳大文件失敗已經(jīng)確認(rèn)是丟包引發(fā)的重傳風(fēng)暴后還有一個(gè)我親測(cè)有效的思路把要傳的大文件拆成若干小段用腳本循環(huán)傳。比如一個(gè)35MB的鏡像拆分成每段5MBTFTP逐段傳輸后重新拼接split -b 5M zImage zImage_part_ for part in zImage_part_*; do tftp -p 192.168.1.10 -l $part done在開發(fā)板上再把這幾個(gè)部分拼接回完整鏡像。這個(gè)辦法有點(diǎn)取巧對(duì)于“WiFi鏈路質(zhì)量不算太差但不穩(wěn)定”的環(huán)境很有效。如果鏈路質(zhì)量已經(jīng)很差拆成多段依然會(huì)失敗那就不如老老實(shí)實(shí)解決信號(hào)問(wèn)題。這個(gè)方法不適合日常工作流只適合應(yīng)急比如你人在現(xiàn)場(chǎng)手頭沒(méi)有網(wǎng)線但內(nèi)核編譯好了必須馬上傳到板子上驗(yàn)證。絕大多數(shù)情況下我更建議Wireless環(huán)境里能用NFS就NFS能用串口傳小文件就串口傳。6.4 不同開發(fā)板的WiFi行為差異市面上的開發(fā)板WiFi模塊千差萬(wàn)別同樣是WiFi有的板子連上什么都不干就很穩(wěn)定有的板子省電模式關(guān)不掉、驅(qū)動(dòng)有bugNFS時(shí)不時(shí)掉線。遇到后者別死磕服務(wù)端配置先查無(wú)線驅(qū)動(dòng)和固件版本。我遇到過(guò)的情況是某開發(fā)板的WiFi驅(qū)動(dòng)在某種路由器上場(chǎng)掃描行為異常表現(xiàn)為每隔固定時(shí)間就掉線重連。最后通過(guò)升級(jí)內(nèi)核模塊才徹底解決。這個(gè)經(jīng)驗(yàn)的意思是如果你的服務(wù)端配置、防火墻、信號(hào)質(zhì)量都排查完了仍然不穩(wěn)定去看無(wú)線網(wǎng)卡驅(qū)動(dòng)是不是有已知bug。還有一個(gè)再小不過(guò)但是影響鏈路穩(wěn)定性的點(diǎn)無(wú)線電源管理。很多開發(fā)板默認(rèn)啟用省電模式啟動(dòng)調(diào)試時(shí)CPU是省不了多少電的但網(wǎng)卡會(huì)頻繁休眠。在代碼里加一條 iw dev wlan0 set power_save off 的成本低于后面排查隨機(jī)斷連的時(shí)間成本。最后分享一個(gè)我自己的習(xí)慣我會(huì)在開發(fā)機(jī)上留一個(gè)啟動(dòng)腳本把NFS服務(wù)、TFTP服務(wù)、防火墻狀態(tài)一次檢查完輸出一份清晰的報(bào)告。在WiFi調(diào)試這種多環(huán)節(jié)的場(chǎng)景里腳本檢查比手工排查可靠得多每次改完環(huán)境配置跑一遍能節(jié)省大量時(shí)間。這套WiFi模式下的NFSTFTP環(huán)境本質(zhì)上就是處理好三件事服務(wù)端正確導(dǎo)出、客戶端選用適配無(wú)線鏈路的掛載參數(shù)、鏈路質(zhì)量足夠可靠。從有線轉(zhuǎn)到無(wú)線千萬(wàn)不要以為只是換了傳輸介質(zhì)鏈路特征變了參數(shù)和排查思路都要跟著變。希望這些從現(xiàn)場(chǎng)踩坑里總結(jié)出來(lái)的經(jīng)驗(yàn)?zāi)軒湍闵僮咭恍澛贰?