2011年4月13日 星期三

邏輯、Logic、邏輯、Logic、邏輯、Logic

想來以前還真的沒腦袋XD如何說沒腦袋呢?也就是對寫出來的東西並沒有經過安排,好類似家說的spaghetti code。設計程式好比在建築一般,如果只是不要倒塌,或者設計出一個功能有的系統,就會做得很類似堆砌的工作,好比一個房子建好了,該有的功能都有了,但是用起來就是不順手,外觀就是不好看。

pattern一詞來自建築業,其實當設計師在設計房屋的時候不只考慮到單一的問題,比方說洗手間需要通風良好的地方這個pattern,如果說單單為了這個理由隨便把它放在玄關附近,那整個房子就相當的不倫不類了,一進門就看到洗手間的房子,訪客及主人一進門就聞到洗手間的味道,還真的是不好的印象。所以設計師可能就要考慮把洗手間挪到其他的地方,讓一個問題解決的pattern不會影響到其他的pattern才是整體設計的最高指標。雖然有時無法完全排除,但是總要找到一個相對較好的解決方案。

一個房子該有的功能有了,是最基本的要求,相對於此,整體完整性的設計是更高的標竿,然而整體後面支撐的就是邏輯性、合理性。我過去總是沒有腦袋的把所有問題解決就好,從來沒有思考過如何讓整體得到更好的解決,因為我很少去review自己寫過的東西。

自從看到約耳的文章,認為把code丟掉重新撰寫是一個浪費時間的行為,跟某個日本作者提到,寫下來的文章可以不斷地修改,精煉其中的語文,除了責任感之外更是一種工藝的表現,讓我十分汗顏,我想到我過去丟掉過無數的code,很少對自己的文章重新檢視,以後得好好地加強這個部分,這樣才能做一個好的designer而不是單單的programmer

選擇Python

Python在國內完全比不上國外的發展,幾乎大多數的程式語言不是主流的C#/C++/C跟java就是PHP(web),在不然就是VB,國內的Python資源幾乎是非常缺乏

一開始接觸python是想要在ruby與其中挑選一個,一個script語言,我的要求是可以快速驗證一些我想處理的資料跟想法,而一開始挑選python跟ruby老實說是有點被"騙"了XD,就是網路上那種十分鐘快速建立blogger,我很好奇,如何在十分鐘內完成blogger的coding??結果就是天下沒有白吃的午餐

那麼為何會放棄ruby而選python??因為接觸了rubby一陣子之後,發現他是蠻物件導向的語言,我選擇script就是想輕裝上陣,如果還要考慮物件導向,那麼不就很累人了
難道是OO不好嗎?不是這麼說,瞭解OO, pattern這類的東西,大多是把本來coding的複雜度,轉換為關係的複雜度,只要瞭解到了,你付出了怎樣的成本,獲得怎樣的好處,適度使用OO,也是可以相當的愉快的

python並沒有很完整的支援物件導向,他使用了其他的方式來支援,也就是不是原生的支援物件導向,這點使得他在開發物件導向的程式會受到好些限制,不過同時也換來了相當多的彈性。另外一點不同的是python更強調泛型演算法的部份,他的各種容器幾乎都以泛型的設計為主

我想不少programmer跟我有一樣的"幻覺",認為只要熟悉C/C++之後,其他程式語言上手就很快了,我想這只是語法部分,至於語言中間設計的方式可能要花上三到六個月才能體會出來。如果一個C++換到java很快會發現,他開發速度不如一個java老鳥的人,他或許會解釋為對java library的不熟悉。可是很多不只是java library的問題,比方說code的簡潔程度,code撰寫方式,很多本身是依賴於程式的特性的,更貼近的一點的,比方java有seriable介面來處理物件轉成檔案的問題,可是C++並沒有,這時候C++的programmer可能會自己處理,這並不是表示C++使用者做不出這樣的功能,換了程式語言之後兩者之間生產力的問題。

語言的特色影響了熟悉他與否的programmer生產力,倒不是本身programmer能力的限制,在我慢慢深入python之後,我覺得我會用的程式語言"愈來愈少",因為本身背後設計的原理我能理解大多數的還真的不多!!

2009年7月9日 星期四

Python的基本集合(collection)型態

翻譯作集合(collection)好像蠻弔詭的,因為工程的人很習慣把他們當作set,anyway~
基本上python支援三種(不管是2.6 or 3.0),分別是tuple, list, map

基本上這三種就相當的夠用了,其實也只有兩種,tuple可以看成constant list,不變的list
如果有學過其他程式語言,就更單純了,把list視為list或者動態array,把map當成hash
一切就很自然了

hair = "black", "brown", "blonde", "red"
hair[2]

上面宣告了一個tuple,存取的方式就是使用[]運算子來取得,上面的例子hair[2]就是blonde囉

