首頁 >運維 >linux運維 >伺服器程式設計中對於檔案的操作詳解

伺服器程式設計中對於檔案的操作詳解

PHP中文网
PHP中文网原創
2017-06-20 11:57:021482瀏覽

  linux系統下一切皆文件,透過虛擬檔案系統(VFS)的機制將所有底層屏蔽掉,使用者可以透過統一的介面來實現對不同驅動程式的操作,對於每一個檔案需要一個引用來指示,此時檔案描述子應用程式而生,檔案描述子類似於widows下的handle,對於檔案的大部分操作都是透過這個描述符來操作的,例如read,write。對於每一個檔案描述符,核心使用三種資料結構來管理。

(1)  每個行程在行程表中都有一個記錄項,每個記錄項中都有一個開啟檔案描述符表,可將其視為一個向量,每個描述符佔用一項。與每個檔案描述子相關聯的是:

  (a)  檔案描述符標誌。 (目前只定義了一個檔案描述符標誌FD_CLOEXEC)

  (b)  指向一個檔案表項的指標。

(2)  核心為所有開啟檔案維持一張檔案表。每個文件表項包含:

  (a)  檔案狀態標誌(讀取、寫入、增寫、同步、非阻塞等 )。

  (b)  目前檔案位移量。 (即為lseek函數所操作的值)

  (c)  指向該檔案v節點表項的指標。

(3)  每個開啟檔案(或裝置)都有一個 v 節點結構。 v節點包含了檔案類型和對此檔案進#行各種操作的函數的指標資訊。對於大多數文件, v 節點也包含了該文件的 i 節點(索引節點)。這些資訊是在開啟檔案時從碟上讀入記憶體的,所以所有關於檔案的資訊都是快速可供使用的。例如, i 節點包含了檔案的擁有者、檔案長度、檔案所在的裝置、指向檔案在磁碟上所使用的實際資料區塊的指標等等點。

 

  經過上述檔案系統的三層封裝,每層負責不同的職責,從上到下第一層用於識別文件,第二層用於管理進程獨立數據,第三層管理文件系統元數據,直接關聯一個文件。這種分層思想的一個優點就是上層可以重複使用下層的結構。可能有多個檔案描述符項指向同一個檔案表項,也可以有多個檔案表項指向同一個V節點。

  如果兩個獨立的進程打開了同一個文件,打開此文件的每個進程都得到一個文件表項,但是兩個文件表項的V節點指針指向相同的V節點,這樣的安排使得每個進程都有他自己的對該檔案的當前位移量,並且支援不同的開啟方式(O_RDONLY, O_WRONLY, ORDWR)。

  當一個進程透過fork建立出子進程後,此時父,子進程內的檔案描述符共享同一個檔案表項,也就是說父子進程的檔案描述符的指向相同。一般我們會在fork後關閉掉各自不需要的fd,例如父子進程透過pipe或socketpair進行通信,往往會close掉自己不需要讀(或寫)的一端。只有在沒有檔案描述子引用目前文件表項的時候,close操作才真正銷毀目前文件表項資料結構,有點類似引用計數的想法。這也是網路程式設計中close和shutdown函數的區別,前者只有在最後一個使用該socket的句柄的進程關閉的時候才真正斷開連接,而後者毫不商量直接斷開一側連接。但在多執行緒的環境中,由於父子執行緒共享位址空間,此時檔案描述子共同擁有,只有一份,所以也就不能在執行緒內close掉自己不需要的fd,否則會導致其它需要該fd的線程也受影響。因為父,子進程內打開的檔案描述符共享同一個檔案表項,所以在某些系統的伺服器程式設計中,如果採用preforking模型(伺服器預先派生多個子進程,在每個子進程監聽listenfd來accept連接)就會導致驚群現象的發生,伺服器派生的多個子進程各自調用accept並因而均被投入睡眠,當第一個客戶連接到達時,儘管只有一個進程獲得連接,但是所有進程都被喚醒,這樣導致性能受損。參見UNP P657。

  同時如果fork之後呼叫exec,所有的檔案描述子繼續保持開啟狀態。這可以用來給exec後的程式傳遞某些檔案描述符。同時檔案描述符標誌FD_CLOEXEC 就是用來關閉exec時繼續保持開放的檔案描述符的選項。

  也可以透過dup或fcntl明確複製一個檔案描述符,他們指向相同的檔案表項。透過dup2將檔案描述符複製到製定數值。

  每個進程都有一個檔案描述子表,進程間獨立,兩個進程之間的檔案描述子並無直接關係,所以在進程內可以直接傳遞檔案描述符,但是如果跨越進程傳遞就失去了意義,unix可以透過sendmsg/recvmsg進行專門的檔案描述符的傳遞(參見書UNP 15.7節)。每個行程的前三個檔案描述子分別對應標準輸入,標準輸出,標準錯誤。但是一個進程可開啟的檔案描述符數量是有限制的,如果開啟的檔案描述符太多會出現」Too many open files」的問題。在網頁伺服器中,透過listenfd呼叫呼叫accept時,體現為產生EMFILE錯誤,這主要是因為檔案描述子是系統的重要資源,系統資源是有盡的,系統對單一進程檔案描述子限制預設值一般是1024,使用ulimit -n指令可以查看。當然也可以調高進程檔案描述符數目,但這是治標不治本的方法,因為處理高並發服務時,伺服器資源有限,難免資源枯竭。

  當結合epoll的水平觸發方式來監聽lisenfd的連接時,大量socket連接湧來如果不處理會塞滿TCP的連接隊列,listenfd會一直產生可讀事件,將伺服器陷入忙碌等待,用C++開源網路庫muduo作者陳碩的做法是事先準備一個空閒的文件描述符,當產生EMFILE錯誤時就先關閉這個空閒文件,獲得一個文件描述符名額,再accept拿到一個socket連接的文件描述符,隨後立刻close,這樣就優雅的斷開了與客戶端的連接,最後重新打開空閒文件,把”坑”填上,以備再次出現這種情況時使用。

 1 //在程序开头先”占用”一个文件描述符 2  3 int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); 4 ………… 5  6 //然后当出现EMFILE错误的时候处理这个错误 7  8 peerlen = sizeof(peeraddr); 9 connfd = accept4(listenfd,  (struct sockaddr*)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);10 11 if (connfd == -1)12 {13     if (errno == EMFILE)14     {15         close(idlefd);16         idlefd = accept(listenfd, NULL, NULL);17         close(idlefd);18         idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);19         continue;20     }21     else22         ERR_EXIT("accept4");23 }
#

以上是伺服器程式設計中對於檔案的操作詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn