Categories
程式開發

開發者對操作系統的十大不滿:五十年來沒什麼大改進


過去五十年,操作系統雖然發生了很多變化,但都只是在迭代開發,沒有加入任何的新鮮血液。一切都在向美好的方向發展,但最初的藍本並沒有改變。

五十年來,程序員可選的操作系統無非是 Windows、Mac OS 和 Linux,類似且陳舊,不知道是優勝略汰的選擇還是歷史發展的遺留產物。放棄所有奇怪的字符串格式,不管是程序啟動時傳遞的命令行參數還是程序間通信,然後用簡潔且易於編寫的語言取代結構定義,這樣的想法不香嗎?

現有操作系統:陳舊且類似

我是一名後端程序員。近年來,我為多家軟件製造公司工作,並多次與其他程序員一起從零開始搭建系統。在沒有現成架構的情況下,我也會參與設計系統體系結構。

在捷克國家圖書館工作時,我搭建了可以處理電子刊物的系統,獨自完成了幾乎所有後端代碼,從數據存儲到與現有系統的通信。在另一家不能被提及名字的公司,我負責設計 DDoS 防護系統,基本寫完了大部分通信後端。我還做過 Nubium 的後端開發,為一家公司重新設計並編寫了部分軟件來處理不同服務器之間的文件存儲和重新分發,還寫了一個可以處理使用過數據的軟件。

我寫這些倒不是為了證明我的能力有多強,而是為了說明因為見的多,所以我能夠看到大部分軟件的異曲同工之處。

目前,我們對操作系統的選擇有三大業界標杆:Windows、Mac OS、Linux,還有其他大約佔 0.03% 的衍生小系統,例如 Plan9、BeOS 等。

出於某些原因,幾乎所有現存的操作系統,除了個例,都幾乎一模一樣。我很好奇,這究竟是優勝略汰的選擇還是歷史發展的遺留產物

過去五十年,操作系統雖然發生了很多變化,但都只是在迭代開發,沒有加入任何的新鮮血液。一切都在向美好的方向發展,但最初的藍本並沒有改變。

對操作系統的十大不滿

我對操作系統作為硬件抽像這一概念沒有任何問題,我反對的是操作系統作為用戶界面的概念。我指的當然不是圖形用戶界面,而是可交互的其他所有帶“形狀”的事物。

文件系統是個非常受限制的數據庫

文件系統到底是什麼?答案是:一個有限制的分層鍵值數據庫。

說它有限制的原因在於:文件系統不僅限制了鍵被允許的子集和大小(如果情況允許則會以UTF 的格式存儲),它還限制了值本身,而開發者能存儲的只有字節流。

聽起來很合理,但不要忘了,文件系統還是一個不允許直接存儲任何結構化數據的樹狀結構分層數據庫。索引節點也就是樹狀結構中的分支數量最大隻有數万個,但一個目錄中最多卻可以有數百萬個文件。

開發者對操作系統的十大不滿:五十年來沒什麼大改進 1

在我之前的工作中,為解決 inode 限制不得不求助於 BalancedDiscStorage 模塊,或者是將文件存到三個以原文件 MD5 哈希的首字母命名的子目錄中。

與必備 ACID 四要素的正常數據庫相比,文件系統可以說是非常不稱職。對大多數操作而言,它既不支持原子性也不支持事務,並行寫入和讀取在不同的操作系統中也不盡相同,當然前提是這個操作系統支持並行,它可以說是無法保證任何事情。

過去,我們解決了底層的問題,磁盤扇區、日誌記錄硬盤及其分區,還有RAID,從數據存儲上來說,我們和過去相比進步巨大,但如果從用戶界面的角度來看,這難道不是退步嗎?

最初的文件系統只是一種比喻,將文件存儲到文件夾中,是為幫助當時習慣使用紙筆工作的人們更好地理解其工作原理。然而即使是在現在,在文件系統許多技術的限制上,人們仍然無法擺脫這個比喻帶來的思維定勢。

程序:五十年間沒有什麼大改進

純粹從物理意義上來講,程序不過是存儲字節的序列。而操作系統基本上也就只剩下程序了,OS 的文件數據庫除了控製程序,什麼都做不了。

通常,程序是用某種編程語言編寫的,在編譯之後就會被鏈接到某個數據塊中。然後,它會在打孔卡上打孔(二進制數據),並插入文件櫃正確區域的相應盒子(文件)中。運行時,堆疊的打孔卡從存儲在文件櫃某個位置的盒子中取出並載入到內存中。

程序同樣也可以從命令行中讀取參數,通過使用共享庫調用操作系統的 API、文件系統,向其他程序發送數字信號,並回應這類信號,返回(數字)值或打開 socket。

我不想說這個概念本身就是錯的。但是,這不過是五十年前那套想法迭代發展後的版本,整個系統在這四十年間只是改進了最細枝末節的部分,我們不是在完善這套思路,我們是鑽了牛角尖。

Lisp 機、Smalltalk 以及 Self 環境給我們指了一條不同的路。程序不一定要是字節的集合,它們還可以作為單獨的對像被系統的其他部分調用,可以根據需要動態編譯。

Unix 哲學提出過“多使用小型應用程序的組合來解決問題”,我們為什麼不將這個想法帶到新的高度,並根據程序的不同功能和方法來編寫小型程序呢?這會允許它們以與微服務相同的工作方式,與其他系統進行通信。

文件是沒有結構的數據

二進制數據,乍一看整個主意簡直不能更好,文本才是最普遍的存在。

那麼問題出在了哪裡?

幾乎所有數據都有其獨特的內部結構。當開發者要處理數據而不是四處搬運的時候,分析數據是避無可避的。這就意味著要對數據進行切片,並從中構建結構樹。即使是諸如音頻或視頻數據的字節流也有其自身的迭代結構。

同樣的事情一遍遍發生,每一個程序都要獲取原始數據、構建結構、進行處理,然後再將這些數據折疊回原始二進制數據。

現在的計算機文化沉迷於編譯器和對以元數據的形式承載結構本身的數據的外部描述,在不斷地將原始字節轉換為結構然後再轉換回原始字節時,浪費了大量的 CPU 週期。

作為程序員,我有相當一部分時間都花在解析和轉換數據上,這一切不光可笑甚至還很難受。

過去幾十年,我們見證了 XML、JSON 和 YAML 等格式的快速發展,但這並不是我想在這裡說的。格式不是重點,重點是結構本身。如果能將所有數據結構化豈不是很好?

我並不是在指用XML 語法分析器分析數據,而是以MessagePack、SBE、FlatBuffers 或者是Cap’n Proto 的形式直接加載到內存中;不需要對文本進行評估或者是解決轉義序列和unicode 格式;不需要分析WAV 文件,因為可以直接遍歷塊;數據可以通過其結構進行自我描述,而不用通過外部描述或者是語法分析器;“對象”可以直接序列化,而且是所有編程語言都支持的統一系統,哪怕沒有對像只有結構。

這種系統真的有可能存在嗎?

有結構的通信

儘管我對這些事情有著不滿,但最重要的是人類的潛意識。人們並不會將文件系統看作是數據庫,也不會意識到它們的結構是分層式鍵值數據。我們理所當然地認為程序是二進制的 blob(Binary Large Object),而不是一堆相互聯繫的函數、結構和對象。人們對數據的理解是原始字節的死序列,而不是樹形結構或是圖結構。

除此之外,通信也應該有結構,用於在電腦和程序之間進行溝通。

/sys

Plan9 的出現可以說是朝著結構化計算機通信方法邁進了一大步。然而在我研究之後發現,它的作者雖然知道自己要做什麼,但卻還沒有達成更完整的認知。這也許是 Unix 以及字節流通信概念過於深入的原因。

僅執行某些特殊文件就可以進行系統互動,聽起來非常美好,甚至可以說是革命性的進步。就像僅使用 Linux 一半的特性,比如 /sys 的子系統和 FUSE(用戶態文件系統)。

但是它缺少映射和結構化數據。沒有讀過用戶手冊就永遠不知道自己該寫什麼以及運行後該期待什麼,一切都沒有明確的語義。

描述和期望存在不同的地方,可能是幫助頁面(man page),也可能是在某處的模塊描述中。而真正的期望只是機器代碼或是源代碼的形式存儲於完全不同的位置。一切都是面向字符串的,同樣也沒有標準化。數據類型從未被指定,仔細想想,這只是在文件之上構建的、絲毫沒有現代通信框架優點的原始通信框架。

