標籤

顯示具有 CPU 標籤的文章。 顯示所有文章
顯示具有 CPU 標籤的文章。 顯示所有文章

2009年11月16日 星期一

讀寫SMRAM

http://bbs.pediy.com/showthread.php?t=84835



標 題: 【原創】SMM Rootkit初步 - 讀寫SMRAM(帶你邁入CPU級Rootkit之門)

作 者: 木樁

時 間: 2009-03-29,14:37

鏈 接: http://bbs.pediy.com/showthread.php?t=84835


好吧,我承認標題有點誇大,不介意的都進來BS我把

就像 qihoocom 大俠說的,「SMM XX早就幾萬年前就不是什麼秘密了」,不過國內討論這個的到是鮮見,希望這篇文章能拋磚引玉吧。


之前看到rootkit.com上 Implementing SMM PS/2 Keyboard sniffer 那個代碼,狠狠震撼了一把。於是開始研究SMM,半月下來總算有些收穫。這裡將如何讀寫SMRAM的方法拿出來科普一下,希望更多人能邁入編寫SMM Rootkit的大門。

文章後面提供可以在VPC和VMware兩大虛擬機中運行的代碼,將演示如何在WinXP(SP2)中讀寫SMRAM。(本文只考慮最簡單的系統環境,不涉及 SMM Space Locked(D_LCK)=1 時無法讀寫SMRAM的情況)另外,按 Ivanov 大牛的說法,這種方法侷限性很大,只能在沒有執行 Intel 修補方案的主板上運用(感謝兩大VM模擬的都是I440BX )。


對硬件方面接觸尚淺,文中若有疏漏之處,歡迎指正。



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


首先放下PCI相關的諸多知識,我用最短的篇幅敘述讀寫SMRAM的過程。希望這篇文章能帶你邁出SMM Rootkit的第一步。

首先說說什麼是SMM(System Management Mode)。System Management Mode Hack中說:SMM是專門為電源管理設計的執行規則。當進入SMM後,系統的各個部件可以被關閉或者使用最低的功耗。SMM獨立於其他的系統軟件,也可以被用在其他目的...  該文章中文版點這裡


瞭解SMM是什麼東西就夠了,這篇文章不會涉及太多SMM的東西。相反,讀寫SMRAM更需要的是PCI配置空間(PCI Configuration Space)方面的知識。

先講講兩個訪問PCI配置空間的I/O端口:CF8h 和 CFCh

通過向 CF8h 端口寫一個特殊的地址,我們就可以在 CFCh 端口上讀寫指定寄存器的值。這裡要讀的寄存器是位於配置空間偏移為 72h 的8bit寄存器——SMRAM (System Management RAM Control Register)


首先要介紹一下 CF8h 這個I/O端口。實際上 I/O CF8h 寫的是CONFIG_ADDRESS寄存器,所以 CF8h又叫CONFIG_ADDRESS。其佈局如下[1]:

名稱: CONFIG_ADDRESS寄存器結構.gif 查看次數: 1684 文件大小: 7.6 KB

按上圖格式,31位置1(enable),假設I440BX的北橋82443BX位於Bus: 0, Device:0, Func:0(後面將教你如何確定芯片類型以及取得這幾個值),這樣配置空間的基址就是0x80000000。


通過I/O指令向CF8h寫入0x80000000,就可以從 CFCh(CONFIG_DATA)中讀出82443BX配置空間偏移為0的一個DWORD值了。而這個DWORD的高低16位分別是VendorID和DeviceID,如圖:

名稱: PCI配置空間頭部_cut.gif 查看次數: 1675 文件大小: 8.9 KB


下面在我們去讀配置空間偏移為72h的SMRAM前,首先注意一個特性。不知道你注意到沒有,回頭看看CONFIG_ADDRESS的低2位,它們被定義為 0。且不談PCI規範上的大道理,說白了這個地方為0是為了限制你給出的地址——必須為4的整數倍。瞭解到這一點再看那個Register Number 就比較好理解了,原來這個寄存器編號和偏移是這麼對應的。



好了,那麼偏移為72h (01110010) 的SMRAM,最後兩位置零是70h (01110000),即位於編號為1C (11100)的寄存器上。所以只要向 CF8h 裡寫入0x80000070這個地址,此時從 CFCh 端口讀出的數據就是包含SMRAM的一個DWORD了。


名稱: 82443BX Register Map_SMRAM.gif 查看次數: 1686 文件大小: 24.2 KB


假設讀出來的數據為DWORD 380A0000h (00 00 0A 38),對照上面82443BX寄存器表可知:


代碼:

70h    00
71h    00
72h    0A    SMRAM
73h    38    ESMRAMC



好了,SMRAM寄存器讀出來了,下面進入正題:如何將SMRAM內容映射到0xA0000。

從文獻[2]中瞭解到,只要D_OPEN=1時,對物理內存0xA0000的訪問會變成這樣:

名稱: SMRAM_D_OPEN.jpg 查看次數: 1684 文件大小: 23.0 KB


D_OPEN位在哪?SMRAM = 0Ah又代表什麼?根據文獻[1]記載,SMRAM的各位意義如下(關於SMRAM更詳細解釋,見文章末尾補充資料不部分):

代碼:

   7    6      5     4       3      2 - 0
   0  D_OPEN D_CLS D_LCK G_SMRAME C_BASE_SEG
        |                    1       010
        |-> 我們就是要設置 D_OPEN = 1

原來的0Ah就是 0000 1010。只要第四位的D_LCK不為1,好了,向SMRAM寫入4Ah (0100 1010),於是飛躍光明之巔!



附帶程序中整個映射SMRAM過如下:

1. 首先遍歷PCI設備,找到I440BX的82443BX Host Bridge Controller。

    (VMware Workstation 6.0.x中82443BX 的VendorID: 8086h  DeviceID: 7190h)

    (Virtual PC 2007 6.0.x 中82443BX 的VendorID: 8086h  DeviceID: 7192h)

    很幸運,這兩個虛擬機中,82443BX都位於Bus:0, Device:0, Func:0,所以PCI配置空間基址都是0x80000000。

2. 以I/O偽代碼為例,讀SMRAM:

    out( CF8h, 80000070h )   // 還記得0x80000070怎麼來的嗎?

    in( CFCh, eax )          // 讀出包含SMRAM的一個DWORD,如 380A0000h

    shr eax, 10h             // SMRAM的8bit在第三個字節處,右移16位(2字節)

    // 此時AL中就是SMRAM內容了(0Ah)

3. 置D_OPEN為1,並且C_BASE_SEG設為010

    (C_BASE_SEG在初始化時就是010了,不過為了防止意外,最好重設一下)

    out( CF8h, 80000070h )

    mov eax, 384A0000h       // 寫回D_OPEN置1後的SMRAM(4Ah)

    out( CFCh, eax )

4. 此時對物理內存0xA0000-0xBFFFF讀寫,就是在SMRAM中了。寫完試試D_OPEN=0隱藏看看,讀0xA0000是不是又回到顯存了?

注意:當SMM Space Lock(D_LCK)為1時,這種方法是無法修改SMRAM的,只能另闢蹊徑。



另外,關於讀出的SMRAM內容,一定會有人質疑0xA0000處真的是SMRAM?關於這點,可以去看看SMRAM的佈局http://www.sandpile.org/ia32/smm.htm。

我一般是通過7FF0h (EIP) 和7FF4h (EFLAGS) 這兩個DWORD判斷的(SMBASE默認是0xA0000):

> dump [000A7FC0 - 000A8000]

000A7FC0   00 00 00 00 28 00 00 00 00 04 00 00 F0 0F FF FF

000A7FD0   E1 BB 7E 81 83 A0 00 00 B2 00 00 00 00 00 00 00

000A7FE0   20 42 E6 F9 20 42 E6 F9 80 C2 A1 F9 E0 E0 6E 80

000A7FF0   47 8B A1 F9 46 02 00 00 00 90 03 00 31 00 01 80

               EIP       EFLAGS




引用:

補充資料:

這裡是 Intel 440BX AGPset: 82443BX Host Bridge Controller[1] 中關於SMRAM的詳細解釋:

名稱: SMRAM描述-System Management RAM Control Register.gif 查看次數: 1655 文件大小: 16.2 KB

