Cat 命令的源碼歷史
以前我和我的一些親戚爭論過計算機科學的學位值不值得讀。當時我正在上大學,並要決定是不是該主修計算機。我姨和我表姐覺得我不應該主修計算機。她們承認知道如何編程肯定是很有用且對自己有利的一件事,但是她們認為計算機科學現在發展的如此迅速以至於我學的東西幾乎馬上就過時了。建議我更好是把編程作為輔業,選擇一個基礎原理可以受用終身的領域主修,比如經濟學或物理學。
我知道我姨和我表姐說的不對,並決定主修計算機科學。(對不住啊!)平常人可能會覺得像計算機科學領域和軟體工程專業每隔幾年就完全和之前不一樣了。其原因很容易理解。我們有了個人電腦,然後有了互聯網,有了手機,之後還有了機器學習…… 科技總是在更新,支撐科技發展的原理和技能當然也在改變。當然,最驚人的是其實原理的改變竟然如此之小。我敢肯定,大多數人在知道了他們電腦里一些重要的軟體的歷史是多麼久遠時他們一定會深感震驚。當然我不是說那些刷版本號的浮誇軟體 —— 我電腦上的 Firefox 瀏覽器副本,可能是我用的最多的軟體,可能兩周前就更新過。如果你看了比如 grep
的手冊頁,你就會發現它在 2010 年後就沒有過更新了(至少在 MacOS 上如此)。初版 grep
是在 1974 年寫就的,那時可以算是計算機世界的侏羅紀了。直到現在,人們(還有程序)仍然依賴 grep
來完成日常工作。
我姨和我表姐認為計算機技術就像一系列日漸精緻的沙堡,在潮水抹凈沙灘後新的沙堡完全取代舊的。但事實上,在很多領域上,我們都是不斷積累能夠解決問題的程序。我們可能不得不偶爾修改這些程序以避免軟體無法使用,但大多數情況下我們都可以不修改。grep
是一個簡單的程序,可以解決一個仍然存在的需求,所以它能夠存活下來。 大多數應用程序編程都是在非常高的級別上完成的,它們建立在解決了舊問題的舊程序的金字塔上。 30 年或 40 年前的思路和概念,遠非過時,在很多情況下它們依然在您的筆記本電腦上軟體中存在著。
我想追溯這樣的老程序自第一次寫就以來改變了多少回很有趣。 cat
可能是所有 Unix 實用程序中最簡單的,因此我們以它為例。Ken Thompson 於 1969 年編寫了 cat
的原始實現。如果我告訴別人我的電腦上安裝了個來自 1969 年的程序,這準確嗎?我們電腦上的程序多大了?
感謝這種這種倉庫,我們可以完整的看到 cat
自 1969 年後是如何發展的。我會先聚焦於可以算得上是我的 MacBook 上的 cat
的祖先的 cat
實現。隨著我們從 Unix 上的第一版 cat
追蹤到現在 MacOS 上的 cat
,你會發現,這個程序被重寫的次數比你想的還要多 —— 但是直到現在它運行的方式和五十年前多少是完全一致的。
研究 Unix
Ken Thompson 和 Dennis Ritchie 在 PDP 7 上開始寫 Unix。那還是 1969 年,C 還沒被發明出來,因此所有早期的 Unix 軟體都是用 PDP 7 彙編實現的。他們使用的彙編種類是 Unix 特有的,Ken Thompson 在 DEC(PDP 7 的廠商)提供的彙編器之上加了些特性,實現了自己的彙編器。Thompson 的更改在最初的 Unix 程序員手冊的 as
(也就是彙編器)條目下均有所記錄。
因此,最初的 cat
也是使用 PDP 7 彙編實現的。 我添加了一些注釋,試圖解釋每條指令的作用,但除非你理解 Thompson 在編寫彙編器時加的特性,否則程序仍然很難理解。在那些特性中有兩個很重要:其一是 ;
這個字元可以在一行中用來分隔多條語句,它多出現於在使用 sys
指令時將系統調用的多個參數放在同一行上。其二是, Thompson 的彙編器支持使用 0 到 9 作為「臨時標籤」,這是在程序內可以重用的標籤。因此。就如 Unix 程序員手冊中所說:「對程序員的想像力和彙編程序的符號空間的要求都降低了」。在任何給定的指令內,你都可以使用 nf
和 nb
來引用下一個或最近的臨時標籤 n
。 例如,如果存在標記為 1:
的代碼塊,你就可以使用指令 jmp 1b
從下游代碼跳回該塊。 (但是你不使用 jmp 1f
的話就沒法從上面的代碼跳到這裡。)
初版 cat
最有趣的就是它包含著我們應該認識的符號。有一塊指令塊標記為 getc
,還有一個標記為 putc
,可以看到這兩個符號比 C 標準還古老。第一版的 cat
函數實際上已經包含了這兩個函數的實現。該實現做了輸入緩存,這樣它就不需要一次只讀寫一個字母。
cat
的第一個版本並沒有持續多久。 Ken Thompson 和 Dennis Ritchie 說服貝爾實驗室購買了 PDP 11,這樣他們就能夠繼續擴展和改進 Unix。 PDP 11 的指令集和之前不一樣,因此必須重寫 cat
。 我也注釋了這個第二版 cat
。 它為新的指令集使用新的彙編程序助記符,並利用了 PDP 11 的各種定址模式。(如果你對源代碼中的括弧和美元符號感到困惑,那是因為這些符號用於指示不同的定址模式。)但它也使用 ;
字元和臨時標籤,和 cat
的第一個版本一樣,這意味著當把 as
移植到 PDP 11 上時,必須要保留這些功能。
cat
的第二個版本比第一個版本簡單得多。 它也更有 Unix 味兒,它不只是依靠參數列表,一旦沒給參數列表,它將從 stdin
讀取數據,這也就是今天 cat
仍在做的事情。 你也可以在此版本的 cat
中以 -
為參數,以表示它應該從stdin
讀取。
在 1973 年,為了準備發布第四版 Unix,大部分代碼都用 C 語言重寫了。但是 cat
似乎在之後一段時間內並沒有使用 C 重寫。 cat 的第一個 C 語言實現出現在第七版 Unix 中。 這個實現非常有趣,因為它很簡單。 在所有以後的實現中,這個實現和在 K&R 的 C 語言教科書中用作教學示範的理想化 cat
最相似。這個程序的核心就是經典的兩行:
while ((c = getc(fi)) != EOF)
putchar(c);
當然實際代碼要比這多一些,額外的代碼主要是為了確保你沒有在讀/寫同一個文件。另一個有趣的事情是,cat
的這一版實現只識別一個標誌位 -u
。 -u
標誌可用於避免緩衝輸入和輸出,否則 cat
將以 512 位元組為塊進行輸入輸出。
BSD
在第七版 Unix 之後,Unix 出現了各種衍生品和分支。 MacOS 建立於 Darwin 之上,而 Darwin 又源自 伯克利軟體分發版 (BSD),因此 BSD 是我們最感興趣的 Unix 分支。 BSD 最初只是 Unix 中的實用程序和附加組件的集合,但它最終成為了一個完整的操作系統。直到第四版 BSD,人稱 4BSD,為一大堆新標誌添加了支持之前,BSD 似乎還是依賴於最初的 cat
實現的。cat
的 4BSD 實現 顯然是從原始實現中衍生出來的,儘管它添加了一個新函數來實現由新標誌觸發的行為。已經在文件中使用的 fflg
變數(用於標記輸入是從 stdin
還是文件讀取的)的命名約定,被新添加的 nflg
、bflg
、vflg
、sflg
、eflg
和 tflg
沿襲了下來,這些變數記錄了在調用程序時是否使用了這些新標誌。這些是最後一批添加到 cat
的命令行標誌。如今 cat
的手冊頁列出了這些標誌,沒有其他的標誌了,至少在 Mac OS 上是如此。 4BSD 於 1980 年發布,因此這套標誌已有 38 年歷史。
cat
最後一次被完全重寫是在 BSD NET/2 上,其目的是通過替換 AT&T 發布的全部 Unix 源代碼來規避許可證問題。BSD Net/2 在 1991 年發布。這一版本的 cat
是由 Kevin Fall 重寫的。 Kevin Fall 於 1988 年畢業於加州大學伯克利分校並在下一年成為 計算機系統研究組 (CSRG)的組員,Fall 和我說當時使用 AT&T 代碼的 Unix 工具被列在了 CSRG 的牆上,組員需要從中選出他們想要重寫的工具; Fall 選了 cat
以及 mknod
。 MacOS 系統內自帶的 cat
實現源碼的最上面還有著他的名字。他的這一版 cat
,儘管平淡無奇,在今天還是被無數人使用著。
Fall 的原始 cat 實現 比我們迄今為止看到的版本都要長。 除了支持 -?
幫助標誌外,它沒有增加任何新功能。 從概念上講,它與 4BSD 的實現非常相似。 它長是因為 Fall 將實現分為 「原始」 模式和 「加工」 模式。 「原始」 模式是 cat
的經典實現;它一個字元一個字元的列印文件。 「加工」 模式是帶有所有 4BSD 命令行選項的 cat
。 如此區別不無道理,但這麼辦也擴充了實現規模,因此乍一看其源碼似乎比實際上更複雜。文件末尾還有一個奇特的錯誤處理函數,進一步地增加了實現的長度。
MacOS
在 2001 年,蘋果發布了 MacOS X。這一發布對蘋果意義重大。因為蘋果用了多年的時間嘗試以取代其現有的老舊操作系統(經典的 Mac OS),但是都失敗了。 在 Mac OS X 之前蘋果兩次嘗試在內部創建一個新的操作系統,但兩者都無疾而終。 最後,蘋果收購了史蒂夫·喬布斯的 NeXT 公司,後者開發了一個名為 NeXTSTEP 的操作系統和面向對象編程框架。 蘋果將 NeXTSTEP 作為 Mac OS X 的基礎。因為 NeXTSTEP 部分基於 BSD,使以 NeXTSTEP 為基礎的 Mac OS X 的自然就把 BSD 系的代碼直接帶入蘋果宇宙的中心。
因此,Mac OS X 的非常早期的第一個版本包含了從 NetBSD 項目中提取的 cat
的實現。如今仍保持開發的 NetBSD 最初是 386BSD 的分支,而後者又直接基於 BSD Net/2。所以 Mac OS X 裡面的第一個 cat
的實現就是 Kevin Fall 的 cat
。唯一改變的是,Fall 的錯誤處理函數 err()
被 err.h
提供的 err()
函數取代了。 err.h
是 C 標準庫的 BSD 擴展。
之後不久,這裡的 cat
的 NetBSD 實現被換成了 FreeBSD 中的 MARKDOWN_HASHd077f244def8a70e5ea758bd8352fcd8MARKDOWNHASH
實現。 [根據維基百科](https://en.wikipedia.org/wiki/Darwin(operating_system)),蘋果在 Mac OS X 10.3(Panther)中開始使用 FreeBSD 的實現而不是 NetBSD 的實現。但根據蘋果自己開源的版本,cat
的 Mac OS X 實現在 2007 年發布的 Mac OS X 10.5(Leopard)之前沒有被替換。蘋果為 Leopard 替換的的 FreeBSD 實現與今天蘋果計算機上的實現相同。截至 2018 年,2007 年以來的這個實現仍未被更新或修改。
所以 Mac OS 上的 cat
已經很老了。實際上,這一實現在 2007 年在 MacOS X 上露面兩年前就被發布了。 這個 2005 年的修改 在 FreeBSD 的 Github 鏡像中可見,是在蘋果將其合併入 Mac OS X 前對 FreeBSD 的 cat
實現進行的最後一次更改。所以 Mac OS X 中的實現沒有與 FreeBSD 的 cat
實現保持同步,它如今已經 13 歲了。對於軟體修改了多少代碼才能仍是算是同一軟體這一話題有著曠日持久的爭論。不過,在這種情況下,源文件自 2005 年以來根本沒有變化。
現在 Mac OS 使用的 cat
實現與 Fall 1991 年為 BSD Net/2 版本編寫的實現沒有什麼不同。最大的區別是添加了一個全新的功能來提供 Unix 域套接字支持。FreeBSD 開發人員似乎將 Fall 的 raw_args()
函數和 cook_args()
函數組合成一個名為scanfiles()
的函數。否則,程序的核心就仍是 Fall 的代碼。
我問過 Fall 對編寫了如今被數以百萬計的蘋果用戶(直接或者間接通過依賴 cat
的某些程序)使用的 cat
實現有何感想。Fall,如今是一位顧問,也是最新版《TCP/IP 詳解》的合著者,他說,當人們從了解他對 cat
所做的工作中收穫頗豐時,他感到很驚訝。 Fall 在計算機領域有著悠久的職業生涯,曾參與許多備受矚目的項目,但似乎很多人仍對他在 1989 年重寫 cat
的那六個月的工作感到最為興奮。
百年老程序
在宏偉的發明史中,計算機並不是一項古老的發明。我們已經習慣了百年的照片甚至是百年的視頻短片。但是計算機程序不一樣 —— 它們代表著高科技和新技術。至少,他們是現代的技術造出來的。隨著計算行業的成熟,我們有朝一日會發現自己正在使用有著接近百年歷史的程序嗎?
計算機硬體可能會發生較大的變化,使得我們也許無法讓現在編譯的可執行文件在一個世紀後的硬體上運行。也許編程語言設計的進步讓未來沒有人能理解 C 語言,cat
將來也可能也被別的語言重寫很久了。 (儘管 C 已經存在了五十年了,而且它似乎不會很快就被替換掉。)但除此之外,為什麼不永遠使用我們現在的 cat
?
我認為 cat
的歷史表明,計算機科學中的一些想法確實非常持久。事實上,對於 cat
,這個想法和程序本身都很古老。不準確地說,我的電腦上的 cat
來自 1969 年。但我也可以說我的計算機上的 cat
來自1989 年,當時 Fall 寫了他的 cat
實現。許多其他軟體也同樣古老。因此,也許我們不應該把計算機科學和軟體開發視為不斷破壞現狀和發明新事物的領域。我們的計算機系統是由諸多歷史文物構建的。有時,我們可能會花費更多時間在理解和維護這些歷史文物上,而不是花在編寫新代碼上。
如果你喜歡本文,你可能更喜歡兩周來一篇更新!在推特上關注 @TwoBitHistory 或者訂閱這個 RSS 源 以保證接受到新的文章。
via: https://twobithistory.org/2018/11/12/cat.html
作者:Two-Bit History 選題:lujun9972 譯者:name1e5s 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive