進程打開一個文件,會與三個表發生關聯,分別是:文件描述符表、文件表、索引結點表。
當同一個進程對同一個文件多次使用open時;對一個文件描述符調用dup函數;父進程使用fork創建一個子進程,子進程和上面三個表的關系;當子進程調用exec函數,子進程和上三個表的關系又發生了什么變化;不同的進程打開同一個文件,那么這些進程又是以怎么樣的形式相關聯。本文將解釋這些問題。
文件描述符表、文件表、索引結點表存放地點
每個進程都有一個屬於自己的文件描述符表。
文件表存放在內核空間,由系統里的所有進程共享。
索引結點表也存放在內核空間,由所有進程所共享。
了解這個些表的存放位置很重要。
三個表的作用
文件描述符表:該表記錄進程打開的文件。它的表項里面有一個指針,指向存放在內核空間的文件表中的一個表項。它向用戶提供一個簡單的文件描述符,使得用戶可以通過方便地訪問一個文件。
當進程使用open打開一個文件時,內核就會在這個表中添加一個表項。如果對同一個文件打開多次,那么將有多個表項。使用dup時,也會增加一個表項。
文件表:文件表保存了進程對文件讀寫的偏移量。該表還保存了進程對文件的存取權限。比如,進程以O_RDONLY方式打開文件,這將記錄到對應的文件表表項中。
索引結點表:在文件系統中,也是有一個索引結點表的。如下圖所示:
這兩個索引結點表有千絲萬縷的關系。因為內存中的索引結點表的每一個表項都是從文件系統中讀入的,並且兩個索引結點表有一對一的關系。所以,內存中的索引結點表的每一個表項都對應一個具體的文件。
上面所說的三個表的功能,使得三個表緊密地聯系在一起,文件描述符表項有一個指針指向文件表表項,文件表表項有一個指針指向索引結點表表項。
不同的進程打開同一個文件
不同的進程打開同一個文件,那么他們應該有各自對應的文件表表項。因為文件表表項記錄了進程讀寫文件時的偏移量和存取權限。多個進程不可能共享一個文件偏移量。另外他們各自打開文件的權限也可能是不同的,有的是為了讀、有的為了寫,有的為了讀寫。所以,他們應該有不同的文件表表項。
此外,因為是同一個文件,所以,多個進程會共享同一個索引結點表項。即他們的文件表表項指針會指向同一個索引結點
最終,如下圖所示:
使用dup函數復制一個文件描述符
dup函數是用來復制一個文件描述符的。點擊這個鏈接可以看到,復制得到的文件描述符和原描述符共享文件偏移量和一些狀態。所以dup的作用僅僅是復制一個文件描述符表項,而不會復制一個文件表表項。
於是使用dup函數后,有下圖:
dup函數是一個很重要的函數。平時我們在shell里面通過 > 來進行重定向,就是通過dup函數來實現的。
同一個進程多次打開同一個文件
每打開一次同一個文件,內核就會在文件表中增加一個表項。這是因為每次open文件時使用了不同的讀寫權限,而讀寫權限是保存在文件表表項里面的。
所以,效果圖如下所示:
父進程使用fork創建子進程
由於fork一個子進程,子進程將復制父進程的絕大部分東西(除了進程ID、進程的父進程ID、一些時間屬性、文件鎖)。所以子進程復制了父進程的整個文件描述符表。
結果如下圖所示:
進程調用exec后,文件描述符的保留情況
我們經常會在shell中,輸入 < 進行標准輸入重定向。比如$wc < test.c
其大致的實現如下:
if(fork()==0)//child process { close(0); //關閉鍵盤這個標准輸入 open(inputFile, O_RDONLY); //返回的文件描述符是最小的未使用的整數,此次就是0,實現了重定向 exec(); //執行exec } |
從上面的例子可以看到執行exec后,文件描述符是會保留的。但有時,可能一個進程有很多個文件描述符,執行exec后,都用不着了。那么此時,應該關閉它。這涉及到一個close-on-exec概念,就是在執行exec時,close(關閉)文件描述符。在默認情況下,執行exec是不關閉的。這里有一個系統調用fcntl可以關閉之。
它的原型為:
int fcntl(int fd,int cmd, … /* int arg */);
第一個參數是文件描述符,第二個參數用來指定是要進行的操作。第三個參數依賴於第二個參數
與本文相關的是操作是 F_GETFD和F_SETFD。其分別用來獲取close-on-exec,設置close-on-exec標識的值。
可以通過fcntl(fd, F_SETFD, 1);來關閉文件描述符。
即參數arg為0時,不關閉;為1時關閉。
參考:《UNIX環境高級編程》、《UNIX操作系統設計》