/sys 的結構和對象非常類似,sys.class.gpio.diode 與該文本協議的區別在於,文件的實現是一對未描述的鍵值對,和 JSON 有點類似。但文件也沒有明確指定結構、屬性集、更複雜消息的格式、幫助或者是異常引發的格式和機制。

Sockets

我可以理解 Sockets 的創建原因和工作方式,畢竟在當時,這應該是最佳且合理的選項。然而,在所有通信都已經結構化的今天,我們仍然用著非結構化的二進制數據傳輸格式?

在 Sockets 發明了五十多年後的今天,我們仍然以字節流的形式傳輸數據並不斷提出新的文本協議,我們難道不值得更好的嗎?

人們認為新建結構並使用某些臨時發明出的序列化協議對其進行序列化是件再正常不過的事情,當我們將它傳輸到需要分析器以進行反序列化和原始結構重構的系統時,這個系統基本也是以代碼的形式對數據進行外部描述。幸運的話,我們可以通過反序列化得到足夠接近最初序列化的格式而沒有太多的丟失信息。何必多此一舉呢,直接傳輸結構不好嗎?

ZeroMQ 朝著正確的方向邁進了一步,但就目前看來反響不佳。

命令行參數

如果無視可以通過鼠標點擊就運行的程序,那麼通常情況下,命令行參數是打開程序的唯一選擇。然而幾乎所有程序都有自己獨有的語法來分析這些參數。

理論上來說,存在標準答案和最佳答案兩種,但事實上除非翻 man 頁,否則永遠不知道到底該用什麼語法。如果通過 bash shell 調用程序,那麼就會得到 shell 腳本語言外加其自定義的字符串、變量等一系列亂七八糟的大禮包,我能想到的就有轉義序列、函數的保留字、隨機字符等。所有人都隨心所欲地亂寫一氣,然後不考慮任何內部邏輯或統一性的解碼,最終造成了一團亂麻的局面。

別忘了還有在程序中調用另一個程序的操作。我已經記不清有多少次被迫使用下面這種代碼了:

import subprocess

sp = subprocess.Popen(
  ['7z', 'a', 'Test.7z', 'Test', '-mx9'],
  stderr=subprocess.STDOUT,
  stdout=subprocess.PIPE
)
stdout, stderr = sp.communicate()

如果你認為參數過於復雜,那麼很快就會退化到字符串的拼接版本,而這時它的安全性也就大大降低,甚至還可能導致命令注入,沒有字符串編碼支持的保證,pipe 和tty的的行為將會比你想像的更難受,一切都會變得無比複雜。

我很不能理解這堆東西。命令行參數通常只具有嵌套結構的列表或字典,用統一且簡介的方式編寫這類結構的規範用語不好嗎?比 JSON 寫起來還要簡單,同時也更具表現力。

有一點被我忽略了,如果可以將結構化程序發送到程序,就像是編程語言調用函數一樣,那麼命令行參數就沒必要了。畢竟它的作用不過是通過特定的參數調用特定的函數或是方法,既然如此,為何不選擇一種更直接且方便的方法呢?

env 變量

Env 變量是字典,他們所作的就是將數據映射到結構上,並且可以像字典一樣使用。但由於缺少結構,它們只是使用字符串作為鍵值的一維字典。在 D 語言(譯者註:一種通用計算機程序語言)中,env 變量通過 string [string] env; 聲明。但這樣通常不夠,還需要轉義嵌套或者是使用比 string 更複雜的結構。這就讓人不得不選擇使用序列化。

令人崩潰的是,“還需要給env 變量存儲結構?直接用JSON 或者指向個文件不就好了?”為什麼不能找到統一的方式來傳遞並存儲數據,這樣就不用將bash 中的env 變量的語法和JSON 混合使用了。

配置文件

計算機上幾乎所有不怎麼重要的程序都需要通過配置文件進行配置。至於配置文件在哪裡,在Linux 裡習慣會放在/etc 下,但也有可能會在$HOME,或者$HOME/.config 下,或者在某些隨機的子文件夾中,例如$HOME/.thunderbird /。