名稱: 內容System Management RAM Control Register.gif 查看次數: 1651 文件大小: 81.5 KB


還有些相關知識,有時間再補上...


參考文獻:

[1] Intel 440BX AGPset: 82443BX Host Bridge Controller

    http://download.intel.com/design/chi...s/29063301.pdf

[2] Shawn Embleton, Sherri Sparks, Cliff Zou.

    SMM Rootkits: A New Breed of OS Independent Malware

[3] Loic Duflot, Daniel Etiemble, Olivier Grumelard.

    Using CPU System Management Mode to Circumvent Operating System Security Functions

[4] Intel 64 and IA-32 Architectures Software Developer's Manual Volume 3B: System Programming Guide

    http://www.intel.com/products/processor/manuals/

[5] A tool for FreeBSD to discover SMRAM on i440BX based motherboards

    http://unix.derkeiler.com/Mailing-Li...att-0448/smm.c



測試環境:

Intel Pentium D 3.0GHz 雙核 + Virtual PC 2007(6.0.192.0) + WinXP(SP2)

Intel Pentium D 3.0GHz 雙核 + VMware Workstation (6.0.4-93057) + WinXP(SP2)

AMD Athlon 64 X2 Dual 4000+ + Virtual PC 2007(6.0.192.0) + WinXP(SP2)


Virtual PC 2007下的運行截圖:

名稱: VPC運行截圖.gif 查看次數: 1684 文件大小: 23.2 KB


由於手頭沒有I440BX的主板,無法使用真實機器測試。另外 AMD64 Architecture Programmer's Manual Volume 2: System Programming 也提及了SMM的相關細節,目前還沒有仔細看。

如果你在其他芯片組上測試成功,歡迎給我來信:upbit@126.com


ps: 忘補上一個開發庫了,程序裡用到了WinIO,可以到這裡http://www.internals.com/

    WinIO這個開發庫附帶C和VB的例子,理論上我這裡給的代碼都能翻譯成C或VB的,有興趣的不妨試試

什麼是SYSTEM MANAGEMENT MODE (SMM)


System Management Mode (SMM) is intended to be used for advanced power-management features and other operating-system-independent functions. The chipset is programmed to recognize many types of events and timeouts. When such an event occurs, the chipset asserts the SMI# input pin. At the next instruction boundary, the microprocessor saves its entire state and enters SMM.

一、概述
只有SMI才會引起進入SMM,處理器保護現場,切換到SMRAM裡的一個獨立位址空間執行SMM代碼,RSM指令會使系統返回到原來的正常模式。SMM相當於實模式,沒有特權級和位址映射,可定址4GB,可執行所有I/O和可用系統指令。
SMI優先順序在所有中斷裡最高。當處於SMM狀態,處理器不識別後繼的SMI請求,但第一個SMI請求可以被鎖存,並在系統退出SMM後被處理。
RSM指令只能在SMM狀態下執行,否則會產生「操作符無效」異常(exception)。RSM把SMRAM裡的處理器內容存回處理器,接著把控制權還給被中斷程式。若處理器在SMRAM中偵測到無效狀態,會shut down並產生一特殊匯流排週期以標識該狀態。
當收到SMI時,若處理器處於HALT狀態,處理器從SMM返回會稍有不同,SMBASE位址也會有所改變。
 
二、SMRAM
SMM時,處理器在SMRAM裡執行代碼和存儲資料,也用其來存儲系統管理資訊(如系統配置和power-down設備的特殊資訊)及OEM SPEC資訊。SMRAM被映射到物理空間,最大是4GB,默認是64KB,開始於物理空間裡的SMBASE(硬體RESET後,預設值是3000H)。
處理器在[SMBASE+8000H]尋找SMI處理程式的第一條指令。[SMBASE+FE00H]:[SMBASE+FFFFH]存放處理器狀態,即處理器的所有寄存器值。

SMRAM

SMBASE+FFFFH:
Start of State Aave Area
SMBASE+FE00H:

SMI Handler Entry Point
SMBASE+8000H:



SMBASE+8000H
三、SMI執行環境
SMM與實模相似但有所不同,它可以定址4GB,加上「E」首碼就可以訪問1MB以上空間,DATA和STACK可以在4GB的任何地方。
                                      寄存器初始值:
EFLAGS

0000 0002H

EIP

0000 8000H

CS

SMBase(default: 3000h)

DS、ES、FS、GS、SS

0000H

CR0

Set PE、EM、TS、PG to 0,其他不變

DR7

0400H


四、SMM中的中斷和exception
進入SMM後,所有HW中斷都被禁止(包括可遮罩硬體中斷、單步中斷、中斷點陷阱、NMI、SMI和A20M中斷)。軟體中斷和exception能發生,但建議不要,否則可能會產生不可預料的後果。
NMI會被堵在SMI服務程式入口處,且只有第一個NMI會被鎖存並在退出SMM後被執行。但也可以通過使能INTR引腳並有效INTR的方式來在SMM中使能NMI。在一般情況下,NMI是不可嵌套的,但也有例外。若從NMI處理程式進入SMM,同時有收到新的NMI,就可以在退出SMM後實現嵌套,也就是在老的NMI處理程式裡處理新的NMI。
 
五、在SMM裡保存FPU狀態
有時在SMM裡有必要保存FPU狀態,最安全的辦法是先置處理器於保護模式下再存FPU,儘管FSAVE可以以四種格式中任一種來存PFU內容。一般默認是在實模方式下進行,但若在非16位實模下發生SMI就必須進入保護模式執行FSAVE和FRSTOR,否則無法正確保存和恢復相關FPU資訊。但存完FPU資訊後,SMI處理程式可以繼續在保護模式下執行,但建議其主要在16/32實模下運行。
 
六、SMM版本識別
31                                                        18 17 16 15                                                                  0
Reserved



SMM版本標識



Bit 17 =1 : SMBASE重定位使能
Bit 16=1 :
支援I/O指令重啟
1)
SMBASE
重定位
SMBASE默認是30000H,放在處理器內部的SMBASE寄存器裡,我們可以通過在[SMBASE+FEF8H]處設置SMBASE區來重定位SMRAM。後續SMI請求會在新位址處執行SMI處理程式和存放狀態記憶體(系統重啟後會把SMBASE寄存器又寫成30000H,但INIT不會改變它)。
若SMBASE重定位到1MB以上位址,實模下的軟體操作就不能初始化段寄存器到SMBASE。
SMBASE可用「E」首碼來訪問32位元位址大小。
2)
I/O
指令重啟
就是允許從SMM狀態返回時從被中斷I/O指令處重新執行。I/O指令重啟區[SMBASE+FF00H]控制I/O指令重啟,值為FFH時允許重啟。但該I/O指令並不是造成SMI的原因。
若從I/O指令進入SMM又收到SMI,處理器會先處理新的SMI再重啟該I/O指令。
 
七、自動HALT重啟
         若處理器在HALT狀態進入SMM,要在[SMBASE+FF02]處置自動HALT重啟標誌位元。SMI處理程式可將它清零或不予理睬,這樣返回時就相應地執行HLT指令後的下一條指令或仍回到HALT狀態。
自動HALT重啟標誌值


進入SMM後的值

退出SMM後標誌值

退出SMM狀態後處理器行為

0

0

1

1

0

1

0

1

返回到被中斷程式或任務的下一條指令
不可預料
返回到HLT指令後的下一條指令
回到HALT狀態

八、多處理器SMM注意事項


MP系統中任一處理器都要能回應SMI。


每個處理器都有自己的SMRAM空間。


不同處理器的SMRAM可在同一記憶體空間重疊,但它們的狀態存儲區和動態資料存儲區卻要彼此獨立,代碼和靜態資料可共用。


SMI處理程式要為每個處理器初始化SMBASE。


處理器可通過自己的SMI引腳或從APIC介面收到的SMI來響應本地SMI(APIC介面可將SMI分給不同的處理器)。

edge trigger VS level trigger

http://hi.baidu.com/lzzscor/blog/item/b8a8591ccf40198287d6b6ac.html

邊緣觸發是指每當狀態變化時發生一個io事件,條件觸發是只要滿足條件就發生一個io事件。

