第一章讀書讀書筆記----從hello world說起

        在讀《程序員自我修養》這本書之前,是準備寫讀書筆記的,一來加深自己對本書的理解,而來可以和看到筆記的網友們互相討論。當讀到第三章的時候,心理暗暗偷懶,不想寫博客了,要將自己的所讀所理解的整理爲一篇文章,確實會花費不少時間和精力,可又想了想了當初的目標和想法,還是決定寫下去。

C語言Hello World程序你瞭解多少?

        第一章作者就拋出了一個問題,從一個簡單的C程序的HelloWorld程序(如下所示),你認識到了多少?先不談這個問題的答案,先思考思考,如果你和我一樣並不能儘可能多的聯想到背後所涉及到的複雜而豐富的技術原理…….那麼就一起讀讀這本書吧!

#include <stdio.h>
int main()
{
         printf(“HelloWorld!\n”);
         return0;
}

        作者問了如下幾個問題,大家一起來思考思考? (我做了一些簡化)

        (1)    這個程序爲什麼要編譯之後才能運行?

        (2)    編譯器做了些什麼事情?

        (3)    最後編譯出來的可執行文件裏面有什麼?

        (4)    C語言運行時庫是什麼?

        (5)    如果不同的編譯器,編譯出來的結果一樣嗎?

        (6)    Hello World是怎麼執行的?

        (7)    printf是怎麼實現的?爲什麼可以有不定的參數數量?

        (8)    Hello World運行時在內存中是什麼樣子?

        是不是和我一樣,能說出一些,但又似乎對自己的答案不夠信服啊?這個在之後的章節和書的閱讀中慢慢解開答案(當然了也只是我個人的讀後理解,希望討論)。

計算機五大部件

        無論我們的軟件如何運行起來,他們都是建立在有一臺計算機硬件的基礎之上,最初的計算機結構是由計算機之父馮·諾依曼提出,由輸入設備,輸出設備,存儲器,運算器以及控制器等5大部件組成。


(圖片來自於<<我的青春誰做主--計算機電源面面觀>>)

        輸入設備:典型的輸入設備包括鍵盤,鼠標,麥克風等等

        輸出設備:典型的輸出設備包括顯示器,磁盤,打印機等等

        存儲器:在這個結構中,就是現在所說的內存,用來存儲運行的程序和數據。

        運算器:在計算機指令中的加減乘除等等運算,都由其來完成

        控制器:在程序邏輯控制和輸入/輸出設備之間的交互均有控制器負責,控制器和運算器合起來就是我們所說的CPU,即計算機的大腦。

        計算機和二進制是不可分離的,二進制即只用0和1來表示數值,而計算機也只能識別0和1. 爲啥計算機只能識別0和1呢?計算機歸根到底就是一個可以智能控制的電路板,而在集成電路板中用高電壓代表1,低電壓代表0,也就是表示了兩種不同的狀態,而芯片運行的原理就是布爾代數。詳細的原理可以查看《爲什麼計算機能讀懂1 和 0 ?》。

        計算計算機能夠認識0或者1,那麼最初的設計的邏輯指令和數據都以二進制形式表示,於是也就有了以穿孔紙帶作爲輸入、輸出的計算機,穿孔表示1,無孔表示0. 那時候能玩計算機的真是上古大神了,可是如果一直用1和0來作爲程序和數據輸入,效率低下而且可擴展性非常不好。那用自動化思想來說,凡是機器能做的爲什麼要交給人去做呢?於是慢慢也就有了彙編語言,這不彙編裏面的”mov”, “add”, “dec”等等一眼看去就能知道是什麼含義了,於是那時候的程序員就可以擺脫寫1和0的程序的煩惱了,在寫完彙編程序後,用匯編編譯器直接翻譯爲機器識別的二進制碼就可以了。再接着一些高級語言Pascal,Fortran,C語言啊等等應運而生,面對不同的需求,高級語言的語法也不同,目標卻是一致的,讓程序開發者越來越方便。而他們的編譯器,就幹着一件特別重要的事兒,將高級語言編譯成機器可識別的指令和數據。