至於格式,基本就是個人發揮。可以是(偽)INI 文件、XML、JSON、YAML。甚至可以是某種編程語言,例如 LUA,也可以是幾種語言的混雜,比如 postfix。只要你想,什麼都可以用。

曾經有人調侃表示:每個配置文件都會隨著時間的增加而變得更加複雜,直到有 lisp 的一半糟糕為止。我能理解這種現象的出現的原因,但為什麼不能在系統中統一標準呢?理想情況下,同種數據格式就像是編程語言一樣,那麼為什麼不能直接將對象本身存儲在配置命名空間中合適的位置?

日誌

所有的生產服務都需要在某些地方記錄其日誌,而每一份日誌都要能滿足一些條件,比如有結構地記錄、並行寫訪問、日誌旋轉以及真實記錄等。

然而,目前的解決方案大多各不相同。人們經常會不經思考地使用任何看上去合理的方案:

  1. 日誌結構混亂。雖然都會或多或少的使用可解碼的格式,但其結構和語法幾乎完全不同。很少有人設定多行日誌信息應如何被存儲和轉義,而解碼通常是由正則表達式處理的,這種形式通常經不起考驗,很容易被破壞。

  2. 並行日誌記錄的問題通常會利用單獨的日誌記錄服務器解決。與幾乎其他所有的數據庫不同,文件系統通常並不能保證原子性操作更改(譯者註:指不會被線程調度機制打斷的操作)。如果日誌服務器掛掉了,那麼日誌記錄就沒了。

  3. 日誌旋轉一般也是由外部應用程序處理的,基本也就是定期觸發的任務。我至今還沒有見過任何一個日誌記錄程序智能到在存儲空間不足的情況下依然能夠記錄,不知道有多少的生產服務都是因為這個小問題而停止了工作。另一個難點在於,程序通常會讓日誌文件保持打開狀態,而如果文件在這種時候突然被旋轉,那麼整體結構很有可能被破壞。這一點的解決方案非常粗暴,直接發送信號到程序,而程序必須能夠進行異步響應。我同事近期的騷操作讓我嘲笑了他好久,當時他想要禁用文件日誌,只使用 stdout,於是他將日誌文件的路徑更改為了 /dev/null。問題得到了完美的解決,直到 Python 的 RotatingFileHandler 決定旋轉文件,毫無疑問,整個應用都崩潰了。

  4. 至於傳送機制,有些程序會直接打開文件並開始記錄日誌,有些會將數據發送到 UDP(syslog),還有些則會把 JSON 消息發送到 Sentry。具體怎麼做,完全取決於當時的流行方法,或者是開發者腦袋裡想到了哪個。

日誌對我來說就是個絕佳的例子,幾乎所有的人都被迫來處理這個問題,操作系統還不能做到完全支持。這也是為什麼我們需要轉向發送結構化消息的原因之一。

類似 Kibana、Graylog 這樣的大型軟件包通常是基於 Elasticsearch 的,這可以說是朝著這個方向邁出的重要一步,但他們也僅僅只是強調了這種需求的存在。

對未來操作系統的暢想

放棄所有奇怪的字符串格式,不管是程序啟動時傳遞的命令行參數還是程序間的通信,然後用簡潔且易於編寫的語言取代結構定義,這樣的想法不香嗎?

一種可以在描述數據的同時利用數據類型,諸如 dict、list、int、string、委託或者是繼承。不管用戶還是程序都不用再擔心對結構的大部分解析和猜測,因為操作系統已經幫你把這些都做完了,OS 也就可以直接以其本來的格式獲取數據。

關於對象

當然,我所說的對象(object)並不是編程語言 C++ 或是 Java 中的對象,有些人對此很是敏感。

我所指的是根據其所操作的數據對函數進行分組的一般性概念。不需要使用類(class),不需要寫類的代碼,甚至不需要繼承(inheritance),儘管有些委派(delegation),但用起來很方便。

/sys 文件系統之中的 GPIO 子目錄,包括指定數據寫入方向控製文件,以及一個讀取或寫入數據的數據文件。當然在理想情況下,常規方法複製和實例化對象仍然是可行的,將其傳遞給其他對像或方法,並在內部進行檢查。這裡使用的仍然是原始的對象系統,其中對象由目錄表示,數據由文件表示,方法由控製文件及其對應操作表示。