舉個讀socket的例子,假定經過長時間的沉默後,現在來了100個字 節,這時無論邊緣觸發和條件觸發都會產生一個read ready notification通知應用程序可讀。

應用程序讀了50個字節,然後重新調用api等待io事件。這時條件觸發的api會因為還有50個字節可讀從而立即返回用戶一個read ready notification。而邊緣觸發的api會因為可讀這個狀態沒有發生變化而陷入長期等待。

因此在使用邊緣觸發的api時,要注意每次都要讀到socket返回EWOULDBLOCK為止,否則
這個socket就算廢了。而使用條件觸發的api時,如果應用程序不需要寫就不要關注socket可寫的事件,否則就會無限次的立即返回一個write ready notification。

大家常用的select就是屬於條件觸發這一類,以前本人就犯過長期關注socket寫事件從而
CPU 100%的毛病。

恩,最近用poll也碰到了這個問題,呵呵。

ACPI VS APIC

http://mlsx.xplore.cn/2006/01/12/what-difference-between-acpi-and-apic.html

很多人問道了什麼ACPI,什麼是APIC,他們有沒有關係?名字這麼相近。下面給出我對其的一些理解,具體的解釋可以查看內核文檔庫的內核參數文件:
/usr/src/`uname -r`/Documents/kernel-parameters.txt
ACPI
ACPI就是Advanced Configuration and Power Interface的縮寫,意思是「高級配置與電源接口」。這是英特爾、微軟和TOSHIBA共同開發的一種電源管理標準。
ACPI可實現以下功能:   
  1. 用戶可以使外設在指定時間開關;   
  2. 使用筆記本電腦的用戶可以指定計算機在低電壓的情況下進入低功耗狀態,以保證重要的應用程序運行;   
  3. 操作系統可以在應用程序對時間要求不高的情況下降低時鐘頻率;
  4. 操作系統可以根據外設和主板的具體需求為它分配能源;
  5. 在無人使用計算機時可以使計算機進入休眠狀態,但保證一些通信設備打開;
  6. 即插即用設備在插入時能夠由ACPI來控制。
不過,ACPI和其他的電源管理方式一樣,要想享受到上面這些功能,必須要有軟件和硬件的支援。在軟件方面,Windows 98及其後續產品和Windows 2000都對ACPI給予了全面的支援;而Linux的內核目前對此支援得並不是太理想。硬件方面比較麻煩,除了要求主板、顯卡和網卡等外設要支援 ACPI外,還需要機箱電源的配合。電源在提供5伏電壓給主板的同時,還必須使電流穩定在720毫安以上才可以,這樣它才能夠實現電腦的「睡眠」和「喚醒」。

ACPI共有六種狀態,分別是S0到S5,它們代表的含義分別是:
  • S0–實際上這就是我們平常的工作狀態,所有設備全開,功耗一般會超過80W;
  • S1–也稱為POS(Power on Suspend),這時除了通過CPU時鐘控制器將CPU關閉之外,其他的部件仍然正常工作,這時的功耗一般在30W以下;(其實有些CPU降溫軟件就是利用這種工作原理)
  • S2–這時CPU處於停止運作狀態,總線時鐘也被關閉,但其餘的設備仍然運轉;
  • S3–這就是我們熟悉的STR(Suspend to RAM),這時的功耗不超過10W;
  • S4–也稱為STD(Suspend to Disk),這時系統主電源關閉,但是硬盤仍然帶電並可以被喚醒;
  • S5–這種狀態是最乾脆的,就是連電源在內的所有設備全部關閉,功耗為0。
我們最常用到的是S3狀態,即Suspend to RAM(掛起到RAM)狀態,簡稱STR。顧名思義,STR就是把系統進入STR前的工作狀態數據都存放到RAM中去。

在STR狀態下,電源仍然繼續為記憶體等最必要的設備供電,以確保數據不丟失,而其他設備均處於關閉狀態,系統的耗電量極低。一旦我們按下Power按鈕(主機電源開關),系統就被喚醒,馬上從RAM中讀取數據並恢復到STR之前的工作狀態。RAM的讀寫速度極快,因此我們感到進入和離開STR狀態所花費的時間不過是幾秒鐘而已;

而S4狀態,即 STD(掛起到HDD)與STR的原理是完全一樣的,只不過數據是保存在硬盤中。由於硬盤的讀寫速度比RAM要慢得多,因此用起來也就沒有STR那麼快了。 STD的優點是只通過軟件就能實現,比如Windows 2000就能在不支援STR的硬件上實現STD。
之前的電源管理是APM(Advanced Power Management),那麼ACPI和APM相比有什麼區別呢?

ACPI與APM比較
  • APM 1.0&1.1:由BIOS執行電源管理;
  • APM 1.2:操作系統定義電源管理時間,由BIOS負責執行;
  • ACPI:BIOS收集硬件信息,定義電源管理方案;由操作系統負責執行。
  • APM是一種軟件解決方案,因此是與操作系統有關的, 而ACPI是工業標準,包括了軟件和硬件方面的規範。
APIC
APIC(高級可編程中斷控制器)對計算機來講有兩個作用,
  1. 管理IRQ的分配,可以把傳統的16個IRQ擴展到24個(傳統的管理方式叫PIC),以適應更多的設備。
  2. 管理多CPU。由於Nf2主板並不支援多CPU,所以,APIC關閉直接的影響是減少了可用的IRQ。
不過,如果板卡不是非常多的話,關閉 APIC對系統是沒有什麼影響的。
要實現SMP功能,我們使用的CPU必須具備以下要求:
CPU 內部必須內置APIC單元。Intel 多處理規範的核心就是高級可編程中斷控制器(Advanced Programmable Interrupt Controllers–APICs)的使用。CPU通過彼此發送中斷來完成它們之間的通信。通過給中斷附加動作(actions),不同的CPU可以在某種程度上彼此進行控制。每個CPU有自己的APIC(成為那個CPU的本地APIC),並且還有一個I/O APIC來處理由I/O設備引起的中斷,這個I/O APIC是安裝在主板上的,但每個CPU上的APIC則不可或缺,否則將無法處理多CPU之間的中斷協調。
APIC可能遇到的問題,很多這類問題可以通過BIOS更新來解決。

下面的是通過更改HAL類型來解決

CPU實際運行頻率與BIOS設定頻率不符
NF2的用戶大約有10%的會出現CPU實際運行頻率與BIOS設定頻率不符的問題。我們稱之為「頻率不對」。這種現象帶來的直接後果就是在測試3dmark或跑3D遊戲的時候,會感覺不流暢,也稱之為「頓」。
一般在更改BIOS設置後、更新驅動後重啟時,用測試軟件如Aida32、MBM5等可以看到CPU的運行頻率和你在BIOS裡設置得不一樣,而且差距很大。這個時候,用super pi測試CPU速度,會比平常花費時間長好幾秒,用3dmark跑測試,會比平常低幾百分甚至上千分。在3dmark中看到的CPU頻率,也與BIOS設定不符合。如果出現這種情況,則屬於我們所討論的「頻率不對」的問題。
不過,不是所有的3D遊戲「頓」都是這個原因。判斷的方法是:如果你只有個別遊戲「頓」,或者用上述軟件測試頻率正確,就不是此問題。
如果判斷確實屬此問題,解決的方法也很簡單,經過網友討論,只要關閉APIC功能即可。(注意,是APIC,不是ACPI)。

有一些服務器(比如IBM的,HP的),安裝LINUX時,會給出內核的錯誤,導致無法安裝,這個時候可以在安裝的時候輸入
linux acpi=off noapic
應該是安裝上的。

轉載請註明: 轉載自Linux|系統管理|WEB開發

2009年11月4日 星期三

Intel VMX

轉自:
http://pengsunshine.blogspot.com/2008/03/intel-vmx.html

Intel提供了處理器級的VMX(Virtual-Machine Extensions),從硬件層面支持VT技術。本文及後續的幾篇文章將結合"Intel 64 and IA-32 Architectures Software developer's Manual (Volume 3B)"和自己使用Xen的經驗寫些東西,算是學習筆記吧,不足之處在所難免,哪位看到了希望能共同交流提高 (^_^)。

