處理了一起too many open files的報錯,中途忽然感覺這三個概念很容易混淆,網上其他部落格也是眾說紛紜。於是做了一點考證,專門寫一篇來盡量準確地記錄下。
本文的內容有不少來自linux領域的權威書籍,michael kerrisk所著《the linux programming inte***ce:a linux and unix system programming handbook》的第4、5兩章。
檔案描述符(file descriptor, fd)是linux系統中對已開啟檔案的乙個抽象標記,所有i/o系統呼叫對已開啟檔案的操作都要用到它。這裡的「檔案」仍然是廣義的,即除了普通檔案和目錄外,還包括管道、fifo(命名管道)、socket、終端、裝置等。
檔案描述符是乙個較小的非負整數,並且0、1、2三個描述符總是預設分配給標準輸入、標準輸出和標準錯誤。這就是常用的nohup ./my_script > my_script.log 2>&1 &
命令裡2和1的由來。
linux系統中的每個程序會在其程序控制塊(pcb)內維護屬於自己的檔案描述符表(file descriptor table)。表中每個條目包含兩個域:一是控制該描述符的標記域(flags),二是指向系統級別的開啟檔案表中對應條目的指標。那麼開啟檔案表又是什麼呢?
檔案描述符表、開啟檔案表、inode表之間的關係可以用書中的下圖來表示。注意圖中的fd 0、1、2...只是示意下標,不代表三個標準描述符。
可見,乙個開啟的檔案可以對應多個檔案描述符(不管是同程序還是不同程序),乙個inode也可以對應多個開啟的檔案。開啟檔案表中的一行稱為一條檔案描述(file description),也經常稱為檔案控制代碼(file handle)。
多嘴一句,「控制代碼」這個詞在unix世界中並不很正式,但在windows裡遍地都是。windows nt核心會將記憶體中的所有物件(檔案、視窗、選單、圖示等一切東西)的位址列表維護成整數索引,這個整數就叫做控制代碼,邏輯上講類似於「指標的指標」,感覺上還是有一些相通的地方的。
說了這麼多,用最基礎的posix庫函式寫個示例程式吧。它將乙個檔案中的內容讀出來,並原封不動地寫入另外乙個檔案。
#include #include #define buf_size 1024
int main(int argc,char *ar**)
outputfd = open(
"data_copy.txt",
o_creat | o_wronly | o_trunc,
s_irusr | s_iwusr | s_irgrp | s_iwgrp | s_iroth | s_iwoth
);if (outputfd == -1)
while ((numread = read(inputfd, buf, buf_size)) > 0)
} close(inputfd);
close(outputfd);
exit(exit_success);
}
嚴格來講,posix提供的這些函式只是使用者與核心之前的橋梁,實際仍位於系統呼叫層之上。但是現實應用中,我們一般也把它們叫做系統呼叫了(儘管不太正確)。
要使用open()/read()/write()/close()這些系統呼叫,必須引入fcntl.h標頭檔案。open()返回的是檔案描述符,其引數中傳入的flags和mode值也會儲存在開啟檔案表中。在整個讀、寫並最終關閉檔案的過程中,操作的也都是檔案描述符。
那麼我們在大學c語言課程上學習的「檔案指標」(file pointer)又是什麼呢?這個就比較簡單,繼續看下面的栗子。
#include #include #define buf_size 1024
int main(int argc,char *ar**)
while (!feof(inputfp))
fclose(inputfp);
exit(exit_success);
}
可見,檔案指標就是file結構體的指標,與前兩個概念不屬於同一層。當通過檔案指標操作檔案時,需要呼叫c語言stdio.h中提供的檔案api(fopen()、fread()等),而c標準庫最終呼叫了posix的庫函式。並且「file pointer」這個詞裡的「file」指的是狹義的檔案,不包括管道、裝置等其他東西,所以單純用c api只能操作普通檔案。
file結構體中是包含了檔案描述符的,所以c語言也提供了互相轉換的方法:
int inputfd;
file *inputfp;
inputfd = fileno(inputfp);
inputfp = fdopen(inputfd, "r");
~ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 127961
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
posix message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 127961
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
其中open files一行就表示當前使用者、當前終端、單個程序能擁有的檔案描述符的數量閾值(很多文章都描述錯了這一點),可以用ulimit -n [閾值]
命令來臨時修改,退出登入即失效。如果想要永久修改,可以將ulimit -n [閾值]
寫入使用者的.bash_profile檔案或/etc/profile中,也可以修改/etc/security/limits.conf:
~ vim /etc/security/limits.conf
# 使用者名稱 軟/硬限制 限制項 閾值
root soft nofile 65535
root hard nofile 65535
那麼如何列出各個程序的檔案描述符呢?可以利用lsof
(list open files)命令。這個命令的用法很豐富,本文暫時不表。
既然有了程序級別的描述符數量限制,也就有系統級別的檔案控制代碼數量限制。可以這樣檢視其閾值,以及當前已分配的控制代碼數:
~ cat /proc/sys/fs/file-max
3247469 # 閾值
~ cat /proc/sys/fs/file-nr
# 已分配且使用中 / 已分配但未使用 / 閾值
2976 0 3247469
如果需要臨時修改,可以直接向file-max寫入新值。永久生效的方法是修改/etc/sysctl.conf:
~ vim /etc/sysctl.conf
fs.file-max = 5242880
# 立即生效
~ sysctl -p
最後總結一下吧。
明天公司年會,民那晚安晚安。
檔案控制代碼 檔案描述符
檔案控制代碼和檔案描述符 在我們跨平台開發的時候,經常會碰到這倆個概念 檔案描述符 本質上是乙個索引號 非負整數 系統使用者層可以根據它找到系統核心層的檔案資料。這是乙個posix標準下的概念,常見於linux系統。但windows也有檔案描述符這個概念,但不常用。檔案控制代碼 windows下的概...
檔案控制代碼 檔案描述符
由於程序級檔案描述符表的存在,不同的程序中會出現相同的檔案描述符,它們可能指向同乙個檔案,也可能指向不同的檔案。兩個不同的檔案描述符,若指向同乙個開啟檔案控制代碼 file 將共享同一檔案偏移量。因此,如果通過其中乙個檔案描述符來修改檔案偏移量,那麼從另乙個檔案描述符中也會觀察到變化,無論這兩個檔案...
Linux 檔案控制代碼 檔案描述符
這裡我們先區分好兩個概念 檔案描述符和檔案控制代碼 簡單來說,每個程序都有乙個開啟的檔案表 fdtable 表中的每一項是struct file型別,包含了開啟檔案的一些屬性比如偏移量,讀寫訪問模式等,這是真正意義上的檔案控制代碼。檔案描述符是乙個整數。代表fdtable中的索引位置 下標 指向具體...