008-C語言開發的開始

       上次介紹瞭如何進入保護模式以及完成程序的跳轉,那麼這一次我們就將轉向高級語言編寫主要的程序代碼。但是在這之前我們必須考慮一個問題,那就是在彙編語言中,你可以自由指定你寫的程序的加載和讀取位置(至少也是可以指定相對位置),但是像C語言這樣的高級語言恐怕就沒這麼簡單了。使用C語言編寫程序的時候,編譯器會自己去識別棧區和堆去,自己來決定加載位置,具體的方案取決於編譯器的選擇以及優化參數。這算得上是C語言的缺點,也算是優點。之所以這麼說是因爲,你無法決定的東西你也就不用去關心,內存的分配你大可交給編譯器自己來處理,我們程序員的任務就專心寫代碼邏輯就好。另一方面,我們爲什麼選擇C語言而不是其他的高級語言呢?這是因爲,C語言真的真的是太適合用來寫操作系統了。在早先,有兩位IT行業的大神,他們一個叫K,一個叫R,合稱K&R,他們來原本是在開發UNIX操作系統,但是覺得,用純粹的彙編來進行開發實在效率太低,而且還不太容易找幫手,因爲門檻高,於是兩人腦袋一熱,創造了一個具備很多彙編語言性質的高級語言,專門用於開發UNIX,這便是C語言(當然那個時候還不叫C語言,後來經過改進以後才更名爲C語言)。不過K&R也沒有想到在今天,C語言居然可以這麼盛行,C語言完成的事情已經遠遠超過了它本身的職責。但是,我們通過C語言的誕生至少可以證明一件事,C語言非常適合寫操作系統,因爲它本來就是爲了寫操作系統而誕生的,這纔是老本行。

       不過這還不能成爲全部的理由,說C語言適合寫操作系統,最主要的原因是在於,它同時具有高級語言的思維和彙編語言的靈活。我們看到C語句都是讓人類更容易理解的語言,我們寫C寫的是邏輯,是程序邏輯,而不是計算機的執行邏輯,這使得C具備了高級語言的思維。與此同時,C語言有一個殺手鐗,那就是指針,通過指針,我們可以直接去訪問內存中的某一地址的內容,而不是像其他高級語言那樣對底層實現的封裝保護。因此我們說C同時具備了高級語言的思維和彙編語言的靈活,它是一個介於高級語言和彙編語言之間的一個語言。所以,你可以以一個適用於高級程序員的思維(程序邏輯思維)來寫C代碼,你也可以以一個適用於底層程序員的思維(機器執行思維)來寫C代碼,這就使得C語言成爲了入門程序設計的首選語言,因爲它易於上手,並且在上手以後,往高層可行,往底層亦可。不過這也同時造成了C語言易於上手難於深究的特性。想學會C語言很容易,但是想精通C語言,還是需要時間和功底的。說了這麼多是想表達,我們所開發的操作系統是橫跨底層和高層的,是屬於軟硬件界面的部分,所以,必須同時擁有高級語言思維和底層機器思維才能寫出符合邏輯的優秀的操作系統來。如果非要把這一過程歸到軟件開發的某一部分的話,這應當屬於非常經典又非常深入的面相過程的程序設計。

       用C語言來寫操作系統,我們不得不多多考慮其語言特性。由於其面相過程性,我們寫出的C程序就是由很多個過程(可以暫時把一個過程理解成一個函數吧)組成的,函數之間有着自己的調用邏輯,但是,我們必須制定以下這整個程序的主過程是誰,其他的過程都將會直接或間接唄這一主過程調用。說白一點就是,我們得指定C程序的主函數,並且還要使它編譯後被放在軟盤的0x1b00的位置(之所以是這個地址,這是由上一次書寫的代碼來決定的),所以,在將C程序與我們之前寫的程序進行拼接時,就要找到這個函數位置,然後才能進行正確的拼接。那麼,如何來實現這一過程呢?

       在解釋這一問題之前,要先明白程序從書寫到執行的具體過程。首先來說下彙編程序,一個彙編程序(後綴爲.asm)文件將由彙編器來進行轉換,把我們寫的每一句彙編指令都替換成爲對應的機器碼,生成一個.bin文件,也就是純二進制文件。不過這個.bin文件是沒辦法直接使用的,只有你像我們之前那樣,把它當做一個鏡像文件加載到軟盤裏,然後放到計算機上去執行,並且還是要按照BIOS加載的方式來執行才能完成。但是,我們現在的情況並不是這樣,因爲,我們不是用一個.asm文件就完成了整個操作系統的開發,我們有很多很多的源文件,而最終,這些源文件要整體生成一個鏡像文件纔可以。那麼,要使得這些文件組合在一起,並且是按照正確的方式組合在一起,就需要另外一個工具,叫做鏈接器。我們在編譯源文件的時候不要直接把它生成目標二進制代碼,而是生成一個用於鏈接的中間文件,這個文件中除了程序的具體過程外,應當還需要一個索引表,用於高速鏈接器,我這個文件裏有哪些函數,有哪些變量是用於鏈接的,換句話說,是可以被其他文件來使用的,與此同時,還需要寫出這個文件需要其他文件的哪些函數和變量。鏈接器集合這些信息之後,就可以把所有的這些用於鏈接的文件拼接成一個最終的二進制文件了。

       用於鏈接的文件的擴展名以前是用.obj,但是由於後來在平面設計中,一個產品工程的保存擴展名也用了.obj,雖然這二者沒有半毛錢關係,但是經常容易搞混,所以後來的鏈接文件多以.o作爲擴展名,其實這二者並沒有任何區別,現在都能夠見到。所以,這個.o文件和.bin文件還是有區別的,.bin是成品文件,而.o就是用來鏈接的,它一定要經過鏈接器的處理才行,哪怕僅僅只有一個.o文件也是如此。

       然而,我們的C源文件還需要更多的步驟,我們想,如果能夠把.c文件轉化爲.asm文件,那後面的東西就好辦了,而這個過程已經有軟件實現了,就是C的原始編譯器,例如在GCC中集成的CC1就可以吧.c轉化爲.gas(也就是GNU的彙編),當然可以繼續使用GNU彙編工具編譯成.o,也存在gas向asm轉化的工具,這個都無所謂了,編譯器做的事情。

       我們要明白的就是,C語言文件要先唄轉化爲彙編文件,然後再轉化爲可鏈接文件,最終轉化爲最終的二進制文件。這期間可能會經過很過過程,也會生成很多中間文件,但這些本質是不變的。那麼在瞭解了這些之後,我們就可以放心使用C語言進行開發了。

       既然C語言最終還是要被編譯成彙編語言,那麼,其實C函數和彙編函數是沒有什麼本質區別的,我們當然可以用C語言去調用匯編函數,也可以用彙編語言去調用C函數,只不過都需要加入鏈接頭(C語言中的體現就是函數聲明),剩下的工作交給鏈接器就好。在開始敲C代碼之前,我們還必須瞭解一件事情,就是所謂C語言中數據類型的實質。在經歷了彙編的洗禮之後,我們瞭解到無論是數據還是指令,在馮諾依曼體系結構的計算機中,沒有本質區別。既然指令和數據都沒有本質區別了,那數據和數據之間就更沒有本質區別了。C語言中的數據類型其實在底層看來都是同樣的產物,他們只不過是在內存中一個又一個字節的二進制數而已。那麼C語言的數據類型有什麼意義呢?這當然是針對高級語義上的意義,同樣一段數據,你把它理解成整數,還是浮點數,還是字符,當然會得到不同的意義,注意這個意義是我們人類所理解的意義,而不是對於計算機的意義。我們使用計算機一定不是爲了讓計算機自娛自樂就好,我們費盡心機苦心積慮到最後的目的就是爲了讓計算機爲我們人類服務。所以,把計算機能夠存儲的數據按照合適的方式解讀出來它才能具有意義。這個步驟是相當重要的。因此,C語言作爲高級語言,自然也要達成這個目的。數據類型首先決定了,要一次讀取幾個字節的內存空間,其次還決定了按照哪種方式來解讀這些數據。而至於數據類型之間的類型屏障以及隱式轉換,只不過是編譯器的保護行爲而已。想打破它,用強制類型轉換即可。強制類型轉換並不會改變數據,只是改變了我們讀取數據的方式而已。(注:整型和浮點型的轉換同樣不會改變原始數據,但並不是簡單的換了解讀方式,在這之間還做了一步等值轉換。)所以,在我們進行底層開發的時候,一定要考慮到這一點。再一點要說明的是,作爲C語言的殺手鐗——指針,首先我們要清楚指針也只不過是一種數據類型罷了,但是這個數據會被解讀爲某一內存地址,在實際的開發過程中,我們一定要明白哪些變量是堆棧中的,哪些我們是通過一個指針來直接控制內存中指定位置的數據的,這一點相當重要,如果不搞清楚的話一定會出很多bug,而且無解。關於這些要注意的事項,在以後遇到的時候回專門來說明,也會經常強調。

       在瞭解透徹C語言的這些特性之後,我們就可以正式開始用C語言進行操作系統的開發了。下次將會開始OSMain函數的書寫與講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章