29.1 引言
本章中我們要討論另一個(gè)常用的應(yīng)用程序:NFS(網(wǎng)絡(luò)文件系統(tǒng)),它為客戶(hù)程序提供透明的文件訪問(wèn)。NFS的基礎(chǔ)是Sun RPC:遠(yuǎn)程過(guò)程調(diào)用。我們首先必須描述一下RPC。
客戶(hù)程序使用NFS不需要做什么特別的工作,當(dāng)NFS內(nèi)核檢測(cè)到被訪問(wèn)的文件位于一個(gè)NFS服務(wù)器時(shí),就會(huì)自動(dòng)產(chǎn)生一個(gè)訪問(wèn)該文件的RPC調(diào)用。
我們對(duì)NFS如何訪問(wèn)文件的細(xì)節(jié)并不感興趣,只對(duì)它如何使用Internet的協(xié)議,尤其是UDP協(xié)議,感興趣。
29.2 Sun遠(yuǎn)程過(guò)程調(diào)用
大多數(shù)的網(wǎng)絡(luò)程序設(shè)計(jì)都是編寫(xiě)一些調(diào)用系統(tǒng)提供的函數(shù)來(lái)完成特定的網(wǎng)絡(luò)操作的應(yīng)用程序。例如,一個(gè)函數(shù)完成TCP的主動(dòng)打開(kāi),另一個(gè)完成TCP的被動(dòng)打開(kāi),一個(gè)函數(shù)在一個(gè)TCP連接上發(fā)送數(shù)據(jù),另一個(gè)設(shè)置特定的協(xié)議選項(xiàng)(如激活TCP的keepalive定時(shí)器)。在1.15節(jié)我們提到過(guò)兩個(gè)常用的用于網(wǎng)絡(luò)編程的函數(shù)集(API):插口(socket)和TLI。正像客戶(hù)端和服務(wù)器端運(yùn)行的操作系統(tǒng)可能會(huì)不相同一樣,雙方使用的API也可能會(huì)不相同。由通信協(xié)議和應(yīng)用協(xié)議決定一對(duì)客戶(hù)和服務(wù)器是否可以彼此通信。如果兩臺(tái)主機(jī)連接在一個(gè)網(wǎng)絡(luò)上,并且都有一個(gè)TCP/IP的實(shí)現(xiàn),那么一臺(tái)主機(jī)上的一個(gè)使用C語(yǔ)言編寫(xiě)的、使用插口和TCP的Unix客戶(hù)程序可以和另一臺(tái)主機(jī)上的一個(gè)使用COBOL語(yǔ)言編寫(xiě)的、使用其他API和TCP的大型機(jī)服務(wù)器進(jìn)行通信。
一般來(lái)說(shuō),客戶(hù)發(fā)送命令給服務(wù)器,服務(wù)器向客戶(hù)發(fā)送應(yīng)答。目前為止,我們討論過(guò)的所有應(yīng)用程序—Ping,Traceroute,選路守護(hù)程序、以及DNS、TFTP、BOOTP、SNMP、Telnet、FTP和SMTP的客戶(hù)和服務(wù)器—都是采用這種方式實(shí)現(xiàn)的。
遠(yuǎn)程過(guò)程調(diào)用RPC(Remote Procedure Call)是一種不同的網(wǎng)絡(luò)程序設(shè)計(jì)方法。客戶(hù)程序編寫(xiě)時(shí)只是調(diào)用了服務(wù)器程序提供的函數(shù)。這只是程序員所感覺(jué)到的,實(shí)際上發(fā)生了下面一些動(dòng)作。
1:當(dāng)客戶(hù)程序調(diào)用遠(yuǎn)程的過(guò)程時(shí),它實(shí)際上只是調(diào)用了一個(gè)位于本機(jī)上的、由RPC程序包生成的函數(shù)。這個(gè)函數(shù)被稱(chēng)為客戶(hù)殘樁(stub)。客戶(hù)殘樁將過(guò)程的參數(shù)封裝成一個(gè)網(wǎng)絡(luò)報(bào)文,并且將這個(gè)報(bào)文發(fā)送給服務(wù)器程序。
2:服務(wù)器主機(jī)上的一個(gè)服務(wù)器殘樁負(fù)責(zé)接收這個(gè)網(wǎng)絡(luò)報(bào)文。它從網(wǎng)絡(luò)報(bào)文中提取參數(shù),然后調(diào)用應(yīng)用程序員編寫(xiě)的服務(wù)器過(guò)程。
3:當(dāng)服務(wù)器函數(shù)返回時(shí),它返回到服務(wù)器殘樁。服務(wù)器殘樁提取返回值,把返回值封裝成一個(gè)網(wǎng)絡(luò)報(bào)文,然后將報(bào)文發(fā)送給客戶(hù)殘樁。
4:客戶(hù)殘樁從接收到的網(wǎng)絡(luò)報(bào)文中取出返回值,將其返回給客戶(hù)程序。
網(wǎng)絡(luò)程序設(shè)計(jì)是通過(guò)殘樁和使用諸如插口或TLI的某個(gè)API的RPC庫(kù)例程來(lái)實(shí)現(xiàn)的,但是用戶(hù)程序—客戶(hù)程序和被客戶(hù)程序調(diào)用的服務(wù)器過(guò)程—不會(huì)和這個(gè)API打交道。客戶(hù)應(yīng)用程序只是調(diào)用服務(wù)器的過(guò)程,所有網(wǎng)絡(luò)程序設(shè)計(jì)的細(xì)節(jié)都被RPC程序包、客戶(hù)殘樁和服務(wù)器殘樁所隱藏。
一個(gè)RPC程序包提供了很多好處。
1:程序設(shè)計(jì)更加容易,因?yàn)楹苌倩驇缀鯖](méi)有涉及網(wǎng)絡(luò)編程。應(yīng)用程序設(shè)計(jì)員只需要編寫(xiě)一個(gè)客戶(hù)程序和客戶(hù)程序調(diào)用的服務(wù)器過(guò)程。
2:如果使用了一個(gè)不可靠的協(xié)議,如UDP,像超時(shí)和重傳等細(xì)節(jié)就由RPC程序包來(lái)處理。這就簡(jiǎn)化了用戶(hù)應(yīng)用程序。
3:RPC庫(kù)為參數(shù)和返回值的傳輸提供任何需要的數(shù)據(jù)轉(zhuǎn)換。例如,如果參數(shù)是由整數(shù)和浮點(diǎn)數(shù)組成的,RPC程序包處理整數(shù)和浮點(diǎn)數(shù)在客戶(hù)機(jī)和服務(wù)器主機(jī)上存儲(chǔ)的不同形式。這個(gè)功能簡(jiǎn)化了在異構(gòu)環(huán)境中的客戶(hù)和服務(wù)器的編碼問(wèn)題。
RPC程序設(shè)計(jì)的細(xì)節(jié)可以參看參考文獻(xiàn)[Stevens 1990]的第18章。兩個(gè)常用的RPC程序包是Sun RPC和開(kāi)放軟件基金(OSF)分布式計(jì)算環(huán)境(DCE)的RPC程序包。我們對(duì)于RPC的興趣在于想了解Sun RPC中過(guò)程調(diào)用和過(guò)程返回報(bào)文的形式,因?yàn)楸菊轮杏懻摰木W(wǎng)絡(luò)文件系統(tǒng)使用了它們。Sun RPC的第2版定義在RFC 1057 [Sun Microsystems 1988a]中。
Sun RPC
SunRPC有兩個(gè)版本。一個(gè)版本建立在插口API基礎(chǔ)上,和TCP和UDP打交道。另一個(gè)稱(chēng)為T(mén)I-RPC的(獨(dú)立于運(yùn)輸層),建立在TLIAPI基礎(chǔ)上,可以和內(nèi)核提供的任何運(yùn)輸層協(xié)議打交道。盡管本章中我們只討論TCP和UDP,從討論的觀點(diǎn)來(lái)看,兩者是一樣的。
圖29-1顯示的是使用UDP時(shí),一個(gè)RPC過(guò)程調(diào)用報(bào)文的格式。IP首部和UDP首部是標(biāo)準(zhǔn)的首部,我們已經(jīng)在圖3-1和圖11-2中顯示過(guò)。UDP首部以下是RPC程序包定義的部分。
事務(wù)標(biāo)識(shí)符(XID)由客戶(hù)程序設(shè)置,由服務(wù)器程序返回。當(dāng)客戶(hù)收到一個(gè)應(yīng)答,它將服務(wù)器返回的XID與它發(fā)送的請(qǐng)求的XID相比較。如果不匹配,客戶(hù)就放棄這個(gè)報(bào)文,等待從服務(wù)器返回的下一個(gè)報(bào)文。每次客戶(hù)發(fā)出一個(gè)新的RPC,它就會(huì)改變報(bào)文的XID。但是如果客戶(hù)重傳一個(gè)以前發(fā)送過(guò)的RPC(因?yàn)樗鼪](méi)有收到服務(wù)器的一個(gè)應(yīng)答),重傳報(bào)文的XID不會(huì)修改。
調(diào)用(call)變量在過(guò)程調(diào)用報(bào)文中設(shè)置為0,在應(yīng)答報(bào)文中設(shè)置為1。當(dāng)前的RPC版本是2。接下來(lái)三個(gè)變量:程序號(hào)、版本號(hào)和過(guò)程號(hào),標(biāo)識(shí)了服務(wù)器上被調(diào)用的特定過(guò)程。
證書(shū)(credential)字段標(biāo)識(shí)了客戶(hù)。有些情況下,證書(shū)字段設(shè)置為空值;另外一些情況下,證書(shū)字段設(shè)置為數(shù)字形式的客戶(hù)的用戶(hù)號(hào)和組號(hào)。服務(wù)器可以查看證書(shū)字段以決定是否執(zhí)行請(qǐng)求的過(guò)程。驗(yàn)證(verifier)字段用于使用了DES加密的安全RPC。盡管證書(shū)字段和驗(yàn)證字段是可變長(zhǎng)度的字段,它們的長(zhǎng)度也作為字段的一部分被編碼。
接下來(lái)是過(guò)程參數(shù)(procedure parameter)字段。參數(shù)的格式依賴(lài)于遠(yuǎn)程過(guò)程的定義。接收者(服務(wù)器殘樁)如何知道參數(shù)字段的大小呢?既然使用的是UDP協(xié)議,UDP數(shù)據(jù)報(bào)的大小減去驗(yàn)證字段以上所有字段的長(zhǎng)度就是參數(shù)的大小。如果使用的不是UDP而是TCP,因?yàn)門(mén)CP是一個(gè)字節(jié)流協(xié)議,沒(méi)有記錄邊界,所以沒(méi)有固定的長(zhǎng)度。為了解決這個(gè)問(wèn)題,在TCP首部和XID之間增加了一個(gè)4字節(jié)的長(zhǎng)度字段,告訴接收者這個(gè)RPC調(diào)用由多少字節(jié)組成。這也使得一個(gè)RPC調(diào)用報(bào)文在必要時(shí)可以用多個(gè)TCP段來(lái)傳輸(DNS使用了類(lèi)似的技術(shù),參見(jiàn)習(xí)題14-4)。
圖29-2顯示了一個(gè)RPC應(yīng)答報(bào)文的格式。當(dāng)遠(yuǎn)程過(guò)程返回時(shí),服務(wù)器殘樁將這個(gè)報(bào)文發(fā)送給客戶(hù)殘樁。
應(yīng)答報(bào)文中的XID字段是從調(diào)用報(bào)文的XID字段復(fù)制而來(lái)。應(yīng)答字段設(shè)置為1,以區(qū)別于調(diào)用報(bào)文。如果調(diào)用報(bào)文被接受,狀態(tài)字段設(shè)置為0(如果RPC的版本號(hào)不為2,或者服務(wù)器不能鑒別客戶(hù)的身份,調(diào)用報(bào)文可能被拒絕)。安全的RPC使用驗(yàn)證字段來(lái)標(biāo)識(shí)服務(wù)器。
如果遠(yuǎn)程過(guò)程調(diào)用成功,接受狀態(tài)字段置為0。一個(gè)非零的值可能表示一個(gè)不合法的版本號(hào)或者一個(gè)不合法的過(guò)程號(hào)。如果使用的不是UDP而是TCP,如同RPC調(diào)用報(bào)文一樣,在TCP首部和XID字段之間插入一個(gè)4字節(jié)的長(zhǎng)度字段。
29.3 XDR:外部數(shù)據(jù)表示
外部數(shù)據(jù)表示XDR(eXternal Data Representation)是一個(gè)標(biāo)準(zhǔn),用來(lái)對(duì)RPC調(diào)用報(bào)文和應(yīng)答報(bào)文中的值進(jìn)行編碼。這些值包括RPC首部字段(XID、程序號(hào)、接受狀態(tài)等)、過(guò)程參數(shù)和過(guò)程結(jié)果。采用標(biāo)準(zhǔn)化的方法對(duì)這些值進(jìn)行編碼使得一個(gè)系統(tǒng)中的客戶(hù)可以調(diào)用另一個(gè)不同架構(gòu)的系統(tǒng)中的一個(gè)過(guò)程。XDR在RFC 1014中定義[Sun Microsystems 1987]。
XDR定義了很多數(shù)據(jù)類(lèi)型以及它們?nèi)绾卧谝粋€(gè)RPC報(bào)文中傳輸?shù)木唧w形式(如比特順序,字節(jié)順序等)。發(fā)送者必須采用XDR格式構(gòu)造一個(gè)RPC報(bào)文,然后接收者將XDR格式的報(bào)文轉(zhuǎn)換為本機(jī)的表示形式。例如,在圖29-1和圖29-2中,我們顯示的所有整數(shù)值(XID、調(diào)用字段、程序號(hào)等)都是4字節(jié)的整數(shù)。在XDR中,所有的整數(shù)的確占據(jù)4個(gè)字節(jié)。XDR支持的其他數(shù)據(jù)類(lèi)型包括無(wú)符號(hào)整數(shù)、布爾類(lèi)型、浮點(diǎn)數(shù)、定長(zhǎng)數(shù)組、可變長(zhǎng)數(shù)組和結(jié)構(gòu)。
29.4 端口映射器
包含遠(yuǎn)程過(guò)程的RPC服務(wù)器程序使用的是臨時(shí)端口,而不是知名端口。這就需要某種形式的“注冊(cè)”程序來(lái)跟蹤哪一個(gè)RPC程序使用了哪一個(gè)臨時(shí)端口。在Sun RPC中,這個(gè)注冊(cè)程序被稱(chēng)為端口映射器(port mapper)。
“端口”這個(gè)詞作為Internet協(xié)議族的一個(gè)特征,來(lái)自于TCP和UDP端口號(hào)。既然TIRPC可以工作在任何運(yùn)輸層協(xié)議之上,而不僅僅是TCP和UDP,所以使用TI-RPC的系統(tǒng)中(如SVR4和Solaris 2.2),端口映射器的名字變成了rpcbind。下面我們繼續(xù)使用更為常見(jiàn)的端口映射器的名字。
很自然地,端口映射器本身必須有一個(gè)知名端口:UDP端口111和TCP端口111。端口映射器也就是一個(gè)RPC服務(wù)器程序。它有一個(gè)程序號(hào)(100000)、一個(gè)版本號(hào)(2)、一個(gè)TCP端口111和一個(gè)UDP端口111。服務(wù)器程序使用RPC調(diào)用向端口映射器注冊(cè)自身,客戶(hù)程序使用RPC調(diào)用向端口映射器查詢(xún)。端口映射器提供四個(gè)服務(wù)過(guò)程:
1:PMAPPROC_SET。一個(gè)RPC服務(wù)器啟動(dòng)時(shí)調(diào)用這個(gè)過(guò)程,注冊(cè)一個(gè)程序號(hào)、版本號(hào)和帶有一個(gè)端口號(hào)的協(xié)議。
2:PMAPPROC_UNSET。RPC服務(wù)器調(diào)用此過(guò)程來(lái)刪除一個(gè)已經(jīng)注冊(cè)的映射。
3:PMAPPROC_GETPORT。一個(gè)RPC客戶(hù)啟動(dòng)時(shí)調(diào)用此過(guò)程。根據(jù)一個(gè)給定的程序號(hào)、版本號(hào)和協(xié)議來(lái)獲得注冊(cè)的端口號(hào)。
4:PMAPPROC_DUMP。返回端口映射器數(shù)據(jù)庫(kù)中所有的記錄(每個(gè)記錄包括程序號(hào)、版本號(hào)、協(xié)議和端口號(hào))。
在一個(gè)RPC服務(wù)器程序啟動(dòng),接著被一個(gè)RPC客戶(hù)程序調(diào)用的過(guò)程中,進(jìn)行了以下一些步驟:
1:一般情況下,當(dāng)系統(tǒng)引導(dǎo)時(shí),端口映射器必須首先啟動(dòng)。它創(chuàng)建一個(gè)TCP端點(diǎn),并且被動(dòng)打開(kāi)TCP端口111。它也創(chuàng)建一個(gè)UDP端點(diǎn),并且在UDP端口111等待著UDP數(shù)據(jù)報(bào)的到來(lái)。
2:當(dāng)RPC服務(wù)器程序啟動(dòng)時(shí),它為它所支持的程序的每一個(gè)版本創(chuàng)建一個(gè)TCP端點(diǎn)和一個(gè)UDP端點(diǎn)(一個(gè)給定的RPC程序可以支持多個(gè)版本。客戶(hù)調(diào)用一個(gè)服務(wù)器過(guò)程時(shí),說(shuō)明它想要哪一個(gè)版本)。兩個(gè)端點(diǎn)各自綁定一個(gè)臨時(shí)端口(TCP端口號(hào)和UDP端口號(hào)是否一致無(wú)關(guān)緊要)。服務(wù)器通過(guò)RPC調(diào)用端口映射器的PMAPPROC_SET過(guò)程,注冊(cè)每一個(gè)程序、版本、協(xié)議和端口號(hào)。
3:當(dāng)RPC客戶(hù)程序啟動(dòng)時(shí),它調(diào)用端口映射器的PMAPPROC_GETPORT過(guò)程來(lái)獲得一個(gè)指定程序、版本和協(xié)議的臨時(shí)端口號(hào)。
4:客戶(hù)發(fā)送一個(gè)RPC調(diào)用報(bào)文給第3步返回的端口號(hào)。如果使用的是UDP,客戶(hù)只是發(fā)送一個(gè)包含RPC調(diào)用報(bào)文(見(jiàn)圖29-1)的UDP數(shù)據(jù)報(bào)到服務(wù)器相應(yīng)的UDP端口。服務(wù)器發(fā)送一個(gè)包含RPC應(yīng)答報(bào)文(見(jiàn)圖29-2)的UDP數(shù)據(jù)報(bào)到客戶(hù)作為響應(yīng)。
如果使用的是TCP,客戶(hù)對(duì)服務(wù)器的TCP端口號(hào)做一個(gè)主動(dòng)打開(kāi),然后在建立的TCP連接上發(fā)送一個(gè)RPC調(diào)用報(bào)文。服務(wù)器作為響應(yīng),在連接上發(fā)送一個(gè)RPC應(yīng)答報(bào)文。
程序rpcinfo(8)打印了端口映射器中當(dāng)前的映射記錄(它調(diào)用了端口映射器的PMAPPROC_DUMP過(guò)程)。這里給出的是典型的輸出:
可以看出一些程序確實(shí)支持多個(gè)版本。在端口映射器中,每一個(gè)程序號(hào)、版本號(hào)和協(xié)議的組合都有自己的端口號(hào)映射。
安裝守護(hù)程序(mount daemon)的兩個(gè)版本可以通過(guò)同樣的TCP端口號(hào)(702)和同樣的UDP端口號(hào)(699)來(lái)訪問(wèn),而加鎖管理程序(lock manager)的每個(gè)版本都有各自不同的端口號(hào)。
29.5 NFS協(xié)議
使用NFS,客戶(hù)可以透明地訪問(wèn)服務(wù)器上的文件和文件系統(tǒng)。這不同于提供文件傳輸?shù)腇TP(第27章)。FTP會(huì)產(chǎn)生文件一個(gè)完整的副本。NFS只訪問(wèn)一個(gè)進(jìn)程引用文件的那一部分,并且NFS的一個(gè)目的就是使得這種訪問(wèn)透明。這就意味著任何能夠訪問(wèn)一個(gè)本地文件的客戶(hù)程序不需要做任何修改,就應(yīng)該能夠訪問(wèn)一個(gè)NFS文件。
NFS是一個(gè)使用Sun RPC構(gòu)造的客戶(hù)服務(wù)器應(yīng)用程序。NFS客戶(hù)通過(guò)向一個(gè)NFS服務(wù)器發(fā)送RPC請(qǐng)求來(lái)訪問(wèn)其上的文件。盡管這一工作可以使用一般的用戶(hù)進(jìn)程來(lái)實(shí)現(xiàn)—即NFS客戶(hù)可以是一個(gè)用戶(hù)進(jìn)程,對(duì)服務(wù)器進(jìn)行顯式調(diào)用。而服務(wù)器也可以是一個(gè)用戶(hù)進(jìn)程—因?yàn)閮蓚€(gè)理由,NFS一般不這樣實(shí)現(xiàn)。首先,訪問(wèn)一個(gè)NFS文件必須對(duì)客戶(hù)透明。因此,NFS的客戶(hù)調(diào)用是由客戶(hù)操作系統(tǒng)代表用戶(hù)進(jìn)程來(lái)完成的。第二,出于效率的考慮,NFS服務(wù)器在服務(wù)器操作系統(tǒng)中實(shí)現(xiàn)。如果NFS服務(wù)器是一個(gè)用戶(hù)進(jìn)程,每個(gè)客戶(hù)請(qǐng)求和服務(wù)器應(yīng)答(包括讀和寫(xiě)的數(shù)據(jù))將不得不在內(nèi)核和用戶(hù)進(jìn)程之間進(jìn)行切換,這個(gè)代價(jià)太大。
本節(jié)中,我們考察在RFC1094中說(shuō)明的第2版的NFS [Sun Microsystems 1988b]。[X/Open1991]中給出了Sun RPC、XDR和NFS的一個(gè)更好的描述。[Stern 1991]給出了使用和管理NFS的細(xì)節(jié)。第3版的NFS協(xié)議在1993年發(fā)布,我們?cè)?9.7節(jié)中對(duì)它做一個(gè)簡(jiǎn)單的描述。
圖29-3顯示了一個(gè)NFS客戶(hù)和一個(gè)NFS服務(wù)器的典型配置,圖中有很多地方需要注意。
1:訪問(wèn)的是一個(gè)本地文件還是一個(gè)NFS文件對(duì)于客戶(hù)來(lái)說(shuō)是透明的。當(dāng)文件被打開(kāi)時(shí),由內(nèi)核決定這一點(diǎn)。文件被打開(kāi)之后,內(nèi)核將本地文件的所有引用傳遞給名為“本地文件訪問(wèn)”的框中,而將一個(gè)NFS文件的所有引用傳遞給名為“NFS客戶(hù)”的框中。
2:NFS客戶(hù)通過(guò)它的TCP/IP模塊向NFS服務(wù)器發(fā)送RPC請(qǐng)求。NFS主要使用UDP,最新的實(shí)現(xiàn)也可以使用TCP。
3:NFS服務(wù)器在端口2049接收作為UDP數(shù)據(jù)報(bào)的客戶(hù)請(qǐng)求。盡管NFS可以被實(shí)現(xiàn)成使用端口映射器,允許服務(wù)器使用一個(gè)臨時(shí)端口,但是大多數(shù)的實(shí)現(xiàn)都是直接指定UDP端口2049。
4:當(dāng)NFS服務(wù)器收到一個(gè)客戶(hù)請(qǐng)求時(shí),它將這個(gè)請(qǐng)求傳遞給本地文件訪問(wèn)例程,后者訪問(wèn)服務(wù)器主機(jī)上的一個(gè)本地的磁盤(pán)文件。
5:NFS服務(wù)器需要花一定的時(shí)間來(lái)處理一個(gè)客戶(hù)的請(qǐng)求。訪問(wèn)本地文件系統(tǒng)一般也需要一部分時(shí)間。在這段時(shí)間間隔內(nèi),服務(wù)器不應(yīng)該阻止其他的客戶(hù)請(qǐng)求得到服務(wù)。為了實(shí)現(xiàn)這一功能,大多數(shù)的NFS服務(wù)器都是多線程的—即服務(wù)器的內(nèi)核中實(shí)際上有多個(gè)NFS服務(wù)器在運(yùn)行。具體怎么實(shí)現(xiàn)依賴(lài)于不同的操作系統(tǒng)。既然大多數(shù)的Unix內(nèi)核不是多線程的,一個(gè)共同的技術(shù)就是啟動(dòng)一個(gè)用戶(hù)進(jìn)程(常被稱(chēng)為nfsd)的多個(gè)實(shí)例。這個(gè)實(shí)例執(zhí)行一個(gè)系統(tǒng)調(diào)用,使自己作為一個(gè)內(nèi)核進(jìn)程保留在操作系統(tǒng)的內(nèi)核中。
6:同樣,在客戶(hù)主機(jī)上,NFS客戶(hù)需要花一定的時(shí)間來(lái)處理一個(gè)用戶(hù)進(jìn)程的請(qǐng)求。NFS客戶(hù)向服務(wù)器主機(jī)發(fā)出一個(gè)RPC調(diào)用,然后等待服務(wù)器的應(yīng)答。為了給使用NFS的客戶(hù)主機(jī)上的用戶(hù)進(jìn)程提供更多的并發(fā)性,在客戶(hù)內(nèi)核中一般運(yùn)行著多個(gè)NFS客戶(hù)。同樣,具體實(shí)現(xiàn)也依賴(lài)于操作系統(tǒng)。Unix系統(tǒng)經(jīng)常使用類(lèi)似于NFS服務(wù)器的技術(shù):一個(gè)叫作biod的用戶(hù)進(jìn)程執(zhí)行一個(gè)系統(tǒng)調(diào)用,作為一個(gè)內(nèi)核進(jìn)程保留在操作系統(tǒng)的內(nèi)核中。
大多數(shù)的Unix主機(jī)可以作為一個(gè)NFS客戶(hù),一個(gè)NFS服務(wù)器,或者兩者都是。大多數(shù)PC機(jī)的實(shí)現(xiàn)(MS-DOS)只提供了NFS客戶(hù)實(shí)現(xiàn)。大多數(shù)的IBM大型機(jī)只提供了NFS服務(wù)器功能。
NFS實(shí)際上不僅僅由NFS協(xié)議組成。圖29-4顯示了NFS使用的不同RPC程序。
在這個(gè)圖中,程序的版本是在SunOS 4.1.3中使用的。更新的實(shí)現(xiàn)提供了其中一些程序更新的版本。例如,Solaris 2.2還支持端口映射器的第3版和第4版,以及安裝守護(hù)程序的第2版。SVR4支持第3版的端口映射器。
在客戶(hù)能夠訪問(wèn)服務(wù)器上的文件系統(tǒng)之前,NFS客戶(hù)主機(jī)必須調(diào)用安裝守護(hù)程序。我們?cè)谙旅嬗懻摪惭b守護(hù)程序。
加鎖管理程序和狀態(tài)監(jiān)視器允許客戶(hù)鎖定一個(gè)NFS服務(wù)器上文件的部分區(qū)域。這兩個(gè)程序獨(dú)立于NFS協(xié)議,因?yàn)榧渔i需要知道客戶(hù)和服務(wù)器的狀態(tài),而NFS本身在服務(wù)器上是無(wú)狀態(tài)的(下面我們對(duì)NFS的無(wú)狀態(tài)會(huì)介紹得更多)。[X/Open 1991]的第9,10和11章說(shuō)明了使用加鎖管理程序和狀態(tài)監(jiān)視器進(jìn)行NFS文件鎖定的過(guò)程。
29.5.1 文件句柄
NFS中一個(gè)基本概念是文件句柄(file handle)。它是一個(gè)不透明(opaque)的對(duì)象,用來(lái)引用服務(wù)器上的一個(gè)文件或目錄。不透明指的是服務(wù)器創(chuàng)建文件句柄,把它傳遞給客戶(hù),然后客戶(hù)訪問(wèn)文件時(shí),使用對(duì)應(yīng)的文件句柄。客戶(hù)不會(huì)查看文件句柄的內(nèi)容—它的內(nèi)容只對(duì)服務(wù)器有意義。
每次一個(gè)客戶(hù)進(jìn)程打開(kāi)一個(gè)實(shí)際上位于一個(gè)NFS服務(wù)器上的文件時(shí),NFS客戶(hù)就會(huì)從NFS服務(wù)器那里獲得該文件的一個(gè)文件句柄。每次NFS客戶(hù)為用戶(hù)進(jìn)程讀或?qū)懳募r(shí),文件句柄就會(huì)傳給服務(wù)器以指定被訪問(wèn)的文件。
一般情況下,用戶(hù)進(jìn)程不會(huì)和文件句柄打交道—只有NFS客戶(hù)和NFS服務(wù)器將文件句柄傳來(lái)傳去。在第2版的NFS中,一個(gè)文件句柄占據(jù)32個(gè)字節(jié),第3版中增加為64個(gè)字節(jié)。
Unix服務(wù)器一般在文件句柄中存儲(chǔ)下面的信息:文件系統(tǒng)標(biāo)識(shí)符(文件系統(tǒng)最大和最小的設(shè)備號(hào)),i-node號(hào)(在一個(gè)文件系統(tǒng)中唯一的數(shù)值)和一個(gè)i-node的生成碼(每當(dāng)一個(gè)i-node被一個(gè)不同的文件重用時(shí)就改變的數(shù)值)。
29.5.2 安裝協(xié)議
客戶(hù)必須在訪問(wèn)服務(wù)器上一個(gè)文件系統(tǒng)中的文件之前,使用安裝協(xié)議安裝那個(gè)文件系統(tǒng)。一般情況下,這是在客戶(hù)主機(jī)引導(dǎo)時(shí)完成的。最后的結(jié)果就是客戶(hù)獲得服務(wù)器文件系統(tǒng)的一個(gè)文件句柄。
圖29-5顯示了一個(gè)Unix客戶(hù)發(fā)出mount(8)命令所發(fā)生的情況,它說(shuō)明一個(gè)NFS的安裝過(guò)程。
依次發(fā)生了下面的動(dòng)作。
1:服務(wù)器上的端口映射器一般在服務(wù)器主機(jī)引導(dǎo)時(shí)被啟動(dòng)。
2:安裝守護(hù)程序(mountd)在端口映射器之后被啟動(dòng)。它創(chuàng)建了一個(gè)TCP端點(diǎn)和一個(gè)UDP端點(diǎn),并分別賦予一個(gè)臨時(shí)的端口號(hào)。然后它在端口映射器中注冊(cè)這些端口號(hào)。
3:在客戶(hù)機(jī)上執(zhí)行mount命令,它向服務(wù)器上的端口映射器發(fā)出一個(gè)RPC調(diào)用來(lái)獲得服務(wù)器上安裝守護(hù)程序的端口號(hào)。客戶(hù)和端口映射器交互既可以使用TCP也可以使用UDP,但一般使用UDP。
4:端口映射器應(yīng)答以安裝守護(hù)程序的端口號(hào)。
5:mount命令向安裝守護(hù)程序發(fā)出一個(gè)RPC調(diào)用來(lái)安裝服務(wù)器上的一個(gè)文件系統(tǒng)。同樣,既可以使用TCP也可以使用UDP,但一般使用UDP。服務(wù)器現(xiàn)在可以驗(yàn)證客戶(hù),使用客戶(hù)的IP地址和端口號(hào)來(lái)判別是否允許客戶(hù)安裝指定的文件系統(tǒng)。
6:安裝守護(hù)程序應(yīng)答以指定文件系統(tǒng)的文件句柄。
7:客戶(hù)機(jī)上的mount命令發(fā)出mount系統(tǒng)調(diào)用將第5步返回的文件句柄與客戶(hù)機(jī)上的一個(gè)本地安裝點(diǎn)聯(lián)系起來(lái)。文件句柄被存儲(chǔ)在NFS客戶(hù)代碼中,從現(xiàn)在開(kāi)始,用戶(hù)進(jìn)程對(duì)于那個(gè)服務(wù)器文件系統(tǒng)的任何引用都將從使用這個(gè)文件句柄開(kāi)始。
上述實(shí)現(xiàn)技術(shù)將所有的安裝處理,除了客戶(hù)機(jī)上的mount系統(tǒng)調(diào)用,都放在用戶(hù)進(jìn)程中,而不是放在內(nèi)核中。我們顯示的三個(gè)程序—mount命令、端口映射器和安裝守護(hù)程序—都是用戶(hù)進(jìn)程。
作為一個(gè)例子,在我們的主機(jī)sun(一個(gè)NFS客戶(hù)機(jī))上執(zhí)行:
sun # mount -t nfs bsdi:/usr /nfs/bsdi/usr
這個(gè)命令將主機(jī)bsdi(一個(gè)NFS服務(wù)器)上的/usr目錄安裝成為本地文件系統(tǒng)/nfs/bsdi/usr。圖29-6顯示了結(jié)果。
當(dāng)我們引用客戶(hù)機(jī)sun上的/nfs/bsdi/usr/rstevens/hello.c文件時(shí),實(shí)際上引用的是服務(wù)器bsdi上的文件/usr/rstevens/hello.c。
29.5.3 NFS過(guò)程
現(xiàn)在我們描述NFS服務(wù)器提供的15個(gè)過(guò)程(使用的個(gè)數(shù)與NFS過(guò)程的實(shí)際個(gè)數(shù)不一樣,因?yàn)槲覀儼阉鼈儼凑展δ芊至私M)。盡管NFS被設(shè)計(jì)成可以在不同的操作系統(tǒng)上工作,而不僅僅是Unix系統(tǒng),但是一些提供Unix功能的過(guò)程可能不被其他操作系統(tǒng)支持(例如硬鏈接、符號(hào)鏈接、組的屬主和執(zhí)行權(quán)等)。[Stevens 1992]的第4章包含了Unix文件系統(tǒng)其他的一些信息,其中有些被NFS采用。
1:GETATTR。返回一個(gè)文件的屬性:文件類(lèi)型(一般文件,目錄等)、訪問(wèn)權(quán)限、文件大小、文件的屬主者及上次訪問(wèn)時(shí)間等信息。
2:SETATTR。設(shè)置一個(gè)文件的屬性。只允許設(shè)置文件屬性的一個(gè)子集:訪問(wèn)權(quán)限、文件的屬主、組的屬主、文件大小、上次訪問(wèn)時(shí)間和上次修改時(shí)間。
3:STATFS。返回一個(gè)文件系統(tǒng)的狀態(tài):可用空間的大小、最佳傳送大小等。例如Unix的df命令使用此過(guò)程。
4:LOOKUP。查找一個(gè)文件。每當(dāng)一個(gè)用戶(hù)進(jìn)程打開(kāi)一個(gè)NFS服務(wù)器上的一個(gè)文件時(shí),NFS客戶(hù)調(diào)用此過(guò)程。
5:READ。從一個(gè)文件中讀數(shù)據(jù)。客戶(hù)說(shuō)明文件的句柄、讀操作的開(kāi)始位置和讀數(shù)據(jù)的最大字節(jié)數(shù)(最多8192個(gè)字節(jié))。
6:WRITE。對(duì)一個(gè)文件進(jìn)行寫(xiě)操作。客戶(hù)說(shuō)明文件的句柄、開(kāi)始位置、寫(xiě)數(shù)據(jù)的字節(jié)數(shù)和要寫(xiě)的數(shù)據(jù)。
7:CREATE。創(chuàng)建一個(gè)文件。
8:REMOVE。刪除一個(gè)文件。
9:RENAME。重命名一個(gè)文件。
10:LINK。為一個(gè)文件構(gòu)造一個(gè)硬鏈接。硬鏈接是一個(gè)Unix的概念,指的是磁盤(pán)中的一個(gè)文件可以有任意多個(gè)目錄項(xiàng)(即名字,也叫作硬鏈接)指向它。
11:SYMLINK。為一個(gè)文件創(chuàng)建一個(gè)符號(hào)鏈接。符號(hào)鏈接是一個(gè)包含另一個(gè)文件名字的文件。大多數(shù)引用符號(hào)鏈接的操作(例如,打開(kāi))實(shí)際上引用的是符號(hào)鏈接所指的文件。
12:READLINK。讀一個(gè)符號(hào)鏈接。即返回符號(hào)鏈接所指的文件的名字。
13:MKDIR。創(chuàng)建一個(gè)目錄。
14:RMDIR。刪除一個(gè)目錄。
15:READDIR。讀一個(gè)目錄。例如,Unix的ls命令使用此過(guò)程。
這些過(guò)程實(shí)際上有一個(gè)前綴NFSPROC_,我們把它省略了。
29.5.4 UDP還是TCP
NFS最初是用UDP寫(xiě)的,所有的廠商都提供了這種實(shí)現(xiàn)。最新的一些實(shí)現(xiàn)也支持TCP。TCP支持主要用于廣域網(wǎng),它可以使文件操作更快。NFS已經(jīng)不再局限于局域網(wǎng)的使用。
當(dāng)從LAN轉(zhuǎn)換到W N時(shí),網(wǎng)絡(luò)的動(dòng)態(tài)特征變化得非常大。往返時(shí)間(round-trip time)變動(dòng)范圍大,擁塞經(jīng)常發(fā)生。WAN的這些特征使得我們考慮使用具有TCP屬性的算法——慢啟動(dòng),但是可以避免擁塞。既然UDP沒(méi)有提供任何類(lèi)似的東西,那么在NFS客戶(hù)和服務(wù)器上加進(jìn)同樣的算法或者使用TCP。
29.5.5 TCP上的NFS
伯克利實(shí)現(xiàn)的Net/2NFS支持UDP或者TCP。[Macklem 1991]描述了這個(gè)實(shí)現(xiàn)。讓我們看一下使用TCP有什么不同。
1:當(dāng)服務(wù)器主機(jī)進(jìn)行引導(dǎo)時(shí),它啟動(dòng)一個(gè)NFS服務(wù)器,后者被動(dòng)打開(kāi)TCP端口2049,等待著客戶(hù)的連接請(qǐng)求。這通常是另一個(gè)NFS服務(wù)器,正常的NFS UDP服務(wù)器在UDP端口2049等待著進(jìn)入的UDP數(shù)據(jù)報(bào)。
2:當(dāng)客戶(hù)使用TCP安裝服務(wù)器上的文件系統(tǒng)時(shí),它對(duì)服務(wù)器上的TCP端口2049做一個(gè)主動(dòng)打開(kāi)。這樣就為這個(gè)文件系統(tǒng)在客戶(hù)和服務(wù)器之間形成了一個(gè)TCP連接。如果同樣的客戶(hù)安裝同樣服務(wù)器上的另一個(gè)文件系統(tǒng),就會(huì)創(chuàng)建另一個(gè)TCP連接。
3:客戶(hù)和服務(wù)器在它們連接的兩端都要設(shè)置TCP的keepalive選項(xiàng),這樣雙方都能檢測(cè)到對(duì)方主機(jī)崩潰,或者崩潰然后重啟動(dòng)。
4:客戶(hù)方所有使用這個(gè)服務(wù)器文件系統(tǒng)的應(yīng)用程序共享這個(gè)TCP連接。例如,在圖29-6中,如果在bsdi的/usr目錄下還有另一個(gè)目錄smith,那么對(duì)兩個(gè)目錄/nfs/bsdi/usr/rstevens和/nfs/bsdi/usr/smith下所有文件的引用將共享同樣的TCP連接。
5:如果客戶(hù)檢測(cè)到服務(wù)器已經(jīng)崩潰,或者崩潰然后重啟動(dòng)(通過(guò)收到一個(gè)TCP差錯(cuò)“連接超時(shí)”或者“對(duì)方復(fù)位連接”),它嘗試與服務(wù)器重新建立連接。客戶(hù)做另一個(gè)主動(dòng)打開(kāi),為同一個(gè)文件系統(tǒng)請(qǐng)求重新建立TCP連接。在以前連接上超時(shí)的所有客戶(hù)請(qǐng)求在新的連接上都會(huì)重新發(fā)出。
6:如果客戶(hù)機(jī)崩潰,那么當(dāng)它崩潰時(shí)正在運(yùn)行的應(yīng)用程序也要崩潰。當(dāng)客戶(hù)機(jī)重新啟動(dòng)時(shí),它很可能使用TCP重新安裝服務(wù)器的文件系統(tǒng),這將導(dǎo)致和服務(wù)器的另一個(gè)連接。客戶(hù)和服務(wù)器之間針對(duì)同一個(gè)文件系統(tǒng)的前一個(gè)連接現(xiàn)在打開(kāi)了一半(服務(wù)器方認(rèn)為它還開(kāi)著),但是既然服務(wù)器設(shè)置了keepalive選項(xiàng),當(dāng)服務(wù)器發(fā)出下一個(gè)keepalive探查報(bào)文時(shí),這個(gè)半開(kāi)著的TCP連接就會(huì)被中止。
隨著時(shí)間的流逝,另外一些廠商也計(jì)劃支持TCP上的NFS。
29.6 NFS實(shí)例
我們使用tcpdump來(lái)看一下在典型的文件操作中,客戶(hù)調(diào)用了哪些NFS過(guò)程。當(dāng)tcpdump檢測(cè)到一個(gè)包含RPC調(diào)用(在圖29-1中調(diào)用字段等于0)、目的端口是2049的UDP數(shù)據(jù)報(bào)時(shí),它把數(shù)據(jù)報(bào)按照一個(gè)NFS請(qǐng)求進(jìn)行解碼。類(lèi)似地,如果一個(gè)UDP數(shù)據(jù)報(bào)是一個(gè)RPC應(yīng)答(在圖29-2中應(yīng)答字段為1),源端口是2049,tcpdump就把此數(shù)據(jù)報(bào)作為一個(gè)NFS應(yīng)答來(lái)解碼。
29.6.1 簡(jiǎn)單的例子:讀一個(gè)文件
第一個(gè)例子是使用cat(1)命令將位于一個(gè)NFS服務(wù)器上的一個(gè)文件復(fù)制到終端上:
如同圖29-6所示,主機(jī)sun(NFS客戶(hù)機(jī))上的文件系統(tǒng)/nfs/bsdi/usr實(shí)際上是主機(jī)bsdi(NFS服務(wù)器)上的/usr文件系統(tǒng)。當(dāng)cat打開(kāi)這個(gè)文件時(shí),sun上的內(nèi)核檢測(cè)到這一點(diǎn),然后使用NFS去訪問(wèn)文件。圖29-7顯示了tcpdump的輸出。
當(dāng)tcpdump解析一個(gè)NFS請(qǐng)求或應(yīng)答報(bào)文時(shí),它打印客戶(hù)的XID字段,而不是端口號(hào)。第1行和第2行中的XID字段值是0x7aa6。
客戶(hù)內(nèi)核中的打開(kāi)函數(shù)一次處理文件名/nfs/bsdi/usr/rstevens/hello.c中的一個(gè)成員。當(dāng)處理到/nfs/bsdi/usr時(shí),它發(fā)現(xiàn)這是指向一個(gè)已安裝的NFS文件系統(tǒng)的一個(gè)安裝點(diǎn)。
在第1行中,客戶(hù)調(diào)用GETAT TR過(guò)程取得客戶(hù)已經(jīng)安裝的服務(wù)器目錄的屬性(/usr)。這個(gè)RPC請(qǐng)求,除IP首部和UDP首部之外,包含104個(gè)字節(jié)的數(shù)據(jù)。第2行中的應(yīng)答返回了一個(gè)OK值,除了IP首部和UDP首部之外,包含了96個(gè)字節(jié)的數(shù)據(jù)。在這個(gè)圖中,我們可以看出最小的NFS報(bào)文包含大約100個(gè)字節(jié)的數(shù)據(jù)。
在第3行中,客戶(hù)調(diào)用LOOKUP過(guò)程來(lái)查看rstevens文件。在第4行中收到一個(gè)OK應(yīng)答。LOOKUP過(guò)程說(shuō)明了文件名rstevens和遠(yuǎn)程文件系統(tǒng)被安裝時(shí)由內(nèi)核保存的文件句柄。應(yīng)答中包含了下一步要使用的一個(gè)新的文件句柄。
在第5行中,客戶(hù)使用第4行中返回的文件句柄對(duì)hello.c調(diào)用LOOKUP過(guò)程。在第6行返回了另一個(gè)文件句柄。新的文件句柄就是客戶(hù)在第7行和第9行中引用文件/nfs/bsdi/usr/rstevens/hello.c所使用的文件句柄。我們看到客戶(hù)對(duì)于正在打開(kāi)的路徑名的每個(gè)成員都調(diào)用了一次LOOKUP過(guò)程。
在第7行中,客戶(hù)又調(diào)用了一次GETAT TR過(guò)程,接著在第9行中調(diào)用了READ過(guò)程。客戶(hù)請(qǐng)求從偏移0開(kāi)始的1024個(gè)字節(jié),但是接收到的沒(méi)有這么多(減去RPC字段和其他由READ過(guò)程返回的值的大小,在第10行中返回了38個(gè)字節(jié)的數(shù)據(jù)。這是文件hello.c的實(shí)際大小)。
在這個(gè)例子中,應(yīng)用進(jìn)程對(duì)于內(nèi)核所做的這些RPC請(qǐng)求和應(yīng)答一點(diǎn)兒也不知道。應(yīng)用進(jìn)程只是調(diào)用了內(nèi)核的open函數(shù),后者引起了3個(gè)RPC請(qǐng)求和3個(gè)應(yīng)答(16行),然后應(yīng)用進(jìn)程又調(diào)用了內(nèi)核的read函數(shù),它引起了兩個(gè)請(qǐng)求和兩個(gè)應(yīng)答(710行)。該文件位于一個(gè)NFS文件服務(wù)器,這一點(diǎn)對(duì)客戶(hù)應(yīng)用進(jìn)程來(lái)說(shuō)是透明的。
29.6.2 簡(jiǎn)單的例子:創(chuàng)建一個(gè)目錄
作為另一個(gè)簡(jiǎn)單的例子,我們將當(dāng)前工作目錄改變?yōu)橐粋€(gè)后創(chuàng)建一個(gè)新的目錄:
圖29-8顯示了tcpdump的輸出。
改變目錄引起客戶(hù)調(diào)用了兩次GETATTR過(guò)程(1~4行)。當(dāng)我們創(chuàng)建新的目錄時(shí),客戶(hù)調(diào)用了GETAT TR過(guò)程(56行),接著調(diào)用LOOKUP過(guò)程(78行,用來(lái)驗(yàn)證將創(chuàng)建的目錄不存在),跟著調(diào)用了MKDIR過(guò)程來(lái)創(chuàng)建目錄(9-10行)。在第8行中,應(yīng)答OK并不表示目錄存在。它只是表示過(guò)程返回了。tcpdump并不理解NFS過(guò)程的返回值。它一般打印OK和應(yīng)答報(bào)文中數(shù)據(jù)的字節(jié)數(shù)。
29.6.3 無(wú)狀態(tài)
NFS的一個(gè)特征(NFS的批評(píng)者稱(chēng)之為NFS的一個(gè)瑕疵,而不是一個(gè)特征)是NFS服務(wù)器是無(wú)狀態(tài)的(stateless)。服務(wù)器并不記錄哪個(gè)客戶(hù)正在訪問(wèn)哪個(gè)文件。請(qǐng)注意一下在前面給出的NFS過(guò)程中,沒(méi)有一個(gè)open操作和一個(gè)close操作。LOOKUP過(guò)程的功能與open操作有些類(lèi)似,但是服務(wù)器永遠(yuǎn)也不會(huì)知道客戶(hù)對(duì)一個(gè)文件調(diào)用了LOOKUP過(guò)程之后是否會(huì)引用該文件。
無(wú)狀態(tài)設(shè)計(jì)的理由是為了在服務(wù)器崩潰并且重啟動(dòng)時(shí),簡(jiǎn)化服務(wù)器的崩潰恢復(fù)操作。
29.6.4 例子:服務(wù)器崩潰
在下面的例子中我們從一個(gè)崩潰然后重啟動(dòng)的NFS服務(wù)器上讀一個(gè)文件。這個(gè)例子演示了無(wú)狀態(tài)的服務(wù)器是如何使得客戶(hù)不知道服務(wù)器的崩潰。除了在服務(wù)器崩潰然后重啟動(dòng)時(shí)一個(gè)時(shí)間上的暫停外,客戶(hù)并不知道發(fā)生的問(wèn)題,客戶(hù)應(yīng)用進(jìn)程沒(méi)有受到影響。
在客戶(hù)機(jī)sun上,我們對(duì)一個(gè)長(zhǎng)文件(NFS服務(wù)器主機(jī)svr4上的文件/usr/share/lib/termcap)執(zhí)行cat命令。在傳送過(guò)程中把以太網(wǎng)的網(wǎng)線拔掉,關(guān)閉然后重啟動(dòng)服務(wù)器主機(jī),再重新將網(wǎng)線連上。客戶(hù)被配置成每個(gè)NFS read過(guò)程讀1024個(gè)字節(jié)。圖29-9顯示了tcpdump的輸出。
1~10行對(duì)應(yīng)于客戶(hù)打開(kāi)文件,操作類(lèi)似于圖29-7所示。在第11行我們看到對(duì)文件的第一個(gè)READ操作,在12行返回了1024個(gè)字節(jié)的數(shù)據(jù)。這個(gè)操作一直繼續(xù)到129行(讀1024個(gè)字節(jié)的數(shù)據(jù),跟著一個(gè)OK應(yīng)答)。
在第130行和第131行我們看到兩個(gè)請(qǐng)求超時(shí),并且分別在132行和133行重傳。第一個(gè)問(wèn)題是這里為什么會(huì)有兩個(gè)讀請(qǐng)求,一個(gè)從偏移65536開(kāi)始讀,另一個(gè)從偏移73728開(kāi)始讀?答案是客戶(hù)內(nèi)核檢測(cè)到客戶(hù)應(yīng)用進(jìn)程正在進(jìn)行順序地讀操作,所以試圖預(yù)先取得數(shù)據(jù)塊(大多數(shù)的Unix內(nèi)核都采用了這種預(yù)讀技術(shù))。客戶(hù)內(nèi)核也正在運(yùn)行多個(gè)NFS塊I/O守護(hù)程序,后者試圖代表客戶(hù)產(chǎn)生多個(gè)RPC請(qǐng)求。一個(gè)守護(hù)程序正在從偏移65536處讀8192個(gè)字節(jié)(以1024字節(jié)為一組數(shù)據(jù)塊),而另一個(gè)正在從73728處預(yù)讀8192個(gè)字節(jié)。
客戶(hù)重傳發(fā)生在130~168行。在第169行我們看到服務(wù)器已經(jīng)重啟動(dòng),在它對(duì)第168行的客戶(hù)NFS請(qǐng)求做出應(yīng)答之前,它發(fā)送了一個(gè)ARP請(qǐng)求。對(duì)168行的響應(yīng)被發(fā)送在171行。客戶(hù)的READ操作繼續(xù)進(jìn)行下去。
除了從129行到171行5分鐘的暫停,客戶(hù)應(yīng)用進(jìn)程并不知道服務(wù)器崩潰然后又重啟動(dòng)了。這個(gè)服務(wù)器的崩潰對(duì)于客戶(hù)是透明的。
為了研究這個(gè)例子中的超時(shí)和重傳時(shí)間間隔,首先要意識(shí)到這兒有兩個(gè)客戶(hù)守護(hù)程序,分別有它們各自的超時(shí)。第1個(gè)守護(hù)程序(在偏移65536處開(kāi)始讀)的間隔,四舍五入到兩個(gè)十進(jìn)制小數(shù)點(diǎn),為0.68,0.87,1.74,3.48,6.96,13.92,20.0,20.0,20.0等等。第2個(gè)守護(hù)程序(在偏移73728處開(kāi)始讀)的間隔也是一樣的(精確到兩個(gè)小數(shù)點(diǎn))。可以看出這些NFS客戶(hù)使用了一個(gè)這樣的超時(shí)定時(shí)器:間隔為0.875秒的倍數(shù),上限為20秒。每次超時(shí)后,重傳間隔翻倍:0.875,1.75,3.5,7.0和14.0。
客戶(hù)要重傳多久呢?客戶(hù)有兩個(gè)與此有關(guān)的選項(xiàng)。首先,如果服務(wù)器文件系統(tǒng)是“硬”安裝的,客戶(hù)就會(huì)永遠(yuǎn)重傳下去。但是如果服務(wù)器文件系統(tǒng)是“軟”安裝的,客戶(hù)重傳了固定數(shù)目的次數(shù)之后就會(huì)放棄。在“硬”安裝的情況下,客戶(hù)還有一個(gè)選項(xiàng)決定是否允許用戶(hù)中斷無(wú)限制的重傳。如果客戶(hù)主機(jī)安裝服務(wù)器文件系統(tǒng)時(shí)說(shuō)明了中斷能力,并且如果我們不想在服務(wù)器崩潰之后等5分鐘,等著服務(wù)器重啟動(dòng),就可以鍵入一個(gè)中斷鍵以終止客戶(hù)應(yīng)用程序。
29.6.5 等冪過(guò)程
如果一個(gè)RPC過(guò)程被服務(wù)器執(zhí)行多次仍然返回同樣的結(jié)果,那么就把它叫作等冪過(guò)程(Idempotent Procedure)。例如,NFS的讀過(guò)程是等冪的。正像我們?cè)趫D29-9中看到的,客戶(hù)只是重發(fā)一個(gè)特定的READ調(diào)用直到它得到一個(gè)響應(yīng)。在我們的例子中,重傳的原因是服務(wù)器崩潰了。如果服務(wù)器沒(méi)有崩潰,而是RPC應(yīng)答報(bào)文丟失了(既然UDP是不可靠的),客戶(hù)只是重傳請(qǐng)求,服務(wù)器再一次執(zhí)行同樣的READ過(guò)程。同一個(gè)文件的同一部分被重讀一次,發(fā)送給客戶(hù)。
這種方法行得通的原因在于每個(gè)READ請(qǐng)求指出了讀操作開(kāi)始的偏移位置。如果有一個(gè)NFS過(guò)程要求服務(wù)器讀一個(gè)文件的下N個(gè)字節(jié),這種方法就不行了。除非服務(wù)器被做成是有狀態(tài)的(與無(wú)狀態(tài)相反),如果一個(gè)應(yīng)答丟失了,客戶(hù)重發(fā)讀下N個(gè)字節(jié)的READ請(qǐng)求,結(jié)果將是不一樣的。這就是為什么NFS的READ和WRITE過(guò)程要求客戶(hù)說(shuō)明開(kāi)始的偏移位置的原因。客戶(hù)維護(hù)著狀態(tài)(每個(gè)文件當(dāng)前的偏移位置),而不是服務(wù)器。
不幸的是并不是所有的文件系統(tǒng)操作都是等冪的。例如,考慮下面的動(dòng)作:客戶(hù)NFS發(fā)出REMOVE請(qǐng)求來(lái)刪除一個(gè)文件;服務(wù)器NFS刪除了文件,并回答OK;服務(wù)器的回答丟失了;客戶(hù)NFS超時(shí),然后重傳請(qǐng)求;服務(wù)器NFS找不到指定的文件,回答指出一個(gè)錯(cuò)誤;客戶(hù)應(yīng)用程序接收到一個(gè)錯(cuò)誤表示文件不存在。這個(gè)返回給客戶(hù)應(yīng)用程序的錯(cuò)誤是不對(duì)的—該文件的確存在并且被刪除了。
等冪的NFS過(guò)程是:GETATTR、STATES、LOOKUP、READ、WRITE、READLINK和READDIR。不是等冪的過(guò)程是:CREATE、REMOVE、RENAME、LINK、SYMLINK、MKDIR和RMDIR。SETATTR過(guò)程如果不用來(lái)截?cái)辔募话闶堑葍绲摹?/p>
既然使用UDP總會(huì)發(fā)生響應(yīng)報(bào)文丟失的現(xiàn)象,NFS服務(wù)器需要一種方法來(lái)處理非等冪的操作。大多數(shù)的服務(wù)器實(shí)現(xiàn)了一個(gè)最近應(yīng)答的高速緩存,用于存放非等冪操作最近的應(yīng)答。每當(dāng)服務(wù)器收到一個(gè)請(qǐng)求,它首先檢查這個(gè)高速緩存,如果找到了一個(gè)匹配,就返回以前的應(yīng)答而不再調(diào)用相應(yīng)的NFS過(guò)程。[Juszczak 1989]提供了這種高速緩存的實(shí)現(xiàn)細(xì)節(jié)。
等冪服務(wù)器過(guò)程的概念可以應(yīng)用于任何基于UDP的應(yīng)用程序,而不僅僅是NFS。例如,DNS也提供了一個(gè)等冪服務(wù)。一個(gè)DNS的服務(wù)器可以任意多次地執(zhí)行一個(gè)解析者的請(qǐng)求而沒(méi)有任何不良的后果(如果不考慮網(wǎng)絡(luò)資源浪費(fèi)的話)。
29.7 第3版的NFS
1993年發(fā)布了第3版的NFS協(xié)議規(guī)范[Sun Microsystem 1994]。其實(shí)現(xiàn)有望在1994年成為可能。
我們總結(jié)一下第2版和第3版的主要區(qū)別。下面把兩者分別稱(chēng)為V2和V3。
1:V2中的文件句柄是32字節(jié)的固定大小的數(shù)組。在V3中,它變成了一個(gè)最多為64個(gè)字節(jié)的可變長(zhǎng)度的數(shù)組。在XDR中,一個(gè)可變長(zhǎng)度的數(shù)組被編碼為一個(gè)4字節(jié)的數(shù)組成員個(gè)數(shù)跟著實(shí)際的數(shù)組成員字節(jié)。這樣在實(shí)現(xiàn)時(shí)減少了文件句柄的長(zhǎng)度,例如Unix只需要12個(gè)字節(jié),但又允許非Unix實(shí)現(xiàn)維護(hù)另外的信息。
2:V2將每個(gè)READ和WRITE RPC過(guò)程可以讀寫(xiě)的數(shù)據(jù)限制為8192個(gè)字節(jié)。這個(gè)限制在V3中取消了,這就意味著一個(gè)UDP上的實(shí)現(xiàn)只受到IP數(shù)據(jù)報(bào)大小的限制(65535字節(jié))。這樣允許在更快的網(wǎng)絡(luò)上讀寫(xiě)更大的分組。
3:文件大小以及READ和WRITE過(guò)程開(kāi)始偏移的字節(jié)從32字節(jié)擴(kuò)充到64字節(jié),允許讀寫(xiě)更大的文件。
4:每個(gè)影響文件屬性值的調(diào)用都返回文件的屬性。這樣減少了客戶(hù)調(diào)用GETAT TR過(guò)程的次數(shù)。
5:WRITE過(guò)程可以是異步的,而在V2中要求同步的WRITE過(guò)程。這樣可以提高WRITE過(guò)程的性能。
6:V3中刪去了一個(gè)過(guò)程(STAT FS),增加了七個(gè)過(guò)程:ACCESS(檢查文件訪問(wèn)權(quán)限)、MKNOD(創(chuàng)建一個(gè)Unix特殊文件)、READDIRPLUS(返回一個(gè)目錄中的文件名字和它們的屬性)、FSINFO(返回一個(gè)文件系統(tǒng)的靜態(tài)信息)、FSSTAT(返回一個(gè)文件系統(tǒng)的動(dòng)態(tài)信息)、PAT HCONF(返回一個(gè)文件的POSIX.1信息)和COMMIT(將以前的異步寫(xiě)操作提交到外存中)。
29.8 小結(jié)
RPC是構(gòu)造客戶(hù)-服務(wù)器應(yīng)用程序的一種方式,使得看起來(lái)客戶(hù)只是調(diào)用了服務(wù)器的過(guò)程。所有的網(wǎng)絡(luò)操作細(xì)節(jié)都被隱藏在RPC程序包為一個(gè)應(yīng)用程序生成的客戶(hù)和服務(wù)器殘樁以及RPC庫(kù)的例程中。我們顯示了RPC調(diào)用和應(yīng)答報(bào)文的格式,并且提到了使用XDR對(duì)傳輸?shù)闹颠M(jìn)行編碼,使得RPC客戶(hù)和服務(wù)器可以運(yùn)行在不同架構(gòu)的機(jī)器上。
最廣泛使用的RPC應(yīng)用之一就是Sun的NFS,一個(gè)在各種大小的主機(jī)上廣泛實(shí)現(xiàn)的異構(gòu)的文件訪問(wèn)協(xié)議。我們?yōu)g覽了NFS和它使用UDP和TCP的方式。第2版的NFS協(xié)議定義了15個(gè)過(guò)程。
一個(gè)客戶(hù)對(duì)一個(gè)NFS服務(wù)器的訪問(wèn)開(kāi)始于安裝協(xié)議,返回給客戶(hù)一個(gè)文件句柄。客戶(hù)接著可以使用那個(gè)文件句柄來(lái)訪問(wèn)服務(wù)器文件系統(tǒng)中的文件。在服務(wù)器上,一次檢查文件名的一個(gè)成員,返回每個(gè)成員的一個(gè)新的文件句柄。最后的結(jié)果就是要引用的文件的一個(gè)文件句柄,它可以在隨后的讀寫(xiě)操作中被使用。
NFS試圖把它的所用過(guò)程都做成等冪的,使得如果響應(yīng)報(bào)文丟失了,客戶(hù)只需要重發(fā)一個(gè)請(qǐng)求。我們看到了服務(wù)器崩潰然后又重啟動(dòng)時(shí),一個(gè)客戶(hù)讀服務(wù)器上的一個(gè)文件的例子。