利用 RDTSC 量測時脈週期
在進行程式的時間測量時,可以透過 rdtsc
指令取得時脈週期。 rdtsc
會返回 CPU 啟動之後所經歷的時脈週期數量,因此我們可以透過該指令在兩個區段相減過後的值來計算一段時間,例如量測執行一段程式碼所花費的時間。
在 CPU 啟動之後,會自動累積週期數,並且會將值紀錄在 EDX 和 EAX 暫存器, EDX 為高位元, EAX 為低位元。
EDX 和 EAX 暫存器
暫存器分為 32 位元以及 64 位元暫存器。RDX 和 RAX 皆為 64 位元暫存器,對應的 EDX 與 EAX 則為 32 位元暫存器。32 位元和 64 位元暫存器並不是獨立的暫存器,以 RAX 與 EAX 為例, EAX 佔了 RAX 的低位 32 位元,因此對 EAX 暫存器修改時也會影響到 RAX 暫存器。
參考資料:Day6 - 為什麼要使用暫存器?
C 語言內嵌組合語言
有時,為了能增進程式的效率,或者在 C 語言當中加入一些組合語言程式碼,我們會採用內嵌的方式,將組合語言內嵌在C語言當中。針對這點,gcc提供了如圖 4.10的內嵌語法,讓我們在 C 語言中可以直接撰寫組合語言。其中,assembler template 為組合語言程式、output operands 為輸入參數、input operands 為輸入參數,而 list of clobbered registers 則指定了被更改的暫存器參數列表。
asm( ; 內嵌起始符號 |
在GNU的內嵌組合語言 (assembler template) 中,由於 % 被用作參數 %1, %2, … 的前置字元,因此當遇到暫存器(像是 %eax) 時,必須使用兩個 % 符號 (%%) 作為暫存器的起始標記,因此,在範例 4.24中,原本的 addl %ebx, %eax
指令,就成了 addl %%ebx, %%eax
這個更冗長的指令。
在範例 4.24的內嵌參數部分, :"a"(foo), "b"(bar)
是輸入參數,代表要將 foo
變數傳遞給限制條件為 a
的暫存器,由於在 IA32 中限制條件為 a
的暫存器就是 eax
,因此, foo
變數將會傳給 eax
暫存器。同理, "b"(bar)
參數代表將 bar
傳給 ebx
暫存器。而輸出參數 :"=a"(foo)
則是將 eax
的結果傳回到 foo
變數中。
範例 4.24 內嵌組合語言的C程式:
int main(void) ; ... |
取得 RDTSC 值
由於 rdtsc
是將數值存入 EDX 與 EAX 暫存器,因此我們可以透過在 C 語言內嵌組合語言的方式來取得暫存器的資料。透過以下實做,會返回 rdtsc 值:
1 | uint64_t rdtsc() { |
在上述程式碼第 3 行中, "=a" (lo)
代表將 eax
暫存器的值傳給變數 lo
, "=d" (hi)
代表將 edx
暫存器的值傳給變數 hi
。由於變數 lo
以及 hi
分別只存取 rdtsc
高位以及低位的 32 位元資料,因此在第 4 行需要將變數 hi
先轉型為 64 位元後將再將其值進行左移 32 位元,最後與變數 lo
進行 OR 操作即可取得完整的 rdtsc
值。
一點練習
下列程式碼的目的是輸出 TSC 的時間,在輸出部份為 : "=m" (msr)
的情況下,請修正組合語言,讓這個程式能夠將 TSC 的值放入到 msr
變數中:
題目來源:中正資工:作業系統概論(羅習五教授)
|
解析
首先在第四行會呼叫 rdtsc
,因此在 rdx
以及 edx
會分別存放 rdtsc
所回傳的高 32 位元以及低 32 位元資料,在第 5 行首先將 rdx
的資料左移 32 位元並且在第 7 行將左移後的資料存入記憶體。因此我們只需要在第 6 行中先將 rax
的資料放入記憶體,之後再將 rdx
左移後的資料與記憶體內 rax
的資料進行 OR 運算,最後將記憶體的資料傳給變數 msr
。
1 | #include <stdio.h> |