徹底搞懂文件描述符/文件句柄/文件指針的區(qū)別與聯(lián)系

Prologue

處理了一起too many open files的報錯菇夸,中途忽然感覺文件描述符琼富、文件句柄、文件指針這三個概念很容易混淆庄新,網(wǎng)上其他博客也是眾說紛紜鞠眉。于是做了一點考證,專門寫一篇來盡量準(zhǔn)確地記錄下择诈。

本文的內(nèi)容有不少來自Linux領(lǐng)域的權(quán)威書籍械蹋,Michael Kerrisk所著《The Linux Programming Interface:A Linux and UNIX System Programming Handbook》的第4、5兩章羞芍。

文件描述符 & 文件描述符表

文件描述符(file descriptor, fd)是Linux系統(tǒng)中對已打開文件的一個抽象標(biāo)記哗戈,所有I/O系統(tǒng)調(diào)用對已打開文件的操作都要用到它。這里的“文件”仍然是廣義的荷科,即除了普通文件和目錄外唯咬,還包括管道、FIFO(命名管道)畏浆、Socket胆胰、終端、設(shè)備等全度。

文件描述符是一個較小的非負整數(shù)煮剧,并且0、1、2三個描述符總是默認(rèn)分配給標(biāo)準(zhǔn)輸入勉盅、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤佑颇。這就是常用的nohup ./my_script > my_script.log 2>&1 &命令里2和1的由來。

Linux系統(tǒng)中的每個進程會在其進程控制塊(PCB)內(nèi)維護屬于自己的文件描述符表(file descriptor table)草娜。表中每個條目包含兩個域:一是控制該描述符的標(biāo)記域(flags)挑胸,二是指向系統(tǒng)級別的打開文件表中對應(yīng)條目的指針。那么打開文件表又是什么呢宰闰?

打開文件表 & 文件句柄

內(nèi)核會維護系統(tǒng)內(nèi)所有打開的文件及其相關(guān)的元信息茬贵,該結(jié)構(gòu)稱為打開文件表(open file table)。表中每個條目包含以下域:

  • 文件的偏移量移袍。POSIX API中的read()/write()/lseek()函數(shù)都會修改該值解藻;
  • 打開文件時的狀態(tài)和權(quán)限標(biāo)記。通過open()函數(shù)的參數(shù)傳入葡盗;
  • 文件的訪問模式(只讀螟左、只寫、讀+寫等)觅够。通過open()函數(shù)的參數(shù)傳入胶背;
  • 指向其對應(yīng)的inode對象的指針。內(nèi)核也會維護系統(tǒng)級別的inode表喘先,關(guān)于inode的細節(jié)請參考這篇文章钳吟。

文件描述符表、打開文件表窘拯、inode表之間的關(guān)系可以用書中的下圖來表示红且。注意圖中的fd 0、1树枫、2...只是示意下標(biāo)直焙,不代表三個標(biāo)準(zhǔn)描述符。

可見砂轻,一個打開的文件可以對應(yīng)多個文件描述符(不管是同進程還是不同進程)奔誓,一個inode也可以對應(yīng)多個打開的文件。打開文件表中的一行稱為一條文件描述(file description)搔涝,也經(jīng)常稱為文件句柄(file handle)厨喂。

多嘴一句,“句柄”這個詞在UNIX世界中并不很正式庄呈,但在Windows里遍地都是蜕煌。Windows NT內(nèi)核會將內(nèi)存中的所有對象(文件、窗口诬留、菜單斜纪、圖標(biāo)等一切東西)的地址列表維護成整數(shù)索引贫母,這個整數(shù)就叫做句柄,邏輯上講類似于“指針的指針”盒刚,感覺上還是有一些相通的地方的腺劣。

文件I/O API & 文件指針

說了這么多,用最基礎(chǔ)的POSIX庫函數(shù)寫個示例程序吧因块。它將一個文件中的內(nèi)容讀出來橘原,并原封不動地寫入另外一個文件。

#include <fcntl.h>
#include <sys/stat.h>
#define BUF_SIZE 1024

int main(int argc,char *argv[]) {
  int inputFd, outputFd;
  char buf[BUF_SIZE];
  ssize_t numRead;

  inputFd = open("data.txt", O_RDONLY);
  if (inputFd == -1) {
    exit(EXIT_FAILURE);
  }
  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) {
    exit(EXIT_FAILURE);
  }

  while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0) {
    if (write(outputFd, buf, numRead) != numRead) {
      exit(EXIT_FAILURE);
    }
  }
  
  close(inputFd);
  close(outputFd);
  exit(EXIT_SUCCESS);
}

嚴(yán)格來講涡上,POSIX提供的這些函數(shù)只是用戶與內(nèi)核之前的橋梁趾断,實際仍位于系統(tǒng)調(diào)用層之上。但是現(xiàn)實應(yīng)用中吩愧,我們一般也把它們叫做系統(tǒng)調(diào)用了(盡管不太正確)芋酌。

要使用open()/read()/write()/close()這些系統(tǒng)調(diào)用,必須引入fcntl.h頭文件耻警。open()返回的是文件描述符隔嫡,其參數(shù)中傳入的flags和mode值也會保存在打開文件表中甸怕。在整個讀甘穿、寫并最終關(guān)閉文件的過程中,操作的也都是文件描述符梢杭。

那么我們在大學(xué)C語言課程上學(xué)習(xí)的“文件指針”(file pointer)又是什么呢温兼?這個就比較簡單,繼續(xù)看下面的栗子武契。