list基本上跟tuple差不多,差別是,你不能去改變tuple的內容,但是list可以,list有幾種基本運算
宣告空的list就是list=[],夠簡單吧
list.append(x) 在list尾端加入x
list.count(x) 計算list中x元素的個數
list.extend(x) 跟append差不多
list.index(x,start,end) 在[start,end)搜尋元素x,如果找到回傳index,沒找到會丟出例外
list.insert(index,x) 在index之後插入x
list.pop() 取出list最後一個元素,並且回傳他
list.pop(index) 取出list最後一個元素,並且回傳他
list.remove(x) 刪除x元素
list.reverse() 將list反向排列
list.sort() 排序list的內容

如果有基本資料結構的概念就知道,list可以輕易的做到array, list, stack, queue這幾種資料結構
當然,如果你是實際講求效能的,可能就會懷疑[]這運算子是否為O(1)的時間等等,這倒是我不知道的,如果知道的網友,請告訴我,謝謝
那二維陣列呢?多維度陣列呢?這是很幸運與不幸的事情,幸運的是,只要用簡單list在包含list就可以了,比方說list[5]=[],這樣子,同時你也看到一個問題了,他並不保證list元素中是list,也不保證list中的list長度一致,這就是script language的特色吧

最後看到map型態,就把它當作hash table來操作吧,使用{}來宣告空map,裡面的元素key:value使用分號來分開,如同範例
d = {"root": 18, "blue": [75, "R", 2], 21: "venus", -14: None,"mars": "rover", (4, 11): 18, 0: 45}


這裡也再度看到script language的特色,他並不要求你key是相同型態,也不要求value是相同型態,這同時有好處也有壞處
基本的操作有
map.clear() 清空map
map.copy() 複製map
map.items() 取出所有key,value的pair
map.keys() 取出所有的key
map.values() 取出所有的value
通常讀取map會直接用map[key],新增會用map[key]=value
至於拜訪的方式,晚點再寫吧

2009年3月31日 星期二

Python IDE

一直想要有一款輕盈的IDE,當然其中我認為比較成熟的IDE有eric IDE, eclipse+plugin, NetBeans+plugin,有不少IDE是基於python的,現階段python 3.0剛推出,很多IDE因為這樣不能用orz

eric IDE要編譯QT(M$下)很麻煩,eclispe跟Netbeans啟動都不算快,所以囉,最好找到一款ulipad,感覺還可以,目前正在適應中

2009年3月28日 星期六

Python

最近又開始看重複的看ruby跟python,我實在是個半弔子,我對兩者都不熟悉,不過根據最近的一些觀察,我想我會傾向python,尤其常常在linux底下看到python開發的東西

先說說python吧,這的是一個頗為弔詭的東西,他的語法有強烈的限制性,不過發明的人應該覺得維護性的重要性比較重要,不想要一個語言有千百種寫法。另外一個印像是他有眾多lib,這一點我蠻喜歡的。另外一個詭異的是,這麼不錯的一個語言,竟然缺乏一個良好的IDE,簡直叫人難以接受,一個功能比較齊全的IDE竟然要自行編譯QT等等的才能在M$底下執行(在linux底下就簡單多了)。再加上目前是2.0跨向3.0(很多東西不相容),所以資料跟一些lib有點銜接不上來。種種的現象都叫我很不能"接受"

再回頭看看ruby,他class定義的確顯的蠻幽雅(看看那些java的reader/writer method真的有點orz),也有不錯的IDE,可是他的lib相對於python比較少,資料結構的資源好像也不是那麼多,可是ruby on rails這部分又是比python好。

兩相比較下,python給我的驚奇比ruby多,因為一些功能性上來說,python顯的比較完備(我想拿python來做些簡單程式開發,不大需要RoR的東西,所以Ruby的吸引力比較小),於是我決定進入python的世界,可是如果單單就受歡迎性,ruby因為有RoR的加持,再加上目前很多人再開發web application,ruby聲勢顯的比python大,這也有可能造成以後ruby補完之後擠掉python,呵呵

會想要學習python之類的東西也很簡單,因為目前硬體已經發展到這種程度,程式的開發時間遠大於執行時間,如果在單單驗證想法跟一些簡單的功能上面,script language已經比C/java之類來的簡單。或許python之類的執行能力比C/java之類來的差,可是開發可即刻執行的優點超越了他們。另外script language有個很大的缺點就是,大多沒有特定的GUI,這會讓開發應用程式上面有蠻大的"障礙",因為等於要去銜接某種GUI lib,這在開發上比較吃虧(這一點java就做的很好)

anyway~沒有哪種語言比較好,只是有時殺雞不用牛刀,挑一種適合的程式語言去解決人們碰到的問題才是王道~呵呵

2008年12月30日 星期二

Linux Kernel核心中文手冊

這是一個作者不詳的文件,如果有人知道的話,歡迎通知我,讓我補上,另外這是一份介紹性的文件
==================================================
  Chapter 1

 

