動態

詳情 返回 返回

Linux文件描述符 - 動態 詳情

Linux文件描述符

Linux 系統中,把一切都看做是文件,當進程打開現有文件或創建新文件時,內核向進程返回一個文件描述符(file descriptor,fd)[1, 4],在windows下面,這玩意兒叫file handle,句柄。

文件描述符(file descriptor)就是內核為了高效管理這些已經被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用於指代被打開的文件,所有執行I/O操作的系統調用都通過文件描述符來實現。同時還規定系統剛剛啓動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。這意味着如果此時去打開一個新的文件,它的文件描述符會是3,再打開一個文件文件描述符就是4……[2]。

可以簡單理解成系統維護的文件描述符表是一個數組,下標就是索引(文件描述符),數組內容就是一個個指向文件的指針(如0 -> stdin,1 -> stdout,2-> stderr)。

掌握它,有助於深入理解 Linux 文件系統、I/O 操作,
以及進程間通信(如管道(pipe)套接字(Socket))的實現,可以去Ubuntu系統中簡單完成下面的例子。<( ̄︶ ̄)↗[GO!]

優化界面的Blog:Linux文件描述符

用户程序與內核交互的基本過程

1. 打開文件

當一個用户程序需要訪問某個文件時,它會通過系統調用(如 open())請求內核打開該文件。
內核會根據文件路徑在文件系統中查找文件,併為該文件分配一個文件描述符。這個文件描述符是一個整數,表示該文件在內核中的唯一標識符。
內核維護着一個叫做 文件表(file table)的數據結構,文件描述符實際上就是對這個表中的一個條目的引用。

2. 使用文件描述符讀寫文件

用户程序使用文件描述符來進行後續的文件操作。例如:
讀取文件:用户程序調用 read(fd, ...) 系統調用,內核通過文件描述符 fd 查找對應的文件,並從磁盤中讀取數據,將數據返回給用户程序。
寫入文件:用户程序調用 write(fd, ...) 系統調用,內核通過文件描述符 fd 查找文件,向文件中寫入數據。
文件描述符使得內核能夠識別哪個文件需要被操作,從而實現文件與程序的交互。

3. 文件描述符與文件表

內核通過文件描述符和文件表來管理已打開的文件。每個進程都有一個 文件描述符表,它是一個數組,其中每個索引對應一個文件描述符。這個文件描述符指向內核的文件表項,每個文件表項包含文件的狀態信息(例如當前文件指針、文件權限等)。

4. 文件操作的內核層處理

用户程序和內核之間的交互通常通過系統調用來實現,文件描述符是這些系統調用的接口。內核會根據文件描述符執行相應的操作:
打開文件時,內核會創建或查找該文件的內核對象,並更新文件描述符。
對文件進行讀寫操作時,內核通過文件描述符在文件表中查找文件對象,然後執行 I/O 操作(例如讀取磁盤或寫入磁盤)。

5. 關閉文件

當用户程序完成文件操作後,它會通過系統調用 close(fd) 來關閉文件描述符。
內核會釋放文件描述符所佔用的資源,關閉文件的文件表項,並更新進程的文件描述符表。

通過文件描述符交互的具體例子

假設一個程序需要從文件中讀取數據並進行處理,下面的示例代碼:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[128];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead == -1) {
        perror("read");
        close(fd);
        return 1;
    }

    write(STDOUT_FILENO, buffer, bytesRead);

    close(fd);
    return 0;
}

在這個例子中,文件描述符的作用可以從以下幾個步驟看到:

  • 打開文件時,open() 系統調用將返回一個文件描述符(fd),這個文件描述符在內核中表示 example.txt 文件的句柄。
  • 讀取文件時,read() 系統調用使用文件描述符 fd 來訪問內核中的文件表項,執行 I/O 操作並將文件數據讀取到 buffer 中。
  • 寫入數據時,通過 write(STDOUT_FILENO, ...) 輸出數據,STDOUT_FILENO 是標準輸出的文件描述符(通常是 1)。
  • 關閉文件時,close(fd) 系統調用會釋放文件描述符所佔用的資源,告知內核文件已經關閉。

通過文件描述符,程序可以與操作系統內核進行有效的通信,完成文件系統和其他 I/O 操作。

Linux上查看文件描述符列表

在 Linux 上使用 vim 打開文件時,操作系統通過文件描述符與文件系統進行交互。下面是一個具體例子。

打開文件(使用 Vim)

首先,你使用 vim 打開一個文件(例如 helloworld.cpp):

vim helloworld.cpp

這時,Vim 會啓動並打開 helloworld.cpp 文件。在內核中,Vim 會使用文件描述符來與文件系統交互,即讀取和編輯 helloworld.cpp 文件的內容。


在新 Shell 中查找 Vim 進程的 PID

接着,你可以打開另一個終端(Shell),通過 pidof 命令獲取正在運行的 Vim 進程的進程 ID(PID):

pidof vim

假設返回的 PID 是 40133,説明 Vim 進程的進程號是 40133