#include <stdio.h>
#include <stdlib.h>
#define BUF_SIZE 1024

int main(int argc,char *argv[]) {
  char buf[BUF_SIZE];
  FILE *inputFp;
  size_t numRead;

  inputFp = fopen("data.txt", "r");
  if (inputFp == NULL) {
    exit(EXIT_FAILURE);
  }

  while (!feof(inputFp)) {
    numRead = fread(buf, sizeof(char), sizeof(buf), inputFp);
    printf("%ld\t%s", numRead, buf);
  }

  fclose(inputFp);
  exit(EXIT_SUCCESS);
}

可見募判,文件指針就是FILE結(jié)構(gòu)體的指針,與前兩個概念不屬于同一層咒唆。當(dāng)通過文件指針操作文件時届垫,需要調(diào)用C語言stdio.h中提供的文件API(fopen()、fread()等)全释,而C標(biāo)準(zhǔn)庫最終調(diào)用了POSIX的庫函數(shù)装处。并且“file pointer”這個詞里的“file”指的是狹義的文件,不包括管道浸船、設(shè)備等其他東西妄迁,所以單純用C API只能操作普通文件。

FILE結(jié)構(gòu)體中是包含了文件描述符的李命,所以C語言也提供了互相轉(zhuǎn)換的方法:

int inputFd;
FILE *inputFp;

inputFd = fileno(inputFp);
inputFp = fdopen(inputFd, "r");

文件描述符和文件句柄的限制

文章開頭提到了"too many open files"這條報錯信息登淘,它的實際含義是文件描述符數(shù)量超限。用ulimit -a命令打印出各限制值:

~ 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一行就表示當(dāng)前用戶封字、當(dāng)前終端黔州、單個進程能擁有的文件描述符的數(shù)量閾值(很多文章都描述錯了這一點)耍鬓,可以用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)命令各薇。這個命令的用法很豐富,本文暫時不表君躺。

既然有了進程級別的描述符數(shù)量限制峭判,也就有系統(tǒng)級別的文件句柄數(shù)量限制∽亟校可以這樣查看其閾值林螃,以及當(dāng)前已分配的句柄數(shù):

~ 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

The End

最后總結(jié)一下吧疗认。

  • 文件描述符是進程級別的,文件句柄是系統(tǒng)級別的伏钠,不能混用横漏。它們在不同級別表示已打開的文件。
  • 文件描述符與文件句柄直接關(guān)聯(lián)熟掂,文件句柄與inode直接關(guān)聯(lián)缎浇。
  • 文件描述符在POSIX系統(tǒng)調(diào)用中直接可見,文件指針是C語言在其基礎(chǔ)上的包裝赴肚。
  • 文件句柄在UNIX里不是個正式概念素跺,所以無論在系統(tǒng)還是C語言API中都不顯式存在。

明天公司年會誉券,民那晚安晚安指厌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踊跟,隨后出現(xiàn)的幾起案子踩验,更是在濱河造成了極大的恐慌,老刑警劉巖琴锭,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晰甚,死亡現(xiàn)場離奇詭異,居然都是意外死亡决帖,警方通過查閱死者的電腦和手機厕九,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來地回,“玉大人扁远,你說我怎么就攤上這事俊鱼。” “怎么了畅买?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵并闲,是天一觀的道長。 經(jīng)常有香客問我谷羞,道長帝火,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任湃缎,我火速辦了婚禮犀填,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗓违。我一直安慰自己九巡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布蹂季。 她就那樣靜靜地躺著冕广,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偿洁。 梳的紋絲不亂的頭發(fā)上撒汉,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音父能,去河邊找鬼神凑。 笑死,一個胖子當(dāng)著我的面吹牛何吝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹃唯,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爱榕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坡慌?” 一聲冷哼從身側(cè)響起黔酥,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洪橘,沒想到半個月后跪者,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡熄求,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年渣玲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弟晚。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡忘衍,死狀恐怖逾苫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枚钓,我是刑警寧澤铅搓,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站搀捷,受9級特大地震影響星掰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嫩舟,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一蹋偏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧至壤,春花似錦威始、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至镰绎,卻和暖如春脓斩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畴栖。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工随静, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吗讶。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓燎猛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親照皆。 傳聞我的和親對象是個殘疾皇子重绷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 故事背景 看在分布式架構(gòu)的通信過程中,接觸到了netty膜毁,又通過netty接觸到了句柄和文件描述符昭卓,其實之前就有過...
    陽光丶不銹閱讀 9,148評論 0 11
  • 與linux打交道,盡管可能你只是一個高級語言的碼農(nóng)瘟滨,還是或多或少的要和遇到d這種術(shù)語候醒。今天抽空看了下傳說中的fd...
    sheen口開河閱讀 3,604評論 0 7
  • 一些概念: 1.為了便于統(tǒng)一管理,系統(tǒng)將所有的輸入/輸出設(shè)備都視為文件杂瘸,按文件方式提供給用戶使用倒淫,如目錄的檢索、權(quán)...
    yydounai閱讀 1,026評論 0 4
  • 內(nèi)核(kernel)利用文件描述符(file descriptor)來訪問文件胧沫。文件描述符是非負整數(shù)昌简。打開現(xiàn)存文件...
    踩在浪花上00閱讀 745評論 0 1
  • 在linux系統(tǒng)中把設(shè)備和普通文件也都看做是文件占业,要對文件進行操作就必須先打開文件,打開文件后會得到一個文件描述符...
    踩在浪花上00閱讀 1,132評論 0 1