Hardware Basic(硬件基礎知識)

 

一個操作系統必須和作為它的基礎的硬件系統緊密配合。操作系統需要使用一些只有硬件才能提供的功能。為了完整的瞭解Linux,你需要瞭解底層硬件的基礎知識。本章對於現代PC的硬件進行了。

1975年1月「Popular Electronics」雜誌封面上印出了Altair 8080的圖片,一場革命開始了。

Altair 8080,跟隨早期的「Star Trek epsode」命名,只需要$397,就可由個人電子愛好者自己組裝。它擁有Intel 8080處理器和256字節內存,但是沒有屏幕和鍵盤。以今天的標準來衡量,它太簡陋了。它的發明者,Ed Roberts,製造了名詞「personal computer「來命名他的發明,但現在,PC這個名詞已經用來命名幾乎所有你可以不依靠幫助就可以自己運行起來的計算機。用這個定義,甚至一些十分強 大的Alpha AXP系統也是PC。

愛好者們看到了Altair的潛力,開始為它寫軟件,製造硬件。對於這些早期的先驅來講,它代表著自由:從被神職人員控制和運行的大型批處理的主機 系統中逃脫出來的自由。你可以在自己家裡甚至廚桌上擁有計算機,這使學院的退學生為此著迷並通宵達旦。與此同時出現大量硬件,在一定程度上各自不同,而軟 件專家則樂於為這些新機器撰寫軟件。有諷刺意味的是,IBM在1981年發佈了IBM PC並於1982年早期供貨,從此定義了現代PC的模型。它擁有Intel 8088處理器,64K內存(可以擴充到256K),兩個軟驅和一個80x25的彩色圖卡(CGA),用今天的標準衡量,它功能不算很強,但是它銷售的不 錯。1983年,緊接著推出的IBM PC-XT,則擁有一個豪華的10M硬盤。不久大批公司如Compaq開始製造IBM PC的複製品,PC的結構成為了事實的標準。這個事實的標準使大批硬件公司可以在這個不斷增長的市場上一起競爭,反過來,可以遏制價格,讓用戶滿意。現代 PC承襲了早期PC的許多系統體系特徵。甚至基於最強大的Intel Pentium Pro的系統也可以運行Intel 8086的尋址模式。當Linus Torvalds開始開發後來的Linux時,他選擇了當時最常見和價格最合理的硬件平台:一台Intel 80386 PC。

從PC的外面看,最明顯的部件就是機箱、鍵盤、鼠標和顯示器。在機箱的前面有一些按鈕,一個小屏幕顯示一些數字,還有一個軟驅。現在的大多數系統還有一個CD-ROM期、驅動器。如果你需要保護你的數據,那麼還會有一個備份用的磁帶機。這些設備一律被看作外設。

雖然CPU管理整個系統,但它並不是唯一的智能設備。所有的外設控制器,例如IDE控制器,也都擁有一定程度的智能。在PC內部(圖1.1),你可 以看到一個主板,包括CPU或微處理器、內存和一些ISA或PCI外設控制卡的槽位。其中一些控制器,如IDE磁盤控制器可能內置在系統主板上。

 


CPU

 

CPU,或者說微處理器,是所有計算機系統的心臟。微處理器進行數學運算,邏輯操作並從內存中讀取指令並執行指令,進而控制數據流向。計算機發展的 早期,微處理器的各種功能模塊是由相互分離(並且尺寸上十分巨大)的單元構成。這也是名詞「中央處理單元」的起源。現代的微處理器將這些功能模塊集中在一 塊非常小的矽晶片製造的集成電路上。在本書,名詞CPU、微處理器和處理器交替使用。 微處理器處理二進制數據:這些數據由1和0組成。這些1和0對應電氣開關的開或關。就好像42代表4個10和2個單元,二進制數字由一系列代表2的冪數的 數字組成。這裡,冪數意味著一個數字用自身相乘的次數。10 的一次冪是10,10的2次冪是10x10,10的3次冪是10x10x10,依此類推。二進制0001是十進制1是十進制2,二進制0011是十進制 3,二進制0100是十進制4,等等。所以,十進制42是二進制101010或者(2+8+32或21+23+25)。在計算機程序除了使用二進製表示數 字之外,另一種基數,16進制,也經常用到。在這種進制中,每一位數字表示16的冪數。因為十進制數字只是從0到9,在十六進制中10到15分別用字母 A,B,C,D,E,F表示。例如,十六進制的E是十進制的14,而十六進制的2A是十進制的42(2個16+10)。用C語言的表示法(本書一直使 用),十六進制數字使用前綴「0x」:十六進制的2A寫做0x2A。

 

微處理器可以執行算術運算如加、乘和除,也可以執行邏輯操作例如「X是否大於Y」。