查看 Vim 進程的文件描述符

Linux 系統中的每個進程都有一個對應的 /proc/[pid]/fd 目錄,裏面列出了該進程打開的所有文件的文件描述符。你可以通過以下命令查看 Vim 進程所使用的文件描述符列表:

ll /proc/40133/fd

這裏,40133 是你之前通過 pidof vim 命令獲得的 Vim 進程的 PID。

ll 命令會列出該目錄下的文件,其中每個文件都對應着一個打開的文件描述符(文件句柄)。輸出會類似於:

total 0
dr-x------ 2 allen allen  0 Dec 29 15:58 ./
dr-xr-xr-x 9 allen allen  0 Dec 29 15:58 ../
lrwx------ 1 allen allen 64 Dec 29 15:58 0 -> /dev/pts/8
lrwx------ 1 allen allen 64 Dec 29 15:58 1 -> /dev/pts/8
l-wx------ 1 allen allen 64 Dec 29 15:58 19 -> /home/allen/.vscode-server/data/logs/20241229T150828/ptyhost.log
lrwx------ 1 allen allen 64 Dec 29 15:58 2 -> /dev/pts/8
l-wx------ 1 allen allen 64 Dec 29 15:58 20 -> /home/allen/.vscode-server/data/logs/20241229T150828/remoteagent.log
lrwx------ 1 allen allen 64 Dec 29 15:58 21 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 22 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 23 -> /dev/ptmx
lrwx------ 1 allen allen 64 Dec 29 15:58 4 -> /home/allen/CPP/.helloworld.cpp.swp

這裏的輸出解釋如下:

  • 0,1,2:這是標準輸入、標準輸出和標準錯誤,它們通常會指向終端設備(如 /dev/pts/8)。這些文件描述符是系統默認打開的,用於處理進程的 I/O 操作。
  • 4:這個文件描述符指向你用 Vim 打開的文件 helloworld.cpp。可以看到,/home/allen/CPP/.helloworld.cpp.swp 是文件描述符 4 對應的目標文件。

説明:

  • 在 Linux 中,每個進程都會為打開的文件、管道、設備、套接字等分配一個文件描述符,文件描述符的值通常是從 0 開始遞增的。
  • 標準輸入(0)、標準輸出(1)、標準錯誤(2)是系統自動打開的,而打開的文件 helloworld.cpp 在 Vim 進程中會被分配到文件描述符 3 及以後。

可以看到新打開的 helloworld.cpp 的文件描述符,竟然是4,而不是從3開始,這裏面有一番學問,涉及 vim 的原理。因為vim這種編輯器的原理是先打開源文件並拷貝,然後關閉源文件再打開自己的副本,修改完文件保存的時候直接將副本重命名覆蓋源文件。所以打開源文件的時候用的文件描述符3,然後打開自己的副本是時候就該用文件描述符4了,然後關閉源文件,文件描述符3就被釋放了,我們查看的時候就只剩下了4,這裏它指向的是vim創建的副本文件[3](這裏有更詳細的解釋,這裏是一個通俗的理解)。

檢查文件描述符的具體信息

可以通過查看符號鏈接來了解更多細節,例如,可以用 readlink 命令查看文件描述符指向的文件路徑:

readlink /proc/40133/fd/4

深入理解 Linux 中的文件描述符及其背後的數據結構

要深入理解 Linux 中的文件描述符及其背後的數據結構,我們需要了解內核如何通過三個核心數據結構來管理文件描述符:

  1. 進程級的文件描述符表(Process File Descriptor Table)
  2. 系統級的打開文件描述符表(System-wide Open File Table)
  3. 文件系統的 i-node 表(File System i-node Table)

這三個數據結構[4]共同工作,使得 Linux 系統能夠高效地管理文件 I/O 操作,並確保每個進程對文件的訪問是獨立且有序的。


1. 進程級的文件描述符表

每個運行中的進程都有一個 進程控制塊(PCB),它包含了與進程相關的各種信息。在這個 PCB 中,文件描述符表(也稱為 文件描述符數組)是一個非常重要的數據結構。

  • 文件描述符表的功能:每個進程的文件描述符表記錄着該進程所打開的文件描述符。文件描述符是一個整數,它對應着進程所打開的文件、套接字、管道等資源。
  • 表的結構:文件描述符表是一個數組,每個文件描述符對應數組中的一個位置。例如,標準輸入、標準輸出、標準錯誤默認分別對應文件描述符 0、1、2,而其他文件則由內核為每個進程分配一個較大的文件描述符,如 3、4、5 等。
  • 進程獨立性:進程級文件描述符表是進程私有的。不同進程之間是獨立的,進程 A 使用文件描述符 3 打開的文件,進程 B 如果也打開了一個文件,可能也會被分配文件描述符 3。它們的文件描述符對應的是不同的文件資源。

進程級文件描述符表的關鍵點

  • 每個進程都維護一個自己的文件描述符表。
  • 文件描述符表的每個條目對應一個打開的文件或資源。
  • 文件描述符表存儲的只是文件描述符與內核內部文件對象的引用。

