標籤

2008年11月20日 星期四

Thermal Management

Intel Thermal Monitor

processor 來實作 Thermal Monitor 和 Thermal Monitor 2,會有一個 thermal sensor 來觸發這兩個 monitor,觸發哪一個在製造處理器時就設定好了,透過 interrupt LVT entry (在 local APIC 裡面)可以了解是否有觸發這個 sensor。

在 Intel Thermal Monitor 中,從 Pentium M 的處理器開始支援 Bi-directional PROCHOT#,他透過外部的 agent 驅動,並且要啟動 TCC( Thermal Control Circuitry) 或是 Enhanced TCC。

另外還有所謂的 Demand Mode,當我們 enable 處理器的 clock modulation,TCC 能透過寫入 IA32_CLOCK_MODULATION Model Specific Register 打開,這個 Register 每一次執行都會被複製,並且 hardware 會選出效能最好的解析。

註:duty cycle 工作週期/負載週期

2008年11月17日 星期一

CPU 的 cache 和 latency

http://www.csie.ntu.edu.tw/~r89004/hive/cache/page_1.html

這篇文章主要是探討現在的 CPU 的 cache 和記憶體系統之間的關係。

CPU 速度的進展,一直比記憶體的速度進展要來得快。在 IBM PC XT 的時代,CPU 和記憶體的速度是差不多的。不過,後來 CPU 的速度就愈來愈快。再加上 DRAM 需要 refresh 才能保存資料的特性,DRAM 很快就跟不上 CPU 的速度了。現在的 CPU 都利用了 pipeline 的方式,可以每個 cycle 都 issue 一個(甚至多個)指令,再加上現在的 CPU 時脈也比記憶體的時脈高,記憶體的速度可說是遠遠落在 CPU 之後了。

為了避免記憶體成為 CPU 速度的瓶頸,現在的 CPU 都有 cache 的設計,甚至還有多層的 cache。Cache 的原理,主要是利用到大部分的程式,在處理資料時,都有一定程度的區域性。所以,我們可以用一小塊快速的記憶體,來暫存目前需要的資料。

例如,幾乎所有的程式,大部分的執行時間是花在一些迴圈中。這些迴圈通常都不大,可能只佔整個程式空間的百分之一。如果一個程式經常要執行這段程式數千、甚至數萬次,那就可以把這一小段程式放在 cache 中,CPU 就不需要每次都到很慢的主記憶體中讀取這段程式了。很多一般用途的程式,在存取資料時,也有類似的特性。因此,cache 的幫助非常大。如果沒有 cache 的話,我們就不需要這麼快的 CPU 了,因為系統的速度會卡在記憶體的速度上面。

現在的 CPU 往往也有多層的 cache。例如,Intel 的 Pentium III 500Mhz CPU,有 32KB 的 L1 cache,和 512KB 的 L2 cache。其中,L1 cache 內建在 CPU 內部,速度非常快,而且它是 Harvard 式,即指令用的空間和資料用的空間是分開的。Pentium III 500Mhz CPU 的 L1 cache 是分成 16KB 的 I-cache 和 16KB 的 D-cache。而 L2 cache 則是在 CPU 外面,以 250Mhz 的速度運作。另外,它和 CPU 之間的 bus 也只有 64 bits 寬。L2 cache 通常就不會區分指令和資料的空間,也就是 unified cache。

Cache 對速度有什麼影響呢?這可以由 latency 來表示。CPU 在從記憶體中讀取資料(或程式)時,會需要等待一段時間,這段時間就是 latency,通常用 cycle 數表示。例如,一般來說,如果資料已經在 L1 cache 中,則 CPU 在讀取資料時(這種情形稱為 L1 cache hit),CPU 是不需要多等的。但是,如果資料不在 L1 cache 中(這種情形稱為 L1 cache miss),則 CPU 就得到 L2 cache 去讀取資料了。這種情形下,CPU 就需要等待一段時間。如果需要的資料也不在 L2 cache 中,也就是 L2 cache miss,那麼 CPU 就得到主記憶體中讀取資料了(假設沒有 L3 cache)。這時候,CPU 就得等待更長的時間。

另外,cache 存取資料時,通常是分成很多小單位,稱為 cache line。例如,Pentium III 的 cache line 長度是 32 bytes。也就是說,如果 CPU 要讀取記憶體位址 0x00123456 的一個 32 bits word(即 4 bytes),且 cache 中沒有這個資料,則 cache 會將 0x00123440 ~ 0x0012345F 之間的 32 bytes 資料(即一整個 cache line 長度)都讀入 cache 中。所以,當 CPU 讀取連續的記憶體位址時,資料都已經讀到 cache 中了。

我寫了一個小程式,用來測試 cache 的行為。這個程式會連續讀取一塊記憶體位址,並量測平均讀取時間。這個 程式的執行結果如下:

測試平台:

Pentium III 500Mhz, PC100 SDRAM, 440BX chipset
Celeron 466Mhz, PC100 SDRAM, VIA Apollo Pro 133 chipset



程式的執行檔和原始碼可在這裡下載。

由上面的結果可以看出,當測試的區塊大小在 16KB 以下時,平均的 latency 都在 1 ~ 3 cycles 左右。這顯示出 16KB 的 L1 D-cache 的效果。在測試區塊為 1KB 和 2KB 時,因為額外的 overhead 較高,所以平均的 latency 變得較高,但是在 4KB ~ 16KB 的測試中,latency 則相當穩定。在這個範圍中,由於 Pentium III 和 Celeron 有相同的 L1 cache,所以測試結果是幾乎完全相同的。

在區塊超過 16KB 之後,就沒辦法放入 L1 D-cache 中了。但是它還是可以放在 L2 cache 中。所以,在 Pentium III 的情形下,從 32KB ~ 512KB,latency 都在 10 cycles 左右。這顯示出當 L1 cache miss 而 L2 cache hit 時,所需要的 latency。而 Celeron 的 L2 cache 只有 128KB,但是 Celeron 的 L2 cache 的 latency 則明顯的比 Pentium III 為低。這是因為 Celeron 的 L2 cache 是 on-die,以和 CPU 核心相同的速度運作。而 Pentium III 的 L2 cache 則是分開的,且以 CPU 核心速度的一半運作。

在區塊超過 512KB 之後,L2 cache 就不夠大了(Pentium III 500Mhz 只有 512KB 的 L2 cache)。這時,顯示出來的就是 L1 cache miss 且 L2 cache miss 時,所需要的 latency。在 1024KB 或更大的區塊中,Pentium III 的 latency 都大約是 28 cycles 左右,而 Celeron 的 latency 則超過 70 cycles。這是 CPU 讀取主記憶體時,平均的 latency。而 Celeron 的 latency 較高,應該是因為其外頻較低,而倍頻數較高的緣故(Pentium III 500Mhz 為 5 倍頻,而 Celeron 466 為 7 倍頻)。另外,晶片組的差異也可能是原因之一。

Cache 的效果十分明顯。不過,有時候 cache 是派不上用場的。例如,當資料完全沒有區域性,或是資料量太大的時候,都會讓 cache 的效果降低。例如,在進行 MPEG 壓縮時,存取的資料量很大,而且資料的重複利用率很低,所以 cache 的幫助就不大。另外,像是 3D 遊戲中,如果每個 frame 的三角面個數太多,也會超過 cache 能夠處理的範圍。

現在的電腦愈來愈朝向「多媒體應用」,需要處理的資料量也愈來愈大,因此,要如何善用 cache 就成了一個重要的問題。一個非常重要的方法,就是把讀取主記憶體的 latency 和執行運算的時間重疊,就可以把 latency「藏」起來。通常這會需要 prefetch 的功能,也就是 AMD 在 K6-2 及之後的 CPU,和 Intel 在 Pentium III 之後的 CPU 加入的新功能。在下一篇文章中,我們會討論 prefetch 的原理和用途。

CPU 的 cache 和 latency [Part 2]
在上一篇文章中,已經簡單討論過 CPU 的 cache 和其對 latency 的影響。在這篇文章中,我們就以一個較為實際的例子,並說明 prefetch 的原理和用途。

這裡要用的「實際例子」,其實還是很理想化的。為了和 3D 繪圖扯上一點關係,這裡就用「4x4 的矩陣和 4 維向量相乘」做為例子。不過,一般在 3D 繪圖中,都是用 single precision 的浮點數(每個數需要 32 bits),而這裡為了讓記憶體的因素更明顯,我們使用 double precision 的浮點數(每個數需要 64 bits),也就是一個 4 維向量剛好需要 32 bytes。

在這個例子中,我們採取一個 3D 繪圖中,相當常見的動作,也就是把一大堆 4 維向量,乘上一個固定的 4x4 矩陣。如果向量的個數非常多,超過 CPU 的 cache 所能負擔,那麼 CPU 的表現就會大幅下降。

為了讓大家心裡有個底,這裡先把執行的結果列出來:

測試平台: Pentium III 500Mhz, PC100 SDRAM, 440BX chipset



在程式集可以下載程式的原始碼和執行檔。

首先,我們來看沒有使用 prefetch 指令的結果。事實上,結果相當符合預測。在 L1 D-cache 的範圍內(即小於 16KB 的情形),平均的運算時間相當的穩定,約在 51 ~ 52 cycles 左右。這也是 Pentium III 在計算一個 4x4 矩陣和 4 維向量相乘時(使用 double precision 浮點數),可能達到的最快速度。當然,這個程式是用 C 寫成的。如果直接用手寫組合語言,可能還可以再快個 5 ~ 10 cycles。

當資料量超過 L1 D-cache 的範圍,但是還在 L2 cache 的範圍之內時,所需的時間提高到約 60 cycles 左右。在 Part 1 中,我們已經知道 Pentium III 500Mhz 的 L2 cache 大約有 10 cycles 的 latency,所以這個結果也是相當合理的。

當資料量超過 L2 cache 的範圍時,所有的資料就需要從主記憶體中取得了。從圖上可以很容易的看到,每次運算所需的時間增加到 145 ~ 150 cycles。這有點出乎意料之外:在 Part 1 中,讀取主記憶體的 latency 只有 30 cycles 左右,但是在這裡,latency 增加了約 100 cycles。不過,這個結果並不奇怪。因為在運算結束後,運算的結果必須要寫回記憶體中,而寫回記憶體的動作,需要很多時間。

從這裡可以看到,在資料量超過 L2 cache 的範圍時,CPU 可說是被記憶體的速度限制住了。事實上,如果記憶體的速度不變,那即使是用兩倍快的 CPU,速度的增加也會非常有限。以 3D 遊戲的角度來說,1024KB 或 2048KB 這樣的資料量並不算少見,因為一個 single precision 浮點數的 4 維向量,就需要 16 bytes 的空間。65,536 個 4 維向量就需要 1MB 的空間了。

事實上,記憶體的速度雖慢,但是要完成一個 32 bytes(一個四維向量的大小)的讀寫動作,也只需要 60 ~ 70 cycles 而已(以 Pentium III 500Mhz 配合 PC100 SDRAM 的情形來算)。而在不用 prefetch 的情形下,CPU 的動作類似下圖所示:


現在,Load/Store Unit 變成全速運作了。Execution Units 還是沒有全速運作,但是這是沒辦法的。這種情形,就表示出瓶頸是在 Load/Store Unit,也就是在主記憶體的速度。已經沒有任何方法可以加快執行的速度了(除非加快記憶體的速度)。

要注意的一點是,上面的情形是很少發生的真實世界中的。實際的程式,通常瓶頸都是在運算單元。不過,我們的例子則剛好不是這樣(因為矩陣和向量相乘是很簡單的運算),而是類似圖中的情形。

要怎麼告訴 CPU,在計算的同時將下一個資料載入到 cache 中呢?這時就要用到 prefetch 的指令了。在我們的程式中,執行向量運算的程式如下:

for(i = 0; i < buf_size; i += 4) {
double r1, r2, r3, r4;

// 執行矩陣乘法
r1 = m[0] * v[i] + m[1] * v[i+1] + m[2] * v[i+2] + m[3] * v[i+3];
r2 = m[4] * v[i] + m[5] * v[i+1] + m[6] * v[i+2] + m[7] * v[i+3];
r3 = m[8] * v[i] + m[9] * v[i+1] + m[10] * v[i+2] + m[11] * v[i+3];
r4 = m[12] * v[i] + m[13] * v[i+1] + m[14] * v[i+2] + m[15] * v[i+3];

// 寫回計算結果
v[i] = r1;
v[i+1] = r2;
v[i+2] = r3;
v[i+3] = r4;
}
現在,我們在矩陣乘法的前面插入一個 prefetch 指令,變成:

for(i = 0; i < buf_size; i += 4) {
double r1, r2, r3, r4;

// 執行矩陣乘法
r1 = m[0] * v[i] + m[1] * v[i+1] + m[2] * v[i+2] + m[3] * v[i+3];
// 前一行執行完後,整個 4 維向量已經載入到 cache 中。
// 所以,現在用 prefetch 指令載入下一個 4 維向量。
prefetch(v + i + 4);
// 繼續進行計算
r2 = m[4] * v[i] + m[5] * v[i+1] + m[6] * v[i+2] + m[7] * v[i+3];
r3 = m[8] * v[i] + m[9] * v[i+1] + m[10] * v[i+2] + m[11] * v[i+3];
r4 = m[12] * v[i] + m[13] * v[i+1] + m[14] * v[i+2] + m[15] * v[i+3];

// 寫回計算結果
v[i] = r1;
v[i+1] = r2;
v[i+2] = r3;
v[i+3] = r4;
}
這段程式中的 prefetch 函式,裡面執行的是 SSE 的 prefetchnta 指令。Pentium III 和 Athlon 都支援這個指令(AMD 的 K6-2 中另外有一個 prefetch 指令,是 3DNow! 指令的一部分)。這個指令會將指定的資料載入到離 CPU 最近的 cache 中(在 Pentium III 即為 L1 cache)。

只不過加上這樣一行程式,執行結果就有很大的不同。回到前面的測試結果,我們可以看出,prefetch 指令,在資料已經存在 cache 中的時候,會有相當程度的 overhead(在這裡是大約 10 cycles)。但是,當資料不在 cache 中的時候,效率就有明顯的改善。特別是在資料量為 1024 KB 時,所需時間約為 70 cycles,說明了瓶頸確實是在 Load/Store Unit。在 1024 KB 之後,所需的 cycle 的增加,則是因為在多工系統中難以避免的 task switch 所產生的 overhead。

由此可知,prefetch 指令對於多媒體及 3D 遊戲等資料量極大的應用,是非常重要的。也可以預料,將來的程式一定會更加善用這類的功能,以達到最佳的效率。

5/24/2000, Ping-Che Chen

為什麼OS無法識別4GB的記憶體

http://3g.sina.com.cn/3g/tech/index.php?did=634253&cid=827&cpage=3&sid=0&page=3&pwt=rest3&tid=84&vid=44

記憶體控制器的地址總線  
  與主機板安裝的記憶體直接關聯的是記憶體控制器。記憶體控制器一邊管理記憶體,一邊通過地址總線與CPU通信。記憶體控制器的地址總線決定了可以支援的記憶體地址編碼數量,同時記憶體控制器的實際連接記憶體的地址線決定了可以支援的記憶體容量。Intel把記憶體控制器放在北橋,AMD把記憶體控制器放在CPU。所以要分開介紹。

1、Intel的記憶體控制器
  由於Intel的記憶體控制器放在北橋內,記憶體控制器的改變就與北橋密切相關。我們可以看下面的北橋列表:

表1

  從上面的北橋地址總線發展表可以看出,從P965開始記憶體控制器已經是36位地址總線,可以編64GB的地址代碼,除系統佔用的地址碼,分配給主機板安裝的4GB記憶體綽綽有餘。所以,從P965開始Intel的北橋支援4GB記憶體是沒有問題的。

2、AMD的記憶體控制器
  AMD從支援64位架構的CPU(Athlon64)開始把記憶體控制器集成到CPU。記憶體控制器的地址總線是64位架構的,使用40位,可以支援1000GB的地址編碼。所以,AMD 從Athlon64開始,記憶體控制器支援4GB記憶體也是沒有問題的。

BIOS能支援4GB記憶體  
  BIOS不是主機板廠家自己開發的,BIOS版權控制在AMI等幾個公司。它們與Intel和AMD合作共同開發BIOS。所以當Intel和AMD提出「映射」方式解決4GB記憶體問題,AMI等廠商就會開發出相應的BIOS。早期的BIOS裡面會顯示出「映射」選項,用戶可以開啟或關閉。現在已經是默認開啟,不再顯示這項設置。所以可以說,對於現在的BIOS來講,在支援4GB記憶體方面是沒問題的。

4GB記憶體的「原罪」:X86架構
  CPU和記憶體控制器從硬體上為使用4GB記憶體提供了保障。但是還沒有解決4GB記憶體的問題。障礙來自於個人電腦的體系標準——32位X86架構。早在8086時代,記憶體是銲接在主機板上的,一般也就幾KB的記憶體,IBM規定640KB的地址是最高端,這個地址分配給BIOS,接下來有一段地址分配給系統的顯示設備和I/O設備。電腦的迅速發展很快突破640KB記憶體達到並超過1MB。但是640KB下面這一段地址分配不能改變,因為CPU、DOS操作系統都是從640KB讀取BIOS通過640KB下面的顯示設備暫存地址,I/O設備暫存地址與顯示設備、I/O設備通信、交換數據。如果更改,以前的所有電腦都要作廢。所以必須尋找一個辦法,既不改變640KB地址分配,又可以使用640KB到1MB的記憶體。那就是這種地址分配規則不變,採用「記憶體地址映射」技術,把640KB到500多KB的這一段地址映射到640KB-1MB的空間裡,映射工作由BIOS負責。
操作系統則由兩條DOS命令HIMEM.SYS和EMM386.SYS負責,熟悉DOS的用戶,都會知道這兩條命令。通過映射,就可以讓DOS軟件使用更多的記憶體。記憶體發展到4MB,16MB,都是通過「記憶體映射」技術解決。到現在,個人電腦只要兼容DOS,就必須遵守640KB這種地址記憶體分配規則。  

  奔騰Ⅱ時代,SDRAM記憶體容量達到128MB。那時候的地址總線是32位,最大地址空間是4GB,這時32位X86架構已經完全形成。除保留以前的記憶體地址分配規則外,還要把BIOS地址映射到地址空間的頂端——4GB。一直到815時代的DDR記憶體,915時代的DDR2記憶體。真正使用的記憶體單條最大容量為512MB,系統使用的記憶體不會超過2GB。所以4GB記憶體問題沒有顯現。  945時代時1GB的DDR2記憶體出現後,記憶體控制器的設計者們開始考慮主機板安裝4GB的記憶體條。但當時很少有用戶使用4GB記憶體,4GB記憶體問題依然沒有凸現。

圖1

  其實,Intel在915時期就提出要解決32位X86架構硬體支援4GB記憶體的問題。到P965正式解決時,解決方案還是「記憶體地址映射」。這個方案不是新想出來的,在32位X86架構的服務器系統早已經採用,只是被移植到個人電腦上來。  
從64GB的地址空間頂部開始,映射PCI設備的I/O地址,然後映射系統佔用的3GB到4GB的地址。把3GB到4GB的這段地址代碼留給安裝在主機板上的物理記憶體,就可以使用安裝的全部記憶體了。  

為什麼必須要採用「映射」的辦法?
  因為記憶體的編址必須是連續的,不能斷開。而系統的地址空間(也叫邏輯地址)是可以不連續的。就像我們給街道兩邊的房屋號碼牌一樣,編製方案(相當於邏輯地址)可以規劃很大,可以分割。給到這一條街房屋的號碼牌必須是連續的,這是真實的地址,以便於人們按地址尋找房屋。當然這種「映射」工作還是由BIOS完成。要支援4GB記憶體,還必須要BIOS支援。

圖2

  通過記憶體地址重映射把系統佔用的4GB地址還給物理記憶體,是32位X86架構的規則。AMD也要採用這個規則,雖然它的記憶體控制器集成在CPU內。也正是由於記憶體控制器集成在CPU內,北橋不再含有記憶體控制器。主機板廠家開發AMD平台主機板時,會依據市場定位設計主機板支援的記憶體容量。有支援2GB的,也有支援4GB、8GB或更多。所以AMD平台的4GB問題還要看主機板。

操作系統才是「罪魁禍首」  
  操作系統管理和使用物理記憶體,因此在操作系統內也必須有一個邏輯(虛擬)地址系統,與主機板上的所有存儲地址(包括記憶體的、I/O設備的、BIOS的)對應,也有類似「地址總線」的結構。32位操作系統是按32位地址總線設計,32位的地址暫存器,因此只能管理4GB的地址,去掉系統佔用的,也不能完全使用全部4GB記憶體。個人電腦的32位操作系統都不能支援4GB記憶體,即使是VISTA 32 SP1也不能。VISTA 32 SP1比XP 32 SP3進步一點的是在系統屬性裡面可以顯示實際安裝多少內。
32位的Vista SP1已經能順利識別4GB記憶體,但在任務管理器可以看到系統真正使用的記憶體不到4GB。

  那麼哪些操作系統可以支援並使用4GB記憶體?第一是64位操作系統,因為64位操作系統是按64位地址總線設計的。比如Windows XP 64,Vista 64。第二是具有物理地址擴展功能,並且地址暫存器大於32位的服務器操作系統,但有些具備物理地址擴展的服務器操作系統,由於地址暫存器限於32位也不能支援4GB。
寫在最後,我們可以看出,現在的主機板晶片組或記憶體控制器已經不再是吞食我們記憶體的主要原因。而罪魁禍首則是出在我們使用的32位操作系統上。雖然現在的Vista SP1已經能識別到完整的4GB記憶體,但實際使用的記憶體還是和以前的那3GB左右的沒區別。所以現在來談4GB記憶體的普及依然為時過早。另一方面,有的記憶體廠商為了避免出現記憶體浪費的情況發生,推出了三條1GB記憶體的組合套裝,這也是相當貼心的一個設計。

2008年11月14日 星期五

GPIO、routing

GPIO(General Purpose I/O),其接腳可以供使用者由程式控制自由使用,PIN 腳依現實考量可作為GPI、GPO、GPIO。

因此,GPIO 的 PIN,我們要注意的是用途、in/out、還有電位的high/low,可以透過HW線路圖幫助了解。而這些狀態,我們可以透過 register 去了解控制。

特定 GPIO 可以發 GPIO event,像是 SCI、SMI,例如我們 notebook lid 的動作,透過 LID 的線傳到 EC,EC 再送一個 GPIO EVENT 給南橋,然後處理。

電位的改變有 edge 和 level。edge 的電會一直持續,當電位改變的時候,狀態也跟著改變,例如 high 到 low 為 1,則 low 到 high 為0。
level 則是當電位在低的時候,表 low,高的時候表 high。PIN是吃不同電的,這點我們要注意。

接下來說 routing,為什麼要 routing 呢?一個南橋下接了一大堆 device,符合 PCI 規格的 device
都有 ABCD 四根 PIN 可以發中斷(如果是 single function,就只有 int A),而我們所知的 8259,扣掉master和slave連接的一根,真的能用的只有十五根,甚至有些已經在早期分配給固定的 device,根本不夠用,為了分配剩下的 IRQ,所以我們就做了 routing 的動作。也就是說,本來要透過硬體直接連接 PIN,但是因為不夠用,INTEL 多做了幾支 PIN,PCI DEVICES就連接到這些 PIN,用軟體模擬的方式分配,解決不夠用的問題,我們把這樣的機制稱為 PIRQ Route Controller。

IRQ 的值,我們可以在 PCI SPE OFFSET 3C 的地方看到,另外我們有一根 serial IRQ,透過電位形狀來分辨不同的中斷。

在作業系統驅動程式支援 IRQ 共享的狀態下,從早期的 PIC 只支援 15 IRQ,現在的 APIC 可以支援的 215 個。

Routing 的實作方面,可以去看_PRT這個 control method。

2008年11月3日 星期一

ACPI 體系中的重要名詞

http://root.public.blog.163.com/blog/static/31428336200871102434887/


ACPI 體系中的重要名詞
  DSDT: DSDT 稱做 Differentiated Definition Block,存在於 BIOS 中,並與當前的硬件平台兼容,提供了系統的硬件特性(例如某些設備的內部暫存器和儲存器)的應用策略和配置,在系統初始化的時候,DSDT 被當前系統啟動時初始化到namespace中。
  
  FADT:Fixed ACPI Description table,FADT 中包含了 ACPI 的硬件寄存器組(GPE)的應用和配置(包含它們的硬件地址)也包括DSDT表的硬件地址。
  
  ACPI Namespace: 對ACPI層來說,記憶體維持了一個目錄形式,指向每個設備及 GPE 的 namespace,這個 namespace structure 在初始化的時候由 DSDT 創建,namespace structure 可以透過 loadtable 方法從 BIOS 中載入 DSDT 改變,而每個設備在 ACPI 層中都被描述成一個對象,包含有對這個設備特性和操作策略的描述列表,系統所有類型設備都是保存在同一個 namespace structure 下。在 ACPI OS 層上調用 _ADR 來獲得 Namesapce 的設備名,Namespace 的例子見例 1-1:
  
  OSPM(OS-directed Power Management):OSPM 操作系統支援 ACPI 的一個部分,操作系統(OS)可以從操作系統下驅動程序的角度控制 ACPI 子模組,同時支持 ACPI 包括 SCI 中斷,設備事件,系統事件模式,這些事件模式可以充分支持 Hot-plug 方式。
  
  SCI 中斷:(System Control Interrupt) 系統控制中斷,SCI 中斷是一種源自 ACPI 相容晶片系統中斷,系統映射不同的 ACPI 事件中斷向量以便共享此中斷,當底層硬體產生 SCI 中斷的時候(例如設備插入事件引發中斷),根據通知 OSPM 層處理相對應的 ACPI 事件,OSPM 層會調用預先安裝的中斷。
  
  GPE Block Device 和 GPE 事件:GPE Block Device 是平台設計者可按照 FADT(Fixed ACPI Descriptor Table) 描述表中對應 GPE 的暫存器組及 GPE 的輸入 pin。GPE 設備描述塊中的地址存在於 FADT 中,每個 GPE Block Device 可以容納 128 個 GPE 事件,ACPI 層上提供兩個通用目標暫存器組–GPE0_BLK 和 GPE1_BLK,(也就是說可以響應 256 個 GPE 事件)每個暫存器組中包含兩個等長度的暫存器 GPEx_STS,GPEx_EN,他們的系統地址(硬體地址)都保存在 FADT 中,作為 GPE Blocks 的行為(或者是操作),描述部分存在 ACPI name space 中,用於指示當前的設備的事件,例如設備插入/拔除事件發生的時候,相關的狀態(GPEx_STS中,硬件設計的時候相關設備的事件信號會連接到對應的狀態位置)會被外部的事件所設,生成 SCI,讓 OSPM 層運行相關的 control method 通知 ACPI 層;GPEx_EN 表示每個事件是否可用,一般說來在南橋(ICH4)中有這幾個暫存器,它們的硬體位址保存在 FADT 中。
  
  GPE 事件就是通過 GPE 暫存器組引發 SCI 中斷後,通知 OSPM 層有關設備的事件,例如下面介紹 Hot-Plug 的時候會詳細或者簡略地介紹到總線枚舉,設備檢查,設備喚醒,設備彈出幾個事件。
  
 

  ACPI” Source Language(ASL):ASL 語言是 ACPI 層描述特定的 ACPI 對象的 ACPI 專用語言,並且包括了 ACPI 對象的控制方法(Control method),OEM 廠商和 BIOS 設計者在 BIOS 中使用 ASL 定義所有的設備為 ACPI 對象,並且可以生成 ASL 格式的專門的控制方法,1-1 例就是關于 ASL 的例子:
  ASL 的語法規參看 ACPI Specification Revision 2.0

  AML 和 AML 分析器:AML 是 ACPI 控制方法的虛擬機器語言,AML 執行過程也就是 ACPI 核心驅動層,ACPI 控制方法使用 AML 來進行編寫,但是通常而言對編寫者來說是寫成 ASL 的方式,通過 AML 翻譯器進行翻譯,AML 翻譯器不但具備 ASL 的翻譯的功能,而且可以執行 AML 方法,當用 ASL 編寫的 DSDT 表被載入到名字空間的時候,將會被 AML 翻譯器翻譯成執行時候可以辨別的機器碼,例如關鍵字 SCOPE 在進入 AML 編譯器之前中是以一個 ACSII 編碼保存在 DSDT 中,但 DSDT 被載入名字空間之后將變成 0×10 的單字節數值(AML 操作值為 ScopeOP)。對 AML 的編譯過程和轉換方式,ASL 中的關鍵字可以參看 ACPI Specification Revision 2.0 中 section 17 。

2008年10月20日 星期一

雜亂的上課筆記

不同segment 必用 far call (push ip push cs 再jump)
strong weak ->Module 抽換的概念

2008年10月16日 星期四

CALL BACK

http://www.dev.idv.tw/mediawiki/index.php/%E4%BD%95%E8%AC%82callback_function%EF%BC%9F

Description
簡單的說,如果你使用了某個function,那麼你就是『call'了一個function。如果系統或是函式是要求你給一個function pointer,這個function pointer指到一個實際的函式(多半這個函式是你自己寫的)。然後它會在適當的時間呼叫此function,則此function就是所謂的 callback function。因為這個function是被『callback'了。

舉一個C的例子來說:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define DEFAULT_BLOCK_SIZE (4096)

// 定義callback function的prototype。
typedef void (* CALLBACK) (int);

// 定義了一個名為ShowPercentage的函式。這就是我們的callback函式。
// 他的prototype必須與前面的CALLBACK宣告一致。
void ShowPercentage(int percentage)
{
fprintf(stderr, "%dn%nn", percentage);
}

// 定義了一個CopyFile的函式,這個函式會將參數source所指定檔案複製到
// target參數所指定的檔案去。而且每複製DEFAULT_BLOCK_SIZE數量的資料
// 就會呼叫一次callback參數所指到function一次。
void CopyFile(const char *source, const char *target, CALLBACK callback)
{
char buf[DEFAULT_BLOCK_SIZE] ;
struct stat fs ;
int fdSrc, fdTrg ;
int readBytes = 0, totalReadBytes = 0, percentage = 0;
fdSrc = open(source, O_RDONLY);
fstat(fdSrc, &fs);
fdTrg = open(target,O_CREAT|O_TRUNC|O_RDWR);
// 主要複製資料的迴圈
while((readBytes=read(fdSrc, buf, DEFAULT_BLOCK_SIZE)) > 0)
{
write(fdTrg, buf, readBytes);
totalReadBytes += readBytes ;
//複製資料後就呼叫callback函式去做顯示百分比的動作。
callback( (totalReadBytes*100)/fs.st_size);
}
close(fdTrg);
close(fdSrc);
}

int main(void)
{
// 這個範例中只是利用callback來顯示目前的進度。
// 實際上我們可以利用callback來做更多的動作。
CopyFile("A.TXT", "B.TXT", ShowPercentage);
return 0 ;
}


2008年10月15日 星期三

EFI OVERVIEW










  • CPU RESET ENTRY 在fffffff0 ,正好在SEC CORE的位置,擺在ROM的最上面,SEC在CRB實際上也只有一行CODE,他只是一個MODULE而已,他與PEI放在一起,看成一個特殊的PEI MAIN就好。 EFI則是放在BIOS的最下面。
  • IA32,X64,3.5之後的CODE之後target processor都是用x64。
  • PEI_MAIN、NVSTORAGE(存放DATA的地方)、FV_DYNAMIC、FV_BACKUP、FV_RECOVERY。
  • 每一隻MODULE都是一個FILE
  • EFI想像成小型OS,想像HD是BIOS ROM,FV就是partition,Region name就是他的name。會有幾個partition呢,請看FLASHMAP。
  • DSC FILE 描述你有什麼COMPONENT
    ENV檔為COMPONENT的DEFINE
  • ENTRY點都在ASM裡幾乎不會去變動,做PROJECT出錯,要找RESET點,要去哪裡找呢,在IA32的resetvec.asm
  • 建立STACK才可以做FUNCTION CALL
  • package定義component type,告訴他是POST CODE,才不會COMPILER出錯(DSC檔中)
  • 為什麼第四個phase BDS不是擺在最後面,在CODE的前面先出現呢,BDS其實是做DRIVER的CONNECT,DXE只是載入記憶體,有些DRIVER需要CONNECT才可以開始啟動,想像成WINDOWS的DEVICE MANAGER,可以每個DRIVER ENABLE、DISABLE。
  • BDS想像為一個DXE DRIVER (PWR ON->SEC->PEI(Pre EFI initialization)->DXE(Driver Execution Environment)->BDS(Boot Device Selection)->OS)
  • 在SEC階段 SS可用 DS不可用,不要去動,在C中不要加入STATIC,不然會被放到DS中
  • SEC會LINK PEI MAIN載入它,這時想像PEI是個小型的OS, 此時有很多的FUNCTION CALL可以用,例如FV SERVICE。基本上是一個檔案一個檔案載入進來,順序是系統決定,以PEI來講,大致上跟我們寫的順序一樣,除非有特殊的DEPENDENCY。
  • 所謂的BOOT FV,就是放PEI MAIN的地方,包含PEI CORE 和 MODULES。沒有他就找不到NEXT FV。
  • PEI CODE大部分在ROM執行,但後面可能可以進到MEMORY了
  • Driver 有沒有進去可以看FVMAIN.INF 裡面會列出所有的component
  • FILE就是FILE拉~
  • 每一個INF檔是一個COMPONENT 被放在FVMAIN裡面
  • flashmap.h有所有的flash region的base、size、offset,sub region也有。
  • 有了GUID,PPI即可找到相對的記憶體位置
  • 不要把兩個INF檔放再同一個目錄
  • PEI沒有RAM,怎麼擺資料結構呢 :用STACK去建立HOB LIST
  • 一個PPI,就是一個GUID加上一個POINTER
  • proto type: 定義一個 function應該傳入什麼參數。
  • PEI到PXE的階段,必須把記憶體位址的INFORMATION PASS 給PXE
  • DXE想像為比PEI大一點的小型OS
  • DXE DEPENDENCY的概念:dxs檔描述他需要哪些PROTOCOL
  • 要看DXE DRIVER,先看INF檔描述的ENTRY POINT,傳HANDLE和SYSTEM TABLE,
    SYSTEM TABLE,
  • EFI的概念中,所有的檔案都是EFI檔,都可以執行,不僅可以放在ROM裡,還可以放在HD中,那我們要傳些什麼給他呢,就是一個PATH,有點像是C:\xxx\xxx,在EFI中,我們需要比較完整的路徑,稍後詳述。
  • DXE階段,有TIMER、EVENT的觀念。
  • 8259的IRQ1(KB)幾乎都是被DISABLE的,由於進入PROTECTED MODE,INT一進來我們看到的是32BIT的IDT,那KB是怎麼被抓進來呢,哈我聽不太懂= =,只說是有POOL的概念,參考SPEC的CH5。重聽(5)26:00
  • PROTOCAL

2008年10月14日 星期二

CPU ID

/*
用CPUID指令,首先你可以確定你用的CPU是Intel的。
然後執行:
MOV EAX,01H
CPUID
如果返回的EDX中,低18位為1,那麼這個CPU就是支持序列號的。
此時EAX就是序列號的高32位。這32位對同一型號的CPU是一樣的。
再執行:
MOV EAX,03H
CPUID
此時的EDX:ECX就是序列號的第64位。

要想關閉這個ID,可執行下列代碼:

MOV ECX,119H
RDMSR
OR EAX,00200000H
WRMSR

不過,一旦執行上述代碼,cpu將一直不能取id,直到下次reset。

*/

#include
#include

int main(int argc,char **argv)
{
unsigned long s1,s2;
unsigned char vendor_id[]="------------";
char sel;
printf("Select the function:\n1-------Read CPU id.\n2-------Disable CPU id.\n");
sel=getch();
switch(sel)
{
case '1':
asm xor eax,eax
asm cpuid
asm mov dword ptr vendor_id,ebx
asm mov dword ptr vendor_id[+4],edx
asm mov dword ptr vendor_id[+8],ecx
printf("%s-",vendor_id);
asm mov eax,01h
asm xor edx,edx
asm cpuid
asm mov s1,edx
asm mov s2,eax
printf("%08X\n%08X-",s1,s2);
asm mov eax,03h
asm xor ecx,ecx
asm xor edx,edx
asm cpuid
asm mov s1,edx
asm mov s2,ecx
printf("%08X-%08X\n",s1,s2);
break;
case '2':
asm{
mov ecx,119h
rdmsr
or eax,00200000h
wrmsr
}
printf("CPU id is disabled.\n");
break;
}
return 0;
}

最後說一下,那個關閉CPUID的功能我一直也不能成功,總是非法指令。

-*-*-PATCH-*-*-2002-07-19

找到一個比較正規底程式


#include
#include
#include

//#define cpuid asm emit 0fh asm emit 0a2h

int GetProcessorSerialNumber(unsigned int processor, unsigned int &psn_00_31, unsigned int &psn_32_63, unsigned int &psn_64_95)
{
unsigned int wCPU_SerialNo_00_31 = 0;
unsigned int wCPU_SerialNo_32_63 = 0;
unsigned int wCPU_SerialNo_64_93 = 0;

#define PSN_FLAG 0x4000//(0x1 << 18) int errCode = 0; int psn_falg = PSN_FLAG; DWORD_PTR dwThreadAffinitiMask = (0x1 << processor); SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinitiMask); asm { pushad mov eax,0 cpuid cmp ebx, 'uneG' jne wrong_processor cmp edx, 'Ieni' jne wrong_processor cmp ecx, 'letn' jne wrong_processor // now we have an Intel-Processor: // get CPU feature flag.. mov eax,1 cpuid mov wCPU_SerialNo_64_93,eax and edx,PSN_FLAG cmp edx,PSN_FLAG jne psn_not_supported_or_disabled // get the PSN mov eax,3 cpuid mov wCPU_SerialNo_32_63,edx mov wCPU_SerialNo_00_31,ecx jmp _exit_ wrong_processor: mov errCode, 1; jmp _exit_; psn_not_supported_or_disabled: mov errCode, 2; _exit_: popad } psn_00_31 = wCPU_SerialNo_00_31; psn_32_63 = wCPU_SerialNo_32_63; psn_64_95 = wCPU_SerialNo_64_93; dwThreadAffinitiMask = 0xffffffff; SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinitiMask); return errCode; } int _tmain(int argc, _TCHAR* argv[]) { unsigned int psn_00_31; unsigned int psn_32_63; unsigned int psn_64_95; int iRet = GetProcessorSerialNumber(0, psn_00_31, psn_32_63, psn_64_95); _tprintf(_T("\nPSN (%d): %.8x-%.8x-%.8x\n"), iRet, psn_64_95, psn_32_63, psn_00_31); return 0; } For more info see: Intel?Processor Identification and the CPUID Instruction http://www.intel.com/design/xeon/applnots/241618.htm -*-*-PATCH-*-*-2002-07-19   誰有讀取CPU序列號或其他硬體唯一標識碼的源碼,100大洋相送! -------------------------------------------------------------------------------- amiao_107@163.com amiao107@sohu.com -------------------------------------------------------------------------------- procedure GetCpuInfo; var R: array[0..19] of Char; var CpuID: Integer; begin FillChar(R, 20, 0); asm mov eax, 0 db 0fh, 0a2h // 其實就是cpuid匯編指令
mov dword ptr R[0], ebx
mov dword ptr R[4], edx
mov dword ptr R[8], ecx
mov eax, 1
db 0fh, 0a2h // cpuid
mov CpuID, edx
end;
ShowMessage('CPU製造商為:' + R);
ShowMessage('序列號為:' + IntToStr(CpuID));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
GetCpuInfo;
end;

--------------------------------------------------------------------------------

GZ

--------------------------------------------------------------------------------

procedure GetCpuInfo;
var R: array[0..19] of Char;
var CpuID: Integer;
begin
FillChar(R, 20, 0);
asm
mov eax, 0
db 0fh, 0a2h // 其實就是cpuid匯編指令
mov dword ptr R[0], ebx
mov dword ptr R[4], edx
mov dword ptr R[8], ecx
mov eax, 1
db 0fh, 0a2h // cpuid
mov CpuID, edx
end;
ShowMessage('CPU製造商為:' + R);
ShowMessage('序列號為:' + IntToStr(CpuID));
end;

--------------------------------------------------------------------------------

呵呵,我試試

--------------------------------------------------------------------------------

gz

--------------------------------------------------------------------------------

不行啊,在多台機上測試得出的CPU的ID相同,都為25426431,這是為什麼?

--------------------------------------------------------------------------------

學習!
試了一下:
CPU製造商為:GenuineIntel
序列號為:8452697

--------------------------------------------------------------------------------

怎麼解決這樣的問題,在多台機器上得到的CPU的ID都相同,為什麼會有這樣的情況出現

--------------------------------------------------------------------------------

函數原形如下:
VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);//指向系統信息結構的指針;
例:
var :System_Info;
begin
GetSystemInfo(SysInfo);//獲取CPU信息;
Edit1.text:='系統共有'+IntTostr(SysInfo.dwNumberOfProcessors)+'個CPU';
end;

--------------------------------------------------------------------------------

學習。
試了一下luoweicaisd(笑三少) 的代碼:
CPU製造商為:GenuineIntel
序列號為:8452607
正確!

--------------------------------------------------------------------------------

支持2000嗎?

--------------------------------------------------------------------------------

OK,好使

--------------------------------------------------------------------------------

用上面那段代碼,為什麼多台機器測出的CPU ID 都是一樣的?兩台塞羊(型號一樣)和一台奔三,得到的結果都是一樣的。

--------------------------------------------------------------------------------

下面是得到BIOS資訊的代碼:procedure TForm1.BiosInfo;
const
Subkey: string = ''Hardware\description\system'';
var
hkSB: HKEY;
rType: LongInt;
ValueSize, OrigSize: Longint;
ValueBuf: array[0..1000] of char;
procedure ParseValueBuf(const VersionType: string);
var
I, Line: Cardinal;
S: string;
begin
i := 0;
Line := 0;
while ValueBuf[i] <> #0 do
begin
S := StrPas(@ValueBuf[i]); // move the Pchar into a string
Inc(Line);
Memo1.Lines.Append(Format(''%s Line %d = %s'',
[VersionType, Line, S])); // add it to a Memo
inc(i, Length(S) + 1);
// to point to next sz, or to #0 if at
end
end;
end;
begin
if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(Subkey), 0,
KEY_READ, hkSB) = ERROR_SUCCESS then
try
OrigSize := sizeof(ValueBuf);
ValueSize := OrigSize;
rType := REG_MULTI_SZ;
if RegQueryValueEx(hkSB, ''SystemBiosVersion'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
ParseValueBuf(''System BIOS Version'');
ValueSize := OrigSize;
rType := REG_SZ;
if RegQueryValueEx(hkSB, ''SystemBIOSDate'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
Memo1.Lines.Append(''System BIOS Date '' + ValueBuf);
ValueSize := OrigSize;
rType := REG_MULTI_SZ;
if RegQueryValueEx(hkSB, ''VideoBiosVersion'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
ParseValueBuf(''Video BIOS Version'');
ValueSize := OrigSize;
rType := REG_SZ;
if RegQueryValueEx(hkSB, ''VideoBIOSDate'', nil, @rType,
@ValueBuf, @ValueSize) = ERROR_SUCCESS then
Memo1.Lines.Append(''Video BIOS Date '' + ValueBuf);
finally
RegCloseKey(hkSB);
end;
end;


--------------------------------------------------------------------------------

讀硬盤的序列號
procedure GetHardDriveinfo;
var
VolumeSerialNumber : DWORD;
MaximumComponentLength : DWORD;
FileSystemFlags : DWORD;
SerialNumber : string;
drv:string;
begin
drv:='c:\';//可以改成其他硬盤
GetVolumeInformation(pchar(drv) , nil,0,@VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags,
nil,0);
SerialNumber:=inttostr(VolumeSerialNumber);
ShowMessage(SerialNumber);
end;

--------------------------------------------------------------------------------

許多FAQ中推薦使用GetVolumeInformation來獲取硬盤序列號。但是那獲取的是卷的序列號,而不是硬盤的序列號。卷的序列號是在分區格式化時生成或修改。一些公司使用復制工具來為全部新計算機安裝軟件----通過將一個硬盤復制到其他硬盤,當然,所有這些計算機上卷的資訊(包括序列號)都是相同的。

我建議另外的一個方法:獲取真正硬盤的序列號。
不幸的是,下列代碼只能工作在IDE硬盤上。


//獲取第一個IDE硬盤的序列號
function GetIdeSerialNumber : SerialNumber;
const IDENTIFY_BUFFER_SIZE = 512;
type
TIDERegs = packed record
bFeaturesReg : BYTE; // Used for specifying SMART "commands".
bSectorCountReg : BYTE; // IDE sector count register
bSectorNumberReg : BYTE; // IDE sector number register
bCylLowReg : BYTE; // IDE low order cylinder value
bCylHighReg : BYTE; // IDE high order cylinder value
bDriveHeadReg : BYTE; // IDE drive/head register
bCommandReg : BYTE; // Actual IDE command.
bReserved : BYTE; // reserved for future use. Must be zero.
end;
TSendCmdInParams = packed record
// Buffer size in bytes
cBufferSize : DWORD;
// Structure with drive register values.
irDriveRegs : TIDERegs;
// Physical drive number to send command to (0,1,2,3).
bDriveNumber : BYTE;
bReserved : Array[0..2] of Byte;
dwReserved : Array[0..3] of DWORD;
bBuffer : Array[0..0] of Byte; // Input buffer.
end;
TIdSector = packed record
wGenConfig : Word;
wNumCyls : Word;
wReserved : Word;
wNumHeads : Word;
wBytesPerTrack : Word;
wBytesPerSector : Word;
wSectorsPerTrack : Word;
wVendorUnique : Array[0..2] of Word;
sSerialNumber : Array[0..19] of CHAR;
wBufferType : Word;
wBufferSize : Word;
wECCSize : Word;
sFirmwareRev : Array[0..7] of Char;
sModelNumber : Array[0..39] of Char;
wMoreVendorUnique : Word;
wDoubleWordIO : Word;
wCapabilities : Word;
wReserved1 : Word;
wPIOTiming : Word;
wDMATiming : Word;
wBS : Word;
wNumCurrentCyls : Word;
wNumCurrentHeads : Word;
wNumCurrentSectorsPerTrack : Word;
ulCurrentSectorCapacity : DWORD;
wMultSectorStuff : Word;
ulTotalAddressableSectors : DWORD;
wSingleWordDMA : Word;
wMultiWordDMA : Word;
bReserved : Array[0..127] of BYTE;
end;
PIdSector = ^TIdSector;
TDriverStatus = packed record
// 驅動器返回的錯誤代碼,無錯則返回0
bDriverError : Byte;
// IDE出錯寄存器的內容,只有當bDriverError 為 SMART_IDE_ERROR 時有效
bIDEStatus : Byte;
bReserved : Array[0..1] of Byte;
dwReserved : Array[0..1] of DWORD;
end;
TSendCmdOutParams = packed record
// bBuffer的大小
cBufferSize : DWORD;
// 驅動器狀態
DriverStatus : TDriverStatus;
// 用於保存從驅動器讀出的數據的緩沖區,實際長度由cBufferSize決定
bBuffer : Array[0..0] of BYTE;
end;

var hDevice : THandle;
cbBytesReturned : DWORD;
ptr : PChar;
SCIP : TSendCmdInParams;
aIdOutCmd : Array [0..(SizeOf(TSendCmdOutParams)+IDENTIFY_BUFFER_SIZE-1)-1] of Byte;
IdOutCmd : TSendCmdOutParams absolute aIdOutCmd;

procedure ChangeByteOrder( var Data; Size : Integer );
var ptr : PChar;
i : Integer;
c : Char;
begin
ptr := @Data;
for i := 0 to (Size shr 1)-1 do
begin
c := ptr^;
ptr^ := (ptr+1)^;
(ptr+1)^ := c;
Inc(ptr,2);
end;
end;

begin
Result := ''; // 如果出錯則返回空串
if SysUtils.Win32Platform=VER_PLATFORM_WIN32_NT then // Windows NT, Windows 2000
begin
// 提示! 改變名稱可適用於其他驅動器,如第二個驅動器: '\\.\PhysicalDrive1\'
hDevice := CreateFile( '\\.\PhysicalDrive0', GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0 );
end
else // Version Windows 95 OSR2, Windows 98
hDevice := CreateFile( '\\.\SMARTVSD', 0, 0, nil, CREATE_NEW, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
FillChar(SCIP,SizeOf(TSendCmdInParams)-1,#0);
FillChar(aIdOutCmd,SizeOf(aIdOutCmd),#0);
cbBytesReturned := 0;
// Set up data structures for IDENTIFY command.
with SCIP do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
// bDriveNumber := 0;
with irDriveRegs do
begin
bSectorCountReg := 1;
bSectorNumberReg := 1;
// if Win32Platform=VER_PLATFORM_WIN32_NT then bDriveHeadReg := $A0
// else bDriveHeadReg := $A0 or ((bDriveNum and 1) shl 4);
bDriveHeadReg := $A0;
bCommandReg := $EC;
end;
end;
if not DeviceIoControl( hDevice, $0007c088, @SCIP, SizeOf(TSendCmdInParams)-1,
@aIdOutCmd, SizeOf(aIdOutCmd), cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
with PIdSector(@IdOutCmd.bBuffer)^ do
begin
ChangeByteOrder( sSerialNumber, SizeOf(sSerialNumber) );
(PChar(@sSerialNumber)+SizeOf(sSerialNumber))^ := #0;
Result := PChar(@sSerialNumber);
end;
end;

// 更多關於 S.M.A.R.T. ioctl 的信息可查看:
// http://www.microsoft.com/hwdev/download/respec/iocltapi.rtf

// MSDN庫中也有一些簡單的例子
// Windows Development -> Win32 Device Driver Kit ->
// SAMPLE: SmartApp.exe Accesses SMART stats in IDE drives

// 還可以查看 http://www.mtgroup.ru/~alexk
// IdeInfo.zip - 一個簡單的使用了S.M.A.R.T. Ioctl API的Delphi應用程式

// 注意:

// WinNT/Win2000 - 你必須擁有對硬盤的讀/寫訪問權限

// Win98
// SMARTVSD.VXD 必須安裝到 \windows\system\iosubsys
// (不要忘記在復制後重新啟動系統)

--------------------------------------------------------------------------------

在我的前一篇文章(http://www.ocloud.com/article/viewart.php?id=88)裏我描述了一個通過調用以DFP_RECEIVE_DRIVE_DATA為控制碼的DeviceIoControl函數來獲取序列號,但在NT上只有擁有系統管理員權限的用戶才能使用。現在我找到了讓所有用戶都可以使用的方法。
{ 構件下載:IdeSN.zip }
提示! 在Win9x系統中必須安裝smartvsd.vxd:你只需簡單的將它從\windows\system\目錄中復制到\windows\system\iosubsys\下,然後重新啟動系統。

// (c) Alex Konshin 30 jul 2000

program IdeSN;

// 目的:簡單的控制臺程式以獲取第一個IDE硬盤的序列號

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils; // only for Win32Platform and SysErrorMessage

//-------------------------------------------------------------
function GetIdeDiskSerialNumber : String;
type
TSrbIoControl = packed record
HeaderLength : ULONG;
Signature : Array[0..7] of Char;
Timeout : ULONG;
ControlCode : ULONG;
ReturnCode : ULONG;
Length : ULONG;
end;
SRB_IO_CONTROL = TSrbIoControl;
PSrbIoControl = ^TSrbIoControl;

TIDERegs = packed record
bFeaturesReg : Byte; // Used for specifying SMART "commands".
bSectorCountReg : Byte; // IDE sector count register
bSectorNumberReg : Byte; // IDE sector number register
bCylLowReg : Byte; // IDE low order cylinder value
bCylHighReg : Byte; // IDE high order cylinder value
bDriveHeadReg : Byte; // IDE drive/head register
bCommandReg : Byte; // Actual IDE command.
bReserved : Byte; // reserved. Must be zero.
end;
IDEREGS = TIDERegs;
PIDERegs = ^TIDERegs;

TSendCmdInParams = packed record
cBufferSize : DWORD;
irDriveRegs : TIDERegs;
bDriveNumber : Byte;
bReserved : Array[0..2] of Byte;
dwReserved : Array[0..3] of DWORD;
bBuffer : Array[0..0] of Byte;
end;
SENDCMDINPARAMS = TSendCmdInParams;
PSendCmdInParams = ^TSendCmdInParams;

TIdSector = packed record
wGenConfig : Word;
wNumCyls : Word;
wReserved : Word;
wNumHeads : Word;
wBytesPerTrack : Word;
wBytesPerSector : Word;
wSectorsPerTrack : Word;
wVendorUnique : Array[0..2] of Word;
sSerialNumber : Array[0..19] of Char;
wBufferType : Word;
wBufferSize : Word;
wECCSize : Word;
sFirmwareRev : Array[0..7] of Char;
sModelNumber : Array[0..39] of Char;
wMoreVendorUnique : Word;
wDoubleWordIO : Word;
wCapabilities : Word;
wReserved1 : Word;
wPIOTiming : Word;
wDMATiming : Word;
wBS : Word;
wNumCurrentCyls : Word;
wNumCurrentHeads : Word;
wNumCurrentSectorsPerTrack : Word;
ulCurrentSectorCapacity : ULONG;
wMultSectorStuff : Word;
ulTotalAddressableSectors : ULONG;
wSingleWordDMA : Word;
wMultiWordDMA : Word;
bReserved : Array[0..127] of Byte;
end;
PIdSector = ^TIdSector;

const
IDE_ID_FUNCTION = $EC;
IDENTIFY_BUFFER_SIZE = 512;
DFP_RECEIVE_DRIVE_DATA = $0007c088;
IOCTL_SCSI_MINIPORT = $0004d008;
IOCTL_SCSI_MINIPORT_IDENTIFY = $001b0501;
DataSize = sizeof(TSendCmdInParams)-1+IDENTIFY_BUFFER_SIZE;
BufferSize = SizeOf(SRB_IO_CONTROL)+DataSize;
W9xBufferSize = IDENTIFY_BUFFER_SIZE+16;
var
hDevice : THandle;
cbBytesReturned : DWORD;
pInData : PSendCmdInParams;
pOutData : Pointer; // PSendCmdOutParams
Buffer : Array[0..BufferSize-1] of Byte;
srbControl : TSrbIoControl absolute Buffer;

procedure ChangeByteOrder( var Data; Size : Integer );
var ptr : PChar;
i : Integer;
c : Char;
begin
ptr := @Data;
for i := 0 to (Size shr 1)-1 do
begin
c := ptr^;
ptr^ := (ptr+1)^;
(ptr+1)^ := c;
Inc(ptr,2);
end;
end;

begin
Result := '';
FillChar(Buffer,BufferSize,#0);
if Win32Platform=VER_PLATFORM_WIN32_NT then
begin // Windows NT, Windows 2000
// 獲取 SCSI port handle
hDevice := CreateFile( '\\.\Scsi0:',
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
srbControl.HeaderLength := SizeOf(SRB_IO_CONTROL);
System.Move('SCSIDISK',srbControl.Signature,8);
srbControl.Timeout := 2;
srbControl.Length := DataSize;
srbControl.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;
pInData := PSendCmdInParams(PChar(@Buffer)
+SizeOf(SRB_IO_CONTROL));
pOutData := pInData;
with pInData^ do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
bDriveNumber := 0;
with irDriveRegs do
begin
bFeaturesReg := 0;
bSectorCountReg := 1;
bSectorNumberReg := 1;
bCylLowReg := 0;
bCylHighReg := 0;
bDriveHeadReg := $A0;
bCommandReg := IDE_ID_FUNCTION;
end;
end;
if not DeviceIoControl( hDevice, IOCTL_SCSI_MINIPORT,
@Buffer, BufferSize, @Buffer, BufferSize,
cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
end
else
begin // Windows 95 OSR2, Windows 98
hDevice := CreateFile( '\\.\SMARTVSD', 0, 0, nil,
CREATE_NEW, 0, 0 );
if hDevice=INVALID_HANDLE_VALUE then Exit;
try
pInData := PSendCmdInParams(@Buffer);
pOutData := @pInData^.bBuffer;
with pInData^ do
begin
cBufferSize := IDENTIFY_BUFFER_SIZE;
bDriveNumber := 0;
with irDriveRegs do
begin
bFeaturesReg := 0;
bSectorCountReg := 1;
bSectorNumberReg := 1;
bCylLowReg := 0;
bCylHighReg := 0;
bDriveHeadReg := $A0;
bCommandReg := IDE_ID_FUNCTION;
end;
end;
if not DeviceIoControl( hDevice, DFP_RECEIVE_DRIVE_DATA,
pInData, SizeOf(TSendCmdInParams)-1, pOutData,
W9xBufferSize, cbBytesReturned, nil ) then Exit;
finally
CloseHandle(hDevice);
end;
end;
with PIdSector(PChar(pOutData)+16)^ do
begin
ChangeByteOrder(sSerialNumber,SizeOf(sSerialNumber));
SetString(Result,sSerialNumber,SizeOf(sSerialNumber));
end;
end;


//=============================================================
var s : String;
rc : DWORD;
begin
s := GetIdeDiskSerialNumber;
if s='' then
begin
rc := GetLastError;
if rc=0 then WriteLn('IDE drive is not support SMART feature')
else WriteLn(SysErrorMessage(rc));
end
else WriteLn('Disk serial number: ''', s,'''');
end.

--------------------------------------------------------------------------------

以上文章均轉載於淩雲天地,未加編輯,不是本人所作

--------------------------------------------------------------------------------

感謝fontain等網友的關注,為表感謝,送分了,接分了...

--------------------------------------------------------------------------------

你讀取硬體唯一標識碼的目的是為了控制你的軟件只在一台機器上運行,是嗎?那我建議你讀取網卡的物理位址,可行性有:1、眾所周知:網卡的物理位址獨一無二的;2、網卡的價格很低;怎麼樣?

簡單的 nmake 使用的 Makefile 範例

# 這是一個簡單的 nmake 使用的 Makefile 範例
# 編譯: c:> nmake
# 清除: c:> nmake clean
# 井民全
#

# 對應 GNU make (如果你想使用 MSYS 編譯的話)
# SRC=$(shell ls *.cpp)
SRC=*.cpp

#替換原則: Objs 檔案群= SRC 檔案群換成 .obj
Objs=$(SRC:cpp=obj)
CFLAGS=-nologo /I"$(VC_DIR)include"

# 主要編譯指令: 先編譯所有的 Objs, 然後建立 exe
all:$(Objs) demo.exe

$(Objs):

# 連結指令
# 注意: 不能用 LINK.exe /OUT:$*.exe $<, 因為建立 exe 檔需要所有的 object 檔案, 不能只有改變的檔
demo.exe: $(Objs)
@echo "Linking ..."
LINK.exe /OUT:$*.exe $(Objs)

# 編譯指令
# 定義所有的 obj 檔 都 depend on .cpp 檔
# 所有 .cpp 更改, 都會重新編譯更改的檔案
.cpp.obj::
echo "Compiling ..."
$(CC) $(CFLAGS) -Fd$O\ -c $<

clean:
del *.obj *.exe

#FAQ
# the list of compiler options
# ms-help://MS.MSDNQTR.2006JAN.1033/vccore/html/vcrefcompileroptionslistedalphabetically.htm

2008年9月9日 星期二

分頁架構

http://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap2/paging.html

設定分頁功能在控制暫存器(control registers)中,有三個和分頁功能有關的旗標:PG(CR0 的 bit 31)、PSE(CR4 的 bit 4,在 Pentium 和以後的處理器才有)、和 PAE(CR4 的 bit 5,在 Pentium Pro 和 Pentium II 以後的處理器才有)。PG(paging)旗標設為 1 時,就會開啟分頁功能。PSE(page size extensions)旗標設為 1 時,才可以使用 4MB 的分頁大小(否則就只能使用 4KB 的分頁大小)。而 PAE(physical address extension)是 P6 家族新增的功能,可以支援到 64GB 的實體記憶體(本文中不說明這個功能)。
分頁目錄和分頁表分頁目錄和分頁表存放分頁的資訊(參考「記憶體管理」的「緒論」)。分頁目錄的基底位址是存放在 CR3(或稱 PDBR,page directory base register),存放的是實體位址。在開啟分頁功能之前,一定要先設定好這個暫存器的值。在開啟分頁功能之後,可以用 MOV 命令來改變 PDBR 的值,而在工作切換(task switch)時,也可能會載入新的 PDBR 值。也就是說,每一個工作(task)可以有自己的分頁目錄。在工作切換時,前一個工作的分頁目錄可能會被 swap 到硬碟中,而不再存在實體記憶體中。不過,在工作被切換回來之前,一定要使該工作的分頁目錄存放在實體記憶體中,而且在工作切換之前,分頁目錄都必須一直在實體記憶體中。 分頁目錄和分頁表的 entry 格式如下:

上表中的各個欄位的說明如下:
分頁表基底位址、分頁基底位址:存放分頁表/分頁的基底位址(以實體位址)。在 4KB 的分頁中,分頁表和分頁的位址都必須是 4KB 的倍數,所以用 20 bits 來表示基底位址的最左邊的(most-significant)20 bits。在 4MB 的分頁中,分頁的位址必須是 4MB 的倍數,因此用 10 bits 表示基底位址最左數的 10 bits。
P(present)旗標:表示這個分頁(或分頁表)目前是否存在記憶體中。若 P = 1,則表示這個分頁或分頁表在記憶體中,可以進行位址轉換。若 P = 0,則表示這個分頁不在記憶體中,若對這個分頁進行存取動作,會導致 page fault(#PF)例外。作業系統在將分頁 swap 到硬碟時,要把 P 設為 0;而在把分頁由硬碟中讀入時,則要把 P 設為 1。
R/W(read/write)旗標:當 R/W = 1 時,表示分頁可以寫入;當 R/W = 0 時,表示分頁只能讀取(read-only)。當 CR0 的 WP 旗標(第 16 bit)設為 1 時,所有的程式都不能寫入唯讀的分頁;但 WP 為 0 時,具有 supervisor 等級的程序就可以寫入唯讀的分頁。在指向分頁表的分頁目錄 entry 中,這個旗標對其指向的分頁表中的每個分頁都有效。
U/S(user/supervisor)旗標:當 U/S = 1 時,表示分頁是一個 user level 的分頁,而 U/S = 0 時,表示分頁是一個 supervisor level 的分頁。和 R/W 旗標一樣,在分頁目錄中,這個旗標對其指向的分頁表中的每個分頁都有效。
PWT(page-level write-through)旗標:在 PWT = 1 時,處理器會對這個分頁(或分頁表)做 write-through caching;而 PWT = 0 時,處理器會對這個分頁(或分頁表)做 write-back caching。在 CR0 的 CD(cache disable,第 30 bit)設為 1 時,這個旗標會被忽略。
PCD(page cache disable)旗標:在 PCD = 1 時,處理器不會對這個分頁(或分頁表)進行 cache;而 PCD = 0 時,則會進行 cache。例如,在分頁是對映一 I/O 記憶體時,就需要把 cache 關閉。在 CR0 的 CD 旗標設為 1 時,這個旗標會被忽略。
A(accessed)旗標:在 A = 0 時,若分頁被存取,則處理器會把它設為 1。在被設為 1 之後,處理器不會自動把它設為 0,只有軟體可以把它清為 0。因此,通常在一個分頁被載入實體記憶體時,作業系統會把 A 清為 0。記憶體管理程式或作業系統可以利用這個旗標來決定 swap 的方式。
D(dirty)旗標:在 D = 0 時,若對分頁進行寫入動作,則處理器會把它設為 1。在被設為 1 之後,只有軟體可以把它清為 0。通常作業系統在載入一個分頁之後,會把 D 清為 0。如此一來,要把這個分頁 swap 到硬碟中時,若 D 仍為 0,則表示分頁沒有被修改過,就不需要再寫回硬碟中了。這個旗標在「指向分頁表的分頁目錄 entry」中沒有作用。
PS(page size)旗標:這個旗標只在分頁目錄 entry 中有作用。當 PS = 0 時,表示這是一個 4KB 的分頁,因此 entry 是指向一個分頁表;當 PS = 1 時,表示這是一個 4MB 的分頁,因此 entry 是指向一個分頁。只有在 CR4 的 PSE(page size extensions,第 4 bit)為 1 時,才能存取 4MB 的分頁。
G(global)旗標:這是在 Pentium Pro 及之後的處理器才有的旗標。在本文中不討論。在 Pentium 和之前的處理器,這個旗標視為保留旗標,必須設為 0。
保留和可用部分:保留部分一律要設成 0,而可用部分則可以自己決定用途。如果 P 為 0,則整個 entry(除了 P 之外)都視為可用部分,可供作業系統存放相關的資訊(例如,可以用來存放分頁在硬碟 swap file 中的位置)。
Translation Lookaside Buffers(TLBs)到記憶體中查分頁目錄和分頁表是非常耗時的工作(需要經由較慢的 memory-bus),而查分頁目錄和分頁表又是非常頻繁的事件(幾乎所有的記憶體存取動作都需要),因此,處理器把最近使用的分頁目錄和分頁表的 entry 存放在叫 Translation Lookaside Buffers(TLBs)的 cache 中。只有 CPL 為 0 的程序才能選擇 TLB 的 entry 或是把 TLB 設為無效。無論是在更動分頁目錄或分頁表之後,都要立刻把相對的 TLB entry 設為無效,這樣在下次取用這個分頁目錄或分頁表時,才會更新 TLB 的內容(否則就可能會從 TLB 中讀到舊的資料了)。 要把 TLB 設為無效,只要重新載入 CR3 就可以了。要重新載入 CR3,可以用 MOV 指令(例如:MOV CR3, EAX),或是在工作切換時,處理器也會重新載入 CR3 的值。此外,INVLPG 指令可以把某個特定的 TLB entry 設成無效。不過,在某些狀況下,它會把一些 TLB entries 甚至整個 TLB 都設為無效。INVLPG 的參數是該分頁的位址,處理器會把 TLB 中存放該分頁的 entry 設為無效。
分頁的規劃分頁機制在多工作業系統中是很重要的。在多工作業系統中,往往同時執行很多個程式,因此,記憶體可能常常會用盡。但是,即使一個程式載入大量的資料到記憶體中,也很少會同時使用到全部的資料。這時候,把暫時不需要的資料寫入硬碟(或其它類似的裝置)中,就可以空出位置載入其它的程式了。不過,為了管理的方便,分頁的大小往往是固定的。例如,在 IA-32 架構下,分頁的大小是 4KB。分頁如果太大,則在 swap 時,常常會 swap 到不需要 swap 的部分;而若分頁太小,則過於破碎,不易管理,也缺乏效率。 在 i486 和之前的處理器中,分頁的大小只有一種選擇:4KB。在大部分情形中,這個大小還算適當。但是,在某些情形下,可能會需要更大的分頁。因此,在 Pentium 和以後的處理器,就增加了 4MB 的分頁大小。然而,4MB 在一般的情形中,實在是太大了,實用性也降低。不過,4MB 的分頁在某些狀況下還是有用的。例如:為了方便管理,可以把作業系統的核心放在 4MB 的分頁中,而一般應用程式則使用 4KB 的分頁。此外,在 Linux 作業系統中還有一種用法:Linux 作業系統的核心部分常常需要使用實體位址,因此在 Linux 中,應用程式和核心是使用不同的分頁目錄。核心的分頁目錄便是將線性記憶體直接對映到實體記憶體中。在這種情形下,就很適合使用 4MB 的分頁模式。不過,要注意一點:4MB 的分頁模式,只有在 Pentium 及以後的處理器才能使用。

2008年9月8日 星期一

C 字串

http://oaunix.hlhs.hlc.edu.tw/~program/hkin/string.htm#a8

簡 介 下一節 到頁頂


C 沒 有 字 串 型 態 , 因 此 我 們 要 用 字 元 陣 列 來 處 理 字 串 , 例 如 :


char string[] = { 'H', 'e', 'l', 'l', 'o' };

i 0 1 2 3 4
word[i] 'H' 'e' 'l' 'l' 'o'

如 果 要 顯 示 這 個 字 串 , 就 要 用 printf 和 for 迴 圈 逐 個 字 元 顯 示 。

例 子 :

void print_string(char string[], int length) {
int i;

for (i = 0 ; i < i =" 0;" st1 =" %s\nst2" st1 =" Hello" st2 =" World">

main() {
printf("Press Enter to quit\n");
while (getchar() != '\n') ;
}


說 明 :


每 次 呼 叫 getchar 函 數 都 會 等 候 按 鍵 , 按 鍵 後 傳 回 該 鍵 。


例 子 :

#include <stdio.h>

void read_line(char string[], int max_length) {
char ch;
int i = 0;

do {
ch = getchar();
string[i] = ch;
i++;
}
while (ch != '\n' && i <= max_length); string[i-1] = '\0'; }

main() { char string[21]; read_line(string, 20); printf("%s", string); }

執 行 結 果 : This is a line. This is a line.

說 明 : 輸 入 字 串 。
void read_line(char string[], int max_length) 用 read_line 函 數 來 輸 入 字 串 , 可 以 控 制 每 個 輸 入 的 字 元 , 比 scanf 更 有 彈 性 , max_length 是 字 元 數 目 上 限 , 用 來 避 免 字 串 溢 滿 。 while (ch != '\n' && i <= max_length) 如 果 按 Enter 鍵 或 i 大 過 max_length 就 離 開 。 string[i-1] = '\0'; 在 字 串 結 尾 加 入 '\0' 。 其 它 字 串 語 法 上一節 下一節 到頁頂 有 時 一 個 字 串 太 長 , 你 想 分 幾 行 來 寫 它 , 可 以 在 一 行 的 結 尾 寫 反 斜 號 , 例 如 : char lower[] = "abcdefghijklmnopqrstuvwxyz"; 可 以 寫 成 : char lower[] = "abcdefghij\ klmnopqrst\ uvwxyz"; 留 意 字 串 會 由 下 一 行 的 開 頭 繼 續 的 , 所 以 如 果 寫 : char lower[] = "abcdefghij\ klmnopqrst\ uvwxyz"; 就 會 變 成 : char lower[] = "abcdefghij klmnopqrst uvwxyz"; 還 有 另 一 種 更 方 便 的 寫 法 , 就 是 把 一 個 字 串 寫 成 多 個 獨 立 的 字 串 , 例 如 : "OneTwoThree" 可 以 寫 成 : "One" "Two" "Three" 所 以 lower 陣 列 也 可 以 寫 成 : char lower[] = "abcdefghij" "klmnopqrst" "uvwxyz";  

實 例 上一節 下一節 到頁頂 實 例 : 字 數 統 計 上一節 下一節 到頁頂 一 般 文 字 處 理 程 式 都 有 字 數 統 計 功 能 。 假 設 每 個 英 文 字 都 是 由 一 列 連 逐 的 英 文 字 母 組 成 , 例 如 : This is a line. 可 分 為 「 This 」 「 is 」 「 a 」 「 line 」 4 個 字 , 而 : This's a line. 其 中 「 This's 」 可 分 為 「 This 」 「 s 」 兩 個 字 , 因 為 單 引 號 不 是 英 文 字 。

例 子 :

int is_alpha(char ch) { return ('a' <= ch && ch <= 'z') ('A' <= ch && ch <= 'Z'); }
int count_word(char string[]) { int i, count, looking_for_word; count = 0; looking_for_word = 1;

for (i = 0 ; string[i] != '\0' ; i++) { if ( is_alpha(string[i]) ) { if (looking_for_word) { looking_for_word = 0; count++; } } else { looking_for_word = 1; } } return count; } main() { char st[] = "Wow! Great job."; printf( "%s\nWords: %i", st, count_word(st) ); }

執 行 結 果 : Wow! Great job. Words: 3

說 明 : 字 元 可 分 為 兩 類 : 「 英 文 字 母 」 (Alphabet) 和 非 英 文 字 母 , 統 計 方 法 就 是 由 左 至 右 逐 個 字 元 處 理 , 如 果 字 元 不 是 英 文 字 母 , 就 等 待 英 文 字 母 出 現 , 一 出 現 就 把 字 數 加 一 , 然 後 就 不 用 再 等 待 , 直 至 字 元 不 是 英 文 字 母 為 止 。 i   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 string[i]   'W' 'o' 'w' '!' ' ' 'G' 'r' 'e' 'a' 't' ' ' 'j' 'o' 'b' '.' '\0' is_alpha(string[i])   1 1 1 0 0 1 1 1 1 1 0 1 1 1 0 0 looking_for_word 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 1 1 count 0 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3   int is_alpha(char ch) is_alpha 函 數 檢 查 一 個 字 元 是 否 英 文 字 母 。

looking_for_word 如 果 looking_for_word 是 1 , 表 示 正 等 待 著 下 一 個 字 , 即 是 等 待 英 文 字 母 。 if ( is_alpha(string[i]) ) { if (looking_for_word) { looking_for_word = 0; count++; } 如 果 現 正 等 待 著 下 一 個 字 , 而 string[i] 又 是 英 文 字 母 的 話 , 就 把 字 數 加 一 , 與 及 不 用 等 待 下 一 個 字 。 looking_for_word = 1; 如 果 string[i] 不 是 英 文 字 母 , 就 等 待 下 一 個 字 的 來 臨 。  

實 例 : 基 本 字 串 處 理 上一節 下一節 到頁頂 實 例 : 基 本 字 串 處 理 : 字 串 長 度 上一節 下一節 到頁頂

例 子 :
int string_length(char string[]) { int i; i = 0; while (string[i] != '\0') i++; return i; } main() { char st[21] = "012345"; printf("st = %s ; Length = %i", st, string_length(st)); } 執 行 結 果 : st = 012345 ; Length = 6

說 明 : while (string[i] != '\0') i++; 當 字 元 不 是 '\0' , 就 把 該 字 元 計 算 入 長 度 。

實 例 : 基 本 字 串 處 理 : 字 串 複 製 上一節 下一節 到頁頂 複 製 基 本 型 態 的 資 料 可 以 用 等 於 符 號 , 不 過 它 不 適 用 於 陣 列 , 即 是 你 不 能 寫 : string1 = string2; 所 以 複 製 字 串 必 須 逐 個 字 元 複 製 。 例 子 : void string_copy(char to[], char from[]) { int i; i = 0; while ( (to[i] = from[i]) != '\0' ) i++; } main() { char st1[21] = "012345", st2[21] = "abcde"; printf("Before: st2 = %s\n", st2); string_copy(st2, st1); printf("After : st2 = %s\n", st2); } 執 行 結 果 : Before: st2 = abcde After : st2 = 012345 說 明 : while ( (to[i] = from[i]) != '\0' ) i++; 與 「 字 串 長 度 」 例 子 差 不 多 , 也 是 檢 查 字 元 是 否 '\0' 。 這 句 會 先 執 行 : to[i] = from[i] 然 後 才 把 from[i] 的 字 元 與 '\0' 比 較 , 即 是 先 複 製 , 後 檢 查 , 因 此 在 迴 圈 完 結 時 , to 字 串 結 尾 會 有 '\0' 。

實 例 : 基 本 字 串 處 理 : 字 串 比 較 上一節 下一節 到頁頂 你 也 不 能 寫 : if (string1 == string2) 來 比 較 字 串 , 而 正 確 的 做 法 就 是 把 兩 個 字 串 內 的 所 有 對 應 的 字 元 作 比 較 , 如 果 有 一 個 字 元 不 同 的 話 , 就 算 是 不 相 等 了 , 例 如 : char string1[] = "abcde"; char string2[] = "abdde"; 因 為 string1[2] 是 'c' , string2[2] 是 'd' , 所 以 它 們 不 相 等 。 其 實 , 字 串 也 可 以 好 像 數 目 一 樣 , 把 兩 樣 東 西 作 比 較 , 會 有 3 種 情 況 : 等 於 、 大 於 及 小 於 。

什 麼 ? 文 字 也 有 大 小 之 分 嗎 ? 對 啊 , 還 記 得 ASCII 碼 嗎 ? 每 個 字 元 都 有 一 個 碼 , 我 們 可 以 跟 據 這 個 碼 來 決 定 字 元 的 大 小 , 而 比 較 方 法 很 簡 單 , 只 需 把 兩 個 字 串 由 頭 至 尾 逐 個 字 元 作 比 較 便 可 , 所 謂 的 「 由 頭 至 尾 」 , 就 是 由 指 數 0 的 字 元 開 始 , 直 至 出 現 不 同 的 字 元 , 或 者 到 了 字 串 結 尾 。 為 什 麼 不 是 「 由 尾 至 頭 」 呢 ? 可 能 沒 有 實 際 用 途 吧 , 因 為 「 由 頭 至 尾 」 可 以 把 英 文 字 排 列 到 好 像 字 典 一 樣 。 例 如 比 較 以 上 的 string1 和 string2 , 首 先 比 較 指 數 0 字 元 , 兩 者 都 是 'a' , 然 後 比 較 指 數 1 字 元 , 兩 者 都 是 'b' , 然 後 比 較 指 數 2 字 元 , string1[2] 是 'c' , 它 的 ASCII 碼 是 63h , 而 string2[2] 是 'd' , 它 的 ASCII 碼 是 64h , 所 以 'd' 比 'c' 大 , 即 是 string2 比 string1 大 。 我 們 可 以 寫 個 函 數 , 分 別 傳 回 0 、 1 和 -1 來 代 表 等 於 、 大 於 及 小 於 , 例 如 : int string_compare(char st1[], char st2[]); 情況 傳回 st1 等 於 st2 0 st1 大 於 st2 1 st1 小 於 st2 -1  

例 子 : int string_compare(char st1[], char st2[]) { int i; i = 0; while (st1[i] == st2[i] && st1[i] != '\0') i++; if (st1[i] == st2[i]) { return 0; } else { return (st1[i] > st2[i])? 1 : -1;
}
}

main() {
char st1[21] = "abcde", st2[21] = "abdde", st3[21] = "abcdefg";

printf("%s compare %s = %i\n", st1, st2, string_compare(st1, st2));
printf("%s compare %s = %i\n", st1, st1, string_compare(st1, st1));
printf("%s compare %s = %i\n", st2, st1, string_compare(st2, st1));
printf("%s compare %s = %i\n", st1, st3, string_compare(st1, st3));
}

執 行 結 果 :

abcde compare abdde = -1
abcde compare abcde = 0
abdde compare abcde = 1
abcde compare abcdefg = -1


說 明 :



i = 0;
while (st1[i] == st2[i] && st1[i] != '\0') i++;

由 指 數 0 開 始 比 較 , 直 至 出 現 不 相 同 字 元 或 st1[i] 是 結 尾 字 元 , 所 以 執 行 迴 圈 後 , 指 數 i 的 字 元 是 不 相 同 的 字 元 , 或 者 是 '\0' 。 為 什 麼 只 檢 查 st1[i] 而 不 用 檢 查 st2[i] 呢 ? 即 為 什 麼 不 寫 :


while (st1[i] == st2[i] && st1[i] != '\0' && st2[i] != '\0') i++;

因 為 如 果 st1[i] 等 於 st2[i] , 那 麼 st1[i] 不 等 於 什 麼 , 也 代 表 st[2] 不 等 於 什 麼 , 所 以 檢 查 st1 或 st2 也 沒 所 謂 , 不 必 兩 個 都 檢 查 了 。


if (st1[i] == st2[i]) {
return 0;
} else {
return (st1[i] > st2[i])? 1 : -1;
}

如 果 st1[i] 等 於 st2[i] , 就 表 示 兩 個 字 串 是 相 等 的 , 因 此 傳 回 0 , 這 時 候 , st1[i] 和 st2[i] 都 會 等 於 '\0' 。 如 果 它 們 不 相 等 , 就 跟 據 它 們 的 大 小 來 傳 回 1 或 -1 。


abcde compare abcdefg = -1

兩 個 不 同 長 度 的 字 串 也 沒 有 特 別 , 當 比 較 到 "abcdefg" 的 'f' 時 , 剛 好 到 了 另 一 字 串 的 '\0' :

a b c d e \0    
a b c d e f g \0

'f' 的 ASCII 碼 是 66h , '\0' 的 是 0 , 因 此 "abcde" 比 "abcdefg" 小 。


string_compare 函 數 好 像 Perl 的 cmp 運 算 子 。


實 例 : 基 本 字 串 處 理 : 字 串 加 法 上一節 下一節 到頁頂


兩 個 字 串 相 加 . 即 是 把 其 中 一 個 字 串 接 駁 到 另 一 字 串 的 尾 部 。

例 子 :

void string_concat(char st1[], char st2[], char result[]) {
int i, j;

i = 0;
while ( (result[i] = st1[i]) != '\0' ) i++;
j = i;
while ( (result[i] = st2[i - j]) != '\0' ) i++;
}

main() {
char st1[21] = "abcde", st2[21] = "012345", st3[21];

string_concat(st1, st2, st3);
printf("%s + %s = %s\n", st1, st2, st3);
}

執 行 結 果 :

abcde + 012345 = abcde012345


說 明 :



i = 0;
while ( (result[i] = st1[i]) != '\0' ) i++;

先 把 st1 複 製 到 result 。


j = i;
while ( (result[i] = st2[i - j]) != '\0' ) i++;

然 後 複 製 st2 。



實 例 : 基 本 字 串 處 理 : 字 串 位 置 上一節 下一節 到頁頂


尋 找 某 個 子 字 串 在 另 一 字 串 的 開 始 位 置 。

例 子 :

/* Insert "string_length" function here */

int index(char st[], char subst[]) {
int st_start, sti, substi, limit, result, st_length, subst_length;

result = -1;
st_length = string_length(st);
subst_length = string_length(subst);

limit = st_length - subst_length;
if (limit < st_start =" 0" sti =" st_start;" substi =" 0;" substi ="="" result =" st_start;" s =" %i\n" s =" %i\n" s =" %i\n" 012345 =" 0" 012345 =" 3" 012345 =" -1" result =" -1;" limit =" st_length" st_start =" 0" 3 =" limit" substi ="="" i =" 0;" st1 =" %s\n" 3 =" %s\n" 100 =" %s\n" 1 =" %s\n" 0 =" %s\n" st1 =" 012345" 3 =" 012" 100 =" 5" 1 =" Index" 0 =" 說" st_length =" string_length(st);" insert_length =" string_length(insert);" i =" st_length">= start ; i--) {
st[i + insert_length] = st[i];
}

for (i = 0 ; i < before =" %s\n" after =" %s\n" before =" 012345" after =" 01bcd2345" i =" st_length">= start ; i--) {
st[i + insert_length] = st[i];
}

搬 遷 位 置 start 或 之 後 的 字 元 , 包 括 '\0' , 以 便 把 insert 放 到 空 出 來 的 位 置 。

搬遷前 0 1 2 3 4 5 \0      
搬 遷 後 0 1 2 3 4 2 3 4 5 \0
插 入 後 0 1 b c d 2 3 4 5 \0

 


for (i = 0 ; i < insert_length ; i++) {
st[start + i] = insert[i];
}

插 入 insert 。


 


實 例 : 基 本 字 串 處 理 : 字 串 移 除 上一節 下一節 到頁頂


例 子 :

/* Insert "string_length" function here */

void string_remove(char st[], int start, int length) {
int i, limit;

if ( string_length(st) <= start ) return;

i = start + 1;
limit = start + length;
while (st[i] != '\0' && i < limit) i++;

while ( (st[i - length] = st[i]) != '\0' ) i++;
}

main() {
char st1[21] = "012345";

printf("st1 = %s\n", st1);
string_remove(st1, 2, 3);
printf("Index 2, length 3 = %s\n", st1);
}

執 行 結 果 :

st1 = 012345
Index 2, length 3 = 015


說 明 :



i = start + 1;
limit = start + length;
while (st[i] != '\0' && i < limit) i++;

去 到 要 移 除 的 字 串 的 後 一 個 字 元 , 即 i = limit = 5 , 或 去 到 '\0' 。 然 後 把 字 串 剩 餘 的 部 份 , 包 括 '\0' , 複 製 到 start 位 置 。

複製前 0 1 2 3 4 5 \0
複 製 後 0 1 5 \0 4 5 \0

 


while ( (st[i - length] = st[i]) != '\0' ) i++;

把 字 串 剩 餘 的 部 份 複 製 到 start 位 置 。


 


實 例 : 基 本 字 串 處 理 : 字 串 取 代 上一節 下一節 到頁頂


例 子 :

/* Insert "string_length" function here */
/* Insert "string_remove" function here */
/* Insert "string_insert" function here */

void string_replace(char st[], char out[], char in[]) {
int i, out_length;

i = index(st, out);
if (i != -1) {
out_length = string_length(out);
string_remove(st, i, out_length);
string_insert(st, in, i);
}
}

main() {
char st1[21] = "012345";

printf("Before, st1 = %s\n", st1);
string_replace(st1, "123", "OneTwoThree");
printf("After, st1 = %s\n", st1);
}

執 行 結 果 :

Before, st1 = 012345
After, st1 = 0OneTwoThree45


說 明 :



/* Insert "string_length" function here */
/* Insert "string_remove" function here */
/* Insert "string_insert" function here */

string_replace 函 數 會 用 到 string_length 、 string_remove 和 string_insert 函 數 , 其 運 作 原 理 是 : 找 尋 要 移 除 的 字 串 , 如 果 找 到 , 就 移 除 它 , 然 後 插 入 新 的 字 串 。


i = index(st, out);

找 尋 要 移 除 的 字 串 , 傳 回 來 的 子 字 串 位 置 i 會 用 在 string_remove 和 string_insert 函 數 。


if (i != -1)

如 果 找 到 的 話 。



實 例 : 字 典 上一節 下一節 到頁頂


本 節 介 紹 一 個 字 典 程 式 , 它 讓 你 輸 入 英 文 字 , 然 後 會 顯 示 它 的 解 釋 。 你 可 以 定 義 一 個 「 字 典 條 目 」 (Entry) 結 構 :


struct entry {
char word[11];
char definition[31];
}

一 個 條 目 包 括 英 文 字 word 和 解 釋 definition 。 假 設 這 個 字 典 有 5 個 條 目 , 你 可 以 用 結 構 陣 列 來 儲 存 這 個 字 典 :


struct entry dictionary[5];

這 樣 就 可 以 寫 :


dictionary[i].word
dictionary[i].definition

來 表 示 第 i 個 條 目 的 英 文 字 和 解 釋 。

你 可 以 寫 個 函 數 來 搜 尋 這 個 字 典 :


entry_index = lookup(dictionary, word, num_entry);

word 是 要 查 詢 的 英 文 字 , num_entry 是 條 目 數 目 , entry_index 是 word 在 dictionary 陣 列 的 位 置 。

例 子 :

struct entry {
char word[11];
char definition[51];
};

/* Insert "string_compare" function here */

int lookup(struct entry dictionary[], char word[], int num_entry) {
int i;

for (i = 0 ; i < num_entry ; i++) {
if ( string_compare(word, dictionary[i].word) == 0 ) return i;
}

return -1;
}

main() {
struct entry dictionary[5] = {
{"apple", "round fruit with firm juicy flesh"},
{"boy", "male child"},
{"cat", "small furry domesticated animal"},
{"dog", "common domestic animal kept by human"},
{"egg", "the cell from which the young is formed"},
};
char word[11];
int num_entry = 5, entry_index;

printf("Type '9' to quit.\n");
while (1) {
printf("Enter word: ");
scanf("%10s", word);

if (word[0] == '9') break;

entry_index = lookup(dictionary, word, num_entry);

if (entry_index != -1) {
printf("%s\n\n", dictionary[entry_index].definition);
} else {
printf("Sorry, the word is not in my dictionary.\n\n");
}
}
}

執 行 結 果 :

Type '9' to quit.
Enter word: apple
round fruit with firm juicy flesh

Enter word: egg
the cell from which the young is formed

Enter word: fork
Sorry, the word is not in my dictionary.

Enter word: 9


說 明 :


lookup 函 數 會 傳 回 查 詢 字 串 在 字 典 中 的 指 數 , 如 果 找 不 到 字 串 , 就 傳 回 -1 。



總 結 上一節 到頁頂


C 的 字 串 結 尾 字 元 是 '\0' , 使 用 結 尾 字 元 的 好 處 是 不 用 留 意 字 串 長 度 。

2008年9月1日 星期一

不好的早晨

不順,手機忘記帶,蓋子找不到,早上差點來不及,哀
希望下午順利

2008年8月29日 星期五

HOOK

1. test point: 代表類似 Task ID的意義;即做一件事前先丟值(byte)到Port 80.代表"準備要做這件事了". 其值只拿來當作ID;兩兩 tasks的 test point的比較沒有意義 !

2. hook address: 在 POST所做的 tasks 都由幾個要件組成:
a. ID(that is, test point)
b. hook routine (<-你指的 hook address吧...)
c. post routine

b代表:在 c之前要做的事情;hook的英文是"勾";想像有 routine在"這點"被"勾"進來做,做完了做 post routine.

3. flash:將 bios image "寫" 到 ROM中. 當 BIOS checksum fail 或是 有發現特定裝置存在時,BIOS會決定要做 "flash"的動作.

Bootblock中的 flow可分成兩部份: task and flash phase;若沒有需要 flash,則跑完 task table後就離開;若需要 flash則加跑 flash table的部份.

4. BIOS shadow當然在 memory ready/stable之後;shadow code當然放在 ROM中.
Bootblock 是開機後第一個會跑的. 跑完 BB才跑 BIOS entry point(通常 1st instruction is cli). 沒有所謂的 shadow bootblock. 至於BIOS shadow,可以搜尋本網站,有頗詳細的敘述.

鉤子是一種机制,用來截獲你發住指定窗口的各種消息包括鍵盤消息,mouse消息及日志等,你可以查看MSDN或<>中打破進程邊界一節中有詳細敘述.

hook是WINDOWS提供的一種消息處理机制,它使得程序員可以使用子過程來監視系統消息,並在消息到達目標過程前得到處理
使用HOOK 將會降低系統效率,因為它增加了系統處量消息的工作量。建議在必要時才使用HOOK,並在消息處理完成后立即移去該HOOK。
WINDOWS提供了几種不同類型的HOOKS;不同的HOOK可以處理不同的消息。例如,WH_MOUSE HOOK用來監視鼠標消息。
HOOK過程
為了攔截特定的消息,你可以使用SetWindowsHookEx函數在該類型的HOOK鏈中安裝你自己的HOOK函數。該函數語法如下:
public function MyHook(nCode,wParam,iParam) as long
‘加入代碼







運行机制

1、鉤子鏈表和鉤子子程:

每一個Hook都有一個與之相關聯的指針列表,稱之為鉤子鏈表,由系統來維護。這個列表的指針指向指定的,應用程序定義的,被Hook子程調用的回調函數,也就是該鉤子的各個處理子程。當與指定的Hook類型關聯的消息發生時,系統就把這個消息傳遞到Hook子程。一些Hook子程可以只監視消息,或者修改消息,或者停止消息的前進,避免這些消息傳遞到下一個Hook子程或者目的窗口。最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最后,也就是后加入的先獲得控制權。

Windows 並不要求鉤子子程的卸載順序一定得和安裝順序相反。每當有一個鉤子被卸載,Windows 便釋放其占用的內存,並更新整個Hook鏈表。如果程序安裝了鉤子,但是在尚未卸載鉤子之前就結束了,那麼系統會自動為它做卸載鉤子的操作。

鉤子子程是一個應用程序定義的回調函數(CALLBACK Function),不能定義成某個類的成員函數,只能定義為普通的C函數。用以監視系統或某一特定類型的事件,這些事件可以是與某一特定線程關聯的,也可以是系統中所有線程的事件。

鉤子子程必須按照以下的語法:

LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
);

HookProc是應用程序定義的名字。

nCode參數是Hook代碼,Hook子程使用這個參數來确定任務。這個參數的值依賴于Hook類型,每一種Hook都有自己的Hook代碼特征字符集。

wParam和lParam參數的值依賴于Hook代碼,但是它們的典型值是包含了關于發送或者接收消息的信息。

2、鉤子的安裝與釋放:

使用API函數SetWindowsHookEx()把一個應用程序定義的鉤子子程安裝到鉤子鏈表中。SetWindowsHookEx函數總是在Hook鏈的開頭安裝Hook子程。當指定類型的Hook監視的事件發生時,系統就調用與這個Hook關聯的Hook鏈的開頭的Hook子程。每一個Hook鏈中的Hook子程都決定是否把這個事件傳遞到下一個Hook子程。Hook子程傳遞事件到下一個Hook子程需要調用CallNextHookEx函數。

HHOOK SetWindowsHookEx(
int idHook, // 鉤子的類型,即它處理的消息類型
HOOKPROC lpfn, // 鉤子子程的地址指針。如果dwThreadId參數為0
// 或是一個由別的進程創建的線程的標識,
// lpfn必須指向DLL中的鉤子子程。
// 除此以外,lpfn可以指向當前進程的一段鉤子子程代碼。
// 鉤子函數的入口地址,當鉤子鉤到任何消息后便調用這個函數。
HINSTANCE hMod, // 應用程序實例的句柄。標識包含lpfn所指的子程的
DLL。
// 如果dwThreadId 標識當前進程創建的一個線程,
// 而且子程代碼位于當前進程,hMod必須為NULL。
// 可以很簡單的設定其為本應用程序的實例句柄。
DWORD dwThreadId // 與安裝的鉤子子程相關聯的線程的標識符。
// 如果為0,鉤子子程與所有的線程關聯,即為全局鉤子。
);

函數成功則返回鉤子子程的句柄,失敗返回NULL。

以上所說的鉤子子程與線程相關聯是指在一鉤子鏈表中發給該線程的消息同時發送給鉤子子程,且被鉤子子程先處理。

在鉤子子程中調用得到控制權的鉤子函數在完成對消息的處理后,如果想要該消息繼續傳遞,那麼它必須調用另外一個SDK中的API函數CallNextHookEx來傳遞它,以執行鉤子鏈表所指的下一個鉤子子程。這個函數成功時返回鉤子鏈中下一個鉤子過程的返回值,返回值的類型依賴于鉤子的類型。這個函數的原型如下:

LRESULT CallNextHookEx
(
HHOOK hhk;
int nCode;
WPARAM wParam;
LPARAM lParam;
);

hhk為當前鉤子的句柄,由SetWindowsHookEx()函數返回。

NCode為傳給鉤子過程的事件代碼。

wParam和lParam 分別是傳給鉤子子程的wParam值,其具體含義與鉤子類型有關。


鉤子函數也可以通過直接返回TRUE來丟棄該消息,並阻止該消息的傳遞。否則的話,其他安裝了鉤子的應用程序將不會接收到鉤子的通知而且還有可能產生不正确的結果。

鉤子在使用完之后需要用UnHookWindowsHookEx()卸載,否則會造成麻煩。釋放鉤子比較簡單,UnHookWindowsHookEx()只有一個參數。函數原型如下:

UnHookWindowsHookEx
(
HHOOK hhk;
);

函數成功返回TRUE,否則返回FALSE。

3、一些運行机制:

在Win16環境中,DLL的全局數据對每個載入它的進程來說都是相同的;而在Win32環境中,情況卻發生了變化,DLL函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有。當進程在載入DLL時,操作系統自動把DLL地址映射到該進程的私有空間,也就是進程的虛擬地址空間,而且也復制該DLL的全局數据的一份拷貝到該進程空間。也就是說每個進程所擁有的相同的DLL的全局數据,它們的名稱相同,但其值卻並不一定是相同的,而且是互不干涉的。


因此,在Win32環境下要想在多個進程中共享數据,就必須進行必要的設置。在訪問同一個Dll的各進程之間共享存儲器是通過存儲器映射文件技朮實現的。也可以把這些需要共享的數据分離出來,放置在一個獨立的數据段里,並把該段的屬性設置為共享。必須給這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數据段中。

#pragma data_seg預處理指令用于設置共享數据段。例如:

#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()

在#pragma data_seg("SharedDataName")和#pragma data_seg()之間的所有變量將被訪問該Dll的所有進程看到和共享。再加上一條指令#pragma comment(linker,"/section:.SharedDataName,rws"),那麼這個數据節中的數据可以在所有DLL的實例之間共享。所有對這些數据的操作都針對同一個實例的,而不是在每個進程的地址空間中都有一份。

當進程隱式或顯式調用一個動態庫里的函數時,系統都要把這個動態庫映射到這個進程的虛擬地址空間里(以下簡稱"地址空間")。這使得DLL成為進程的一部分,以這個進程的身份執行,使用這個進程的堆棧。

4、系統鉤子與線程鉤子:

SetWindowsHookEx()函數的最后一個參數決定了此鉤子是系統鉤子還是線程鉤子。


線程勾子用于監視指定線程的事件消息。線程勾子一般在當前線程或者當前線程派生的線程內。


系統勾子監視系統中的所有線程的事件消息。因為系統勾子會影響系統中所有的應用程序,所以勾子函數必須放在獨立的動態鏈接庫(DLL) 中。系統自動將包含"鉤子回調函數"的DLL映射到受鉤子函數影響的所有進程的地址空間中,即將這個DLL注入了那些進程。

几點說明:

(1)如果對于同一事件(如鼠標消息)既安裝了線程勾子又安裝了系統勾子,那麼系統會自動先調用線程勾子,然后調用系統勾子。

(2)對同一事件消息可安裝多個勾子處理過程,這些勾子處理過程形成了勾子鏈。當前勾子處理結束后應把勾子信息傳遞給下一個勾子函數。

(3)勾子特別是系統勾子會消耗消息處理時間,降低系統性能。只有在必要的時候才安裝勾子,在使用完畢后要及時卸載。


--------------------------------------------------------------------------------

鉤子類型

每一種類型的Hook可以使應用程序能夠監視不同類型的系統消息處理机制。下面描述所有可以利用的Hook類型。

1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks

WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以監視發送到窗口過程的消息。系統在消息發送到接收窗口過程之前調用WH_CALLWNDPROC Hook子程,並且在窗口過程處理完消息之后調用WH_CALLWNDPROCRET Hook子程。

WH_CALLWNDPROCRET Hook傳遞指針到CWPRETSTRUCT結构,再傳遞到Hook子程。

CWPRETSTRUCT結构包含了來自處理消息的窗口過程的返回值,同樣也包括了與這個消息關聯的消息參數。

2、WH_CBT Hook

在以下事件之前,系統都會調用WH_CBT Hook子程,這些事件包括:

1. 激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件;

2. 完成系統指令;

3. 來自系統消息隊列中的移動鼠標,鍵盤事件;

4. 設置輸入焦點事件;

5. 同步系統消息隊列事件。


Hook子程的返回值确定系統是否允許或者防止這些操作中的一個。

3、WH_DEBUG Hook

在系統調用系統中與其他Hook關聯的Hook子程之前,系統會調用WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許系統調用與其他Hook關聯的Hook子程。

4、WH_FOREGROUNDIDLE Hook

當應用程序的前台線程處于空閑狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先級的任務。當應用程序的前台線程大概要變成空閑狀態時,系統就會調用WH_FOREGROUNDIDLE Hook子程。

5、WH_GETMESSAGE Hook

應用程序使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函數返回的消息。你可以使用WH_GETMESSAGE Hook去監視鼠標和鍵盤輸入,以及其他發送到消息隊列中的消息。

6、WH_JOURNALPLAYBACK Hook

WH_JOURNALPLAYBACK Hook使應用程序可以插入消息到系統消息隊列。可以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的鼠標和鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的鼠標和鍵盤事件就是無效的。

WH_JOURNALPLAYBACK Hook是全局Hook,它不能象線程特定Hook一樣使用。

WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處理來自回放Hook當前消息之前需要等待多長時間(毫秒)。這就使Hook可以控制實時事件的回放。

WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。

7、WH_JOURNALRECORD Hook

WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這個Hook記錄連續的鼠標和鍵盤事件,然后通過使用WH_JOURNALPLAYBACK Hook來回放。

WH_JOURNALRECORD Hook是全局Hook,它不能象線程特定Hook一樣使用。

WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。

8、WH_KEYBOARD Hook

在應用程序中,WH_KEYBOARD Hook用來監視WM_KEYDOWN and WM_KEYUP消息,這些消息通過GetMessage or PeekMessage function返回。可以使用這個Hook來監視輸入到消息隊列中的鍵盤消息。

9、WH_KEYBOARD_LL Hook

WH_KEYBOARD_LL Hook監視輸入到線程消息隊列中的鍵盤消息。

10、WH_MOUSE Hook

WH_MOUSE Hook監視從GetMessage 或者 PeekMessage 函數返回的鼠標消息。使用這個Hook監視輸入到消息隊列中的鼠標消息。

11、WH_MOUSE_LL Hook

WH_MOUSE_LL Hook監視輸入到線程消息隊列中的鼠標消息。

12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks

WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通過安裝了Hook子程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook監視所有應用程序消息。


WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間過濾消息,這等价于在主消息循環中過濾消息。


通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循環里一樣。

13、WH_SHELL Hook

外殼應用程序可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程序是激活的並且當頂層窗口建立或者銷毀時,系統調用WH_SHELL Hook子程。

WH_SHELL 共有5鐘情況:

1. 只要有個top-level、unowned 窗口被產生、起作用、或是被摧毀;

2. 當Taskbar需要重畫某個按鈕;

3. 當系統需要顯示關于Taskbar的一個程序的最小化形式;

4. 當目前的鍵盤布局狀態改變;

5. 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程序)。

按照慣例,外殼應用程序都不接收WH_SHELL消息。所以,在應用程序能夠接收WH_SHELL消息之前,應用程序必須調用SystemParametersInfo function注冊它自己。

參考資料:http://www.zhenwm.com/article.asp?id=1
end function
其中MyHook可以隨便命名,其它不能變。該函數必須放在模塊段。nCode指定HOOK類型。wParam,iParam的取值隨nCode不同而不同,它代表了某種類型的HOOK的某個特定的動作。

2008年8月28日 星期四

BIg real mode

http://www.programmer-club.com/pc2020v5/forum/showSameTitleN.asp?board_pc2020=assembly&id=5188&keyword=

我查了討論區有關Big Mode/Flat Mode/Protected Mode/Big Real Mode/Flat Memort Model 以及相關的Intel 說明,但是對於這個Mode 還是不太懂, 我看到的資料都是說 protect mode 跟 Big Mode (我統一這個說法以免混淆)不一樣,但是不一樣的地方在哪裡?先說說我目前了解的地方:

80386 開始的保護模式PM提出了虛擬記憶體VM的概念(80286就有,只是386開始落實),VM最主要的概念是讓OS以為有很多用不完的記憶體,為了達成這個理想提出了利用分段(Segment)及分頁的方式,原本真實模式RM下是利用段暫存器*10h+偏移位址方式來表示出記憶體位址,而這個方式可以看作是 base address + limit 的方式,簡單說就是原本方式 F000:FFFF=F000*10h + FFFF = FFFFF 可以看作是Base address=F0000 他的區段大小的限制在FFFF(64k),所以F0000~FFFFF是他的區段。

除了這個觀念外另一個觀念就是RM的時候因為暫存器16 bit 所以沒辦法定址到全部的位址因此才使用上述的這種方式,但是到了386年代暫存器已經32 bit,所以一般暫存器就可以存取4G的位址而不需要用到"段暫存器"。 因為可以存取到4G而不需要段暫存器,所以在80386的保護模式的虛擬記憶體的分段概念中段暫存器就不是拿來當作存放Base Address用途,而是拿來當段描述的選擇器Segment Selector(或稱之為Index也可以看作是Offset)。

另外就是保護模式要保護什麼?他就是要保護程式彼此之間不會去存取到對方的程式段或是資料段以免發生問題,因此在386 PM模式裡面他去描述了像是一些存取權限/優先權(Ring 0~3)...等的東西,因為要描述這些東西所以他使用了64 bit的資料結構去描述 base address+Limit+優先權+存取權限,簡單說就是每一筆的描述都是64 bit(也有人說8 Bytes)大家稱這個描述叫做段描述符(Segment Descriptor),因為有很多筆所以程式設計師必須在實際的記憶體區塊去分配一個陣列放這些描述的資料,而這個陣列是大家都看得到的(PUBLIC),所以稱之為GDT(Gobal Descriptor Table),而前面說的段暫存器就是用來選這個陣列裡面的那一筆資料,那另外一個問題出現了,程式設計師分配了一會記憶體區塊來描述這些段的資訊那這個Table到底在哪裡? 因此386加入了一個暫存器叫做GDTR指向GDT的入口(GDT的Base Address),你可以利用LGDT指令把GDTR指向GDT... 因為保護所以每個任務都會有屬於自己的記憶體區段,所以每個任務也會有各自的段描述,稱之為LDT(Local Descriptor Table),LDT也是陣列,裡面也有段描述...為了統一處理,所以GDT裡面除了段描述SD0/SD1...SD N 之外還有描述LDT 0/LDT1...LDT N在哪個記憶體區段的資料(這邊要特別強調,GDT分配到一塊記憶體的區段,LDT也是,但是放在GDT裡面的LDT 0只是用來指向LDT是放那一塊記憶體的描述),那他們也設計了一個暫存器叫做LDTR來當作Index(選GDT陣列中的LDT描述),那至於你要選哪一個Table就是看段暫存器的設定,段暫存器 bit 2 =0 就是選GDT,=1就是LDT, bit 1:0=RPL ,其他的bit15:3才是當作Index。

GDT (假設放在00028000)
SD0
SD1
SD2
LDT0 (假設放在0003A000)
LDT0 ------------------------------> SD0
LDT1 SD1
LDT2 SD2

總結 :
1.RM 因為暫存器只有16 bit 所以使用Segment*10h+Offset方式,但是這個方式可看作Base Address+Limit概念

2.PM 模式提出VM概念:利用Segment 跟 Paging,他有3種定址方式: 邏輯位址(Logical Address)、線性位址(Linear Address)、和實體位址(Physical Address)。
a.所謂的「實體位址」,就是指系統的記憶體的真正位址,它的範圍由 00000000H 到 FFFFFFFFH,共有 4GB。
b.在沒有使用分頁(Paging)功能的時候,線性位址是直接對映到實體位址的,也就是說,線性位址就等同於實體位址。不過,在開啟分頁分能之後,一個線性位址可能沒有相對映的實體位址(因為它所對映的記憶體可能被 swap 到硬碟裡了)。
c.邏輯位址則和實際模式類似,仍然是 segment:offset 的形式,只不過現在 offset 的大小改成 32 bit 而已,而 segment 仍然是 16 bit。另外就是段暫存器當作是Segment Selector。

3.段暫存器用來當Index 是bit 15:3 所以是2^13 ,給ㄧ個段描述最大可以4G,所以看作是有2^13 * 4G這樣多的記憶體可以用

4.分段方式可以分成兩種:
a. Flat segmentation model : 所有區段對應整個32 bit實體空間,最少要兩個區段,1個程式段,1個資料段,描述在GDT,當Base=0 Limit=4G時可以存取整個實體位址
b.Multi-Segmentation Model:每個程式有自己的LDT,LDT由Segment Selector組成

5.澄清專有名詞: Big Mode / Flat Mode : BIOS 用來存取4G方式 (旗標BIOS Inside pp.3-17)

Flat Memory Model :應該指的是Flat segmentation model (Paper Author: Steve Gorman Title: Programming with the Intel architecture in the flat memory model)

Virtual Protected Mode/ Protected Mode : 虛擬記憶體概念的保護模式

Flat segmentation model : 分段方式的其中一種 (Assembly Language for Intel-Based Computer 4th edition,KIP R.IRVINE,全華科技)

問題:
1.一般我們看到的 xxxx:zzzzzzzzh 表示方式是指Segment Selecotr: Offset 嗎?

2.DOS底下也可以用平坦區段模式(Flat Segmentation Mode)存取4G,那他跟Big Mode有什麼不同?

3.PM模式中的分段方式裡面的平坦區段模式就可以存取4G實體位址,那麼跟Big Mode有什麼不同

4.進入保護模式PM方式是設定GDT/LDT,設定CR0,而Big Mode也是一模一樣的方式,他們有什麼不一樣?

5.Big Mode為什麼設定好Flat Segmentation Mode後要從PM切換回去RM,並且開啟A20 ?

6.RM底下開啟A20不是只能存取1M + 64k - 16 嗎? 為什麼他切進去PM設定好之後切換回來就可以存取4G ?

7.當Big Mode切換回RM之後定址方式是 Segment:Offset 還是 0: Offset 還是 Segment Selector:Offset(這個應該不可能因為RM底下沒有GDT)???

A:
在 Real Mode 下,改變 Selector 只會更新 Base Address。唯有在 Far Jump 時,CS 的 Limit 會被重載入!至於 A20 ,是由 Chipset 或 KBC 來控制 CPU 的 A20 Mask,所以無關 PM 或 RM。如果 A20 未開,就如同 CPU 的 A20 address line 不會動作一樣,那麼你可以想像會有什麼結果了!不過現在 AMD K8 似乎已沒有 A20M pin,這個古老的東西存在的目的只在相容性而已,說不定未來 Intel 也會拿掉這個東西!(286 我並不了解,也不知是否有特殊限制)
Hidden=Segment Base Address+Segment Limit+ Attri+...
分成固定與非固定兩部份: Segment Base Address是非固定,其他為固定

Real Mode底下
MOV AX,F000
MOV DS,AX ; 此時非固定部分會更新成,目前DS*10

PM Mode底下
MOV BX,Selector
MOV DS,BX ;此時固定與非固定部分都會更新

為什麼要做Short JMP?
因為 CPU 不只一條指令管線(instruction pipeline), 所以會預先提取指令(instruction fetching)並解碼(decoding), 但同樣的指令在 Protected Mode 和 Real Mode 中的解碼結果是不同的, 所以要將指令管線中在 Real Mode 解碼的部分清掉, 怎麼清呢? 執行會產生"程序控制權轉移"的指令即可, 如 jmp,jxx,call,ret,int,iret 等指令.

每個段都有自己的隱含暫存器,CPU定址是利用隱含暫存器的Base Addr + Offset 而不是Seg*10h+Offset (80286以後就不是這樣定址了) 在80386裡面隱含暫存器是64 bit,它分成幾部份:段暫存器Base Addr+ 段暫存器Limit+段暫存器屬性

有人說A20打開就可以存取0~4G了,這是對的嗎? 這個問題要考慮CPU定址方式跟位址線。當A20=0,就算你能定址0~4G,你也只能存取偶數位址。當A20=1 ,而CPU的隱含暫存器如果沒有設定成【Base Addr】=00000000,【Limit】跟【段暫存器屬性】改變成FFFFF * 4K=4GB你也沒辦法定址0~4G,所以答案告訴我們都是CPU在決定一切,A20=1只是必要的一個過程而不是打開就可以存取0~4G。

而這個答案也告訴我們只要你有辦法設定【隱含暫存器】不管那個CPU模式你都能去存取0~4G。真實模式下沒辦法設定【隱含暫存器】,唯有進入保護模式才能設定Big Real Mode 這個模式指的就是改變CPU狀態讓我們可以在真實模式底下存取0~4G的記憶體位址。

這方式步驟就是在真實模式設定好GDT,然後進入保護模式中設定好【隱含暫存器】,接著切換回去真實模式並且開啟A20。

1.找ㄧ塊記憶體區塊放置GDT Table(我們要改變的段描述也在那裡面,設定Limit=4G)
2.載入段描述表(用LGDT指令會將GDT位址放入, GDTR暫存器)
3.設定控制暫存器CR0讓CPU進入保護模式
4.做一個Short JMP,清空CPU Prefetch,因為 CPU 不只一條指令管線(instruction pipeline) , 所以會預先提取指令(instruction fetching)並解碼(decoding) ,但同樣的指令在 Protected Mode 和 Real Mode 中的解碼結果是不同的,所以要將指令管線中在 Real Mode 解碼的部分清掉,怎麼清呢? 執行會產生"程序控制權轉移"的指令即可, 如 jmp,jxx,call,ret,int,iret 等指令。
5.執行CLI指令關閉所有中斷請求以免錯誤(保護模式內段暫存器是Selector,如呼叫中斷會發生錯誤,有解決方式但超過討論範圍)
6.載入索引給你將來會使用到的段暫存器,讓CPU能夠自己去將段描述載入【隱含暫存器】
7.設定CR0,關閉保護模式,回保護模式(因為目的已經達到)
8.做一個FAR JMP ,清空CPU Prefetch ,並且給定正確的CS:IP
9.真實模式下開啟A20 位址線。

;;In Real Mode:;設定一些相關設定
LGDT ;;把GDT 放到LDTR
EnablePMMode ;;改變CR0進入保護模式
JMP Flush1 ;;丟棄已經存於CPU預先存取佇列中的指令
Flush1: ;; 這裡以經是保護模式MOV DS,BX ;;
裝Selector,給你要用的段暫存器DS/ES/FS…等
........
........
DisablePMMode ;; 改變CR0關閉保護模式
DB 0EAH ;;Far JMP回Real Mode,
DW OFFSET Flush2 ;;你程式碼的段內偏移位址
DW F000H ; ;你程式碼的段位址

Flush2: ;; 這裡以經是真實模式

XOR AX,AX
MOV DS,AX ;;DS=0,以後就可以用DS:OFFSET方式存取4G
.......

EnableA20 ;;開啟A20, by Harrison 2006.09.27


====================================================================
我想請問一下你所謂的"RESET CPU"是指HW RESET嗎? 因為我最近在看BOOTBLOCK跟POST 的Code,理論上CPU Reset 一定是去FFFF_FFF0讀第一行指令,然後看讀取的第一條指令要做什麼,但是我最近發現第一條指令是Short JMP 到BootBlock,而不是Far JMP(Far JMP才會跳到1M的地方,因為會改變CS跟IP,造成隱含暫存器的Base Addr=F0000h),所以我覺得很奇怪,感覺BIOS從開機後執行都是在A20 Enable且存取的都是4G那邊的位址,一直到了他去初始化一些HW後才回歸到1M那邊,另外A20 Disable 跟Warm Boot真的有影響嗎?
據我所知,如果你是Phoenix BIOS,你去看第一條指令,他是跳到BoolBlock執行,你可以用反組譯工具或是用Debug工具去FFFF_FFF0看,他是E9 XX XX, 'E9'是Short Jmp

FFFF_FFF0 CPU RESET來這邊讀,如果有裝BootBlock,他會做short jmp 'E9' to bookblock(真正放BootBlock程式碼的地方)

FFFE_XXXX 假設這邊是實際上 BootBlock 進入點的地方,這邊會去判斷一些事情
如果F000:FFF0 是E9,代表沒有Shadow 過
如果F000:FFF0 是EA,代表已經Shadow 過,也就可能是Warm Boot

F000:FFF0 CPU RESET一定不會讀這邊,不管是Warm Boot還是Cold Boot(因為CPU RESET一定是去FFFF_FFF0,去4G而不是1M,雖然都是同一個地方同資料,但是對CPU來說不一樣),如果已經從FFFF_FFF0 JMP到某個地方去開始執行BootBlock後,再來讀取這邊的資料的話,你會讀到跟FFFF_FFF0一樣的資料,也就是E9 xx xx,另外當POST過程中如果Shadow 相容性的資料後,這邊的資料會變成 EA F000:E05B,簡單說就是:
Shadow 前: F000:FFF0讀到的資料跟FFFF_FFF0一樣, E9 XX XX
Shadow 後: F000:FFF0讀到的資料會變成EA F000:E05B

F000:E05B BIOS Entry Point (BIOS POST Test),這邊決定的是Cold Boot或是Warm Boot(註:以BIOS觀點,因為HW的Cold/Warm Boot跟BIOS觀點不同)
P.S CPU RESET後的狀態

CS:F000 <--注意,80286之後不用CS定址
EIP:0000FFF0 <--16 bit只能看到FFF0,因為Segment Limit=64k
隱含暫存器的Base Addr=FFFF0000 <--真正被拿來定址的暫存器
隱含暫存器的Segment Limit=64k

所以第一條指令定址的方式是 Base Addr+IP=FFFF000+FFF0=FFFFFFF0 <--4G的地方

如果第一條指令是FAR JMP,則會改變CS跟IP(也就是隱含暫存器內的Base Addr會重新計算,計算方式是 Base Addr=目前CS*10h=F0000h, IP=跳躍的位址, 所以會定址方式 Base Addr+IP也就是F0000+IP,可以看做F000:IP <--1M的地方)

如果第一條指令是Short JMP,理論上Base Addr不會改變(因為Short JMP只會改變IP不會改變CS,因此Base Addr不會重新計算),所以是FFFF0000+IP <--保持這個狀態直到做了FAR JMP/FAR CALL才會改變,也就是我們說的跳到1M的地方.

因此有人說CPU讀取的第一條指令一定是FAR JMP,做了FAR JMP後就會跳到1M的地方執行,這種說法沒錯,但是這種情況是沒有BootBlock下才這樣。

忘記說明一下最近K完SPEC後發現底下的表示法的不同,這是南北橋SPEC裡面的表示法:FFFF_FFF0 <--代表4G那邊的位址,以前我一直以為FFFF:FFF0 ,我很豬頭吧! 呵呵!

跟FFFFFFF0 表示方式一樣

F000:FFF0 <--代表F000*10h + FFF0

我自己說明一下免得大家搞糊塗我所表示的地方...^^

2008年8月27日 星期三

NVRAM

1. CMOS 不是固定128BYTE,通常都會70h&71h port 這一對IO PORT可以存取你說的128BYTES,72h & 73h 這對PORT可以存取額外的128 bytes 若你的南橋有支援的話74h & 75h 還有128BYTES可用
2. NVRAM 就是非揮發性記憶體,也就是可以不依靠電源就可以永久記住 CMOS其實並不算因為只要主機板的小電池沒電他就無法保持資料 所以FLASHROM=NVRAM
3. DMI table 固定資料的部份會寫在FLASHROM,這是你刷新BIOS就寫進去了但是有些動態資料就是POST的過程中算出來的,而不管是動態或靜態資料都會在POST的過程中,存到F000:0000 的區段內
4. 有任何變更FLASHROM的時候通常為BIOS刷新
5. NVRAM可以放一大堆東西,例如PASSWORD或是存放一些s3要回存的資料

1. BIOS是Flash ROM也可以稱之為NVRAM,因為他有存放一些資料,例如Boot code/DMITable/ESCD/PASSWORD....等

2. 在南橋晶片組裡面有 128/256 Byte/...等的 "SRAM ",就靠那顆2.8V的鋰電池,讓它變成NVRAM(可能稱不上是NVRAM).

3.除了上述的地方之外還是有可能有其他的方有NVRAM...所以BIOS的Flash part除了放程式碼/OPROM/...開機會用到的東西之外,還多放了一些像是DMI tabl/ESCD/PASSWRD..等的東西,這樣子說法對吧?如果上述說法是正確的,也就是說一開始的時候BIOS是放在EEPROM,CMOS與RTC放在一個晶片裡面,後來因為技術進步,所以使用NVRAM取代EEPROM,因為可以存放一些資料速度也比較快,而CMOS則被南僑晶片整合進去...以上是我看到資料與大大說明後的心得,如有誤請指正!

2008年8月26日 星期二

暫存




ACPI SPEC中 EC INTERFACE兩種




SHARED PRIVATE

COMPAL用


SHAREDSMI - SMI」SCI共用一組COMMAND跟DATA的ADDRESS


Q:到底是BIOS還是OS











EC工作原理 INDEX IO PORT DATA IO PORT

主動向OS做請求使用INTERRUPT ACPI定義為SMI SCI兩種INT













PRIVATE INTERFACE

SMI接手的是BIOS SCI是OS



DOS下INT皆會產生SCI EC需要獨立的SCI跟OS做溝通



EC使用INT跟OS溝通

EC用獨立的SCI跟OS作溝通

WINDOWS下INT皆會產生SCI



EC 只能有256BYTE的RAM SPACE

但系統可以有muiltiple的EC存在

但EC的RAM SPACE最多只能到256BYTE

SMSBUS 整理

SMbus 最早是由 Intel 公司提出來的. 現在由 SBS 管理維護這一個規格. 此規格是用 PhilipsI2C 簡化而來. SMbus 是由兩條訊號所組成的一種匯流排. 是為了在系統上較慢速的裝置及電源管理裝置之間的溝通使用. 使系統可取得這些裝置的製造廠商,型號,一些控制資訊,錯誤訊息及狀態.
這兩條訊號為 SMBCLK 和 SMBDATA. 這和 I2C 上的 Clock(SCL) 和 Data(SDA) 是一樣的.











( 取材自 Philips I2C )
上圖為一 SMbus 的架構圖. 不同的裝置都接在同一 Bus 上. 在 SMbus 上只有一個 Master. 所有的命令均由此 Master 發出. 其他的裝置 (Slave) 只能接收 Master 發出的命令或回覆資料給 Master.









上圖為 SMbus 開始及結束 bus 的圖. 當 SCL 為 High 而 SDA 由 High 變 Low 時表示開始一個 SMbus 的命令. 當 SCL 為 High 而 SDA 由 Low 變 High 時表示結束一個 SMbus 的命令. 這二個狀況在 Smbus 裡是唯一的. 在一般傳送資料時均不可能發生. 而在一般傳送資料時則是在每一次 SCL 的上升緣時的 SDA 狀態來決定. 這些資料包含了仲裁,確認,送出資料給那一個裝置及送出的資料.或要取得那一個裝置的資料及由裝置送出的資料.





關於I2C Bus與SMBus,許多人很少去談論與瞭解兩者的細節差異,包括很多國外的簡報文件也經常將兩者混寫、交雜描述、交替運用。
確實,在一般運用下,I2C Bus與SMBus沒有太大的差別,從實體接線上看也幾乎無差異,甚至兩者直接相連多半也能相安無誤地正確互通並運作。不過若真要仔細探究,其實還是有諸多不同,如果電子設計工程師不能明辨兩者的真實差異,那麼在日後的開發設計的驗證除錯階段時必然會產生困擾,為此本文將從各層面來說明I2C Bus與SMBus的細微區別,期望能為各位帶來些許助益。 
附註:關於I2C Bus的基礎,可參考筆者之前的「I2C介面之線路實務」,網址為: http://www.digitimes.com.tw/n/article.asp?id=304799064272FED148256FDC00481D68
或參考Philips半導體網站的I2C官方規格: http://www.semiconductors.philips.com/acrobat/literature/9398/39340011.pdf

運用背景、版本演進之別 

首先從規格的制訂背景開始,I2C是在設計電視應用時所發創的介面,首版於1992年發表;而SMBus(System Management Bus)則是Intel與Duracell(金頂電池)共同制訂筆記型電腦所用的智慧型電池(Smart Battery)時所發創的介面,首版於1995年發表,不過SMBus文件中也提及,SMBus確實是參考自I2C,並以I2C為基礎所衍生成。 
I2C起源於電視設計,但之後朝通用路線發展,各種電子設計都有機會用到I2C;而SMBus則在之後為PC所制訂的先進組態與電源管理介面(Advanced Configuration & Power Interface;ACPI)規範中成為基礎的管理訊息傳遞介面、控制傳遞介面。 

雖然I2C與SMBus先後制訂時間不同,但都在2000年左右進入成熟化改版,I2C的過程改版以加速為主要訴求,而SMBus以更切合Smart Battery及ACPI的需求為多。 
I2C三次主要改版: 1992年 v1.0 1998年 v2.0 2000年 v2.1 
SMBus三次主要改版: 1995年 v1.0 1998年 v1.1 2000年 v2.0
 













▲圖說:MAXIM公司的MAX6641晶片,具有溫度監督及風扇控制功能(用PWM脈寬調變方式控制風扇轉速),圖中腳位7、8即是SMBus(圈處),其他裝置可透過SMBus與此晶片溝通,取得溫度及相關資訊,或進行命令操控。(圖/MAXIM-IC.com) 

電氣特性差異:邏輯位準定義、限流、相關限制 
I2C的Hi/Lo邏輯準位有兩種認定法:相對認定與絕對認定,相對認定是依據Vdd的電壓來決定,Hi為0.7 Vdd,Lo為0.3 Vdd,絕對認定則與TTL準位認定相同,直接指定Hi/Li電壓,Hi為3.0V,Lo為1.5V。相對的SMBus只有絕對認定,且準位與I2C有異,Hi為2.1V,Lo為0.8V,與I2C不全然吻合但也算部分交集。不過,SMBus後來也增訂一套更低電壓的準位認定,Hi為1.4V,Lo為0.6V,這是為了讓運用SMBus的裝置能更省成本而有的作法。 
瞭解電壓後再來是電流,由於SMBus一起頭就是運用在筆記型電腦內,所以低用電的表現優於I2C,只需100uA就能維持工作,I2C卻要到3mA,同樣的低用電特性也反應在漏電流(Leakage Current)的要求上,I2C最大的漏電流為10uA,SMBus為1uA,但是1uA似乎過度嚴苛,使運用SMBus的裝置在驗證測試時耗費過多的成本與心力,因此之後的SMBus 1.1版放寬了漏電流上限,最高可至5uA。 
再者是相關限制,I2C有線路電容的限制,SMBus卻沒有,但也有相類似的配套規範,即是準位下拉時的電流限制,當SMBus的開集極接腳導通其閘極而使線路接地時,流經接地的電流不得高於350uA,另外拉升電流(即相同的開集極接腳開路時)也一樣有規範,最小不低於100uA,最高也是不破350uA。 
既然對電流有限制,那麼也可容易地推斷對提升電阻的阻值之範圍要求,I2C在5V Vdd時當大於1.6k ohm,在3V Vdd時當大於1k ohm,類似的SMBus於5V Vdd時當大於14k ohm,3V Vdd時當大於8.5k ohm,不過這個定義並非牢不可破,就一般實務而言,在SMBus上也可用2.4k∼3.9k ohm範疇的阻值。 
附註:I2C的時脈線稱SCK或SCL,資料線稱SDA。SMBus的時脈線稱SMBCLK,資料線稱SMBDAT。 




▲圖說:I2C與SMBus在邏輯位準的電壓定義不盡相同,基本上I2C的定義較為寬裕、彈性,而SMBus則更專注在省電方面的要求。(圖/MAXIM-IC.com) 
時序差別與考驗 實體層面的空間要求完後,再來就是實體層面的時間,即是時序(Timing)方面的差別。 
先以運作頻率來說,I2C此方面相當寬裕,最低頻可至0Hz(直流狀態,等於時間暫停),高可至100kHz(Standard Mode)、400kHz(Fast Mode)、乃至3.4MHz(High Speed Mode),相對的SMBus就很拘限,最慢不慢於10kHz,最快不快於100kHz。很明顯的,I2C與SMBus的交集運作頻率即是10kHz∼100kHz間。 
用於筆記型電腦的電池管理或PC組態管理、用電管理的SMBus,很容易體會不需要更高運作頻率的理由,只要傳遞小資料量的監督訊息、控制指令本就不用過於高速,而朝向廣遍運用的I2C自然希望用更高的傳輸以因應各種可能的需求。然而大家可能會疑惑,為何SMBus有最低速的要求?何不放寬到與I2C相同的毫無最低速限呢? 
SMBus一定要維持10kHz以上的運作時脈,主要也是為了管理監控,另一個用意是只要在保持一定傳速運作的情況下加入參數,就可輕鬆獲知匯流排目前是否處於閒置(Idle)中,省去逐一偵測傳輸過程中的停斷(STOP)信號,或持續保有停斷偵測並輔以額外參數偵測,如此對匯流排閒置後的再取用會更有效快速。 
傳速要求之後還有資料持留時間(Data Hold Time)的要求,SMBus規定SMBCLK線路的準位下降後,SMBDAT上的資料必須持續保留300nS,但I2C卻沒有對此有相同的強制要求。類似的,SMBus對介面被重置(Reset)後的恢復時間(Timeout)也有要求,一般而言是35mS,I2C這方面亦無約束,可以任意延長時間。相同的SMBus也要求無論是在主控端(Master)或受控端(Slave),其時脈處於Lo準位時的最長持續時間不得超越限制,以免因為長時間處在Lo準位,而致收發兩端時序脫軌(失去同步,造成後續誤動作)。 
還有,I2C與SMBus在準位的上升時間、下降時間等也有不同的細部要求,此點必要時也必須進行確認,或在驗證過程中稍加留意。 

▲圖說:Smart Battery或ACPI的實現、監督、與操控,最底層都需要SMBus(圈處)作為後援,圖為簡易的多組式智慧型電池系統,圖中有Smart Battery A、B兩組電池。(圖/SBS-Forum.org) 
「已妥」與「未妥」機制的強制性差別 
不單是電氣、時序有別,更高層次的協定機制也有不同。在I2C中,主控端要與受控端通訊前,會在匯流排上廣播受控端的位址資訊,每個受控端都會接收到位址資訊,但只有與該位址資訊相切合的受控端會在位址資訊發佈完後發出「已妥」的回應(Acknowledge;ACK),讓主控端知道對應的受控端確實已經備妥,可以進行通訊。但是,I2C並沒有強制規定受控端非要作出回應不可,也可以默不作聲,即便默不作聲,主控端還是會接續工作,開始進行資料傳遞及下達讀/寫指令,如此的機制在一般運用中還是可行,但若是在一些即時(Real Time)性的應用上,任何的動作與機制都有一定的時限要求,這種可有可無式的回應法就會產生問題,可能會導致受控端無法接收資訊。 
相同的情形,在SMBus上是不允許受控端在接收位址資訊後卻不發出回應,每次都要回應,為何要強制回應?其實與SMBus的應用息息相關,SMBus上所連接的受控裝置有時是動態加入、動態移除的,例如換裝一顆新電池,或筆記型電腦接上船塢埠等,如果接入的裝置已經改變卻不回應,則主控端的程式所掌握的並非是整體系統的最新組態,就會造成誤動作。 
類似的情形也適用於ACPI,PC機內機外經常有一些裝置可動態增入、移除,如機內風扇、外接印表機等,這些也一樣該強制對主控端廣發的位址資訊作出完整回應。 
位址動作方面有異,資料傳輸方面也有異。在I2C方面,Slave雖然對Master所發出的位址作出回應,但在後續的資料傳遞中,可能因某些事務必須先行處理、因應而無法持續原有的傳輸,這時候Slave就要對Master發出「未妥」的回應(Not Acknowledge;NACK),向Master表示Slave正為他務忙碌中。 
而SMBus方面,與I2C相同的,會以NACK的回訊向Master表達Slave尚未收妥傳遞的資訊,但是SMBus的Slave會在後續的每個Byte傳輸中都發出NACK回訊,這樣設計的原因是因為SMBus沒有其他可向Master要求重發(Resend)的表示法。更直接說就是:NACK機制是SMBus標準中的強制必備,任何的訊息傳遞都很重要,不允許有漏失。 

▲圖說:I2C在完成一段位址或資料資訊的傳輸後,受接端可發出訊息收妥(ACK)、未妥(NACK)的回應,SMBus也具相同的機制,但由於應用之故有更強制的回應要求。(圖/Semiconductors.Philips.com) 
傳輸協定的子集、超集 
互動知會機制上有強制與否的差別,協定方面也是。SMBus的通訊協定與協定中所用的訊息格式,其實只是取自I2C規範中,對於資料傳輸格式定義中的子集合(Subset)而已。所以,如果將I2C與SMBus交混連接,則I2C裝置在存取SMBus裝置時,只能使用SMBus範疇的協定與格式,若使用I2C的標準存取方式反而無法正確存取。 
另外,I2C規範中有一種稱為「General Call」的廣呼方式,當發出「0000000」的位址資訊後,所有I2C上的Slave裝置統統要對此作出反應,此機制適合用在Master要對所有的Slave進行廣播性訊息更新與溝通上,是一種總體、批次的運作方式。 
SMBus一樣有General Call機制,但在此之外SMBus還多了一種特用的ALERT(警訊)機制,不過這必須於時脈線與資料線外再追加一條線(稱為:SMBSUS)才能實現,ALERT雖名為警訊但其實是中斷(Interrupt)的用意,Slave可以將SMBSUS線路的電位拉低(ALERT#,#表示低準位有效),這時就等於向Master發出一個中斷警訊,要求Master儘速為某一Slave提供傳輸服務。Master要回應這個服務要求,是透過I2C/SMBus的時脈線與資料線來通訊,但要如何知道此次的通訊只是Master對Slave的一般性通訊?還是特別針對Slave的中斷需求而有的服務回應? 
這主要是透過Master發出的位址資訊來區別,若為回應中斷的服務,位址資訊必然是「0001100」,當Slave接收到「0001100」的位址資訊,就知道這是Master特為中斷而提供的服務通訊。因此,韌體工程師須留心,規劃時必須讓所有的Slave都不能佔用「0001100」這個位址,以供ALERT機制運用(當然!若現在與未來都不會用上ALERT機制則可儘管佔用)。事實上各種進階的規範標準(如Smart Battery、ACCESS.bus、VESA DDC等)都在I2C的短定址中訂立了一些為自用而保留的位址,這在最初設計與定義時就該有所留意,以免因先行佔用而導致日後須改寫韌體的麻煩。 
補充提醒的是,SMBSUS一樣是開集極外加提升電阻的線路,所以有一個Slave將電位拉下後,其餘Slave偵測到電位被拉下,表示已有Slave正在與Master進行中斷需索與回應服務,須等待搶到中斷服務權的Slave確實被服務完畢,重新將SMBSUS釋放回高準位後,才能持續以「看誰能先將線路準位拉低?」的方式來爭取中斷服務。 
最後,若有進一步興趣的讀者,筆者建議可參考兩份資料: 
1.SMBus 2.0版規範(SMBus規格網站) http://www.smbus.org/specs/smbus20.pdf
2.比較I2C與SMBus(MAXIM公司網站) 
 
▲圖說:MAXIM公司的MAX6641晶片之典型應用方式,圖左為溫度感測電路,圖右上為風扇轉速控制電路,圖右圈處即是SMBus介面電路。(圖/MAXIM-IC.com)
轉自

2008年8月12日 星期二

第一篇

公司不能上BBS
這裡主要是希望能夠讓我紀錄一些程式語言和工作上的心得