處理器的執行由外部時鍾控制。這個時鍾,即系統時鍾,對處理器產生穩定的時鍾脈衝,在每一個時鍾脈衝裡,處理器執行一些工作。例如,處理器可以在每 一個時鍾脈衝裡執行一條指令。處理器的速度用系統時鍾的頻率來描述。一個100Mhz的處理器每秒鍾接受到100,000,000次時鍾脈衝。用時鍾頻率 來描述CPU的能力是一種誤解,因為不同的處理器在每一次時鍾脈衝中執行的工作量不同。雖然如此,如果所有的條件同等,越快的時鍾頻率表示處理器的能力越 強。處理器執行的指令非常簡單,例如:「把內存位置X的內容讀到寄存器Y中「。寄存器是微處理器的內部存儲空間,用來存儲數據並進行操作。執行的操作可能 使處理器停止當前操作而轉去執行內存中其他地方的指令。正是這些微小的指令集合在一起,賦予現代的微處理器幾乎無限的能力,因為它每秒可以執行數百萬甚至 數十億的指令。

 

執行指令時必須從內存中提取指令,指令自身也可能引用內存中的數據,這些數據也必須提取到內存中並在需要的時候保存到內存中去。

 

一個微處理器內部寄存器的大小、數量和類型完全決定於它的類型。一個Intel 80486處理器和一個Alpha AXP處理器的寄存器組完全不同。另外,Intel是32位寬而Alpha AXP是64位寬。但是,一般來講,所有特定的處理器都會有一些通用目的的寄存器和少量專用的寄存器。大多數處理器擁有以下特殊用途的專用的寄存器:

 

Program Counter(PC)程序計數器

這個寄存器記錄了下一條要執行的指令的地址。PC的內容在每次取指令的時候自動增加。

Stack Pointer(SP)堆棧指針

處理器必須能夠存取用於臨時存儲數據的大容量的外部讀寫隨機存取內存(RAM)。堆棧是一種用於在外部內存中存放和恢復臨時數據的方法。通常,處理 器提供了特殊的指令用於將數據壓在堆棧中,並在以後需要是取出來。堆棧使用LIFO(後進先出)的方式。換句話說,如果你壓入兩個值x和y到堆棧中,然後 從堆棧中彈出一個值,那麼你會得到y的值。

一些處理器的堆棧向內存頂部增長,而另一些向內存的底部增長。還有一些處理器兩種方式都可以支持,例如:ARM。

 

Processor Status(PS)

指令可能產生結果。例如:「X寄存器的內容是否大於Y寄存器的內容?「可能產生真或假的結果。PS寄存器保留這些結果以及處理器當前狀態的其他信息。多數處理器至少有兩種模式:kernel(核心態)和user(用戶態),PS寄存器會紀錄能夠確定當前模式的那些信息。

 


Memory(內存)

 

所有系統都具有分級的內存結構,由位於不同級別的速度和容量不同的內存組成。

最快的內存是高速緩存存儲器,就像它的名字暗示的一樣-用於臨時存放或緩存主內存的內容。這種內存非常快但是比較昂貴,因此多數處理器芯片上內置有 少量的高速緩衝存儲器,而大多數高速緩存存儲器放在系統主板上。一些處理器用一塊緩存內存同時緩存指令和數據,而另一些處理器有兩塊緩存內存-一個用於指 令,另一個用於數據。Alpha AXP處理器有兩個內置的內存高速緩存存儲器:一個用於數據(D-Cache),另一個用於指令(I-Cache)。它的外部高速緩衝存儲器(或B- Cache)將兩者混在一起。

最後一種內存是主內存。相對於外部高速緩存存儲器而言速度非常慢,對於CPU內置的高速緩存存儲器,主內存簡直是在爬。

高速緩存存儲器和主內存必須保持同步(一致)。換句話說,如果主內存中的一個字保存在高速緩存存儲器的一個或多個位置,那麼系統必須保證高速緩存存 儲器和主內存的內容一樣。使高速緩衝存儲器同步的工作一部分是由硬件完成,另一部分則是由操作系統完成的。對於其它一些系統的主要任務,硬件和軟件也必須 緊密配合。

 


Buses(總線)

系統板的各個組成部分由被稱為總線的連接系統互連在一起。系統總線分為三種邏輯功能:地址總線、數據總線和控制總線。地址總線指定了數據傳輸的內存 位置(地址),數據總線保存了傳輸的數據。數據總線是雙向的,它允許CPU讀取,也允許CPU寫。控制總線包含了各種信號線用於在系統中發送時鍾和控制信 號。有許多種不同的總線類型,ISA和PCI總線是系統用於連接外設的常用方式。

 


Controllers and Peripherals (控制器和外設)

 