2. 系統級的打開文件描述符表

系統級的 打開文件描述符表 是內核維護的一個全局數據結構,用來管理系統中所有進程共享的文件資源。每當一個進程打開文件時,內核會在此表中創建一項記錄,表示這個文件被打開。

該表中的每項記錄包含以下信息

  • 當前文件偏移量:每個文件都有一個當前的讀取或寫入位置。當進程執行 read()write() 操作時,內核會根據該文件的偏移量進行相應的讀寫操作。在每次讀取時,偏移量會自動更新,也可以通過 lseek() 系統調用顯式地修改偏移量。
  • 打開文件時的標識:由 open() 系統調用的 flags 參數指定,如只讀、只寫、讀寫等。內核在打開文件時會記錄這些標識,用於後續的訪問控制。
  • 文件訪問模式:當進程通過 open() 打開文件時,內核會記錄文件的訪問模式(如只讀模式 O_RDONLY,只寫模式 O_WRONLY,讀寫模式 O_RDWR)以及其他訪問權限(如 O_APPENDO_NONBLOCK 等)。
  • 與信號驅動相關的設置:某些文件(如終端設備)可以通過信號驅動模式進行 I/O 操作。這些設置會記錄在系統級的打開文件表中,以便內核在合適的時機處理信號。
  • 與文件的 i-node 關聯:系統級的打開文件描述符表項會保存指向文件系統 i-node 表項的指針。i-node 表項包含了該文件的元數據(如文件大小、權限、時間戳等)。

關鍵點

  • 每個進程打開文件時,系統級打開文件表會創建相應的記錄。
  • 所有進程共享系統級的打開文件描述符表,通過這個表來管理文件的偏移量和訪問模式等信息。

3. 文件系統的 i-node 表

i-node索引節點(Index Node)的縮寫,是文件系統用來存儲文件元數據的一種數據結構。每個文件都有一個對應的 i-node,i-node 不存儲文件的內容,而是存儲與文件相關的各種屬性和元數據。

i-node 表包含以下內容

  • 文件類型:例如普通文件、目錄文件、符號鏈接、套接字、FIFO 等。
  • 文件權限:表示文件的訪問權限(如讀、寫、執行權限)。
  • 文件大小:文件的實際大小(字節數)。
  • 時間戳:包括文件的創建時間、修改時間和訪問時間(如 ctimemtimeatime 等)。
  • 指向文件數據塊的指針:i-node 會存儲指向文件實際數據塊的指針(對於小文件直接存儲指針,對於大文件使用間接塊)。這些指針幫助操作系統在磁盤上定位文件內容。
  • 文件鎖列表:如果文件被加鎖,i-node 會存儲一個指向鎖信息的指針。
  • 引用計數:記錄有多少個進程或文件描述符正在使用該文件。如果引用計數為 0,則表示該文件可以被刪除。

i-node 的關鍵點

  • i-node 存儲文件的元數據,而不存儲文件的實際內容。
  • 每個文件在文件系統中都有唯一的 i-node。
  • 文件的內容由磁盤上的數據塊來存儲,而 i-node 中存儲的是指向這些數據塊的指針。

文件描述符如何協同工作

文件描述符表、系統級的打開文件描述符表和 i-node 表相互協作來管理文件資源:

  1. 進程級文件描述符表 存儲進程所打開的文件描述符,它是進程私有的。當進程通過 open() 打開一個文件時,內核會在進程的文件描述符表中分配一個文件描述符,並且該文件描述符指向 系統級的打開文件描述符表 中的一個記錄。
  2. 系統級的打開文件描述符表 存儲所有打開文件的狀態信息,如文件偏移量、訪問模式等,並且每個表項都會指向對應文件的 i-node
  3. i-node 表 存儲文件的元數據(如權限、大小等)以及文件內容所在的磁盤位置。每個打開的文件都會通過 i-node 來訪問文件的實際數據。

當進程進行文件操作時(如 read()write()),操作首先通過進程級的文件描述符表查找對應的文件描述符,然後在系統級的打開文件描述符表中查找該文件的狀態信息,並通過 i-node 訪問文件的實際數據。


總結

  • 進程級的文件描述符表:每個進程獨立維護,記錄當前進程打開的文件描述符。
  • 系統級的打開文件描述符表:所有進程共享,記錄文件的狀態信息和 i-node 引用。
  • 文件系統的 i-node 表:記錄文件的元數據和實際數據的位置信息。

這三個數據結構協作,使得 Linux 系統能夠高效且靈活地管理文件 I/O 操作,確保進程之間的文件訪問獨立且有序,並且能夠在多進程環境中正確地管理文件資源。

參考

1、[理解Linux的文件描述符FD與Inode]
2、[理解linux中的file descriptor(文件描述符)]
3、[徹底弄懂 Linux 下的文件描述符(fd)]
4、[Linux文件描述符到底是什麼?]

Add a new 評論

Some HTML is okay.