ipynb和py文件一样吗_文件描述符了解一下
作者 | 田偉然
回首向來蕭瑟處,歸去,也無風雨也無晴。
杏仁工程師,關注編碼和詩詞。
前言
?文件描述符在unix系統中幾乎無處不在
- 網絡接口 select、poll、epoll 涉及到文件描述符
- IO接口 read、write 也涉及到文件描述符
從形式上來看文件描述就是一個整數,那么我們可不可以更進一步去了解一下呢?本文打算通過一步一步實驗去了解文件描述符到底是什么, 并在最后通過Linux內核相關的源碼進行驗證。
一個獲取文件描述符的實例
我們可以通過 open 系統調用得到一個指定文件的文件描述符。
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 errorn");return EXIT_FAILURE;}// 打印獲取到的文件描述符printf("demo.txt fd = %d n", fd);return EXIT_SUCCESS; }然后使用 GCC 編譯,執行編譯后的程序,我們就可以得到 demo.txt 的文件描述符了。
不出意外你將得到以下的執行結果:
$ echo hello>>demo.txt $ gcc test.c -o -test $ ./test $ demo.txt fd = 3和方法簽名一致,文件描述符是一個整型數。你可以嘗試多次執行該程序, 你會發現打印的文件描述符始終都是 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個文件
- 標準輸入(0)
- 標準輸出(1)
- 標準錯誤(2)
為什么是 3 ?因為 0、1、2 被占用了啊......等等!文件描述符難道是遞增的?我也不知道啊, 要不寫個程序試試。
這里應該還有一個疑問:為什么前一節多次執行程序都是返回 3 ,而在代碼里調用兩次 open 打開同樣的文件卻是 3 和 4 ?這個問題在后面多進程時會再提到。文件描述符是遞增的嗎?
為了驗證文件描述符是遞增的, 我設計了這樣一個程序1. 調用兩次 open , 分別得到兩個文件描述符 3、 42. 調用 close 函數將文件描述符 3 關閉3. 再次調用 open 函數打開同一個文件如果文件描述符的規則是遞增的,第 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 = 4b = 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 = 3parent_pid = 28210, child_fd = 3每個進程打開的都是同一個文件,而且返回的文件描述符也是一樣的。前面我們已經得知每個文件的描述符并不是固定的,這樣看來,每個進程都單獨維護了一個文件描述符的集合啊。還記得最開始實驗時,我們對編譯好的程序多次執行都是打印的 3,但是在代碼里對同一個文件 open 兩次卻是返回的 3 和 4 嗎?這是因為在 shell 每次執行程序,其實都是創建了一個新的進程在執行。而在代碼里連續調用兩次,始終是在一個進程下執行的。
先總結一下
通過上面的實驗,我們可以得出文件描述的一些規律1.文件描述符就是一個整形2.每個進程默認打開 0、1、2 三個文件描述符, 新的文件描述符都是從 3 開始分配3.一個文件描述符被回收后可以再次被分配 (文件描述符并不是遞增的)4.每個進程單獨維護了一個文件描述符的集合
Show me the code
talk is cheap , show me the codeBy: 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;// 獲取fdtablefdt = 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結構體數組的索引。
相信關于后面
- 被回收后的文件描述符如何再次分配
- 文件描述符為什么從0開始
- 文件描述符為什么不能為負數
這些問題你都能迎刃而解了。
參考
1. Linux內核源碼在線查看
2. 孤兒進程與僵尸進程總結
3. 文件描述符
全文完
我們正在招聘 Java 工程師,歡迎有興趣的同學投遞簡歷到 rd-hr@xingren.com 。
總結
以上是生活随笔為你收集整理的ipynb和py文件一样吗_文件描述符了解一下的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python是基于哪个系统的_pytho
- 下一篇: python自带的数据库_Python小