哈嘍,我是子牙老師。今天咱們聊聊這個話題吧,Linux作為當今科技世界的地基,我們越來越接近真理了,有木有?
這個文章的角度,你可能全網都很難找到第二篇如此系統講透這個問題的文章
你可能想問:你之前不是寫過操作系統嗎,怎么又寫一個Linux系統?
我之前寫的,或者說做的課程,或者你們從這個網站上下載的,都是操作系統內核
操作系統內核,是一套管理硬件的程序,用戶是用不了的。但是內核給開發用戶態程序提供了豐富的API。基于Linux內核的API,開發出了Linux系統,即用戶可以使用的用戶態。其中包括:ubuntu、centos、redhat、Android、鴻蒙……Linux內核牛不牛逼?
基于Linux內核提供的API,基于Linux操作系統,又誕生了今天科技世界的基礎設施:AI層、redis、MySQL、nginx、docker…還有很多很多關鍵基礎設施,比如kvm……
我之前說Linux是當今科技世界的地基,有人說我亂說,真是無知者無畏。我后來想明白了,所處高度不同,眼前看到的風景自然不同,何必與夏蟲語冰
寫一個操作系統內核,是你學習計算機專業課《操作系統導論》《計算機組成原理》《數據結構》……或者說計算機學科所有專業課,最頂級的方式!而且生動有趣形象,那些看不見摸不著的理論,你在寫的過程中,都變得具象化了:頁表、內存映射、缺頁異常、寫時復制、進程切換、線程調度、線程上下文、中斷觸發……
那怎么開發一個用戶態的Linux系統呢?以下,enjoy
緣起
看到這種圖后,我的大腦開始拋出無數的問題
我后面理清了思緒,大概有這些問題:
- 用戶態能看到的頂層進程是1號進程,那有沒有0號進程呢?
- 0號進程在Linux內核的設計中承擔了什么樣的角色?
- 我記得最開始玩Linux的時候,用戶態的頂層進程是init,與當前systemd之間什么關系?
- 用戶態的頂層進程systemd,在內核態長啥樣
- 自己開發的Linux系統,是如何與Linux內核關聯起來的,比如busybox
- Linux內核是如何進入用戶態的
- Linux內核是如何基于一個可執行文件起一個進程的
……
如果你恰好也有這些問題,受實力所限,無法得到答案,那太好了。BTW,技術還行哦,對Linux的理解能到這個程度
瓦特?你都不知道我在說什么?那你現在的水平,在AI時代,是非常危險的!趕緊去提升實力吧!ChatGPT能幫你寫代碼?ChatGPT能幫任何使用他的人寫代碼,你跟別人的區別在哪呢?或者說,誰能更好的使用AI呢?是那些對行業理解得更廣更深的人,對吧
同樣使用ChatGPT,你覺得你跟我,誰能最大化發揮ChatGPT的價值?
Linux始祖進程
看《玫瑰的故事》,黃亦玫的女兒名字叫太初,我覺得蠻好聽的。太初其實就是始祖的意思,只不過含蓄一些。你們覺得,黃亦玫希望她的女兒是誰的始祖呢?我覺得應該是自己的始祖吧,永遠做自己,不被世俗污染與束縛,滿滿的大愛
《道德經》中說“道生一,一生二,二生三,三生萬象”。在道家的角度,零即為道。我猜Linux內核如果是中國人寫的,它的進程結構應該是這樣的
那Linux內核的始祖進程是誰呢?init_task,即0號進程。這個進程不像其他進程,是通過函數__do_fork創建出來的,這個進程是內核開發工程師編織出來的,如圖
你是不是想問:為什么是編織,而不是通過程序創建?那你是否想過,今天的科技世界的源頭,一定有一個編譯器,是用二進制寫出來的。一樣的道理,總得有雞才有蛋。你是不是想問,沒有蛋哪來的雞?雞可以造出來,或者由其他物種變異而來,蛋就真的沒辦法
CPU何時切入0號進程執行的呢?這個有點特別,CPU不會切入0號進程,內核開發工程師會將0號進程與BSP核(CPU啟動核)進行綁定。這個操作是在Linux內核的很早期完成的,在創建1號進程、啟用所有AP核之前,在這兩個階段之前,很關鍵。代碼沒找著,自己寫代碼測的,Linux內核是這么干的
關于0號進程,還有一個關鍵點就是,當BSP核執行完所有初始化動作,0號進程就進化為idle進程,就是當CPU沒事干的時候執行的進程,對應的代碼:CPU進入低功耗,響應中斷喚醒
更特別的是,進化為idle進程的0號進程可以被多個CPU核同時運行!
Linux1號進程
1號進程就好理解很多了。但是如果你想搞明白它與用戶態,與一個可執行文件是如何關聯上的,牽扯的東西就比較多了。但是于我而言,沒啥難度,畢竟對于Linux,我已經建立了較為完整的認知
先看1號進程的創建,0號進程創建了兩個內核線程:kernel_init(1號)、kthread(2號)
kernel_init內核線程就是1號進程systemd的內核態,kernel_init是如何進入用戶態的呢?三個途徑
接下來從源碼層面講解內核線程kernel_init進入用戶態細節,其實就是函數run_init_process
函數run_init_process的調用鏈,我已經畫好了圖。
進入用戶態
從上一段觀點可知,Linux內核進入用戶態,默認會去找init程序,先說對init的處理吧
run_init_process調用鏈中,會進入一個非常核心的函數:load_elf_binary,這個函數就是完成了進程的創建,內存空間長這樣。其實就是將硬盤上的init程序,按照進程內存空間布局規范,實現程序內存化,又稱進程態
看代碼區吧,這是kernel_init內核線程進入用戶態要執行的地方。理論上每個進程的代碼區都是從0x40000開始的,但是實際中略有偏差,我們看init程序的代碼入口點:0x400890,記住這個位置,后面會講到
這時候進程就創建完了,等待調度。還有一點講下,所有進程的入口函數都不是可執行文件中的那個,而是ret_from_fork,在這設置的
這個函數非常非常非常關鍵,就是所有進程由內核態進入用戶態的那座橋!關于內核第一次由內核態切用戶態,這個不懂匯編、中斷的小伙伴可能看不懂了。這里是模擬中斷跨態切換實現的。我就不展開講了,直接看代碼跟內存吧。當init進程獲得調度,會進入函數ret_from_fork
在這個函數的執行過程中,我們找到init程序的入口點0x400890就找到答案了
至此,秘密全部揭開!太暢快了!
對了,還有init與systemd,它們都是內核線程kernel_init進入用戶態的存在形式。早期是init,現在是systemd
如果你看懂了這篇文章,你就知道如何寫一個Linux系統。