外設指實在的設備,如由系統板或系統板插卡上的控制芯片所控制的圖形卡或磁盤。IDE控制芯片控制IDE磁盤,而SCSI控制芯片控制SCSI磁 盤。這些控制器通過不同的總線連接到CPU並相互連接。現在製造的大多數系統都是用PCI或ISA總線將系統的主要部件連接在一起。控制器本身也是象 CPU一樣的處理器,它們可以看作CPU的智能助手,CPU擁有系統的最高控制權。

 

所有的控制器都是不同的,但是通常它們都有用於控制它們的寄存器。CPU上運行的軟件必須能夠讀寫這些控制寄存器。一個寄存器可能包含描述錯誤的狀 態碼,另一個寄存器可能用於控制用途,改變控制器的模式。一個總線上的每一個控制器都可以分別被CPU尋址,這樣軟件設備驅動程序就可以讀寫它的寄存器進 而控制它。IDE電纜是一個好例子,它給了你分別存取總線上每一個驅動器的能力。另一個好例子是PCI總線,允許每一個設備(如圖形卡)被獨立存取。

 


Address Spaces(尋址空間)

 

連接CPU和主內存的系統總線以及連接CPU和系統硬件外設的總線是分離的。硬件外設所擁有的內存空間稱為I/O空間。I/O空間本身可以再進一步 劃分,但是我們現在先不討論。CPU可以訪問系統內存空間和I/O空間,而控制器只能通過CPU間接訪問系統內存。從設備的角度來看,比如軟驅控制器,它 只能看到它的控制寄存器所在的地址空間(ISA),而非系統內存。一個CPU用不同的指令去訪問內存和I/O空間。例如,可能有一條指令是「從I/O地址 0x3f0讀取一個字節到X寄存器「。這也是CPU通過讀寫系統硬件外設處於I/O地址空間的寄存器從而控制外設的方法。在地址空間中,普通外設(如 IDE控制器,串行端口,軟驅控制器等等)的寄存器在PC外設的多年發展中已經成了定例。I/O空間的地址0x3f0正是串行口(COM1)的控制寄存器 的地址。

 

有時控制器需要直接從系統內存讀取大量內存,或直接寫大量數據到系統內存中去。比如將用戶數據寫到硬盤上去。在這種情況下,使用直接內存存取(DMA)控制器,允許硬件設備直接存取系統內存,當然,這種存取必須在CPU的嚴格控制和監管下進行。

 


Timer(時鍾)

所有操作系統需要知道時間,現代PC包括一個特殊的外設,叫做實時時鍾(RTC)。它提供了兩樣東西:可靠的日期和精確的時間間隔。RTC有自己的 電池,所以即使PC沒有加電,它仍在運行。這也是為什麼PC總是「知道」正確的日期和時間。時間間隔計時允許操作系統精確地調度基本工作。

 

 

Chapter 2

 

Software Basic(軟件基礎)

程序是用於執行特定任務的計算機指令組合。程序可以用彙編語言,一種非常低級的計算機語言來編寫,也可以使用和機器無關的高級語言,比如C語言編 寫。操作系統是一個特殊的程序,允許用戶通過它運行應用程序,比如電子錶和文字處理等等。本章介紹了基本的編程原理,並簡介操作系統的目的和功能。

 

2.1 Computer Languages(計算機語言)

 

2.1.1.彙編語言

 

CPU從內存中讀取和執行的指令對於人類來講無法理解。它們是機器代碼,精確的告訴計算機要做什麼。比如十六進制數0x89E5,是Intel 80486的指令,將寄存器ESPEBP中。早期計算機中最初的軟件工具之一是彙編程序,它讀入人類可以閱讀的源文件,將其裝配成機器代碼。彙編語言明確 地處理對寄存器和對數據的操作,而這種操作對於特定的微處理器而言是特殊的。Intel X86微處理器的彙編語言和Alpha AXP微處理器的彙編語言完全不同。以下Alpha AXP彙編代碼演示了程序可以執行的操作類型:

 

Ldr r16, (r15) ; 第一行

Ldr r17, 4(r15) ; 第二行

Beq r16,r17,100; 第三行

Str r17, (r15); 第四行

100: ; 第五行

 

第一條語句(第一行)將寄存器15指定的地址中的內容加載到寄存器16中。第二條指令將緊接著的內存中的內容加載到寄存器17中。第三行比較寄存器 16和寄存器17,如果相等,分支到標號100,否則,繼續執行第四行,將寄存器17的內容存到內存中。如果內存中的數據相同,就不必存儲數據。編寫彙編 級的程序需要技巧而且十分冗長,容易出錯。Linux系統的核心很少的一部分是用彙編語言編寫,而這些部分之所以使用彙編語言只是為了提高效率,並且和具 體的微處理器相關。

 

2.1.2 The C Programming Language and Compiler (C語言和編譯器)

 

使用彙編語言編寫大型程序十分困難,消耗時間,容易出錯而且生成的程序不能移植,只能束縛在特定的處理器家族。更好的選擇是使用和機器無關的語言, 例如C。C允許你用邏輯算法描述程序和要處理的數據。被稱為編譯程序(compiler)的特殊程序讀入C程序,並將它轉換為彙編語言,進而產生機器相關 的代碼。好的編譯器生成的彙編指令可以和好的彙編程序員編寫的程序效率接近。大部分Linux核心是用C語言編寫的。以下的C片斷:

if (x != y)

x = y;

執行了和前面示例中彙編代碼完全一樣的操作。如果變量x的內容和變量y的內容不一樣,變量y的內容被拷貝到變量x。C代碼用例程(routine) 進行組合,每一個例程執行一項任務。例程可以返回C所支持的任意的數值或數據類型。大型程序比如Linux核心分別由許多的C語言模塊組成,每一個模塊有 自己的例程和數據結構。這些C源代碼模塊共同構成了邏輯功能比如文件系統的處理代碼。

 

C支持多種類型的變量。一個變量是內存中的特定位置,可用符號名引用。上述的C片斷中,x和y引用了內存中的位置。程序員不需要關心變量在內存中的具體位置,這是連接程序(下述)必須處理的。一些變量包含不同的數據例如整數、浮點數等和另一些則包含指針。

 

指針是包含其它數據在內存中的地址的變量。假設一個變量x,位於內存地址0x80010000, 你可能有一個指針px,指向x。 Px可能位於地址0x80010030。Px的值則是變量x的地址,0x80010000。

 

C允許你將相關的變量集合成為結構。例如:

Struct {

Int I;

Char b;

} my_struct;

是一個叫做my_struct的數據結構,包括兩個元素:一個整數(32位)I和一個字符(8位數據)b。

 

2.1.3 Linkers(連接程序)

 

連接程序將幾個目標模塊和庫文件連接在一起成為一個單獨的完整程序。目標模塊是彙編程序或編譯程序的機器碼輸出,它包括機器碼、數據和供連接程序使 用的連接信息。比如:一個目標模塊可能包括程序的所有數據庫功能,而另一個目標模塊則包括處理命令行參數的函數。連接程序確定目標模塊之間的引用關係,即 確定一個模塊所引用的例程和數據在另一個模塊中的實際位置。Linux核心是由多個目標模塊連接而成的獨立的大程序。

 

2.2 What is an Operating System(什麼是操作系統?)

 

沒有軟件,計算機只是一堆發熱的電子元件。如果說硬件是計算機的心臟,則軟件就是它的靈魂。操作系統是允許用戶運行應用程序的一組系統程序。操作系 統將系統的硬件抽象,呈現在用戶和應用程序之前的是一個虛擬的機器。是軟件造就了計算機系統的特點。大多數PC可以運行一到多個操作系統,而每一個操作系 統從外觀和感覺上都大不相同。Linux由不同功能的部分構成,這些部分總體組合構成了Linux操作系統。Linux最明顯的部分就是Kernel自 身,但是如果沒有shell或libraries一樣沒有用處。

 

為了瞭解什麼是操作系統,看一看在你輸入最簡單的命令時發生了什麼:

 

$ls

Mail c images perl

Docs tcl

$

這裡的$是登錄的shell輸出的提示符(此例是bash):表示shell在等候你(用戶)輸入命令。輸入ls引發鍵盤驅動程序識別輸入的字符, 鍵盤驅動程序將識別的字符傳遞給shell去處理。shell先查找同名的可執行映像,它找到了/bin/ls, 然後調用核心服務將ls執行程序加載到虛擬內存中並開始執行。ls執行程序通過執行核心的文件子系統的系統調用查找文件。文件系統可能使用緩存的文件系統 信息或通過磁盤設備驅動程序從磁盤上讀取文件信息,也可能是通過網絡設備驅動程序同遠程主機交換信息而讀取本系統所訪問的遠程文件的詳細信息(文件系統可 以通過NFS網絡文件系統遠程安裝)。不管文件信息是如何得到的,ls都將信息輸出,通過顯示驅動程序顯示在屏幕上。

 

以上的過程看起來相當複雜,但是它說明了即使是最簡單的命令也是操作系統各個功能模塊之間共同協作的結果,只有這樣才能提供給你(用戶)一個完整的系統視圖。

 

2.2.1 Memory management(內存管理)

 

如果擁有無限的資源,例如內存,那麼操作系統所必須做的很多事情可能都是多餘的。所有操作系統的一個基本技巧就是讓少量的物理內存工作起來好像有相 當多的內存。這種表面看起來的大內存叫做虛擬內存,就是當軟件運行的時候讓它相信它擁有很多內存。系統將內存分為容易處理的頁,在系統運行時將這些頁交換 到硬盤上。而應用軟件並不知道,因為操作系統還使用了另一項技術:多進程。

 

2.2.2 Processes (進程)

 

進程可以看作一個在執行的程序,每一個進程都是正在運行的特定的程序的獨立實體。如果你觀察一下你的Linux系統,你會發現有很多進程在運行。例如:在我的系統上輸入ps 顯示了以下進程:

$ ps

PID TTY STAT TIME COMMAND

158 pRe 1 0:00 -bash

174 pRe 1 0:00 sh /usr/X11R6/bin/startx

175 pRe 1 0:00 xinit /usr/X11R6/lib/X11/xinit/xinitrc --

178 pRe 1 N 0:00 bowman

182 pRe 1 N 0:01 rxvt -geometry 120x35 -fg white -bg black

184 pRe 1 <>

185 pRe 1 <>

187 pp6 1 9:26 /bin/bash

202 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black

203 ppc 2 0:00 /bin/bash

1796 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black

1797 v06 1 0:00 /bin/bash

3056 pp6 3 <>

3270 pp6 3 0:00 ps

$

 

如果我的系統擁有多個CPU那麼每個進程可能(至少在理論上如此)都在不同的CPU上運行。不幸的是,只有一個,所以操作系統又使用技巧,在短時間 內依次運行每一個進程。這個時間段叫做時間片。這種技巧叫做多進程或調度,它欺騙了每一個進程,好像它們是唯一的進程。進程相互之間受到保護,所以如果一 個進程崩潰或不能工作,不會影響其他進程。操作系統通過給每一個進程一個獨立的地址空間來實現保護,進程只能訪問它自己的地址空間。  

2.2.3 Device Drivers(設備驅動程序)

 

設備驅動程序組成了Linux核心的主要部分。象操作系統的其他部分一樣,它們在一個高優先級的環境下工作,如果發生錯誤,可能會引發嚴重問題。設 備驅動程序控制了操作系統和它控制的硬件設備之間的交互。比如:文件系統向IDE磁盤寫數據塊是使用通用塊設備接口。驅動程序控制細節,並處理和設備相關 的部分。設備驅動程序和它驅動的具體的控制器芯片相關,所以,如果你的系統有一個NCR810的SCSI控制器,那麼你需要NCR810的驅動程序。

 

2.2.4 The Filesystems(文件系統)

 

象Unix一樣,在Linux裡,系統對獨立的文件系統不是用設備標示符來存取(比如驅動器編號或驅動器名稱),而是連接成為一個樹型結構。 Linux在安裝新的文件系統時,把它安裝到指定的安裝目錄,比如/mnt/cdrom,從而合併到這個單一的文件系統樹上。Linux的一個重要特徵是 它支持多種不同的文件系統。這使它非常靈活而且可以和其他操作系統良好共存。Linux最常用的文件系統是EXT2,大多數Linux發佈版都支持。

 

文件系統將存放在系統硬盤上的文件和目錄用可以理解的統一的形式提供給用戶,讓用戶不必考慮文件系統的類型或底層物理設備的特性。Linux透明的 支持多種文件系統(如MS-DOS和EXT2),將所有安裝的文件和文件系統集合成為一個虛擬的文件系統。所以,用戶和進程通常不需要確切知道所使用的文 件所在的文件系統的類型,用就是了。

 

塊設備驅動程序掩蓋了物理塊設備類型的區別(如IDE和SCSI)。對於文件系統來講,物理設備就是線性的數據塊的集合。不同設備的塊大小可能不 同,如軟驅一般是512字節,而IDE設備通常是1024字節,同樣,對於系統的用戶,這些區別又被掩蓋。EXT2文件系統不管它用什麼設備,看起來都是 一樣的。

 

2.3 Kernet Data Structures(核心數據結構)

 

操作系統必須紀錄關於系統當前狀態的許多信息。如果系統中發生了事情,這些數據結構就必須相應改變以反映當前的實際情況。例如:用戶登錄到系統中的時候,需要創建一個新的進程。核心必須相應地創建表示此新進程的數據結構,並和表示系統中其他進程的數據結構聯繫在一起。  

這樣的數據結構多數在物理內存中,而且只能由核心和它的子系統訪問。數據結構包括數據和指針(其他數據結構或例程的地址)。乍一看,Linux核心 所用的數據結構可能非常混亂。其實,每一個數據結構都有其目的,雖然有些數據結構在多個的子系統中都會用到,但是實際上它們比第一次看到時的感覺要簡單的 多。

 

理解Linux核心的關鍵在於理解它的數據結構和核心處理這些數據結構所用到的大量的函數。本書以數據結構為基礎描述Linux核心。論及每一個核心子系統的算法,處理的方式和它們對核心數據結構的使用。

 

2.3.1 Linked Lists(連接表)

 

Linux使用一種軟件工程技術將它的數據結構連接在一起。多數情況下它使用鏈表數據結構。如果每一個數據結構描述一個物體或者發生的事件的單一的 實例,比如一個進程或一個網絡設備,核心必須能夠找出所有的實例。在鏈表中,根指針包括第一個數據結構或單元的地址,列表中的每一個數據結構包含指向列表 下一個元素的指針。最後元素的下一個指針可能使0或NULL,表示這是列表的結尾。在雙向鏈表結構中,每一個元素不僅包括列表中下一個元素的指針,還包括 列表中前一個元素的指針。使用雙向鏈表可以比較容易的在列表中間增加或刪除元素,但是這需要更多的內存存取。這是典型的操作系統的兩難情況:內存存取數還 是CPU的週期數。

 

2.3.2 Hash Tables

 

鏈接表是常用的數據結構,但是遊歷鏈接表的效率可能並不高。如果你要尋找指定的元素, 可能必須查找完整個表才能找到。Linux使用另一種技術:Hashing 來解決這種侷限。Hash table是指針的數組或者說向量表。數組或向量表是在內存中依次存放的對象。書架可以說是書的數組。數組用索引來訪問,索引是數組中的偏移量。再來看書 架的例子,你可以使用在書架上的位置來描述每一本書:比如第5本書。

 

Hash table是一個指向數據結構的指針的數組,它的索引來源於數據結構中的信息。如果你用一個數據結構來描述一個村莊的人口,你可以用年齡作為索引。要找出 一個指定的人的數據,你可以用他的年齡作為索引在人口散列表中查找,通過指針找到包括詳細信息的數據結構。不幸的是,一個村莊中可能很多人年齡相同,所以 散列表的指針指向另一個鏈表數據結構,每一個元素描述同齡人。即使這樣,查找這些較小的鏈表仍然比查找所有的數據結構要快。

 

Hash table可用於加速常用的數據結構的訪問,在Linux裡常用hash table來實現緩衝。緩衝是需要快速存取的信息,是全部可用信息的一個子集。數據結構被放在緩衝區並保留在那裡,因為核心經常訪問這些結構。使用緩衝區 也有副作用,因為使用起來比簡單鏈表或者散列表更加複雜。如果數據結構可以在緩衝區找到(這叫做緩衝命中),那麼一切很完美。但是如果數據結構不在緩衝區 中,那麼必須查找所用的相關的數據結構,如果找到,那麼就加到緩衝區中。增加新的數據結構到緩衝區中可能需要廢棄一個舊的緩衝入口。Linux必須決定廢 棄那一個數據結構,風險在於廢棄的可能使Linux下一個要訪問的數據結構。

 

2.3.3 Abstract Interfaces(抽象接口)

 

Linux核心經常將它的接口抽象化。接口是以特定方式工作的一系列例程和數據結構。比如:所有的網絡設備驅動程序都必須提供特定的例程來處理特定 的數據結構。用抽象接口的方式可以用通用的代碼層來使用底層特殊代碼提供的服務(接口)。例如網絡層是通用的,而它由底層符合標準接口的同設備相關的代碼 提供支持。

通常這些底層在啟動時向高一層登記。這個登記過程常通過在鏈接表中增加一個數據結構來實現。例如,每一個連結到核心的文件系統在核心啟動時進行登記 (或者如果你使用模塊,在文件系統第一次使用時向核心登記)。你可以查看文件/proc/filesystems來檢查那些文件系統進行了登記。登記所用 的數據結構通常包括指向函數的指針。這是執行特定任務的軟件函數的地址。再一次用文件系統登記的例子,每一個文件系統登記時傳遞給Linux核心的數據結 構都包括一個和具體文件系統相關的例程地址,在安裝文件系統時必須調用。

2008年11月2日 星期日

Refactoring跟Unit Test

打開refactoring的書皮,他有手法的列表,我看完之後,天阿,是簡單阿,可是...可是...這不是人做的工作阿~~~~~隨便找個程式開始refactoring,很容易發現,你很難在上百個refactoring步驟之後還保證你的程式無誤,瑣碎的步驟本身就是引入bug的危機阿

接著當然作者提出了一個相當有效且正面的手法Unit Test,有了Unit Test你就會有勇氣(很可惜不是保證程式正確,不過我真的感覺到Unit Test跟張網子一樣,你摔下去有人接著,雖然未必接得住XD),有這機制的監控,也比較容易保持程式的正確性;他帶來另外一個好處是,很容易小步驟的修改程式,而非wafterfall的開發手法

C++比較可悲一點,他的refactoring的工具不如java多跟完整,對於java來說,我覺得目前做得相當好的是eclipse(雖然我覺得這工具在寫GUI程式上面蠻陽春的,不要跟我提起他的plug-in,我都裝不起來:P),有了工具終於可以讓程式設計師好好的做設計,不然光重複打字、搬動、重新命名、刪除,我想就有一堆錯誤了。

給將要進入refactoring跟使用unit test的人,請千萬記得做兩件事,一,找到好的工具,二,使用Unit Test ( P.S. 當然先把refactoring跟unit test怎樣使用弄懂)