先看一下虛擬機的體系結構:Intel VMX的體系結構可劃分為兩層:VMM和VM。

  • VMM(Virtual-Machine Monitors)作為host,具有對processor(s)和平台硬件的完全控制權限。它為guest提供VCPU(virtual processor)的抽象,並允許guest直接運行在邏輯處理器(logical processor)上。VMM具有對處理器資源、物理內存、中斷和IO的選擇控制的權利。(舉例:Xen就是一種VMM。)
  • VM(Virtual-Machine)相應地作為guest,其實是提供了一種guest軟件環境:它維護一個棧,其中包含了OS和application software。其每個操作都獨立於其他的VM,並且使用由同一個物理平台所提供的對處理器、內存、硬盤、顯卡、IO訪問的統一接口。此外,這個棧並不知道VMM的存在。運行於VM中的軟件其權限是受限的,這樣才能保障VMM對整個平台資源的完全控制。(舉相應的例子:Xen上跑的Guest OS就是VM。)


下面來簡要介紹一下VMX operation。支持虛擬化的處理器其虛擬化相關的操作被稱為VMX operation 。它分為兩類:VMX root operation 和 VMX non-root operation 。通常來說,VMM 運行於 VMX root operation 而 guest 運行於 VMX non-root operation 。兩種operation之間的轉換被稱為VMX transitions:從root到non-root被稱為VM entries,而從non-root到root則稱為VM exits。簡單可如下圖所示:




處於VMX root operation的CPU其行為與在VMX operation之外是基本一樣的,最根本的不同之處在於其增加了一套新的VMX指令集,且能存儲到特定控制寄存器的值是有限的。而處於VMX non-root operation的CPU起行為是受限的,且經過了修改以幫助實現virtualization。與其普通的operation不同,特定的指令(包括新增的VMCALL指令)和事件將導致VM exits從而進入VMM:由於這些VM exits代替了以前正常的行為,所以在VMX non-root operation中的軟件的功能是受限的(也正是這種限制保證了VMM能夠始終具有控制處理器資源的能力)。

從 guest的角度,沒有任何一個Guest可見的位來指示一個邏輯處理器是否處於VMX non-root operation,這樣VMX就能保證guest並不知道其正在運行於一個VM中。即便是CPL(current privilege level)為0,VMX operation也給guest加了限制,這樣guest software就可以完全不必改變其原始的設計,這也簡化了VMM的開發。

現在就可以看一下VMM與Guest之間的交互了。大體的流程是這樣子的:



  1. oftware執行VMXON指令進入VMX operation
  2. 通過VM entries,VMM就可以進入VM的guest中(VMM通過VMLANCH和VMRESUME來觸發VM entry,並通過VM exits重新獲得控制權)
  3. VM exits將控制權轉移到由VMM定義的entry point(VMM可以採取適當的動作來觸發VM exit,然後再使用一個VM entry就可以返回到VM中)。
  4. 最後,VMM通過VMXOFF指令關閉自身並退出VMX operation

既然已經涉及到VMX指令,下面又引出一個非常重要的數據結構VMCS(Virtual-Machine Control Structure),這個數據結構在下文中還要非常詳細地介紹。對VMCS的訪問是由一組被稱作VMCS pointer(每個logical processor有一個VMCS pointer)的處理器狀態來管理的。VMCS pointer是一個64位的VMCS地址,可通過VMPTRST和VMPTRLD指令對其進行讀寫;VMM可以使用VMREAD、VMWRITE、 VMCLEAR指令對VMCS進行配置。對VMM所管理的每個VM,VMM可以使用不同的VMCS;且對VM中的每個logical processor(or vcpu),VMM也可以為每個vcpu使用不同的VMCS。

VMX operation需要處理器支持,那麼如何從軟件層面斷定一個處理器是否支持VMX operation:通過CPUID --- 如果CPUID.1:ECX.VMX[bit 5] = 1,那說明該CPU支持VMX operation。現有的VMX體系結構的設計具有良好的可擴展性,software可以使用一個VMX capability MSRs集來獲得VMX新增的擴展特性。