對象位於最底層的鍵值數據上,如果該鍵存儲的是方法名,那麼就可以執行代碼,否則返回數據。對象與數據庫中的 /key-value/ 條目的區別不是很大,主要在於存儲代碼以及委派的能力,委派表現在如果在 child 中找不到該 key,則會搜索其 parent。

因此,我這裡所說的對象,都是允許委派、引用其他鍵值結構或對象、可以反射,並在理想情況下具有同像性的一般性鍵值結構。

關於消息

網絡協議最底層能夠保持結構化消息格式統一這一點令我很是驚嘆,每一個 TCP/IP 的數據包都有明確的頭部(header)和地址,可以在全球範圍內大規模流通。既然如此,為什麼這種統一不能存在於計算機或者更高層的結構之間呢?

在單個計算機或是在互聯網範圍內處理單個對象的方法(method)或者是對象本身,它們的本質是一樣的嗎?

數據庫而非文件系統

經過思考,我認為文件系統徹底被數據庫取代是無法避免的。我所指的數據庫並不是某個特定的 SQL 數據庫,而是支持存儲數據類型、原子性、索引、事務、日誌以及任意結構化數據,包括大塊純二進制數據存儲的通用結構化系統。

可以自動保存任意結構的數據輸入,而不需要經過無用的序列化和反序列化過程。這樣的最終結果會是一串字節流,而我們也不必糾結這串字節流到底是存在於內存中還是磁盤中。

作為數據庫中可尋址代碼塊集合的程序

一旦有了允許本地存儲結構化信息的文件系統,那麼程序以大量連續字節序列的形式與世隔絕地存儲就不再有意義,相反,人們會選擇從中搭建類似微服務架構的東西。

如果以一種足夠抽象的角度來看,那麼程序就是一個對象,是一個數據的集合,是由其所包含的函數所操控。它只會以某些標準形式進行封裝與通信(stdin/out/err,套接字,信號,環境,錯誤代碼,文件寫入等等)。如果一個文件系統可以直接存儲這些對象,那麼為何不讓該對象的個體或是方法同樣可以被從外部訪問?

數據被發布之後,基於數據流的舊方法(套接字、文件等等)就可以不用了,你只需返回結構化數據即可。代碼仍然可編譯,也可用各種編程語言, 不同之處在於結果。與其直接將二進制 blob 發送到 CPU 並將其與其他進程和系統隔離開,我們將直接得到 API 調用和其輸出的定義。有點類似於共享庫,只不過它是系統本機元素的最終形式。

配置格式和文本解析的終結

配置文件以及其多種多樣的格式存世是因為文件系統不能存儲其結構。因為如果結構可以被存儲,那麼人們就可以直接存儲對像或所有的鍵值對,當下次需要使用的時候直接調用。這樣就可以不用在 [configuration] 區域加上字符 RUN = 1 ,然後解析,反序列化,然後再次序列化。相反,我們可以直接在配置對像中將 RUN 保存為 True。

因為格式上的區別沒有了,有的只是結構上的不同,這樣我們就不再需要和 JSON 和 CSV 打交道了。

總結簡單來說,我們需要可以保留數據類型而不是文件系統的層次結構數據庫:既包含了存儲為可以互相調用的程序的集合,又包括外部可調用函數、配置文件和用戶數據,這些都是可訪問以及可尋址結構的形式;簡化用於在用戶輸入編碼的某種結構時所使用的輕量級文本格式的讀寫;程序直接交換結構,而並非使用文本協議進行通信。

總之,對五十年前設計的迭代改進已經足夠了,現在是時候提出新方案了。

免責聲明:

這篇文章是我個人的想法,我並沒想影響或者改變誰。可能你會覺得這些想法很奇怪或者荒謬,那麼也請保留你的意見。

如果你認為這篇文章都是沒深度的廢話,那麼請去讀一讀 Genera 上面的文章:http://bitsavers.trailing-edge.com/pdf/symbolics/software/genera_8/Genera_Concepts.pdf

你會發現幾十年前就有能夠實現我所寫內容的圖形系統。而這個系統如此出色以至於現在都還有愛好者群體存在。

參考鏈接:

http://blog.rfox.eu/en/Programmer_s_critique_of_missing_structure_of_oper.html