作者 | 田偉然
杏仁工程師,關注編碼和詩詞。
檔案描述符在unix系統中幾乎無處不在
從形式上來看檔案描述就是乙個整數,那麼我們可不可以更進一步去了解一下呢?我們可以通過open
系統呼叫得到乙個指定檔案的檔案描述符。
open
函式需要傳入乙個檔案路徑和操作模式, 呼叫會返回乙個整型的檔案描述符, 具體方法簽名如下
/**
* path 代表檔案路徑* oflag 代表檔案的開啟模式,比如讀,寫等*/int open(char *path, int oflag, ...)
我們寫一段簡單的**來驗證一下
#include #include #include int main(int argc, char* ar**) // 列印獲取到的檔案描述符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 #include #include int main(int argc, char* ar**)
下面是最終的執行結果:$ gcc test.c -o test
$ ./test
$ fd_a = 3, fd_b = 4
儘管是同乙個檔案, 得到的檔案描述符卻並不一樣,說明每個檔案的描述符並不是固定的。可是檔案描述符每次都是從 3 開始的,這是為什麼呢 ?熟悉unix系統的同學應該知道,系統建立的每個程序缺省會開啟3個檔案
為什麼是 3 ?因為 0、1、2 被占用了啊......等等!檔案描述符難道是遞增的?我也不知道啊, 要不寫個程式試試。
這裡應該還有乙個疑問:為什麼前一節多次執行程式都是返回 3 ,而在**裡呼叫兩次為了驗證檔案描述符是遞增的, 我設計了這樣乙個程式open
開啟同樣的檔案卻是 3 和 4 ?這個問題在後面多程序時會再提到。
1. 呼叫兩次 open , 分別得到兩個檔案描述符 3、 4
2. 呼叫 close 函式將檔案描述符 3 關閉
3. 再次呼叫 open 函式開啟同乙個檔案如果檔案描述符的規則是遞增的,第 3 步返回的結果就應該是 5 。show me the code:
#include #include #include #include int main(int argc, char* ar**)
編譯執行$ gcc test.c -o test$ ./test
$ a = 3, b = 4b = 4, c = 3
第三次開啟的結果是 3 ,這說明檔案描述符不是遞增的。而且從結果上來看,檔案描述符被**掉後是可以再次分配的。前面討論的上下文都是在單程序下,如果是多個程序同時開啟同乙個檔案,檔案描述符會一樣嗎?程式很簡單,就是父子程序各自開啟同乙個檔案, 並列印該檔案的檔案描述符。ps: 下面的**並不規範,可能會產生殭屍程序和孤兒程序,但這並不是本文的重點......#include #include #include #include int main(int argc, char* ar**) else 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.每個程序單獨維護了乙個檔案描述符的集合
talk is cheap , show me the code下面就需要在 linux核心 的原始碼中去尋找真相了。既然實驗表明每個程序單獨維護了檔案描述符集合, 那就從和程序相關的結構體by:linus benedict torvalds
task_struct
入手,該結構體放在/include/linux/sched.h
標頭檔案中。我將這個結構體的**精簡了一下, 只保留了一些分析需要關注的屬性struct task_struct ;
注意struct files_struct *files
,注釋說該屬性代表著開啟的檔案資訊,那這就沒得跑了。繼續看files_struct
結構體,該結構體定義在/include/linux/fdtable.h
標頭檔案中:struct files_struct
相信你也一眼就看見了fdtable
這個結構體,見名知意,這不就是檔案描述符表嗎?那麼它是不是維護了所有的檔案描述符呢?struct fdtable ;
在fdtable
裡面有乙個file
結構體的陣列,這個陣列代表著該程序開啟的所有檔案。先將上面的結構用乙個圖畫下來:
這個原始碼結構展示了每個程序單獨維護了乙個檔案描述符的集合的資訊。
但是檔案描述符是什麼,以及它的生成規則還是沒有找到。那只能換個方向,從函式呼叫去尋找了。open
和close
都涉及到對檔案描述符的操作,由於close
函式更加簡單,就從close
為入口進行分析。
下面是close
函式的內部系統呼叫:
syscall_define1(close, unsigned int, fd)
close
呼叫了__close_fd
函式, 該函式定義在/fs/file.c
檔案中,下面是簡化後的**:int __close_fd(struct files_struct *files, unsigned fd)
這裡面又出現了我們熟悉的結構體files_struct
,
注意file = fdt->fd[fd]
這一段**。fdt
就是fdtable
結構體,它的 fd 屬性就是開啟的所有檔案陣列,這樣一看也就恍然大悟了。使用者傳進來的 fd 引數實際就是fdtable
內的檔案陣列的索引。所以,檔案描述符其實就是file結構體陣列的索引。相信關於後面
這些問題你都能迎刃而解了。
述符全文完
ipynb檔案與py檔案互轉
方法一 在 ipynb所在目錄下,開啟終端,並輸入命令 jupyter nbconvert to script ipynb其中 ipynb是要轉換檔案的名字,轉換後在該目錄下出現 py檔案。方法二 啟動jupyter notebook 在網頁下找打ipynb檔案,然後選擇file download ...
ipynb檔案 轉換為py檔案
jupyter提供了這個轉換功能,執行jupyter notebook,執行後將開啟乙個網頁 上傳ipynb檔案,然後選擇 file download as python py 然後就可以生成python檔案了 在.ipynb 檔案所在的目錄下開啟乙個終端,然後輸入 jupyter nbconver...
Linux中硬鏈結和拷貝檔案(cp)一樣嗎?
答案是不一樣的。我們都知道在linux中,建立了乙個檔案的硬鏈結後,就算刪除了原始檔,我們依然可以開啟這個硬鏈結,而不是像軟鏈結一樣找不到檔案。那這不就是拷貝了乙份檔案嗎?當然不是的,拷貝檔案 cp 會重新建立乙個檔案並且複製相同的內容,占用新的空間,新的inode,這是兩個檔案。而硬鏈結實際上是增...