現在再來看看如何使能和進入VMX operation:進入VMX operation之前,system software通過設置CR4.VMXE[bit 13] = 1 來使能VMX,之後就可以通過VMXON指令來進入VMX operation。如果CR4.VMXE = 0,VMXON將導致一個invalid-opcode異常(#UD),而且一旦進入VMX operation,CR4.VMXE就無法在此中被清零;system software通過VMXOFF離開VMX operation,只有在VMX operation外部CR4.VMXE才能被清零。

VMXON由IA32_FEATURE_CONTROL MSR (MSR address 3AH)所控制,當一個logical processor被rest時,該MSR被清零。此MSR的相關位如下:

  • Bit 0 is the lock bit. 若該位被清零,VMXON就會觸發一個general-protection exception;若該位被置1,向此MSR進行WRMSR也會觸發general-protection exception,直到a power up reset condition發生時該MSR才能被修改。系統BIOS可以通過該比特位來禁用VMX,若要打開VMX支持,BIOS必須設置該MSR的bit 0, bit 1, bit 2。
  • Bit 1 enables VMXON in SMX operation. 若該位被清零,SMX operation中的VMXON將觸發一個general-protection exception。若在不同時支持VMX和SMX的logical processors上試圖設置該位,將會觸發general-protection exceptions。(若一個logical processor自從最後一次執行GETSEC[SENTER]為止GETSEC[SEXIT]還未被執行,則稱其in SMX operation)
  • * Bit 2 enables VMXON outside SMX operation. 若該位被清零,在SMX operation外部進行VMXON將觸發一個general-protection exception。在不支持VMX的logical processors上試圖設置該位將觸發general-protection exceptions。(若一個logical processor還未執行GETSEC[SENTER]或最後一次執行過GETSEC[SENTER]後又執行了GETSEC[SEXIT],則稱其 outside SMX operation)

在執行VMXON之前,software應該分配出(保留)一塊4KB對齊的內存區域供logical processor用以支持VMX operation。這個內存區域就被稱為VMXON region。

最後,簡單說說VMX operation上的限制:VMX operation對processor operation作了一些限制,諸如:
  • 在VMX operation中,處理器將對CR0和CR4的某些具體位填充固定值(CR0.PE, CR0.NE, CR0.PG, cR4.VMXE的值都必須為1)。如果這些位中任何一位的值並不是它應該的值,VMXON就會fail。在VMX operation中任何試圖使用CLTS, LMSW, MOV CR等指令改變這些位的值都將導致general-protection exception。VM entry或VM exit都無法將這些位的值設置為其不應該的值。(CR0.PE和CR0.PG的限制就說明了VMX operation必須處於paged protected mode,這也使得guest software無法運行於unpaged protected mode或real-address mode中)
  • 若logical processor處於A20M mode,VMXON會fail。一旦處理器進入VMX operation, A20M的中斷就會被block,這樣VMX operation中A20M mode當然是不可能的了。
  • 只要logical processor處於VMX root operation,INIT signal就會被block;在VMX non-root operation中它是不會被block的,此時INITs將觸發VM exits。

從源碼級別,Xen裡面的vmx.c描述的就是Intel VMX體系結構相關的VM Exits支持:對照著Intel的Manual可以找到Xen對其的具體實現。

2009年10月21日 星期三

Speed Step

關於1A0(IA32_MISC_ENABLE)以及199(IA32_PERF_CTL),198(IA32_PERF_STATUS),自己project的CPU一定有Spec可以看到的,如果沒有就向Intel的FAE要吧。
; R9 m5 }% ^0 u9 q6 D計匠網論壇IA-32 ASDM volume 3也提到一點點,特別是1A0(IA32_MISC_ENABLE).
/ Y' s* D' A7 i+ |www.ufoit.com
! f& c, E/ n- [6 g5 p oBIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,計算機研發,EC資料,EC開發,PS/2,KBC對於Intel speedstep,我這裡摘抄一篇深圳頂星數碼的同志所發表一篇絕好的技術簡介文檔給你做參考。
1 V3 z' r& x1 s' i$ [0 }4 Q7 vBIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,計算機研發,EC資料,EC開發,PS/2,KBC
; S8 u0 |3 ?8 z2 m2 U( o& d* y H計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC

英特爾增強SpeedStep技術簡介BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,計算機研發,EC資料,EC開發,PS/2,KBC; V+ d7 i+ \3 Q6 J' t
來自:http://www.topstardigital.cn/TechIntro-14.htm

www.ufoit.com# g& @1 a# D/ {. u
計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC# |8 ~% h8 i: F0 G' X
對於英特爾的SpeedStep節能技術,很多人可能已不再陌生。SpeedStep技術是一項旨在徹底區分台式機CPU與筆記本電腦專用CPU的性 能,並與系統運行速度有密切關係的新技術,原來的代號是「Geyserville」。簡單地說,採用SpeedStep技術的筆記本電腦,可以根據不同的 使用環境,對CPU的運行速度進行合理調整。SpeedStep技術的基本工作過程是:如果筆記本電腦使用的是外接電源,CPU將按照正常的主頻率及電壓 運行;當專用軟件檢測到筆記本電腦切換到電池電源供電時,將自動切換筆記本電腦CPU的主頻率及電壓至較低運行狀態;當筆記本電腦恢復使用外接電源 時,CPU又自動切換回到最高主頻率及正常電壓下工作,使之全速運行。從技術上分析可知,同一處理器工作時,運行速度越快,工作電壓越高,功耗也越大;相 反,處理器的運行速度降低後,工作電壓會降低,功耗和發熱量也會降低。 BIOS技術網站,BIOS技術論壇,BIOS入門,BIOS開發,計算機研發,EC資料,EC開發,PS/2,KBC, R, j- `: Y1 `, g" @% s

; _0 U4 k, v( R" g1 x8 }1 Z* [# r# m計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC在 去年(2003年),英特爾又推出了內部代號為「Geyserville III」的增強SpeedStep技術,首先在Pentium M處理器實現,支持的平台包括Odem和Motora等855系列芯片組。其與原有的SpeedStep主要有兩點區別:第一點,增強的 SpeedStep(簡稱EIST)使得原有的性能等級從兩階增加到多階,是的性能切換更加靈活,例如,1.4G Pentium 4M的有600M和1.4G兩檔,但是對於支持1.4G Pentium M處理器而言,就有600M、800M、1G、1.2G和1.4G五個頻率檔次。第二點,增強的SpeedStep可以直接通過對處理器的MSR寄存器編 程來實現頻率,大大減少了頻率切換時間,而原來的SpeedStep依賴於對Chipset的IO端口編程實現,所以切換延時較大。計匠網論壇/ L8 } I+ r! x
BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,計算機研發,EC資料,EC開發,PS/2,KBC/ b6 r# g/ m' B3 y. v4 l% ?8 E
下面我們簡單介紹一下對於SpeedStep的支持固件編程相關知識。計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC0 S, L3 M& R0 |) F' y( B I
www.ufoit.com, r, Q+ A4 ]9 K7 `9 u
我們知道,處理器的性能狀態是由離散的電壓和頻率對定義的。在不同的頻率和電壓下,處理器的執行性能也是不同。EIST提供了一個全新的方法來改變 CPU的性能狀態,這種方法本身是基於CPU MSR寄存器,而不是基於Chipset的,所以這種方法具有硬件軟件的轉變過渡時間少、系統設計簡單等特點。Intel的奔騰M處理器提供了兩個MSR 寄存器來實現EIST功能,一個是IA32_PERF_CTL和IA32_PERF_STS,我們通過寫IA32_PERF_CTL來實現CPU性能的狀 態轉變,主要是設置BUS_RATIO_SEL和VID_SEL位域。一旦設置完之後,CPU隨後馬上進行RATIO和VID的切換,如果在切換的過程中 間有新的值更新IA32_PERF_CTL寄存器,那麼,在CPU完成系統狀態切換之後,CPU馬上進行本次新的性能切換。
4 l/ a; U. ~& ]* ^. U計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC
* e3 f$ K9 U) |( W2 u) W: U 如果我們讀取IA32_PERF_CTL,這只表明上次更新所期望的CPU性能置,並不表示CPU的當前性能值,因為,如果CPU處在thermal throttle的過程中,那麼,CPU會暫停性能的切換,直到thermal throttle的結束。& O9 y8 V$ F1 L/ r) }( U

' h) f6 h1 }) _計匠網論壇 如果我們想確定CPU的當前性能狀態,我們可以通過讀取IA32_PERF_STS寄存器的值來確定。這個寄存器包括一些主要信息,如 BUS_RATIO_STS和VID_STS域表明當前的頻率和電壓值,這些信息是動態更新的。這個寄存器同樣也包括了系統BOOT時和系統所支持的最大 性能點。 BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,計算機研發,EC資料,EC開發,PS/2,KBC5 y* h- K: e1 \2 J& z
BIOS技術網站,BIOS技術論壇,BIOS入門,BIOS開發,計算機研發,EC資料,EC開發,PS/2,KBC) y7 P" I1 p' `* L, ]( g8 G
在Windows XP下,OS查找PSS表,找出其想進入的狀態,查找控制值,然後把控制值寫入控制寄存器中間。系統有兩種方式進入實現控制動作,一種是透過SMM模式進 行,一種是OSPM通過G3寄存器直接寫。到底是採用哪種方式依靠_PCT這個對象決定。而在_PCT內部,實際上是根據系統OSPM的性能確定返回哪種 控制方式的,也就是採用SMM Model和Processor Native Mode。BIOS技術網站,BIOS技術論壇,BIOS入門,BIOS開發,計算機研發,EC資料,EC開發,PS/2,KBC" F! L* n! u( ]
BIOS技術網站,BIOS技術論壇,BIOS入門,BIOS開發,計算機研發,EC資料,EC開發,PS/2,KBC9 F9 V3 m7 X6 F6 D5 u! q5 ?7 |
我們知道,OS是這樣實現處理器性能的控制的,首先,OSPM通過ACPI表得知系統支持的處理器狀態,然後OSPM根據其對於系統當前狀態的評估,確 定進入哪種處理器狀態。在確定進入何種狀態之後,OSPM決定這種方法是否是其支持的方法,否則回去調用CPU的驅動程序完成相關動作。一般OSPM直接 支持的方法就是IO之類的傳統方法。 計匠網論壇6 u o! B- @' o7 {

$ x; w2 X: {9 _! v3 M* \BIOS技術網站,BIOS技術論壇,BIOS入門,BIOS開發,計算機研發,EC資料,EC開發,PS/2,KBC 所 以,如果OSPM不是透過SMM去完成對於MSR的IA32_PERF_CTL和IA32_PERF_STS操作,那麼,OSPM必須去調用處理器的驅動 程序,驅動程序的存在必須告知ACPI,使得ACPI的_PCT的返回是直接對於IA32_PERF_CTL和IA32_PERF_STS操作,而這是依 賴於_PDC這個方法完成的,_PDC是OSPM用來告訴ACPI其是否支持直接對於IA32_PERF_CTL和IA32_PERF_STS操作,是 OSPM向ACPI傳遞消息的一種途徑。
# S3 c3 q0 o& R5 hwww.ufoit.com計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC% h' ^" P8 v4 U7 w' o
_FDC是OSPM在進行所有處理器操作之前首先調用的一個方法,這個方法有處理器的驅動程序向ACPI傳送一個DWORD的變量,這個變量包含由處理器 廠商定義的格式,對於不同處理器的能力。根據_FDC傳遞過來系統軟件支持的能力,ACPI調整_PCT和PSS方法。計算機研發論壇,BIOS技術網站,BIOS技術論壇,BIOS SPEC,BIOS手冊,BIOS入門,ACPI,SMBIOS,BIOS技術,EC資料,EC開發,PS/2,KBC5 @6 Y: p4 f' \/ \ e3 Q) V

; f$ E+ u" G" R' Y" X* z 支持Intel EIST的_FDC的工作原理是這樣的,缺省的,系統BIOS是採用IO地址映射的方式提供_PCT和_PSS的,也就是SMM方式操作CPU的MSR寄 存器直接對IA32_PERF_CTL和IA32_PERF_STS操作。如果沒有處理器驅動程序去調用_PDC這樣,通過系統ACPI、SMI服務例程 和處理器的服務程序,我們可以實現EIST所帶來的優點,達到性能和節電的兼顧。

2009年6月10日 星期三

CPUID詳解

標 題: CPUID詳解[增加TLB與Cache]
作 者: Pr0Zel
時 間: 2006-02-21 22:00
鏈 接: http://bbs.pediy.com/showthread.php?threadid=21646
詳細信息:

這是文章最後一次更新,加入了TLB與Cache信息等資料
前言:論壇上面有人不明白CPUID指令的用法,於是就萌生寫這篇文章的想法,
若有錯誤話請大俠指出,謝謝了 ^^
論壇的式樣貌似有問題,若式樣問題導致閱讀問題的話,可以在文章尾下載txt文檔閱讀.

論壇上面有人不明白CPUID指令的用法,於是就萌生寫這篇文章的想法,
若有錯誤話請大俠指出,謝謝了 ^^
CPUID是Intel Pentium以上級CPU內置的一個指令(486級及以下的CPU不支持),它用於識別某一類型的CPU,它能返回CPU的級 別(family),型號(model),CPU步進(Stepping ID)及CPU字串等信息,從此命令也可以得到CPU的緩存與TLB信息.
CPUID返回數據類型是在EAX寄存器裡面定義的,而指令返回的數值則在存儲在EAX,EBX,ECX和EDX寄存器裡面.
返回的信息分兩部分:基本信息與擴展信息.在EAX輸入0-3參數時,它返回的CPU的基本信息;而在EAX輸入0x8000000至0x800000x 時,它返回的是CPU的擴展信息(extended function information).擴展信息只包含在Pentium 4及以後的CPU 上,Pentium 4以前的CPU無法取得它的擴展信息.

如下面的表:
CPU級別 基本信息 擴展信息
486及以前的CPU 不可用 不可用
Pentium 0x1 不可用
Pentium Pro,Pentium 2 0x2 不可用
Pentium 3 0x3 不可用
Pentium 4 0x2 0x80000004
Xeon(至強) 0x2 0x80000004

假若輸入高於該處理器的值時,CPUID指令返回的是該CPU的輸入最高值的返回值(這一句不知道怎麼說才好),
比如在在Pentium 4上輸入0x4,則CPU返回值與輸入0x2的返回值一樣.

下面的表是輸入值與返回值的關係:
輸入值 返回值
-----------------------------------------------------------------
0x0 EAX CPU基本參數的輸入值
EBX "Genu"
ECX "Intel"
EDX "inel"
------------------------------------------------------------------
0x1 EAX CPU的級別,型號及步進
EBX 信息很多,下面介紹
ECX 保留
EDX 特徵信息(Feature Information)
------------------------------------------------------------------
0x2 EAX到EDX返回的都是緩存和TLB的信息
------------------------------------------------------------------
0x3 EAX 保留
EBX 保留
ECX CPU序列號(0 - 31bit) (只是在Pentium 3中才有效)
EDX CPU序列號(32 - 63bit) (只是在Pentium 3 中才有效)
------------------------------------------------------------------

0x80000000 EAX 擴展信息輸入數最大值(具有擴展信息的CPU才能返回)
EBX 保留
ECX 保留
EDX 保留
------------------------------------------------------------------
0x80000001 EAX CPU特徵(Signature)和擴展特徵位(Extended Feature Bits)
EBX 到 ECX 保留
------------------------------------------------------------------
0x80000002 EAX 處理器字串(Processor Brand String)
EBX 處理器字串(續)
ECX 處理器字串(續)
EDX 處理器字串(續)
------------------------------------------------------------------
0x80000003 EAX 處理器字串(續)
EBX 處理器字串(續)
ECX 處理器字串(續)
EDX 處理器字串(續)
------------------------------------------------------------------
0x80000004 EAX 處理器字串(續)
EBX 處理器字串(續)
ECX 處理器字串(續)
EDX 處理器字串(續)
------------------------------------------------------------------

當輸入0x1時,EBX返回值是:
第 0 - 7位: CPU字串索引 (Brand Index)
第 8 - 15位: CLFLUSH線大小(CLFLUSH line size) (返回值*8 = cache line size)
第16 - 23位: 保留
第24 - 31位: 處理器APIC物理標號 (Processor local APIC physical ID)

當輸入0x1時,EDX返回的擴展信息解釋如下:

位 標號 解釋
0 FPU Floating Point Unit On-Chip. CPU是否內置浮點計算單元
1 VME Virtual 8086 Mode Enhancements. 是否支持虛擬8086模式
2 DE Debugging Extensions. 是否支持調試功能.
3 PSE Page Size Extension. 是否支持大於4MB的分頁.
4 TSC Time Stamp Counter. 是否支持RDTSC指令.(注:RDTSC指令可以計算出CPU的頻率)
5 MSR Module Specific Registers RDMSR and WRMSR Instructions. 是否支持RDMSR與WRMSR (*注1)
6 PAE Physical Address Extension. 是否支持大於32bit的物理地址.
7 MCE Machine Check Exception. (*注2)
8 CX8 CMPXCHG8B Instruction. 是否支持8bytes(64bit)數的比較與交換指令.
9 APIC APIC On-Chip.是否支持APIC(Advanced Programmable Interrupt Controller)
10 保留
11 SEP SYSENTER and SYSEXIT Instructions.是否支持SYSENTER與SYSEXIT指令.(*注3)
12 MTRR Memory Type Range Registers. 是否支持MTTR(*注4)
13 PGE PTE Global Bit. 是否支持全局頁面目錄入口標誌位 (global bit in page directory entries)
14 MCA Machine Check Architecture. 是否支持MCA,MCA是Pentium4,Xeon,P6級處理器的一個錯誤報告機制
15 CMOV Conditional Move Instructions. CMOV指令是否可用.(請問誰可以解釋一下CMOV是什麼命令?)
16 PAT Page Attribute Table. 是否支持PAT,PAT允許操作系統指定4K大小的線性內存空間
17 PSE-36 32-bit Page Size Extension. 是否支持4GB的擴展內存
18 PSN Processor Serial Number. 是否支持處理器序列號.(P3有效)
19 CLFSH CLFLUSH Instruction.是否支持CLFLUSH.(*注5)
20 保留
21 DS Debug Store. 是否支持把調試信息寫入緩存,
22 ACPI ACPI Processor Performance Modulation Registers. 處理器使用特別的寄存器以允許軟件控制處理器的運行週期.
23 MMX Inter MMX Technology.是否支持MMX
24 FXSR FXSAVE and FXRSTOR Instructions. FXSAVE與FXRSTOR指令是否可用(*注6)
25 SSE SSE.是否支持SSE.
26 SSE2 是否支持SSE2.
27 SS Self Snoop. 處理器是否支持總線監視,以防止儲存器衝突.
28 保留
29 TM Thermal Monitor.CPU是否支持溫度控制.
30 & 31 保留
--------------------------------------------------------------
注1: RDMSR: Load MSR specified by ECX into EDX:EAX
WRMSR: Write the value in EDX:EAX to MSR specified by ECX
注2:
原文是 Exception 18 is defined for Machine Checks,including CR4.MCE for controlling the feature. This feature does not define themodel-specific implementations of machine-check error logging, reporting, andprocessor shutdowns. Machine Check exception handlers may have to depend onprocessor version to do model specific processing of the exception, or test for thepresence of the Machine Check feature.

注3: SYSENTER: Fast call to privilege level 0 system procedures
SYSEXIT: Fast return to privilege level 3 user code.

注4:
原文是 The MTRRcap MSR contains feature bits that describe what memory types are supported, how manyvariable MTRRs are supported, and whether fixed MTRRs are supported.

注5: CLFLUSH: Flushes cache line containing m8.

注6: FXSAVE: Save the x87 FPU, MMX, XMM, and MXCSR register
FXSTOR: Restore the x87 FPU, MMX, XMM, and MXCSR register


按照這個,就可以自己寫一個CPU檢測程序了;


#include

void main()
{
unsigned long DBaseIndex, DFeInfo, DFeInfo2, DCPUBaseInfo;
unsigned long DFeIndex, DCPUExInfo, i;
unsigned long DOther[4], DTLB[4], DProceSN[2];
char cCom[13];
char cProStr[49];
unsigned int j;

_asm
{
xor eax, eax
cpuid
mov DBaseIndex ,eax
mov dword ptr cCom ,ebx
mov dword ptr cCom+4 ,ecx //AMD CPU要把ecx改為edx
mov dword ptr cCom+8 ,edx //AMD CPU要把edx改為ecx

mov eax, 1
cpuid
mov DCPUBaseInfo, eax
mov DFeInfo, ebx
mov DFeInfo2, edx

mov eax, 0x80000000
cpuid
mov DFeIndex, eax

mov eax, 0x80000001
cpuid
mov DCPUExInfo, eax

mov eax, 0x80000002
cpuid
mov dword ptr cProStr , eax
mov dword ptr cProStr + 4 , ebx
mov dword ptr cProStr + 8 , ecx
mov dword ptr cProStr + 12 ,edx

mov eax, 0x80000003
cpuid
mov dword ptr cProStr + 16 , eax
mov dword ptr cProStr + 20 , ebx
mov dword ptr cProStr + 24 , ecx
mov dword ptr cProStr + 28 , edx

mov eax, 0x80000004
cpuid
mov dword ptr cProStr + 32 , eax
mov dword ptr cProStr + 36 , ebx
mov dword ptr cProStr + 40 , ecx
mov dword ptr cProStr + 44 , edx
}

if( DBaseIndex >= 2 )
{
_asm
{
mov eax, 2
cpuid
mov DTLB[0], eax
mov DTLB[2], ebx
mov DTLB[3], ecx
mov DTLB[4], edx
}
}
if(DBaseIndex == 3)
{
_asm
{
mov eax, 3
cpuid
mov DProceSN[0], ecx
mov DProceSN[1], edx
}
}

cCom[12] = '\0'; //加一個結尾符
printf( "CPU廠商: %s\n", cCom );
printf( "CPU字串: %s\n", cProStr );
printf( "CPU基本參數: Family:%X Model:%X Stepping ID:%X\n", (DCPUBaseInfo & 0x0F00) >> 8,
(DCPUBaseInfo & 0xF0) >> 4, DCPUBaseInfo & 0xF );
printf( "CPU擴展參數: Family:%X Model:%X Stepping ID:%X\n", (DCPUExInfo & 0x0F00) >> 8,
(DCPUExInfo & 0xF0) >> 4, DCPUExInfo & 0xF );

printf( "CPU字串索引: 0x%X\n", DFeInfo & 0xFF );
printf( "CLFLUSH線大小: 0x%X\n", ( DFeInfo & 0xFF00 ) >> 8 );
printf ( "處理器APIC物理標號:0x%X\n", ( DFeInfo & 0xF000 ) >> 24 );
if( DBaseIndex >= 2)
{
printf( "CPU Cache & TLB Information: " );
for(j = 0; j <>> 8) ) printf( "%.2X ", (DTLB[j] & 0xFF00) >> 8 );
if( !((DTLB[j] & 0xFF0000) >> 16) ) printf( "%.2X ",( DTLB[j] & 0xFF0000) >> 16);
if( !((DTLB[j] & 0xFF000000) >> 24) ) printf( "%.2X ",( DTLB[j] & 0xFF000000) >> 24);
}
printf("\n");
}

if( DBaseIndex == 3 )
{
printf( "CPU序列號是:%X%X\n", DProceSN[0], DProceSN[1] );
}
printf( "FPU: %d\t\t", DFeInfo2 & 0x00000001 ); //下面是調用某BLOG上面的代碼,懶得寫了 ^^
printf( "VME: %d\t\t", (DFeInfo2 & 0x00000002 ) >> 1 );
printf( "DE: %d\n", (DFeInfo2 & 0x00000004 ) >> 2 );
printf( "PSE: %d\t\t", (DFeInfo2 & 0x00000008 ) >> 3 );
printf( "TSC: %d\t\t", (DFeInfo2 & 0x00000010 ) >> 4 );
printf( "MSR: %d\n", (DFeInfo2 & 0x00000020 ) >> 5 );
printf( "PAE: %d\t\t", (DFeInfo2 & 0x00000040 ) >> 6 );
printf( "MCE: %d\t\t", (DFeInfo2 & 0x00000080 ) >> 7 );
printf( "CX8: %d\n", (DFeInfo2 & 0x00000100 ) >> 8 );
printf( "APIC: %d\t", (DFeInfo2 & 0x00000200 ) >> 9 );
printf( "SEP: %d\t\t", (DFeInfo2 & 0x00000800 ) >> 11 );
printf( "MTRR: %d\n", (DFeInfo2 & 0x00001000 ) >> 12 );
printf( "PGE: %d\t\t", (DFeInfo2 & 0x00002000 ) >> 13 );
printf( "MCA: %d\t\t", (DFeInfo2 & 0x00004000 ) >> 14 );
printf( "CMOV: %d\n", (DFeInfo2 & 0x00008000 ) >> 15 );
printf( "PAT: %d\t\t", (DFeInfo2 & 0x00010000 ) >> 16 );
printf( "PSE-36: %d\t", (DFeInfo2 & 0x00020000 ) >> 17 );
printf( "PSN: %d\n", (DFeInfo2 & 0x00040000 ) >> 18 );
printf( "CLFSN: %d\t", (DFeInfo2 & 0x00080000 ) >> 19 );
printf( "DS: %d\t\t", (DFeInfo2 & 0x00200000 ) >> 21 );
printf( "ACPI: %d\n", (DFeInfo2 & 0x00400000 ) >> 22 );
printf( "MMX: %d\t\t", (DFeInfo2 & 0x00800000 ) >> 23 );
printf( "FXSR: %d\t", (DFeInfo2 & 0x01000000 ) >> 24 );
printf( "SSE: %d\n", (DFeInfo2 & 0x02000000 ) >> 25 );
printf( "SSE2: %d\t", (DFeInfo2 & 0x04000000 ) >> 26 );
printf( "SS: %d\t\t", (DFeInfo2 & 0x08000000 ) >> 27 );
printf( "TM: %d\n", (DFeInfo2 & 0x20000000 ) >> 29 );

printf("\n其它信息:\n");
printf("----------------------------------------\n");
printf("In \t\tEAX \t\tEBX \t\tECX \t\tEDX");
for( i = 0x80000004; i <= DFeIndex; ++i ) { DOther[0] = DOther[1] = DOther[2] = DOther[3] = 0; _asm { mov eax, i cpuid mov DOther[0], eax mov DOther[1], ebx mov DOther[2], ecx mov DOther[3], edx } printf( "\n0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X", i, DOther[0], DOther[1], DOther[2], DOther[3] ); } printf( "\n" ); system( "pause" ); }


TLB與Cache信息詳解:
0x00 Null descriptor
0x01 Instruction TLB: 4K-Byte Pages, 4-way set associative, 32 entries 0x02 Instruction TLB: 4M-Byte Pages, 4-way set associative, 2 entries
0x03 Data TLB: 4K-Byte Pages, 4-way set associative, 64 entries
0x04 Data TLB: 4M-Byte Pages, 4-way set associative, 8 entries
0x06 1st-level instruction cache: 8K Bytes, 4-way set associative, 32 byte line size
0x08 1st-level instruction cache: 16K Bytes, 4-way set associative, 32 byte line size
0x0A 1st-level data cache: 8K Bytes, 2-way set associative, 32 byte line size
0x0C 1st-level data cache: 16K Bytes, 4-way set associative, 32 byte line size 0x22 3rd-level cache: 512K Bytes, 4-way set associative, 64 byte line size
0x23 3rd-level cache: 1M Bytes, 8-way set associative, 64 byte line size
0x25 3rd-level cache: 2M Bytes, 8-way set associative, 64 byte line size
0x29 3rd-level cache: 4M Bytes, 8-way set associative, 64 byte line size
0x40 No 2nd-level cache or, if processor contains a valid 2nd-level cache, no 3rd-level cache
0x41 2nd-level cache: 128K Bytes, 4-way set associative, 32 byte line size
0x42 2nd-level cache: 256K Bytes, 4-way set associative, 32 byte line size
0x43 2nd-level cache: 512K Bytes, 4-way set associative, 32 byte line size
0x44 2nd-level cache: 1M Byte, 4-way set associative, 32 byte line size
0x45 2nd-level cache: 2M Byte, 4-way set associative, 32 byte line size
0x50 Instruction TLB: 4-KByte and 2-MByte or 4-MByte pages, 64 entries
0x51 Instruction TLB: 4-KByte and 2-MByte or 4-MByte pages, 128 entries
0x52 Instruction TLB: 4-KByte and 2-MByte or 4-MByte pages, 256 entries
0x5B Data TLB: 4-KByte and 4-MByte pages, 64 entries
0x5C Data TLB: 4-KByte and 4-MByte pages,128 entries
0x5D Data TLB: 4-KByte and 4-MByte pages,256 entries
0x66 1st-level data cache: 8KB, 4-way set associative, 64 byte line size
0x67 1st-level data cache: 16KB, 4-way set associative, 64 byte line size
0x68 1st-level data cache: 32KB, 4-way set associative, 64 byte line size
0x70 Trace cache: 12K-μop, 8-way set associative
0x71 Trace cache: 16K-μop, 8-way set associative
0x72 Trace cache: 32K-μop, 8-way set associative
0x79 2nd-level cache: 128KB, 8-way set associative, sectored, 64 byte line size 0x7A 2nd-level cache: 256KB, 8-way set associative, sectored, 64 byte line size 0x7B 2nd-level cache: 512KB, 8-way set associative, sectored, 64 byte line size 0x7C 2nd-level cache: 1MB, 8-way set associative, sectored, 64 byte line size
0x82 2nd-level cache: 256K Bytes, 8-way set associative, 32 byte line size
0x84 2nd-level cache: 1M Byte, 8-way set associative, 32 byte line size
0x85 2nd-level cache: 2M Byte, 8-way set associative, 32 byte line size

舉一個例子,一個CPU執行 mov eax, 2 cpuid 後, 返回值如下:
EAX 0x665B5001 EBX 0x0 ECX 0x0 EDX 0x007A7000

就代表了這個CPU的Cache信息是:
1. 0x66:1st-level data cache: 8KB, 4-way set associative, 64 byte line size
2. 0x5B:Data TLB: 4-KByte and 4-MByte pages, 64 entries
3. 0x50:Instruction TLB: 4-KByte and 2-MByte or 4-MByte pages, 64 entries
4. 0x01:Instruction TLB: 4K-Byte Pages, 4-way set associative, 32 entries
5. 0x7A:2nd-level cache: 256KB, 8-way set associative, sectored, 64 byte line size 6. 0x70:Trace cache: 12K-μop, 8-way set associative

CPU字串索引 (Brand Index)詳解:
0x0: CPU不支持Brand Index
0x1: Celeron CPU
0x2: Pentium 3
0x3: Pentium 3 Xeon
0x4-0x7: 未用
0x8: Pentium 4 可惜我是用AMD CPU的,有好些功能都不能親自試驗
參考資料:IA-32 Intel Architecture Software Developer's Manual

2009年2月13日 星期五

PROCHOT#

http://it.sohu.com/20050228/n224465028.shtml


為彌補了第一代溫度監控技朮的缺陷,提高監控能力,Intel開發了第2代溫度監控技朮。

第二代溫度監控系統的一個突出特點是在微處理器內部集成了溫度控制電路(Thermal Control Circuit,TCC),由微處理器自身執行溫度控制功能,同時,微處理器內設置了兩個相互獨立的熱敏二极管,D1是本地熱敏二极管,所測信號提供給TCC,D2則為遠端熱敏二极管,其測量結果用于實現主板控制功能及顯示核心溫度,如圖8。




圖8 注∶第2代溫度監控系統框圖

我們先看看TCC是如何發揮作用的。TCC定義了兩種工作狀態:激活態和非激活態。TCC的狀態與PROCHOT#信號的電平高低相對應,PROCHOT#為低電平時,TCC為激活態,否則處于非激活態。當微處理器核心溫度達到警戒溫度(Warning Temperature)時,溫度檢測電路將PROCHOT#信號置為低電平,從而激活TCC。TCC激活后,采取“抑制任務周期”(Throttle duty Cycle)的方式(如圖9),使微處理器有效頻率下降,從而達到降低功耗的目的。當微處理器的溫度降低后(低于警戒溫度1℃以上),TCC回到非激活態,微處理器恢復到“標稱頻率”。可見,TCC實質上是一個由微處理器溫度控制的頻率調節器。



圖9 注∶TCC激活時,任務周期減少

如果發生災難性冷卻失敗的情況,使微處理器溫度超出极限溫度(thermal Trip),TCC將設THERMTRIP#信號為低電平,BIOS芯片檢測到這一變化后,直接關閉微處理器時鐘信號,並通過PWM控制器封鎖VRM向微處理器供電,直到溫度降到极限溫度以下,RESET#信號有效,THERMTRIP#才會重新變為高電平,系統才能繼續工作。否則THERMTRIP#總為低電平,系統就停留在暫停狀態。“當微處理器離開風扇的時候”,Pentium 4微處理器之所以能夠安然無恙,答案就在這里。

小知識∶警戒溫度與极限溫度有什麼不同?
微處理器警戒溫度(warning temperature)和极限溫度(thermal trip)都是指核心溫度,但它們所代表的意義有所不同。警戒溫度是能夠保証微處理器穩定運行的溫度;极限溫度也叫最高核心溫度(Maximum die temperature)或關机溫度(Shutdown temperature),是防止微處理器免于燒毀的溫度。

各款微處理器的警戒溫度和极限溫度值是制造商根据微處理器的制造工藝和封裝形式及封裝材料确定的,並在技朮白皮書中給出。為防止用戶自行設定而帶來危險,Intel已將Pentium 4微處理器的警戒溫度和极限溫度寫入TCC內的ROM單元中,用戶無法修改它們。

現在有不少主板的BIOS中也可以設置警戒溫度和關机溫度,不過可選的數值都比較保守,例如警戒溫度最大值為70℃、關机溫度為85℃,這是遠低于TCC內設定值的。

兼顧性能和可靠性是第2代溫度監控技朮的優秀之處。由公式P = CV2f(其中C是等效電容容量;V是工作電壓)可知,頻率f與能耗P之間是一種線性關系,降低頻率是減少發熱量的有效途徑。這種通過降低有效頻率實現降溫的措施,比之以前那種關斷時鐘信號的做法顯然要聰明一些,避免了因強行關閉微處理器,而導致數据丟失的情況。

Pentium 4處理器中的PROCHOT#引腳還有另外兩個實用的功能。其中的一個功能是向主板發出報警信號??PROCHOT#引腳為低電平時,說明微處理器核心溫度超過了警戒溫度,此時微處理器工作在較低的頻率上。如果超出警戒溫度(電腦用戶利用工具軟件可以獲得這個信息),應及時檢查散熱器安裝是否妥當,風扇轉速是否正常。

PROCHOT#引腳的另一個功能是可以保護主板上的其他元件。PROCHOT#引腳采用雙工設計??信號既可以從這根信號線出去,也能進得來。主板設計者可利用這一特性為供電模塊提供保護,當供電模塊的溫度超出警戒溫度時,監控電路輸出一個低電平到PROCHOT#引腳以激活TCC,通過降低微處理器功耗來達到保護供電模塊的目的。

可見,Pentium 4處理器不僅能自保平安,還能對供電電路提供保護,細微之處體現出設計者的良苦用心。同時,將TCC集成到微處理器內不僅對自身更加安全,也簡化了主板設計,降低了主板制造成本。可以說,第2代溫度監控技朮是一個給微處理器制造商與下游主板廠商帶來雙贏的技朮。

小知識∶如何設置BIOS中的“Processor speed throttling”?
Pentium 4主板的BIOS中通常有“Processor speed throttling ”之類的選擇項,用于選擇超警戒溫度后處理器任務周期(duty cycle)占全部周期的比例,在處理器頻率不變的情況下,這個比例越大說明處理器的工作效率越高。其中有“Automatic”和“On demand” 兩種選擇,“Automatic(自動)”表示任務周期的占空比為50%,也就是說比正常頻率低一半;“On demand(按要求)”下面有12.5%、25%、…、87.5%等多種選擇,選擇的數值越小,則任務周期的比例越小,降頻幅度也越大。

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

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、網卡的價格很低;怎麼樣?