文章首發自 blog.cc1324.cchtml
文件描述符在unix系統中幾乎無處不在linux
從形式上來看文件描述就是一個整數,那麼咱們可不能夠更進一步去了解一下呢?shell
本文打算經過一步一步實驗去了解文件描述符究竟是什麼, 並在最後經過Linux內核相關的源碼進行驗證。數組
咱們能夠經過 open
系統調用獲得一個指定文件的文件描述符。bash
open
函數須要傳入一個文件路徑和操做模式, 調用會返回一個整型的文件描述符, 具體方法簽名以下網絡
/** * path 表明文件路徑 * oflag 表明文件的打開模式,好比讀,寫等 */
int open(char *path, int oflag, ...) 複製代碼
咱們寫一段簡單的代碼來驗證一下函數
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
// 以只讀模式打開 demo.txt 文件
int fd = open("demo.txt", O_RDONLY);
if (fd == -1) {
perror("open demo.txt error\n");
return EXIT_FAILURE;
}
// 打印獲取到的文件描述符
printf("demo.txt fd = %d \n", fd);
return EXIT_SUCCESS;
}
複製代碼
而後使用 GCC 編譯,執行編譯後的程序,咱們就能夠獲得 demo.txt
的文件描述符了。ui
不出意外你將獲得如下的執行結果:atom
$ echo hello>>demo.txt
$ gcc test.c -o -test
$ ./test
$ demo.txt fd = 3
複製代碼
和方法簽名一致,文件描述符是一個整型數。spa
你能夠嘗試屢次執行該程序, 你會發現打印的文件描述符始終都是 3。
難道每一個文件的文件描述符都是固定的?
爲了驗證前面的猜測,我在程序裏面連續調用兩次 open
函數,並打印兩次返回的文件描述符, 代碼以下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
int fd_a = open("demo.txt", O_RDONLY);
int fd_b = open("demo.txt", O_RDONLY);
printf("fd_a = %d, fd_b = %d \n", fd_a, fd_b);
return EXIT_SUCCESS;
}
複製代碼
下面是最終的執行結果:
$ gcc test.c -o test
$ ./test
$ fd_a = 3, fd_b = 4
複製代碼
儘管是同一個文件, 獲得的文件描述符卻並不同,說明每一個文件的描述符並非固定的。
但是文件描述符每次都是從 3 開始的,這是爲何呢 ?
熟悉UNIX系統的同窗應該知道,系統建立的每一個進程默認會打開3個文件:
爲何是 3 ? 由於 0、一、2 被佔用了啊......
等等!文件描述符難道是遞增的?我也不知道啊, 要不寫個程序試試。
這裏應該還有一個疑問:爲何前一節屢次執行程序都是返回 3 ,而在代碼裏調用兩次
open
打開一樣的文件倒是 3 和 4 ?這個問題在後面多進程時會再提到。
爲了驗證文件描述符是遞增的, 我設計了這樣一個程序
若是文件描述符的規則是遞增的,第 3 步返回的結果就應該是 5 。
Show me the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
// 第一次打開
int a = open("demo.txt", O_RDONLY);
// 第二次打開
int b = open("demo.txt", O_RDONLY);
printf("a = %d, b = %d \n", a, b);
// 關閉a文件描述符
close(a);
// 第三次打開
int c = open("demo.txt", O_RDONLY);
printf("b = %d, c = %d \n", b, c);
return EXIT_SUCCESS;
}
複製代碼
編譯執行
$ gcc test.c -o test
$ ./test
$ a = 3, b = 4
b = 4, c = 3
複製代碼
第三次打開的結果是 3 ,這說明文件描述符不是遞增的。
並且從結果上來看,文件描述符被回收掉後是能夠再次分配的。
前面討論的上下文都是在單進程下,若是是多個進程同時打開同一個文件,文件描述符會同樣嗎?
fork
函數能夠建立多個進程, 該函數返回一個 int 值, 當返回值爲 0 時表明當前是子進程正在執行,非 0 就爲父進程在執行。(爲了簡化代碼,就不考慮進程建立失敗的狀況了)
程序很簡單,就是父子進程各自打開同一個文件, 並打印該文件的文件描述符:
PS: 下面的代碼並不規範,可能會產生殭屍進程和孤兒進程,但這並非本文的重點......
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
int npid = fork();
if (npid == 0 ){
// 子進程
int child_fd = open("demo.txt", O_RDONLY);
pid_t child_pid = getpid();
printf("child_pid = %d, child_fd = %d \n", child_pid, child_fd);
} else {
// 父進程
int parent_fd = open("demo.txt", O_RDONLY);
pid_t parent_pid = getpid();
printf("parent_pid = %d, parent_fd = %d \n", parent_pid, parent_fd);
}
return EXIT_SUCCESS;
}
複製代碼
編譯執行
$ gcc test_process.c -o test_process
$ ./test_process
$ child_pid = 28212, child_fd = 3
parent_pid = 28210, child_fd = 3
複製代碼
每一個進程打開的都是同一個文件,並且返回的文件描述符也是同樣的。
前面咱們已經得知每一個文件的描述符並非固定的,這樣看來,每一個進程都單獨維護了一個文件描述符的集合啊。
還記得最開始實驗時,咱們對編譯好的程序屢次執行都是打印的 3,可是在代碼裏對同一個文件 open
兩次倒是返回的 3 和 4 嗎?
這是由於在 shell 每次執行程序,其實都是建立了一個新的進程在執行。
而在代碼裏連續調用兩次,始終是在一個進程下執行的。
經過上面的實驗,咱們能夠得出文件描述的一些規律
talk is cheap , show me the code
By: Linus Benedict Torvalds
下面就須要在 Linux內核 的源碼中去尋找真相了。
既然實驗代表每一個進程單獨維護了文件描述符集合, 那就從和進程相關的結構體 task_struct
入手,該結構體放在 /include/linux/sched.h
頭文件中。
我將這個結構體的代碼精簡了一下, 只保留了一些分析須要關注的屬性
struct task_struct {
...
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
...
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
pid_t pid;
pid_t tgid;
...
};
複製代碼
注意 struct files_struct *files
,註釋說該屬性表明着打開的文件信息,那這就沒得跑了。
繼續看 files_struct
結構體,該結構體定義在 /include/linux/fdtable.h
頭文件中:
struct files_struct {
// 打開的文件數
atomic_t count;
...
// fdtable 維護着全部的文件描述符
struct fdtable *fdt;
struct fdtable fdtab;
...
// 下一個可用文件描述符
unsigned int next_fd;
...
};
複製代碼
相信你也一眼就看見了 fdtable
這個結構體,見名知意,這不就是文件描述符表嗎? 那麼它是否是維護了全部的文件描述符呢?
struct fdtable {
// 最大文件描述符
unsigned int max_fds;
// 全部打開的文件
struct file **fd; /* current fd array */
...
};
複製代碼
在 fdtable
裏面有一個 file
結構體的數組,這個數組表明着該進程打開的全部文件。
先將上面的結構用一個圖畫下來:
這個源碼結構展現了每一個進程單獨維護了一個文件描述符的集合的信息。
可是文件描述符是什麼,以及它的生成規則仍是沒有找到。那隻能換個方向,從函數調用去尋找了。
open
和 close
都涉及到對文件描述符的操做,因爲 close
函數更加簡單,就從 close
爲入口進行分析。
下面是 close
函數的內部系統調用:
SYSCALL_DEFINE1(close, unsigned int, fd)
{
int retval = __close_fd(current->files, fd);
...
return retval;
}
複製代碼
close
調用了 __close_fd
函數, 該函數定義在/fs/file.c
文件中,下面是簡化後的代碼:
int __close_fd(struct files_struct *files, unsigned fd)
{
struct file *file;
struct fdtable *fdt;
// 獲取fdtable
fdt = files_fdtable(files);
// *** 經過文件描述符獲取file結構體指針
file = fdt->fd[fd];
rcu_assign_pointer(fdt->fd[fd], NULL);
// 回收文件描述符
__put_unused_fd(files, fd);
return filp_close(file, files);
}
複製代碼
這裏面又出現了咱們熟悉的結構體 files_struct
,注意 file = fdt->fd[fd]
這一段代碼。
fdt
就是 fdtable
結構體,它的 fd 屬性就是打開的全部文件數組,這樣一看也就恍然大悟了。
用戶傳進來的 fd 參數實際就是 fdtable
內的文件數組的索引。
因此, 文件描述符其實就是file結構體數組的索引。
相信關於後面
這些問題你都能迎刃而解了。