linux內核源碼雖然是用C寫的,不過其中有不少用嵌入式彙編直接操做底層硬件的「宏函數」,要想順利的理解內核理論和具體實現邏輯,學會看嵌入式彙編是必修課,下面內容是學習過程當中的筆記;當作回顧時的參考。linux
1、嵌入式彙編語法:編程
一、格式函數
1 asm("彙編語句" //"("以前用asm 或 __asm__ 意爲"()"的內容是彙編語句 2 :輸出寄存器 3 :輸入寄存器 4 :會被修改的寄存器);
除第一行之外,後面帶冒號的行若不使用就均可一省略。學習
輸出寄存器:表示當這段嵌入彙編執行完以後,那些寄存器用於存放輸出數據。這些寄存器會分別對應一C語言表達式值或一個內存地址;spa
輸入寄存器:表示在開始執行彙編代碼時,這裏指定的一些寄存器中應存放的輸入值,它們也分別對應着一C變量或常數值;code
會被修改的寄存器:表示你已對其中列出的寄存器中的值進行了改動,gcc編譯器不能再依賴於它原來對這些寄存器加載的值,若是必要的話,gcc須要從新加載這些寄存器。所以咱們須要把那些沒有在輸出/輸入寄存器中的部分列出,可是在彙編語句中明確使用到或隱含使用到的寄存器名列在這個部分。blog
二、實例內存
1 #define get_seg_byte(seg,addr) \ 2 ( { \ 3 register char _res ; \ //定義了一個寄存器變量——res 4 _asm_("push %%fs ; \ //保存fs寄存器原值 5 mov %%ax,%%fs ; \ //用seg設置fs 6 movb %%fs:%2,%%al ; \ //取seg:addr處1字節內容到al寄存器 7 pop %%fs " \ //恢復fs寄存器原內容 8 : "=a" (_res) \ 9 : "0" (seg), "m" ( * (addr) ) ) ; \ 10 _res ; } )
這段代碼定義了一個嵌入式彙編語言函數。一般使用匯編語言最方便的方法是把他們放在一個宏內。用圓括號括住的組合語句(花括號中的語句)「({})」能夠做爲表達式使用,其中最後一行的變量_res是該表達式的輸出值。由於宏語句須要定義在一行上,所以這裏使用反斜槓「\」將這些語句連成一行。這條紅第一將被替換到程序中引用改宏名稱的地方。第一行定義了宏的名稱,即宏函數名稱get_seg_byte(seg,addr)。第三行定義了一個寄存器變量_res。該變量將被保存在一個寄存器中,以便快速訪問和操做。若是想指定寄存器(如eax),那麼咱們能夠把改句寫成"register char _res asm("ax");",其中asm也能夠寫成_asm_。第四行上的_asm_表示嵌入式彙編語句的開始。第4-7行的4條語句是AT&T格式的彙編語句。另外,爲了讓gcc編譯產生的彙編語言程序中寄存器名稱前有一個百分號「%」,在嵌入彙編語句寄存器名稱前就必須寫上兩個百分號「%%」。get
第8行即輸出寄存器,該語句的含義是在這段代碼運行結束後將eax所表明的的寄存器的值放入_res變量中,做爲本函數的輸出值,「=a」中的「a」稱爲加載代碼,「=」表示這是輸出寄存器,而且其中的值將被輸出值替代。加載代碼是CPU寄存器,內存地址以及一些數值的簡寫字母代號。第9行表示在這段代碼開始運行時將seg放到eax寄存器中,「0」表示使用與上面相同位置上的輸出寄存器。而((*addr))表示一個內存偏移地址值。爲了在上面彙編語句中使用該地址值,嵌入式彙編程序規定把輸出和輸入寄存器按統一順序編號,順序是從輸出寄存器序列從左到右從上到下以「%0」開始,分別記爲%0、%1···%9.所以,輸出寄存器的編號是%0(這裏只有一個輸出寄存器),輸入寄存器前一部分(「0」(seg))的編號是%1,然後部分的編號是%2。上面第6行上的%2即表明(*(addr))這個內存偏移量。編譯器
三、輸入輸出寄存器格式說明
「0」表示使用與上面相同位置上的輸出寄存器
四、特別說明
使用Intel CPU時, 當須要進行函數調用時,有如下原則:1、eax 、edx、ecx的內容必須由調用者自行保存;2、ebx、esi、edi得內容必須由被調函數保護,當被調這要使用這些寄存器中的任何一個時,要實如今本身的棧中保存其內容,由於調用者不負責管理;函數操做結束後再還原回去;另外ebp、esp的使用也要遵循第二原則。