29.1 引言
本章中我們要討論另一個常用的應用程序:NFS(網(wǎng)絡文件系統(tǒng))段只,它為客戶程序提供透明的文件訪問额衙。NFS的基礎是Sun RPC:遠程過程調(diào)用霎褐。我們首先必須描述一下RPC陋葡。
客戶程序使用NFS不需要做什么特別的工作右莱,當NFS內(nèi)核檢測到被訪問的文件位于一個NFS服務器時桃笙,就會自動產(chǎn)生一個訪問該文件的RPC調(diào)用氏堤。
我們對NFS如何訪問文件的細節(jié)并不感興趣,只對它如何使用Internet的協(xié)議搏明,尤其是UDP協(xié)議鼠锈,感興趣。
29.2 Sun遠程過程調(diào)用
大多數(shù)的網(wǎng)絡程序設計都是編寫一些調(diào)用系統(tǒng)提供的函數(shù)來完成特定的網(wǎng)絡操作的應用程序星著。例如购笆,一個函數(shù)完成TCP的主動打開,另一個完成TCP的被動打開虚循,一個函數(shù)在一個TCP連接上發(fā)送數(shù)據(jù)同欠,另一個設置特定的協(xié)議選項(如激活TCP的keepalive定時器)。在1.15節(jié)我們提到過兩個常用的用于網(wǎng)絡編程的函數(shù)集(API):插口(socket)和TLI横缔。正像客戶端和服務器端運行的操作系統(tǒng)可能會不相同一樣铺遂,雙方使用的API也可能會不相同。由通信協(xié)議和應用協(xié)議決定一對客戶和服務器是否可以彼此通信茎刚。如果兩臺主機連接在一個網(wǎng)絡上襟锐,并且都有一個TCP/IP的實現(xiàn),那么一臺主機上的一個使用C語言編寫的膛锭、使用插口和TCP的Unix客戶程序可以和另一臺主機上的一個使用COBOL語言編寫的粮坞、使用其他API和TCP的大型機服務器進行通信笛质。
一般來說,客戶發(fā)送命令給服務器捞蚂,服務器向客戶發(fā)送應答妇押。目前為止,我們討論過的所有應用程序—Ping姓迅,Traceroute敲霍,選路守護程序、以及DNS丁存、TFTP肩杈、BOOTP、SNMP解寝、Telnet扩然、FTP和SMTP的客戶和服務器—都是采用這種方式實現(xiàn)的。
遠程過程調(diào)用RPC(Remote Procedure Call)是一種不同的網(wǎng)絡程序設計方法聋伦》蚺迹客戶程序編寫時只是調(diào)用了服務器程序提供的函數(shù)。這只是程序員所感覺到的觉增,實際上發(fā)生了下面一些動作兵拢。
1:當客戶程序調(diào)用遠程的過程時,它實際上只是調(diào)用了一個位于本機上的逾礁、由RPC程序包生成的函數(shù)说铃。這個函數(shù)被稱為客戶殘樁(stub)∴诼模客戶殘樁將過程的參數(shù)封裝成一個網(wǎng)絡報文腻扇,并且將這個報文發(fā)送給服務器程序。
2:服務器主機上的一個服務器殘樁負責接收這個網(wǎng)絡報文砾嫉。它從網(wǎng)絡報文中提取參數(shù)幼苛,然后調(diào)用應用程序員編寫的服務器過程。
3:當服務器函數(shù)返回時焰枢,它返回到服務器殘樁蚓峦。服務器殘樁提取返回值,把返回值封裝成一個網(wǎng)絡報文济锄,然后將報文發(fā)送給客戶殘樁。
4:客戶殘樁從接收到的網(wǎng)絡報文中取出返回值霍转,將其返回給客戶程序荐绝。
網(wǎng)絡程序設計是通過殘樁和使用諸如插口或TLI的某個API的RPC庫例程來實現(xiàn)的,但是用戶程序—客戶程序和被客戶程序調(diào)用的服務器過程—不會和這個API打交道避消〉吞玻客戶應用程序只是調(diào)用服務器的過程召夹,所有網(wǎng)絡程序設計的細節(jié)都被RPC程序包、客戶殘樁和服務器殘樁所隱藏恕沫。
一個RPC程序包提供了很多好處监憎。
1:程序設計更加容易,因為很少或幾乎沒有涉及網(wǎng)絡編程婶溯。應用程序設計員只需要編寫一個客戶程序和客戶程序調(diào)用的服務器過程鲸阔。
2:如果使用了一個不可靠的協(xié)議,如UDP迄委,像超時和重傳等細節(jié)就由RPC程序包來處理褐筛。這就簡化了用戶應用程序。
3:RPC庫為參數(shù)和返回值的傳輸提供任何需要的數(shù)據(jù)轉(zhuǎn)換叙身。例如渔扎,如果參數(shù)是由整數(shù)和浮點數(shù)組成的,RPC程序包處理整數(shù)和浮點數(shù)在客戶機和服務器主機上存儲的不同形式信轿。這個功能簡化了在異構(gòu)環(huán)境中的客戶和服務器的編碼問題晃痴。
RPC程序設計的細節(jié)可以參看參考文獻[Stevens 1990]的第18章。兩個常用的RPC程序包是Sun RPC和開放軟件基金(OSF)分布式計算環(huán)境(DCE)的RPC程序包财忽。我們對于RPC的興趣在于想了解Sun RPC中過程調(diào)用和過程返回報文的形式愧旦,因為本章中討論的網(wǎng)絡文件系統(tǒng)使用了它們。Sun RPC的第2版定義在RFC 1057 [Sun Microsystems 1988a]中定罢。
Sun RPC
SunRPC有兩個版本笤虫。一個版本建立在插口API基礎上,和TCP和UDP打交道祖凫。另一個稱為TI-RPC的(獨立于運輸層)琼蚯,建立在TLIAPI基礎上,可以和內(nèi)核提供的任何運輸層協(xié)議打交道惠况。盡管本章中我們只討論TCP和UDP遭庶,從討論的觀點來看,兩者是一樣的稠屠。
圖29-1顯示的是使用UDP時峦睡,一個RPC過程調(diào)用報文的格式。IP首部和UDP首部是標準的首部权埠,我們已經(jīng)在圖3-1和圖11-2中顯示過榨了。UDP首部以下是RPC程序包定義的部分。
事務標識符(XID)由客戶程序設置攘蔽,由服務器程序返回龙屉。當客戶收到一個應答,它將服務器返回的XID與它發(fā)送的請求的XID相比較满俗。如果不匹配,客戶就放棄這個報文转捕,等待從服務器返回的下一個報文作岖。每次客戶發(fā)出一個新的RPC,它就會改變報文的XID五芝。但是如果客戶重傳一個以前發(fā)送過的RPC(因為它沒有收到服務器的一個應答)痘儡,重傳報文的XID不會修改。
調(diào)用(call)變量在過程調(diào)用報文中設置為0枢步,在應答報文中設置為1沉删。當前的RPC版本是2。接下來三個變量:程序號价捧、版本號和過程號丑念,標識了服務器上被調(diào)用的特定過程。
證書(credential)字段標識了客戶结蟋。有些情況下脯倚,證書字段設置為空值;另外一些情況下嵌屎,證書字段設置為數(shù)字形式的客戶的用戶號和組號推正。服務器可以查看證書字段以決定是否執(zhí)行請求的過程。驗證(verifier)字段用于使用了DES加密的安全RPC宝惰。盡管證書字段和驗證字段是可變長度的字段植榕,它們的長度也作為字段的一部分被編碼。
接下來是過程參數(shù)(procedure parameter)字段尼夺。參數(shù)的格式依賴于遠程過程的定義尊残。接收者(服務器殘樁)如何知道參數(shù)字段的大小呢?既然使用的是UDP協(xié)議淤堵,UDP數(shù)據(jù)報的大小減去驗證字段以上所有字段的長度就是參數(shù)的大小寝衫。如果使用的不是UDP而是TCP,因為TCP是一個字節(jié)流協(xié)議拐邪,沒有記錄邊界慰毅,所以沒有固定的長度。為了解決這個問題扎阶,在TCP首部和XID之間增加了一個4字節(jié)的長度字段汹胃,告訴接收者這個RPC調(diào)用由多少字節(jié)組成。這也使得一個RPC調(diào)用報文在必要時可以用多個TCP段來傳輸(DNS使用了類似的技術(shù)东臀,參見習題14-4)着饥。
圖29-2顯示了一個RPC應答報文的格式。當遠程過程返回時啡邑,服務器殘樁將這個報文發(fā)送給客戶殘樁贱勃。
應答報文中的XID字段是從調(diào)用報文的XID字段復制而來。應答字段設置為1谤逼,以區(qū)別于調(diào)用報文贵扰。如果調(diào)用報文被接受,狀態(tài)字段設置為0(如果RPC的版本號不為2流部,或者服務器不能鑒別客戶的身份戚绕,調(diào)用報文可能被拒絕)。安全的RPC使用驗證字段來標識服務器枝冀。
如果遠程過程調(diào)用成功舞丛,接受狀態(tài)字段置為0。一個非零的值可能表示一個不合法的版本號或者一個不合法的過程號果漾。如果使用的不是UDP而是TCP球切,如同RPC調(diào)用報文一樣,在TCP首部和XID字段之間插入一個4字節(jié)的長度字段绒障。
29.3 XDR:外部數(shù)據(jù)表示
外部數(shù)據(jù)表示XDR(eXternal Data Representation)是一個標準吨凑,用來對RPC調(diào)用報文和應答報文中的值進行編碼。這些值包括RPC首部字段(XID户辱、程序號鸵钝、接受狀態(tài)等)、過程參數(shù)和過程結(jié)果庐镐。采用標準化的方法對這些值進行編碼使得一個系統(tǒng)中的客戶可以調(diào)用另一個不同架構(gòu)的系統(tǒng)中的一個過程恩商。XDR在RFC 1014中定義[Sun Microsystems 1987]。
XDR定義了很多數(shù)據(jù)類型以及它們?nèi)绾卧谝粋€RPC報文中傳輸?shù)木唧w形式(如比特順序必逆,字節(jié)順序等)怠堪。發(fā)送者必須采用XDR格式構(gòu)造一個RPC報文,然后接收者將XDR格式的報文轉(zhuǎn)換為本機的表示形式名眉。例如粟矿,在圖29-1和圖29-2中,我們顯示的所有整數(shù)值(XID璧针、調(diào)用字段嚷炉、程序號等)都是4字節(jié)的整數(shù)。在XDR中探橱,所有的整數(shù)的確占據(jù)4個字節(jié)申屹。XDR支持的其他數(shù)據(jù)類型包括無符號整數(shù)、布爾類型隧膏、浮點數(shù)哗讥、定長數(shù)組、可變長數(shù)組和結(jié)構(gòu)胞枕。
29.4 端口映射器
包含遠程過程的RPC服務器程序使用的是臨時端口杆煞,而不是知名端口。這就需要某種形式的“注冊”程序來跟蹤哪一個RPC程序使用了哪一個臨時端口。在Sun RPC中决乎,這個注冊程序被稱為端口映射器(port mapper)队询。
“端口”這個詞作為Internet協(xié)議族的一個特征,來自于TCP和UDP端口號构诚。既然TIRPC可以工作在任何運輸層協(xié)議之上蚌斩,而不僅僅是TCP和UDP,所以使用TI-RPC的系統(tǒng)中(如SVR4和Solaris 2.2)范嘱,端口映射器的名字變成了rpcbind送膳。下面我們繼續(xù)使用更為常見的端口映射器的名字。
很自然地丑蛤,端口映射器本身必須有一個知名端口:UDP端口111和TCP端口111叠聋。端口映射器也就是一個RPC服務器程序。它有一個程序號(100000)受裹、一個版本號(2)碌补、一個TCP端口111和一個UDP端口111。服務器程序使用RPC調(diào)用向端口映射器注冊自身名斟,客戶程序使用RPC調(diào)用向端口映射器查詢脑慧。端口映射器提供四個服務過程:
1:PMAPPROC_SET。一個RPC服務器啟動時調(diào)用這個過程砰盐,注冊一個程序號闷袒、版本號和帶有一個端口號的協(xié)議。
2:PMAPPROC_UNSET岩梳。RPC服務器調(diào)用此過程來刪除一個已經(jīng)注冊的映射囊骤。
3:PMAPPROC_GETPORT。一個RPC客戶啟動時調(diào)用此過程冀值。根據(jù)一個給定的程序號也物、版本號和協(xié)議來獲得注冊的端口號。
4:PMAPPROC_DUMP列疗。返回端口映射器數(shù)據(jù)庫中所有的記錄(每個記錄包括程序號滑蚯、版本號、協(xié)議和端口號)抵栈。
在一個RPC服務器程序啟動告材,接著被一個RPC客戶程序調(diào)用的過程中,進行了以下一些步驟:
1:一般情況下古劲,當系統(tǒng)引導時斥赋,端口映射器必須首先啟動。它創(chuàng)建一個TCP端點产艾,并且被動打開TCP端口111疤剑。它也創(chuàng)建一個UDP端點滑绒,并且在UDP端口111等待著UDP數(shù)據(jù)報的到來。
2:當RPC服務器程序啟動時隘膘,它為它所支持的程序的每一個版本創(chuàng)建一個TCP端點和一個UDP端點(一個給定的RPC程序可以支持多個版本疑故。客戶調(diào)用一個服務器過程時棘幸,說明它想要哪一個版本)谊却。兩個端點各自綁定一個臨時端口(TCP端口號和UDP端口號是否一致無關緊要)母剥。服務器通過RPC調(diào)用端口映射器的PMAPPROC_SET過程,注冊每一個程序定拟、版本扫茅、協(xié)議和端口號蹋嵌。
3:當RPC客戶程序啟動時,它調(diào)用端口映射器的PMAPPROC_GETPORT過程來獲得一個指定程序葫隙、版本和協(xié)議的臨時端口號栽烂。
4:客戶發(fā)送一個RPC調(diào)用報文給第3步返回的端口號。如果使用的是UDP恋脚,客戶只是發(fā)送一個包含RPC調(diào)用報文(見圖29-1)的UDP數(shù)據(jù)報到服務器相應的UDP端口腺办。服務器發(fā)送一個包含RPC應答報文(見圖29-2)的UDP數(shù)據(jù)報到客戶作為響應。
如果使用的是TCP糟描,客戶對服務器的TCP端口號做一個主動打開怀喉,然后在建立的TCP連接上發(fā)送一個RPC調(diào)用報文。服務器作為響應船响,在連接上發(fā)送一個RPC應答報文躬拢。
程序rpcinfo(8)打印了端口映射器中當前的映射記錄(它調(diào)用了端口映射器的PMAPPROC_DUMP過程)。這里給出的是典型的輸出:
可以看出一些程序確實支持多個版本见间。在端口映射器中聊闯,每一個程序號、版本號和協(xié)議的組合都有自己的端口號映射米诉。
安裝守護程序(mount daemon)的兩個版本可以通過同樣的TCP端口號(702)和同樣的UDP端口號(699)來訪問菱蔬,而加鎖管理程序(lock manager)的每個版本都有各自不同的端口號。
29.5 NFS協(xié)議
使用NFS史侣,客戶可以透明地訪問服務器上的文件和文件系統(tǒng)拴泌。這不同于提供文件傳輸?shù)腇TP(第27章)。FTP會產(chǎn)生文件一個完整的副本抵窒。NFS只訪問一個進程引用文件的那一部分弛针,并且NFS的一個目的就是使得這種訪問透明。這就意味著任何能夠訪問一個本地文件的客戶程序不需要做任何修改李皇,就應該能夠訪問一個NFS文件削茁。
NFS是一個使用Sun RPC構(gòu)造的客戶服務器應用程序宙枷。NFS客戶通過向一個NFS服務器發(fā)送RPC請求來訪問其上的文件。盡管這一工作可以使用一般的用戶進程來實現(xiàn)—即NFS客戶可以是一個用戶進程茧跋,對服務器進行顯式調(diào)用慰丛。而服務器也可以是一個用戶進程—因為兩個理由,NFS一般不這樣實現(xiàn)瘾杭。首先诅病,訪問一個NFS文件必須對客戶透明。因此粥烁,NFS的客戶調(diào)用是由客戶操作系統(tǒng)代表用戶進程來完成的贤笆。第二,出于效率的考慮讨阻,NFS服務器在服務器操作系統(tǒng)中實現(xiàn)芥永。如果NFS服務器是一個用戶進程,每個客戶請求和服務器應答(包括讀和寫的數(shù)據(jù))將不得不在內(nèi)核和用戶進程之間進行切換钝吮,這個代價太大埋涧。
本節(jié)中,我們考察在RFC1094中說明的第2版的NFS [Sun Microsystems 1988b]奇瘦。[X/Open1991]中給出了Sun RPC棘催、XDR和NFS的一個更好的描述。[Stern 1991]給出了使用和管理NFS的細節(jié)耳标。第3版的NFS協(xié)議在1993年發(fā)布醇坝,我們在29.7節(jié)中對它做一個簡單的描述。
圖29-3顯示了一個NFS客戶和一個NFS服務器的典型配置麻捻,圖中有很多地方需要注意纲仍。
1:訪問的是一個本地文件還是一個NFS文件對于客戶來說是透明的。當文件被打開時贸毕,由內(nèi)核決定這一點郑叠。文件被打開之后,內(nèi)核將本地文件的所有引用傳遞給名為“本地文件訪問”的框中明棍,而將一個NFS文件的所有引用傳遞給名為“NFS客戶”的框中乡革。
2:NFS客戶通過它的TCP/IP模塊向NFS服務器發(fā)送RPC請求。NFS主要使用UDP摊腋,最新的實現(xiàn)也可以使用TCP沸版。
3:NFS服務器在端口2049接收作為UDP數(shù)據(jù)報的客戶請求。盡管NFS可以被實現(xiàn)成使用端口映射器兴蒸,允許服務器使用一個臨時端口视粮,但是大多數(shù)的實現(xiàn)都是直接指定UDP端口2049。
4:當NFS服務器收到一個客戶請求時橙凳,它將這個請求傳遞給本地文件訪問例程蕾殴,后者訪問服務器主機上的一個本地的磁盤文件笑撞。
5:NFS服務器需要花一定的時間來處理一個客戶的請求。訪問本地文件系統(tǒng)一般也需要一部分時間钓觉。在這段時間間隔內(nèi)茴肥,服務器不應該阻止其他的客戶請求得到服務。為了實現(xiàn)這一功能荡灾,大多數(shù)的NFS服務器都是多線程的—即服務器的內(nèi)核中實際上有多個NFS服務器在運行瓤狐。具體怎么實現(xiàn)依賴于不同的操作系統(tǒng)。既然大多數(shù)的Unix內(nèi)核不是多線程的批幌,一個共同的技術(shù)就是啟動一個用戶進程(常被稱為nfsd)的多個實例础锐。這個實例執(zhí)行一個系統(tǒng)調(diào)用,使自己作為一個內(nèi)核進程保留在操作系統(tǒng)的內(nèi)核中逼裆。
6:同樣郁稍,在客戶主機上,NFS客戶需要花一定的時間來處理一個用戶進程的請求胜宇。NFS客戶向服務器主機發(fā)出一個RPC調(diào)用,然后等待服務器的應答恢着。為了給使用NFS的客戶主機上的用戶進程提供更多的并發(fā)性桐愉,在客戶內(nèi)核中一般運行著多個NFS客戶。同樣掰派,具體實現(xiàn)也依賴于操作系統(tǒng)从诲。Unix系統(tǒng)經(jīng)常使用類似于NFS服務器的技術(shù):一個叫作biod的用戶進程執(zhí)行一個系統(tǒng)調(diào)用,作為一個內(nèi)核進程保留在操作系統(tǒng)的內(nèi)核中靡羡。
大多數(shù)的Unix主機可以作為一個NFS客戶系洛,一個NFS服務器,或者兩者都是略步。大多數(shù)PC機的實現(xiàn)(MS-DOS)只提供了NFS客戶實現(xiàn)描扯。大多數(shù)的IBM大型機只提供了NFS服務器功能。
NFS實際上不僅僅由NFS協(xié)議組成趟薄。圖29-4顯示了NFS使用的不同RPC程序绽诚。
在這個圖中,程序的版本是在SunOS 4.1.3中使用的杭煎。更新的實現(xiàn)提供了其中一些程序更新的版本恩够。例如,Solaris 2.2還支持端口映射器的第3版和第4版羡铲,以及安裝守護程序的第2版蜂桶。SVR4支持第3版的端口映射器。
在客戶能夠訪問服務器上的文件系統(tǒng)之前也切,NFS客戶主機必須調(diào)用安裝守護程序扑媚。我們在下面討論安裝守護程序妥曲。
加鎖管理程序和狀態(tài)監(jiān)視器允許客戶鎖定一個NFS服務器上文件的部分區(qū)域。這兩個程序獨立于NFS協(xié)議钦购,因為加鎖需要知道客戶和服務器的狀態(tài)檐盟,而NFS本身在服務器上是無狀態(tài)的(下面我們對NFS的無狀態(tài)會介紹得更多)。[X/Open 1991]的第9,10和11章說明了使用加鎖管理程序和狀態(tài)監(jiān)視器進行NFS文件鎖定的過程押桃。
29.5.1 文件句柄
NFS中一個基本概念是文件句柄(file handle)葵萎。它是一個不透明(opaque)的對象,用來引用服務器上的一個文件或目錄唱凯。不透明指的是服務器創(chuàng)建文件句柄羡忘,把它傳遞給客戶,然后客戶訪問文件時磕昼,使用對應的文件句柄卷雕。客戶不會查看文件句柄的內(nèi)容—它的內(nèi)容只對服務器有意義票从。
每次一個客戶進程打開一個實際上位于一個NFS服務器上的文件時漫雕,NFS客戶就會從NFS服務器那里獲得該文件的一個文件句柄。每次NFS客戶為用戶進程讀或?qū)懳募r峰鄙,文件句柄就會傳給服務器以指定被訪問的文件浸间。
一般情況下,用戶進程不會和文件句柄打交道—只有NFS客戶和NFS服務器將文件句柄傳來傳去吟榴。在第2版的NFS中魁蒜,一個文件句柄占據(jù)32個字節(jié),第3版中增加為64個字節(jié)吩翻。
Unix服務器一般在文件句柄中存儲下面的信息:文件系統(tǒng)標識符(文件系統(tǒng)最大和最小的設備號)兜看,i-node號(在一個文件系統(tǒng)中唯一的數(shù)值)和一個i-node的生成碼(每當一個i-node被一個不同的文件重用時就改變的數(shù)值)。
29.5.2 安裝協(xié)議
客戶必須在訪問服務器上一個文件系統(tǒng)中的文件之前狭瞎,使用安裝協(xié)議安裝那個文件系統(tǒng)细移。一般情況下,這是在客戶主機引導時完成的脚作。最后的結(jié)果就是客戶獲得服務器文件系統(tǒng)的一個文件句柄葫哗。
圖29-5顯示了一個Unix客戶發(fā)出mount(8)命令所發(fā)生的情況,它說明一個NFS的安裝過程球涛。
依次發(fā)生了下面的動作劣针。
1:服務器上的端口映射器一般在服務器主機引導時被啟動。
2:安裝守護程序(mountd)在端口映射器之后被啟動亿扁。它創(chuàng)建了一個TCP端點和一個UDP端點捺典,并分別賦予一個臨時的端口號。然后它在端口映射器中注冊這些端口號从祝。
3:在客戶機上執(zhí)行mount命令襟己,它向服務器上的端口映射器發(fā)出一個RPC調(diào)用來獲得服務器上安裝守護程序的端口號引谜。客戶和端口映射器交互既可以使用TCP也可以使用UDP擎浴,但一般使用UDP员咽。
4:端口映射器應答以安裝守護程序的端口號。
5:mount命令向安裝守護程序發(fā)出一個RPC調(diào)用來安裝服務器上的一個文件系統(tǒng)贮预。同樣贝室,既可以使用TCP也可以使用UDP,但一般使用UDP仿吞。服務器現(xiàn)在可以驗證客戶滑频,使用客戶的IP地址和端口號來判別是否允許客戶安裝指定的文件系統(tǒng)。
6:安裝守護程序應答以指定文件系統(tǒng)的文件句柄唤冈。
7:客戶機上的mount命令發(fā)出mount系統(tǒng)調(diào)用將第5步返回的文件句柄與客戶機上的一個本地安裝點聯(lián)系起來峡迷。文件句柄被存儲在NFS客戶代碼中,從現(xiàn)在開始你虹,用戶進程對于那個服務器文件系統(tǒng)的任何引用都將從使用這個文件句柄開始绘搞。
上述實現(xiàn)技術(shù)將所有的安裝處理,除了客戶機上的mount系統(tǒng)調(diào)用售葡,都放在用戶進程中看杭,而不是放在內(nèi)核中。我們顯示的三個程序—mount命令挟伙、端口映射器和安裝守護程序—都是用戶進程。
作為一個例子模孩,在我們的主機sun(一個NFS客戶機)上執(zhí)行:
sun # mount -t nfs bsdi:/usr /nfs/bsdi/usr
這個命令將主機bsdi(一個NFS服務器)上的/usr目錄安裝成為本地文件系統(tǒng)/nfs/bsdi/usr尖阔。圖29-6顯示了結(jié)果。
當我們引用客戶機sun上的/nfs/bsdi/usr/rstevens/hello.c文件時榨咐,實際上引用的是服務器bsdi上的文件/usr/rstevens/hello.c介却。
29.5.3 NFS過程
現(xiàn)在我們描述NFS服務器提供的15個過程(使用的個數(shù)與NFS過程的實際個數(shù)不一樣,因為我們把它們按照功能分了組)块茁。盡管NFS被設計成可以在不同的操作系統(tǒng)上工作齿坷,而不僅僅是Unix系統(tǒng),但是一些提供Unix功能的過程可能不被其他操作系統(tǒng)支持(例如硬鏈接数焊、符號鏈接永淌、組的屬主和執(zhí)行權(quán)等)。[Stevens 1992]的第4章包含了Unix文件系統(tǒng)其他的一些信息佩耳,其中有些被NFS采用遂蛀。
1:GETATTR。返回一個文件的屬性:文件類型(一般文件干厚,目錄等)李滴、訪問權(quán)限螃宙、文件大小、文件的屬主者及上次訪問時間等信息所坯。
2:SETATTR谆扎。設置一個文件的屬性。只允許設置文件屬性的一個子集:訪問權(quán)限芹助、文件的屬主堂湖、組的屬主、文件大小周瞎、上次訪問時間和上次修改時間苗缩。
3:STATFS。返回一個文件系統(tǒng)的狀態(tài):可用空間的大小声诸、最佳傳送大小等酱讶。例如Unix的df命令使用此過程。
4:LOOKUP彼乌。查找一個文件泻肯。每當一個用戶進程打開一個NFS服務器上的一個文件時,NFS客戶調(diào)用此過程慰照。
5:READ灶挟。從一個文件中讀數(shù)據(jù)《咀猓客戶說明文件的句柄稚铣、讀操作的開始位置和讀數(shù)據(jù)的最大字節(jié)數(shù)(最多8192個字節(jié))。
6:WRITE墅垮。對一個文件進行寫操作惕医。客戶說明文件的句柄算色、開始位置抬伺、寫數(shù)據(jù)的字節(jié)數(shù)和要寫的數(shù)據(jù)。
7:CREATE灾梦。創(chuàng)建一個文件峡钓。
8:REMOVE。刪除一個文件若河。
9:RENAME能岩。重命名一個文件。
10:LINK牡肉。為一個文件構(gòu)造一個硬鏈接捧灰。硬鏈接是一個Unix的概念,指的是磁盤中的一個文件可以有任意多個目錄項(即名字,也叫作硬鏈接)指向它毛俏。
11:SYMLINK炭庙。為一個文件創(chuàng)建一個符號鏈接。符號鏈接是一個包含另一個文件名字的文件煌寇。大多數(shù)引用符號鏈接的操作(例如焕蹄,打開)實際上引用的是符號鏈接所指的文件。
12:READLINK阀溶。讀一個符號鏈接腻脏。即返回符號鏈接所指的文件的名字。
13:MKDIR银锻。創(chuàng)建一個目錄永品。
14:RMDIR。刪除一個目錄击纬。
15:READDIR鼎姐。讀一個目錄。例如更振,Unix的ls命令使用此過程炕桨。
這些過程實際上有一個前綴NFSPROC_,我們把它省略了肯腕。
29.5.4 UDP還是TCP
NFS最初是用UDP寫的献宫,所有的廠商都提供了這種實現(xiàn)。最新的一些實現(xiàn)也支持TCP实撒。TCP支持主要用于廣域網(wǎng)姊途,它可以使文件操作更快。NFS已經(jīng)不再局限于局域網(wǎng)的使用知态。
當從LAN轉(zhuǎn)換到W N時吭净,網(wǎng)絡的動態(tài)特征變化得非常大。往返時間(round-trip time)變動范圍大肴甸,擁塞經(jīng)常發(fā)生。WAN的這些特征使得我們考慮使用具有TCP屬性的算法——慢啟動囚巴,但是可以避免擁塞原在。既然UDP沒有提供任何類似的東西,那么在NFS客戶和服務器上加進同樣的算法或者使用TCP彤叉。
29.5.5 TCP上的NFS
伯克利實現(xiàn)的Net/2NFS支持UDP或者TCP庶柿。[Macklem 1991]描述了這個實現(xiàn)。讓我們看一下使用TCP有什么不同秽浇。
1:當服務器主機進行引導時浮庐,它啟動一個NFS服務器,后者被動打開TCP端口2049,等待著客戶的連接請求审残。這通常是另一個NFS服務器梭域,正常的NFS UDP服務器在UDP端口2049等待著進入的UDP數(shù)據(jù)報。
2:當客戶使用TCP安裝服務器上的文件系統(tǒng)時搅轿,它對服務器上的TCP端口2049做一個主動打開病涨。這樣就為這個文件系統(tǒng)在客戶和服務器之間形成了一個TCP連接。如果同樣的客戶安裝同樣服務器上的另一個文件系統(tǒng)璧坟,就會創(chuàng)建另一個TCP連接既穆。
3:客戶和服務器在它們連接的兩端都要設置TCP的keepalive選項,這樣雙方都能檢測到對方主機崩潰雀鹃,或者崩潰然后重啟動幻工。
4:客戶方所有使用這個服務器文件系統(tǒng)的應用程序共享這個TCP連接。例如黎茎,在圖29-6中囊颅,如果在bsdi的/usr目錄下還有另一個目錄smith,那么對兩個目錄/nfs/bsdi/usr/rstevens和/nfs/bsdi/usr/smith下所有文件的引用將共享同樣的TCP連接工三。
5:如果客戶檢測到服務器已經(jīng)崩潰迁酸,或者崩潰然后重啟動(通過收到一個TCP差錯“連接超時”或者“對方復位連接”),它嘗試與服務器重新建立連接俭正〖轺蓿客戶做另一個主動打開,為同一個文件系統(tǒng)請求重新建立TCP連接掸读。在以前連接上超時的所有客戶請求在新的連接上都會重新發(fā)出串远。
6:如果客戶機崩潰,那么當它崩潰時正在運行的應用程序也要崩潰儿惫。當客戶機重新啟動時澡罚,它很可能使用TCP重新安裝服務器的文件系統(tǒng),這將導致和服務器的另一個連接肾请×羯Γ客戶和服務器之間針對同一個文件系統(tǒng)的前一個連接現(xiàn)在打開了一半(服務器方認為它還開著),但是既然服務器設置了keepalive選項铛铁,當服務器發(fā)出下一個keepalive探查報文時隔显,這個半開著的TCP連接就會被中止。
隨著時間的流逝饵逐,另外一些廠商也計劃支持TCP上的NFS括眠。
29.6 NFS實例
我們使用tcpdump來看一下在典型的文件操作中,客戶調(diào)用了哪些NFS過程倍权。當tcpdump檢測到一個包含RPC調(diào)用(在圖29-1中調(diào)用字段等于0)掷豺、目的端口是2049的UDP數(shù)據(jù)報時,它把數(shù)據(jù)報按照一個NFS請求進行解碼。類似地当船,如果一個UDP數(shù)據(jù)報是一個RPC應答(在圖29-2中應答字段為1)题画,源端口是2049,tcpdump就把此數(shù)據(jù)報作為一個NFS應答來解碼生年。
29.6.1 簡單的例子:讀一個文件
第一個例子是使用cat(1)命令將位于一個NFS服務器上的一個文件復制到終端上:
如同圖29-6所示婴程,主機sun(NFS客戶機)上的文件系統(tǒng)/nfs/bsdi/usr實際上是主機bsdi(NFS服務器)上的/usr文件系統(tǒng)。當cat打開這個文件時抱婉,sun上的內(nèi)核檢測到這一點档叔,然后使用NFS去訪問文件。圖29-7顯示了tcpdump的輸出蒸绩。
當tcpdump解析一個NFS請求或應答報文時衙四,它打印客戶的XID字段,而不是端口號患亿。第1行和第2行中的XID字段值是0x7aa6传蹈。
客戶內(nèi)核中的打開函數(shù)一次處理文件名/nfs/bsdi/usr/rstevens/hello.c中的一個成員。當處理到/nfs/bsdi/usr時步藕,它發(fā)現(xiàn)這是指向一個已安裝的NFS文件系統(tǒng)的一個安裝點惦界。
在第1行中,客戶調(diào)用GETAT TR過程取得客戶已經(jīng)安裝的服務器目錄的屬性(/usr)咙冗。這個RPC請求沾歪,除IP首部和UDP首部之外,包含104個字節(jié)的數(shù)據(jù)雾消。第2行中的應答返回了一個OK值灾搏,除了IP首部和UDP首部之外,包含了96個字節(jié)的數(shù)據(jù)立润。在這個圖中狂窑,我們可以看出最小的NFS報文包含大約100個字節(jié)的數(shù)據(jù)。
在第3行中桑腮,客戶調(diào)用LOOKUP過程來查看rstevens文件泉哈。在第4行中收到一個OK應答。LOOKUP過程說明了文件名rstevens和遠程文件系統(tǒng)被安裝時由內(nèi)核保存的文件句柄破讨。應答中包含了下一步要使用的一個新的文件句柄旨巷。
在第5行中,客戶使用第4行中返回的文件句柄對hello.c調(diào)用LOOKUP過程添忘。在第6行返回了另一個文件句柄。新的文件句柄就是客戶在第7行和第9行中引用文件/nfs/bsdi/usr/rstevens/hello.c所使用的文件句柄若锁。我們看到客戶對于正在打開的路徑名的每個成員都調(diào)用了一次LOOKUP過程搁骑。
在第7行中,客戶又調(diào)用了一次GETAT TR過程,接著在第9行中調(diào)用了READ過程仲器∶郝剩客戶請求從偏移0開始的1024個字節(jié),但是接收到的沒有這么多(減去RPC字段和其他由READ過程返回的值的大小乏冀,在第10行中返回了38個字節(jié)的數(shù)據(jù)蝶糯。這是文件hello.c的實際大小)辆沦。
在這個例子中昼捍,應用進程對于內(nèi)核所做的這些RPC請求和應答一點兒也不知道。應用進程只是調(diào)用了內(nèi)核的open函數(shù)肢扯,后者引起了3個RPC請求和3個應答(16行)妒茬,然后應用進程又調(diào)用了內(nèi)核的read函數(shù),它引起了兩個請求和兩個應答(710行)蔚晨。該文件位于一個NFS文件服務器乍钻,這一點對客戶應用進程來說是透明的。
29.6.2 簡單的例子:創(chuàng)建一個目錄
作為另一個簡單的例子铭腕,我們將當前工作目錄改變?yōu)橐粋€后創(chuàng)建一個新的目錄:
圖29-8顯示了tcpdump的輸出银择。
改變目錄引起客戶調(diào)用了兩次GETATTR過程(1~4行)累舷。當我們創(chuàng)建新的目錄時浩考,客戶調(diào)用了GETAT TR過程(56行),接著調(diào)用LOOKUP過程(78行笋粟,用來驗證將創(chuàng)建的目錄不存在)怀挠,跟著調(diào)用了MKDIR過程來創(chuàng)建目錄(9-10行)。在第8行中害捕,應答OK并不表示目錄存在绿淋。它只是表示過程返回了。tcpdump并不理解NFS過程的返回值尝盼。它一般打印OK和應答報文中數(shù)據(jù)的字節(jié)數(shù)吞滞。
29.6.3 無狀態(tài)
NFS的一個特征(NFS的批評者稱之為NFS的一個瑕疵,而不是一個特征)是NFS服務器是無狀態(tài)的(stateless)盾沫。服務器并不記錄哪個客戶正在訪問哪個文件裁赠。請注意一下在前面給出的NFS過程中,沒有一個open操作和一個close操作赴精。LOOKUP過程的功能與open操作有些類似佩捞,但是服務器永遠也不會知道客戶對一個文件調(diào)用了LOOKUP過程之后是否會引用該文件。
無狀態(tài)設計的理由是為了在服務器崩潰并且重啟動時蕾哟,簡化服務器的崩潰恢復操作一忱。
29.6.4 例子:服務器崩潰
在下面的例子中我們從一個崩潰然后重啟動的NFS服務器上讀一個文件莲蜘。這個例子演示了無狀態(tài)的服務器是如何使得客戶不知道服務器的崩潰。除了在服務器崩潰然后重啟動時一個時間上的暫停外帘营,客戶并不知道發(fā)生的問題票渠,客戶應用進程沒有受到影響。
在客戶機sun上芬迄,我們對一個長文件(NFS服務器主機svr4上的文件/usr/share/lib/termcap)執(zhí)行cat命令问顷。在傳送過程中把以太網(wǎng)的網(wǎng)線拔掉,關閉然后重啟動服務器主機禀梳,再重新將網(wǎng)線連上杜窄。客戶被配置成每個NFS read過程讀1024個字節(jié)出皇。圖29-9顯示了tcpdump的輸出羞芍。
1~10行對應于客戶打開文件,操作類似于圖29-7所示郊艘。在第11行我們看到對文件的第一個READ操作荷科,在12行返回了1024個字節(jié)的數(shù)據(jù)。這個操作一直繼續(xù)到129行(讀1024個字節(jié)的數(shù)據(jù)纱注,跟著一個OK應答)畏浆。
在第130行和第131行我們看到兩個請求超時,并且分別在132行和133行重傳狞贱。第一個問題是這里為什么會有兩個讀請求刻获,一個從偏移65536開始讀,另一個從偏移73728開始讀瞎嬉?答案是客戶內(nèi)核檢測到客戶應用進程正在進行順序地讀操作尉咕,所以試圖預先取得數(shù)據(jù)塊(大多數(shù)的Unix內(nèi)核都采用了這種預讀技術(shù))串前≌茄眨客戶內(nèi)核也正在運行多個NFS塊I/O守護程序曙旭,后者試圖代表客戶產(chǎn)生多個RPC請求。一個守護程序正在從偏移65536處讀8192個字節(jié)(以1024字節(jié)為一組數(shù)據(jù)塊)便监,而另一個正在從73728處預讀8192個字節(jié)扎谎。
客戶重傳發(fā)生在130~168行。在第169行我們看到服務器已經(jīng)重啟動烧董,在它對第168行的客戶NFS請求做出應答之前毁靶,它發(fā)送了一個ARP請求。對168行的響應被發(fā)送在171行逊移≡み海客戶的READ操作繼續(xù)進行下去。
除了從129行到171行5分鐘的暫停胳泉,客戶應用進程并不知道服務器崩潰然后又重啟動了啡浊。這個服務器的崩潰對于客戶是透明的觅够。
為了研究這個例子中的超時和重傳時間間隔,首先要意識到這兒有兩個客戶守護程序巷嚣,分別有它們各自的超時。第1個守護程序(在偏移65536處開始讀)的間隔钳吟,四舍五入到兩個十進制小數(shù)點廷粒,為0.68,0.87,1.74,3.48,6.96,13.92,20.0,20.0,20.0等等。第2個守護程序(在偏移73728處開始讀)的間隔也是一樣的(精確到兩個小數(shù)點)红且“泳ィ可以看出這些NFS客戶使用了一個這樣的超時定時器:間隔為0.875秒的倍數(shù),上限為20秒暇番。每次超時后嗤放,重傳間隔翻倍:0.875,1.75,3.5,7.0和14.0。
客戶要重傳多久呢次酌?客戶有兩個與此有關的選項。首先舆乔,如果服務器文件系統(tǒng)是“硬”安裝的岳服,客戶就會永遠重傳下去。但是如果服務器文件系統(tǒng)是“軟”安裝的希俩,客戶重傳了固定數(shù)目的次數(shù)之后就會放棄吊宋。在“硬”安裝的情況下,客戶還有一個選項決定是否允許用戶中斷無限制的重傳颜武。如果客戶主機安裝服務器文件系統(tǒng)時說明了中斷能力璃搜,并且如果我們不想在服務器崩潰之后等5分鐘,等著服務器重啟動鳞上,就可以鍵入一個中斷鍵以終止客戶應用程序这吻。
29.6.5 等冪過程
如果一個RPC過程被服務器執(zhí)行多次仍然返回同樣的結(jié)果,那么就把它叫作等冪過程(Idempotent Procedure)因块。例如橘原,NFS的讀過程是等冪的。正像我們在圖29-9中看到的涡上,客戶只是重發(fā)一個特定的READ調(diào)用直到它得到一個響應趾断。在我們的例子中,重傳的原因是服務器崩潰了吩愧。如果服務器沒有崩潰芋酌,而是RPC應答報文丟失了(既然UDP是不可靠的),客戶只是重傳請求雁佳,服務器再一次執(zhí)行同樣的READ過程脐帝。同一個文件的同一部分被重讀一次同云,發(fā)送給客戶。
這種方法行得通的原因在于每個READ請求指出了讀操作開始的偏移位置堵腹。如果有一個NFS過程要求服務器讀一個文件的下N個字節(jié)炸站,這種方法就不行了。除非服務器被做成是有狀態(tài)的(與無狀態(tài)相反)疚顷,如果一個應答丟失了旱易,客戶重發(fā)讀下N個字節(jié)的READ請求,結(jié)果將是不一樣的腿堤。這就是為什么NFS的READ和WRITE過程要求客戶說明開始的偏移位置的原因阀坏。客戶維護著狀態(tài)(每個文件當前的偏移位置)笆檀,而不是服務器忌堂。
不幸的是并不是所有的文件系統(tǒng)操作都是等冪的。例如酗洒,考慮下面的動作:客戶NFS發(fā)出REMOVE請求來刪除一個文件士修;服務器NFS刪除了文件,并回答OK寝蹈;服務器的回答丟失了李命;客戶NFS超時,然后重傳請求箫老;服務器NFS找不到指定的文件封字,回答指出一個錯誤;客戶應用程序接收到一個錯誤表示文件不存在耍鬓。這個返回給客戶應用程序的錯誤是不對的—該文件的確存在并且被刪除了阔籽。
等冪的NFS過程是:GETATTR、STATES牲蜀、LOOKUP笆制、READ、WRITE涣达、READLINK和READDIR在辆。不是等冪的過程是:CREATE、REMOVE度苔、RENAME匆篓、LINK、SYMLINK寇窑、MKDIR和RMDIR鸦概。SETATTR過程如果不用來截斷文件,一般是等冪的甩骏。
既然使用UDP總會發(fā)生響應報文丟失的現(xiàn)象窗市,NFS服務器需要一種方法來處理非等冪的操作先慷。大多數(shù)的服務器實現(xiàn)了一個最近應答的高速緩存,用于存放非等冪操作最近的應答咨察。每當服務器收到一個請求论熙,它首先檢查這個高速緩存,如果找到了一個匹配摄狱,就返回以前的應答而不再調(diào)用相應的NFS過程赴肚。[Juszczak 1989]提供了這種高速緩存的實現(xiàn)細節(jié)。
等冪服務器過程的概念可以應用于任何基于UDP的應用程序二蓝,而不僅僅是NFS。例如指厌,DNS也提供了一個等冪服務刊愚。一個DNS的服務器可以任意多次地執(zhí)行一個解析者的請求而沒有任何不良的后果(如果不考慮網(wǎng)絡資源浪費的話)。
29.7 第3版的NFS
1993年發(fā)布了第3版的NFS協(xié)議規(guī)范[Sun Microsystem 1994]踩验。其實現(xiàn)有望在1994年成為可能鸥诽。
我們總結(jié)一下第2版和第3版的主要區(qū)別。下面把兩者分別稱為V2和V3箕憾。
1:V2中的文件句柄是32字節(jié)的固定大小的數(shù)組牡借。在V3中,它變成了一個最多為64個字節(jié)的可變長度的數(shù)組袭异。在XDR中钠龙,一個可變長度的數(shù)組被編碼為一個4字節(jié)的數(shù)組成員個數(shù)跟著實際的數(shù)組成員字節(jié)。這樣在實現(xiàn)時減少了文件句柄的長度御铃,例如Unix只需要12個字節(jié)碴里,但又允許非Unix實現(xiàn)維護另外的信息。
2:V2將每個READ和WRITE RPC過程可以讀寫的數(shù)據(jù)限制為8192個字節(jié)上真。這個限制在V3中取消了咬腋,這就意味著一個UDP上的實現(xiàn)只受到IP數(shù)據(jù)報大小的限制(65535字節(jié))。這樣允許在更快的網(wǎng)絡上讀寫更大的分組睡互。
3:文件大小以及READ和WRITE過程開始偏移的字節(jié)從32字節(jié)擴充到64字節(jié)根竿,允許讀寫更大的文件。
4:每個影響文件屬性值的調(diào)用都返回文件的屬性就珠。這樣減少了客戶調(diào)用GETAT TR過程的次數(shù)寇壳。
5:WRITE過程可以是異步的,而在V2中要求同步的WRITE過程嗓违。這樣可以提高WRITE過程的性能九巡。
6:V3中刪去了一個過程(STAT FS),增加了七個過程:ACCESS(檢查文件訪問權(quán)限)蹂季、MKNOD(創(chuàng)建一個Unix特殊文件)冕广、READDIRPLUS(返回一個目錄中的文件名字和它們的屬性)疏日、FSINFO(返回一個文件系統(tǒng)的靜態(tài)信息)、FSSTAT(返回一個文件系統(tǒng)的動態(tài)信息)撒汉、PAT HCONF(返回一個文件的POSIX.1信息)和COMMIT(將以前的異步寫操作提交到外存中)沟优。
29.8 小結(jié)
RPC是構(gòu)造客戶-服務器應用程序的一種方式,使得看起來客戶只是調(diào)用了服務器的過程睬辐。所有的網(wǎng)絡操作細節(jié)都被隱藏在RPC程序包為一個應用程序生成的客戶和服務器殘樁以及RPC庫的例程中挠阁。我們顯示了RPC調(diào)用和應答報文的格式,并且提到了使用XDR對傳輸?shù)闹颠M行編碼溯饵,使得RPC客戶和服務器可以運行在不同架構(gòu)的機器上侵俗。
最廣泛使用的RPC應用之一就是Sun的NFS,一個在各種大小的主機上廣泛實現(xiàn)的異構(gòu)的文件訪問協(xié)議丰刊。我們?yōu)g覽了NFS和它使用UDP和TCP的方式隘谣。第2版的NFS協(xié)議定義了15個過程。
一個客戶對一個NFS服務器的訪問開始于安裝協(xié)議啄巧,返回給客戶一個文件句柄寻歧。客戶接著可以使用那個文件句柄來訪問服務器文件系統(tǒng)中的文件秩仆。在服務器上码泛,一次檢查文件名的一個成員,返回每個成員的一個新的文件句柄澄耍。最后的結(jié)果就是要引用的文件的一個文件句柄噪珊,它可以在隨后的讀寫操作中被使用。
NFS試圖把它的所用過程都做成等冪的逾苫,使得如果響應報文丟失了卿城,客戶只需要重發(fā)一個請求。我們看到了服務器崩潰然后又重啟動時铅搓,一個客戶讀服務器上的一個文件的例子瑟押。