現(xiàn)代操作系統(tǒng)最核心抽象之一 —— 文件
本文來自我的大規(guī)模數(shù)據(jù)系統(tǒng)專欄《系統(tǒng)日知錄》,專注存儲、數(shù)據(jù)庫、分布式系統(tǒng)、AI Infra 和計(jì)算機(jī)基礎(chǔ)知識。點(diǎn)擊??這里訂閱,解鎖更多文章。你的支持,是我前行的最大動力。
我們對文件(File)如此熟悉,以至于很少去思考其本質(zhì)和關(guān)聯(lián)的一些概念。本文參考 XV6 小冊[1]將會簡單梳理下文件抽象的本質(zhì)、妙處和一些細(xì)節(jié)。
本質(zhì)說到文件,用慣了圖形化操作系統(tǒng)的我們,第一反應(yīng)是:文件夾中的一個(gè)個(gè)圖標(biāo)。但現(xiàn)代操作系統(tǒng)鼻祖 —— Unix 最初設(shè)計(jì)“文件”時(shí),對其定義遠(yuǎn)不止于此。即使在今天的 Linux、MacOS 、Windows 的應(yīng)用開發(fā)者眼里,文件的范圍也要更大的多。
這也是軟件世界最常見的迷思之一——普通用戶眼里的形形色色的圖標(biāo),在系統(tǒng)程序員眼里都是流動的數(shù)據(jù)。普通用戶眼中的操作系統(tǒng)其實(shí)是程序員常說的 GUI[2](Graphic User Interface,用戶圖形界面) ,也即一個(gè)表面層。依靠進(jìn)程(Process)、文件(File)、管道(Pipe)等核心抽象構(gòu)建的各種系統(tǒng)調(diào)用和周邊工具,才是真正的向下管理硬件、向上封裝接口的操作系統(tǒng)。它是由一段段數(shù)據(jù)和一個(gè)個(gè)控制有機(jī)組合而成的。
這里借用超棒科幻片 The Matrix[3] (黑客帝國)一幕名場景,來助大家體會下這種感覺:

值得一提的是,在時(shí)下大火的 AI 背后的深度學(xué)習(xí),也是基于矩陣的( Matrix-Based)。此外,筆者從事過的領(lǐng)域——圖數(shù)據(jù)庫的老大 Neo4j,以及其開源的最流行的圖查詢語言——OpenCypher,得名靈感都來自于本系列電影(Neo 就是主角,Cypher 是第一集中的叛徒)。之前在面試 Neo4j 的時(shí)候,還和他們的 talent acquisition 求證過這一點(diǎn)。
不得不說,這個(gè)拍于 1999 年的電影系列,用一幕幕精彩的場景堆砌出的各種隱喻和明喻,都十分精準(zhǔn)和超前。這是唯一一個(gè)我看了超過三遍的系列。
回到正題,在 Unix 設(shè)計(jì)中,文件的本質(zhì)是什么?答曰——字節(jié)流。
因此,操作系統(tǒng)中的“文件”遠(yuǎn)不止存在于外存(磁盤或 SSD)那些各種格式的文件,任何 IO 設(shè)備(網(wǎng)絡(luò)通信、外設(shè)設(shè)備)、管道,甚至內(nèi)存本身,都可以作為文件被進(jìn)程打開——因?yàn)樗麄兌伎梢员划?dāng)做字節(jié)流。正是這種抓住本質(zhì)的抽象,使得操作系統(tǒng)長我們今天看到的這樣——其他過于復(fù)雜的抽象和實(shí)現(xiàn)都淹沒在了歷史長河中。
妙處那么,使用字節(jié)流來作為文件的抽象有何妙處呢?答案是軟件工程中提到的最多的一個(gè)詞——解耦。有了這種抽象,進(jìn)程(對 CPU 運(yùn)算能力的抽象)就無需關(guān)心各種設(shè)備和組件底層細(xì)節(jié)。這會極大簡化各種系統(tǒng)程序和應(yīng)用程序?qū)τ布氖褂梅绞健?/p>
在這種“萬法歸一”的抽象基礎(chǔ)上,還有一個(gè)很重要的伴生抽象——“管道”(Pipe)。如果說文件是對單個(gè)對象的抽象,那么管道就是連接不同對象的關(guān)鍵。沒有管道,文件頂多是“字節(jié)集”;有了管道,文件才可以流動,成為真正的“字節(jié)流”。
數(shù)據(jù)作為軟件中的基本元素,在流動的過程中,被不同的程序鍛打、組合,最后套一個(gè)表面層,成為一個(gè)個(gè)可以被普通用戶使用的網(wǎng)頁、APP 和桌面軟件。這背后,都離不開文件和管道。
細(xì)節(jié)說完了感性部分,讓我們來看下文件接口細(xì)節(jié)。通常來說,文件是可以被多個(gè)進(jìn)程共享的,因此在軟件的基礎(chǔ)上,我們進(jìn)一步抽象出:文件描述符(file descriptor,縮寫 fd)。它其實(shí)是對文件的一個(gè)視圖,包含一個(gè)文件指向和一個(gè)偏移量。其中偏移量是隱式維護(hù)的。
Kafka 在對 Topic 進(jìn)行抽象的時(shí)候,也是用的類似的方式—— Consumer 會維護(hù)一個(gè)關(guān)聯(lián)到 Topic 的偏移量。從這個(gè)側(cè)面也可以看出文件的抽象是多么成功。實(shí)際上,后來很多數(shù)據(jù)系統(tǒng)的接口都有它的影子。
文件描述符是一個(gè)進(jìn)程范圍的整數(shù)。且我們固定 0 是標(biāo)準(zhǔn)輸入,1 是標(biāo)準(zhǔn)輸出。這樣就可以通過管道來將數(shù)據(jù)流進(jìn)行重定向,從而對不同的“處理過程”進(jìn)行自由組合:
- 每個(gè)處理過程(cmd 工具)都只管專注一小件事。
- 需要輸入就從標(biāo)準(zhǔn)輸入讀取;需要輸出,就輸出到標(biāo)準(zhǔn)輸出。
- 管道負(fù)責(zé)將一個(gè)個(gè)小工具串成一個(gè)復(fù)雜的處理過程。
這就是大名鼎鼎的 KISS[4] (keep it simple,stupid)原則,也是我們?nèi)粘I钪?span style="color:#1e6bb8;font-weight:bold;">拆解復(fù)雜任務(wù)[5]的一種慣常手段,也是現(xiàn)在常見的數(shù)據(jù)流工具 (Spark,F(xiàn)link)的背后原理。
圍繞文件描述符,我們有幾大文件操作語義:
- open:以某種模式打開文件,會返回一個(gè)文件描述符,并隱式的初始化該 fd 的 offset = 0 。
- read(fd, buf, n) :從文件描述符 fd 關(guān)聯(lián)的文件和偏移量處讀取最多 n 個(gè)字節(jié)到 buf 中;讀取成功后,會前移 fd 關(guān)聯(lián)的偏移量(因此調(diào)用者就可以用一個(gè)循環(huán)來連續(xù)讀取,而不用自己維護(hù)偏移量),且返回實(shí)際讀到的字節(jié)數(shù);如果讀取失敗則返回值小于 0。
- write(fd, buf, n) :從 buf 中寫入 n 個(gè)字節(jié)到文件描述符 fd 關(guān)聯(lián)的文件和偏移量處;并且會自動前移 fd 相應(yīng)字節(jié)的偏移量。寫入成功,返回 n;寫入失敗,則返回值會小于 n。
- close(fd) :釋放 fd 和所占資源(比如文件指針和偏移量等信息),且進(jìn)程之后就可以復(fù)用該 fd 標(biāo)號。
有了這幾個(gè)基本的接口,進(jìn)程就可以對文件的字節(jié)流進(jìn)行管理和讀寫。
最后,實(shí)現(xiàn)和承載文件這一抽象的系統(tǒng),我們通常稱為文件系統(tǒng)。當(dāng)然,這又是另外一個(gè)經(jīng)典的話題。用關(guān)系數(shù)據(jù)庫來類比,文件的抽象類似關(guān)系模型,open-read—close 類似 SQL,而文件系統(tǒng)就是類似 DBMS 的那個(gè)實(shí)現(xiàn)系統(tǒng)。
如果大家對這個(gè)話題感興趣可以留言,我可以之后再專門寫文章來聊文件系統(tǒng)。
參考資料
[1]6.S081 參考書 XV6: https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf
[2]GUI: https://en.wikipedia.org/wiki/Graphical_user_interface
[3]黑客帝國名場面: https://movie.douban.com/subject/1291843/
[4]KISS: https://en.wikipedia.org/wiki/KISS_principle
[5]拆解復(fù)雜任務(wù): https://www.qtmuniao.com/2023/08/21/life-engineering-many-passes/
最后歡迎關(guān)注我的公眾號: 木鳥雜記 ,專注分布式系統(tǒng)、數(shù)據(jù)庫和存儲等大規(guī)模數(shù)據(jù)系統(tǒng),關(guān)注后可回復(fù)“資料”領(lǐng)取一份我總結(jié)的分布式系統(tǒng)和數(shù)據(jù)庫的入門資料大全。
