ipynb和py文件一样吗_文件描述符了解一下
作者 | 田偉然
回首向來蕭瑟處,歸去,也無風(fēng)雨也無晴。
杏仁工程師,關(guān)注編碼和詩詞。
前言
?文件描述符在unix系統(tǒng)中幾乎無處不在
- 網(wǎng)絡(luò)接口 select、poll、epoll 涉及到文件描述符
- IO接口 read、write 也涉及到文件描述符
從形式上來看文件描述就是一個整數(shù),那么我們可不可以更進一步去了解一下呢?本文打算通過一步一步實驗去了解文件描述符到底是什么, 并在最后通過Linux內(nèi)核相關(guān)的源碼進行驗證。
一個獲取文件描述符的實例
我們可以通過 open 系統(tǒng)調(diào)用得到一個指定文件的文件描述符。
open 函數(shù)需要傳入一個文件路徑和操作模式, 調(diào)用會返回一個整型的文件描述符, 具體方法簽名如下
/** * 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 errorn");return EXIT_FAILURE;}// 打印獲取到的文件描述符printf("demo.txt fd = %d n", fd);return EXIT_SUCCESS; }然后使用 GCC 編譯,執(zhí)行編譯后的程序,我們就可以得到 demo.txt 的文件描述符了。
不出意外你將得到以下的執(zhí)行結(jié)果:
$ echo hello>>demo.txt $ gcc test.c -o -test $ ./test $ demo.txt fd = 3和方法簽名一致,文件描述符是一個整型數(shù)。你可以嘗試多次執(zhí)行該程序, 你會發(fā)現(xiàn)打印的文件描述符始終都是 3。難道每個文件的文件描述符都是固定的?
每個文件的描述符是固定的嗎?
為了驗證前面的猜想,我在程序里面連續(xù)調(diào)用兩次 open 函數(shù),并打印兩次返回的文件描述符, 代碼如下:
#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; }下面是最終的執(zhí)行結(jié)果:
$ gcc test.c -o test $ ./test $ fd_a = 3, fd_b = 4盡管是同一個文件, 得到的文件描述符卻并不一樣,說明每個文件的描述符并不是固定的。可是文件描述符每次都是從 3 開始的,這是為什么呢 ?
熟悉UNIX系統(tǒng)的同學(xué)應(yīng)該知道,系統(tǒng)創(chuàng)建的每個進程默認(rèn)會打開3個文件
- 標(biāo)準(zhǔn)輸入(0)
- 標(biāo)準(zhǔn)輸出(1)
- 標(biāo)準(zhǔn)錯誤(2)
為什么是 3 ?因為 0、1、2 被占用了啊......等等!文件描述符難道是遞增的?我也不知道啊, 要不寫個程序試試。
這里應(yīng)該還有一個疑問:為什么前一節(jié)多次執(zhí)行程序都是返回 3 ,而在代碼里調(diào)用兩次 open 打開同樣的文件卻是 3 和 4 ?這個問題在后面多進程時會再提到。文件描述符是遞增的嗎?
為了驗證文件描述符是遞增的, 我設(shè)計了這樣一個程序1. 調(diào)用兩次 open , 分別得到兩個文件描述符 3、 42. 調(diào)用 close 函數(shù)將文件描述符 3 關(guān)閉3. 再次調(diào)用 open 函數(shù)打開同一個文件如果文件描述符的規(guī)則是遞增的,第 3 步返回的結(jié)果就應(yīng)該是 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);// 關(guān)閉a文件描述符close(a);// 第三次打開int c = open("demo.txt", O_RDONLY);printf("b = %d, c = %d n", b, c);return EXIT_SUCCESS; }編譯執(zhí)行
$ gcc test.c -o test $ ./test $ a = 3, b = 4b = 4, c = 3第三次打開的結(jié)果是 3 ,這說明文件描述符不是遞增的。而且從結(jié)果上來看,文件描述符被回收掉后是可以再次分配的。前面討論的上下文都是在單進程下,如果是多個進程同時打開同一個文件,文件描述符會一樣嗎?
文件描述符和多進程
fork 函數(shù)可以創(chuàng)建多個進程, 該函數(shù)返回一個 int 值, 當(dāng)返回值為 0 時代表當(dāng)前是子進程正在執(zhí)行,非 0 就為父進程在執(zhí)行。(為了簡化代碼,就不考慮進程創(chuàng)建失敗的情況了)
程序很簡單,就是父子進程各自打開同一個文件, 并打印該文件的文件描述符。PS: 下面的代碼并不規(guī)范,可能會產(chǎn)生僵尸進程和孤兒進程,但這并不是本文的重點......
#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; }編譯執(zhí)行
$ gcc test_process.c -o test_process $ ./test_process $ child_pid = 28212, child_fd = 3parent_pid = 28210, child_fd = 3每個進程打開的都是同一個文件,而且返回的文件描述符也是一樣的。前面我們已經(jīng)得知每個文件的描述符并不是固定的,這樣看來,每個進程都單獨維護了一個文件描述符的集合啊。還記得最開始實驗時,我們對編譯好的程序多次執(zhí)行都是打印的 3,但是在代碼里對同一個文件 open 兩次卻是返回的 3 和 4 嗎?這是因為在 shell 每次執(zhí)行程序,其實都是創(chuàng)建了一個新的進程在執(zhí)行。而在代碼里連續(xù)調(diào)用兩次,始終是在一個進程下執(zhí)行的。
先總結(jié)一下
通過上面的實驗,我們可以得出文件描述的一些規(guī)律1.文件描述符就是一個整形2.每個進程默認(rèn)打開 0、1、2 三個文件描述符, 新的文件描述符都是從 3 開始分配3.一個文件描述符被回收后可以再次被分配 (文件描述符并不是遞增的)4.每個進程單獨維護了一個文件描述符的集合
Show me the code
talk is cheap , show me the codeBy: Linus Benedict Torvalds
下面就需要在 Linux內(nèi)核 的源碼中去尋找真相了。既然實驗表明每個進程單獨維護了文件描述符集合, 那就從和進程相關(guān)的結(jié)構(gòu)體task_struct 入手,該結(jié)構(gòu)體放在 /include/linux/sched.h 頭文件中。我將這個結(jié)構(gòu)體的代碼精簡了一下, 只保留了一些分析需要關(guān)注的屬性
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 ,注釋說該屬性代表著打開的文件信息,那這就沒得跑了。繼續(xù)看 files_struct 結(jié)構(gòu)體,該結(jié)構(gòu)體定義在 /include/linux/fdtable.h頭文件中:
struct files_struct {// 打開的文件數(shù)atomic_t count;...// fdtable 維護著所有的文件描述符struct fdtable *fdt;struct fdtable fdtab;...// 下一個可用文件描述符unsigned int next_fd;... }相信你也一眼就看見了 fdtable 這個結(jié)構(gòu)體,見名知意,這不就是文件描述符表嗎?那么它是不是維護了所有的文件描述符呢?
struct fdtable {// 最大文件描述符unsigned int max_fds;// 所有打開的文件struct file **fd; /* current fd array */... };在fdtable 里面有一個 file 結(jié)構(gòu)體的數(shù)組,這個數(shù)組代表著該進程打開的所有文件。先將上面的結(jié)構(gòu)用一個圖畫下來:
這個源碼結(jié)構(gòu)展示了每個進程單獨維護了一個文件描述符的集合的信息。但是文件描述符是什么,以及它的生成規(guī)則還是沒有找到。那只能換個方向,從函數(shù)調(diào)用去尋找了。open 和 close 都涉及到對文件描述符的操作,由于 close 函數(shù)更加簡單,就從 close 為入口進行分析。
下面是 close 函數(shù)的內(nèi)部系統(tǒng)調(diào)用:
SYSCALL_DEFINE1(close, unsigned int, fd) {int retval = __close_fd(current->files, fd);...return retval; }close調(diào)用了__close_fd函數(shù), 該函數(shù)定義在/fs/file.c文件中,下面是簡化后的代碼:
int __close_fd(struct files_struct *files, unsigned fd) {struct file *file;struct fdtable *fdt;// 獲取fdtablefdt = files_fdtable(files);// *** 通過文件描述符獲取file結(jié)構(gòu)體指針file = fdt->fd[fd];rcu_assign_pointer(fdt->fd[fd], NULL);// 回收文件描述符 __put_unused_fd(files, fd);return filp_close(file, files); }這里面又出現(xiàn)了我們熟悉的結(jié)構(gòu)體 files_struct ,注意 file = fdt->fd[fd] 這一段代碼。fdt 就是 fdtable 結(jié)構(gòu)體,它的 fd 屬性就是打開的所有文件數(shù)組,這樣一看也就恍然大悟了。用戶傳進來的 fd 參數(shù)實際就是 fdtable 內(nèi)的文件數(shù)組的索引。所以,文件描述符其實就是file結(jié)構(gòu)體數(shù)組的索引。
相信關(guān)于后面
- 被回收后的文件描述符如何再次分配
- 文件描述符為什么從0開始
- 文件描述符為什么不能為負數(shù)
這些問題你都能迎刃而解了。
參考
1. Linux內(nèi)核源碼在線查看
2. 孤兒進程與僵尸進程總結(jié)
3. 文件描述符
全文完
我們正在招聘 Java 工程師,歡迎有興趣的同學(xué)投遞簡歷到 rd-hr@xingren.com 。
總結(jié)
以上是生活随笔為你收集整理的ipynb和py文件一样吗_文件描述符了解一下的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python是基于哪个系统的_pytho
- 下一篇: c类网络使用子网掩码有无实际意义_弄懂I