操作系統

        最初的程序只能一個接一個的執行,後來發展了有多道程序(Multiprogramming)的處理方法,即有一個監控程序發現正在執行的程序暫時不需要CPU的使用(比如等待某個I/O事件),則監控程序調度另一個等待執行的程序進行執行。但這種方法太過簡單粗暴,如果正在執行的程序一直佔用CPU,那麼某一個緊急的等待執行的程序還是沒有及時得到調度。接着又有了分時系統(Time-Sharing Sytem),在書中提到在Windows 3.1中,程序調用Yield、GetMesssage和PeekMessage幾個系統調用時,操作系統會判斷是否有其他程序正在等待CPU,如果有,則可能暫停當前程序,讓出CPU。然而在這個分時系統裏面,如果有一個程序直接執行while(1)這種霸佔CPU的死循環,則整個系統也得不到切換。這個主要是書中的描述,但是我認爲這個並不是真正的分時系統,改革進行的不夠徹底,真正的分時,應該是讓程序能夠真正的公平的獲取到執行的時間片。當前長使用的調度方式是多任務(Multi-tasking)系統,這個時候調度策略可以根據每個Task的優先級進行調度(優先級策略:輪轉法,先來先服務,多級反饋隊列等算法,在此暫時就不做過多的研究)。

        說完調度,咱麼再談一談設備的使用。比如要在一個顯示設備上畫一根直線,最原先的方式是往I/O端口0x1001寫一個命令0x1111,然後從端口0x1002讀取一個4字節的顯存地址,然後在先存上畫直線,可是如果換成了另一個設備,可能又是另一種做法?如果直接這樣操作十分的繁瑣,如果劃一條直線只需要調用LineTo函數該多好啊,然後函數的內部屏蔽了各種設備的區別。

        再來談談內存,在程序運行的時候,程序體是要被加載到內存的。在最初的時候面臨着以下幾個問題:

        地址空間不隔離:程序直接訪問內存,這樣很容易訪問到其他程序的內存地址空間,造成程序執行異常。

        內存使用效率低:比如一個128M的內存,如果A程序需要使用80M,這時候需要執行B程序,而B程序也需要80M空間,出現空間不足時,又因爲需要連續的內存空間,則需將A整個程序導出到磁盤,然後再加載B進行執行。如果您還要訪問程序A呢,則又需要將B導出到磁盤,再導入A進行執行。這樣做內存的使用率也是非常低下的。

        程序運行的地址不確定:由於直接訪問物理內存,則在載入程序到內存之前,內存地址是無法確定的。

        於是就引入了虛擬內存的方法去解決這個問題,每一個進程/任務都有自己獨立的虛擬地址空間,一般情況下,比如是32位的地址線,不管實際的物理內存有多少,虛擬內存的空間爲4G大小。有了虛擬內存,程序只需要訪問自己的虛擬地址,然後系統會將其轉換爲實際的物理地址,那麼地址空間不隔離程序運行地址的不正確的問題就解決了。爲了解決內存使用效率低的問題,又有了分頁的概念,簡單的描述就是,將虛擬內存和物理內存分割爲相等大小的一塊一塊的,每一塊的概念也就叫做一頁。Intel Pentium系列處理器支持4KB或者4MB的大小。實際的程序,也將分爲一頁一頁的和實際的物理內存相對應,比如一個128M的物理內存,A程序使用了80M,這時候需要執行B程序,B程序也需要80M的空間,這時候如果完全加載B程序,則A程序只需要讓出32M的空間(如果是4M分頁,則換出8個分頁到磁盤),如果A訪問到被替換到磁盤的分頁,則這時候,只需要將這個分頁從磁盤替換到內存中。再也不需要,牽一髮而動全身了,大大的提高了內存的利用效率。

        以上提到了任務調度,設備使用,內存管理,這麼些高級的管理功能,當然需要有一個響亮的稱號,叫做操作系統。有了操作系統,他可以去負責任務調度,內存分配,他也可以幫我們在內核態實現設備驅動,用戶態提供系統調用,應用程序。大大的方便了廣大程序員和電腦使用者。關於操作系統的理論,廣而深,有空的時候可以慢慢研究。

線程

        一個進程由至少一個線程或者多個線程構成,線程是一個程序執行流程的最小單元。比如CPU有兩個核,一個進程由兩個線程,則可以一個CPU核跑一個線程,實現兩個線程併發執行。一個進程內的線程共享進程空間,共享資源等等,每個線程由線程ID,當前指令指針,寄存器集合和棧等組成。

         線程的狀態大致可分爲3個:

         運行(Running): 線程正在執行

         就緒(Ready): 線程可以立刻執行,但CPU正在被使用

         等待(Waiting): 線程正在等待某一個事件(比如IO)發生,無法執行。

         這三個狀態的切換如下圖所示:


         多線程程序有可能會訪問同一個共享資源,而這個資源很有可能只允許互斥訪問(即同一時間只允許一個線程訪問),或者多個線程之間需要同步執行。(在這裏就不再構造資源互斥,和線程同步的場景了,本書的重點並不在這裏,相信有實際編程經驗的都能夠理解。)對於線程間的互斥執行和同步,一般可以使用信號量,互斥量,臨界區,讀寫鎖等等去實現。

         如果在程序中採用了volatile變量和鎖,就一定要保證多線程的安全嗎?不一定,因爲volatile變量保證的是相關的指令不會在編譯的時候進行優化,互斥量保證了其保護區域內的資源互斥訪問,但是CPU有時候會動態的調整指令優化執行順序,從而讓你的多線程程序變的不安全。這種情況一般稱爲CPU亂序。有一些CPU提供指令(一般稱之爲barrier)阻止CPU將該指令之前的指令交換到barrier之後,反之亦然。

 

        結束語: 知識在不斷的加深,理解和實踐,纔會變成自己的能力和經驗。希望大家有問題一起探討。

 

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