存屏障機制及內(nèi)核相關(guān)源代碼分析
分析人:余旭
分析版本:Linux Kernel 2.6.14 來自于:www.kernel.org
分析開始時間:2005-11-17-20:45:56
分析結(jié)束時間:2005-11-21-20:07:32
編號:2-1 類別:進程管理-準備工作1-內(nèi)存屏障
Email:yuxu9710108@163.com
版權(quán)聲明:版權(quán)保留。本文用作其他用途當(dāng)經(jīng)作者本人同意,轉(zhuǎn)載請注明作者姓名
All Rights Reserved. If for other use,must Agreed By the writer.Citing this text,please claim the writer's name.
Copyright (C) 2005 YuXu
*************************************************************
內(nèi)存屏障是Linux Kernel中常要遇到的問題,這里專門來對其進行研究。一者查閱網(wǎng)上現(xiàn)有資料,進行整理匯集;二者翻閱Linux內(nèi)核方面的指導(dǎo)書,從中提煉觀點;最后,自己加以綜合分析,提出自己的看法。下面將對個問題進行專題分析。
*****************************************************************************
------------------------------------------------------ 專題研究:內(nèi)存屏障--------------------------------
---------------------------------------------------------論壇眾人資料匯集分析---------------------------
set_current_state(),__set_current_state(),set_task_state(),__set_task_state(),rmb(),wmb(),mb()的源代碼中的相關(guān)疑難問題及眾人的論壇觀點:
-----------------------------------------------------------------------------------------------------------------
1.--->ymons 在www.linuxforum.net Linux內(nèi)核技術(shù)論壇發(fā)貼問:
set_current_state和__set_current_state的區(qū)別?
#define __set_current_state(state_value) /
do { current->state = (state_value); } while (0)
#define set_current_state(state_value) /
set_mb(current->state, (state_value))
#define set_mb(var, value) do { var = value; mb(); } while (0)
#define mb() __asm__ __volatile__ ("" : : : "memory")
在linux的源代碼中經(jīng)常有這種設(shè)置當(dāng)前進程狀態(tài)的代碼,但我搞不清楚這兩種用法的不同?有哪位大蝦指點一二,必將感謝不盡!
------------------
2.---> chyyuu(chenyu-tmlinux@hpclab.cs.tsinghua.edu.cn) 在www.linuxforum.net的Linux內(nèi)核技術(shù)上發(fā)貼問:
在kernel.h中有一個define
/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")
在內(nèi)核許多地方被調(diào)用,不知到底是生成什么匯編指令????
請教!!!
--------------------
3.--->tigerl 02-12-08 10:57 在www.linuxforum.net的Linux內(nèi)核技術(shù)提問:
這一句(include/asm-i386/system.h中)定義是什么意思?
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
4.--->jackcht 01-03-02 10:55 在www.linuxforum.net Linux內(nèi)核技術(shù) :
各位大蝦,我在分析linux的時候發(fā)現(xiàn)有一個古怪的函數(shù),就是barrier,俺愣是不知道它是干嘛用的,幫幫我這菜鳥吧,感謝感謝!
還有就是下面這句中的("":::"memory")是什么意思呀,我苦!
# define barrier() _asm__volatile_("": : :"memory")
***********************************眾人的觀點*******************************
ANSWER:
1.jkl Reply:這就是所謂的內(nèi)存屏障,前段時間曾經(jīng)討論過。CPU越過內(nèi)存屏障后,將刷新自已對存儲器的緩沖狀態(tài)。這條語句實際上不生成任何代碼,但可使gcc在barrier()之后刷新寄存器對變量的分配。
2.wheelz發(fā)帖指出:
#define __set_task_state(tsk, state_value) /
do { (tsk)->state = (state_value); } while (0)
#define set_task_state(tsk, state_value) /
set_mb((tsk)->state, (state_value))
set_task_state()帶有一個memory barrier,__set_task_state()則沒有,當(dāng)狀態(tài)state是RUNNING時,因為scheduler可能訪問這個state,因此此時要變成其他狀態(tài)(如INTERRUPTIBLE),就要用set_task_state()而當(dāng)state不是RUNNING時,因為沒有其他人會訪問這個state,因此可以用__set_task_state()反正用set_task_state()肯定是安全的,但 __set_task_state()可能會快些。
自己分析:
wheelz講解很清楚,尤其是指出了__set_task_state()速度會快于set_task_state()。這一點,很多貼子忽略了,這里有獨到之處。在此,作者專門強調(diào)之。
3.自己分析:
1)set_mb(),mb(),barrier()函數(shù)追蹤到底,就是__asm__ __volatile__("":::"memory"),而這行代碼就是內(nèi)存屏障。
2)__asm__用于指示編譯器在此插入?yún)R編語句
3)__volatile__用于告訴編譯器,嚴禁將此處的匯編語句與其它的語句重組合優(yōu)化。即:原原本本按原來的樣子處理這這里的匯編。
4)memory強制gcc編譯器假設(shè)RAM所有內(nèi)存單元均被匯編指令修改,這樣cpu中的registers和cache中已緩存的內(nèi)存單元中的數(shù)據(jù)將作廢。cpu將不得不在需要的時候重新讀取內(nèi)存中的數(shù)據(jù)。這就阻止了cpu又將registers,cache中的數(shù)據(jù)用于去優(yōu)化指令,而避免去訪問內(nèi)存。
5)"":::表示這是個空指令。barrier()不用在此插入一條串行化匯編指令。在后文將討論什么叫串行化指令。
6)__asm__,__volatile__,memory在前面已經(jīng)解釋
7)lock前綴表示將后面這句匯編語句:"addl $0,0(%%esp)"作為cpu的一個內(nèi)存屏障。
8)addl $0,0(%%esp)表示將數(shù)值0加到esp寄存器中,而該寄存器指向棧頂?shù)膬?nèi)存單元。加上一個0,esp寄存器的數(shù)值依然不變。即這是一條無用的匯編指令。在此利用這條無價值的匯編指令來配合lock指令,在__asm__,__volatile__,memory的作用下,用作cpu的內(nèi)存屏障。
9)set_current_state()和__set_current_state()區(qū)別就不難看出。
10)至于barrier()就很易懂了。
11)作者注明:作者在回答這個問題時候,參考了《深入理解LINUX內(nèi)核》一書,陳莉君譯,中國電力出版社,P174
4.xshell 發(fā)貼指出:
#include <asm/system.h>
"void rmb(void);"
"void wmb(void);"
"void mb(void);"
這些函數(shù)在已編譯的指令流中插入硬件內(nèi)存屏障;具體的插入方法是平臺相關(guān)的。rmb(讀內(nèi)存屏障)保證了屏障之前的讀操作一定會在后來的讀操作執(zhí)行之前完成。wmb 保證寫操作不會亂序,mb 指令保證了兩者都不會。這些函數(shù)都是 barrier函數(shù)的超集。解釋一下:編譯器或現(xiàn)在的處理器常會自作聰明地對指令序列進行一些處理,比如數(shù)據(jù)緩存,讀寫指令亂序執(zhí)行等等。如果優(yōu)化對象是普通內(nèi)存,那么一般會提升性能而且不會產(chǎn)生邏輯錯誤。但如果對I/O操作進行類似優(yōu)化很可能造成致命錯誤。所以要使用內(nèi)存屏障,以強制該語句前后的指令以正確的次序完成。其實在指令序列中放一個wmb的效果是使得指令執(zhí)行到該處時,把所有緩存的數(shù)據(jù)寫到該寫的地方,同時使得wmb前面的寫指令一定會在wmb的寫指令之前執(zhí)行。
5.Nazarite發(fā)貼指出:
__volatitle__是防止編譯器移動該指令的位置或者把它優(yōu)化掉。"memory",是提示編譯器該指令對內(nèi)存修改,防止使用某個寄存器中已經(jīng)load的內(nèi)存的值。lock 前綴是讓cpu的執(zhí)行下一行指令之前,保證以前的指令都被正確執(zhí)行。
再次發(fā)貼指出:
The memory keyword forces the compiler to assume that all memory locations in RAM have been changed by the assembly language instruction; therefore, the compiler cannot optimize the code by using the values of memory locations stored in CPU registers before the asm instruction.
6.bx bird 發(fā)貼指出:
cpu上有一根pin #HLOCK連到北橋,lock前綴會在執(zhí)行這條指令前先去拉這根pin,持續(xù)到這個指令結(jié)束時放開#HLOCK pin,在這期間,北橋會屏蔽掉一切外設(shè)以及AGP的內(nèi)存操作。也就保證了這條指令的atomic。
7.coldwind 發(fā)貼指出:
"memory",是提示編譯器該指令對內(nèi)存修改,防止使用某個寄存器中已經(jīng)load的內(nèi)存的值,應(yīng)該是告訴CPU內(nèi)存已經(jīng)被修改過,讓CPU invalidate所有的cache。
通過以上眾人的貼子的分析,自己綜合一下,這4個宏set_current_state(),__set_current_state(), set_task_state(),__set_task_state()和3個函數(shù)rmb(),wmb(),mb()的源代碼中的疑難大都被解決。此處只是匯集眾人精彩觀點,只用來解決代碼中的疑難,具體有序系統(tǒng)的源代碼將在后面給出。
--------------------------------------------------------------------------------------------------------------
mfence,mb(),wmb(),OOPS的疑難問題的突破
--------------------------------------------------------------------------------------------------------------
1.--->puppy love (zhou_ict@hotmail.com )在www.linuxforum.net CPU 與 編譯器 問: 在linux核心當(dāng)中, mb(x86-64)的實現(xiàn)是 ("mfence":::"memory")
我查了一下cpu的manual,mfence用來同步指令執(zhí)行的。而后面的memory clober好像是gcc中用來干擾指令調(diào)度的。但還是不甚了了,哪位能給解釋解釋嗎? 或者有什么文檔之類的可以推薦看看的?
ANSWER:
1.classpath 發(fā)貼指出:
mfence is a memory barrier supported by hardware, and it only makes sense for shared memory systems.
For example, you have the following codes
<codes1>
mfence
<codes2>
mfence or other memory barriers techniques disallows the code motion (load/store)from codes2 to codes1 done by _hardware_ . Some machines like P4 can move loads in codes 2 before stores in codes1, which is out-of-order.
Another memory barrier is something like
("":::"memory"),
which disallows the code motion done by _compiler_. But IMO memory access order is not always guaranteed in this case.
-----
2.canopy 發(fā)貼指出:
我稍微看了一下x86-64的手冊。mfence保證系統(tǒng)在后面的memory訪問之前,先前的memory訪問都已經(jīng)結(jié)束。由于這條指令可能引起memory任意地址上內(nèi)容的改變,所以需要用“memory” clobber告訴gcc這一點。這樣gcc就需要重新從memory中l(wèi)oad寄存器來保證同一變量在寄存器和memory中的內(nèi)容一致。
------------------
3.cool_bird Reply:
內(nèi)存屏障
MB(memory barrier,內(nèi)存屏障) :x86采用PC(處理機)內(nèi)存一致性模型,使用MB強加的嚴格的CPU內(nèi)存事件次序,保證程序的執(zhí)行看上去象是遵循順序一致性(SC)模型,當(dāng)然,即使對于UP,由于內(nèi)存和設(shè)備見仍有一致性問題,這些Mb也是必須的。在當(dāng)前的實現(xiàn)中,wmb()實際上是一個空操作,這是因為目前Intel的CPU系列都遵循“處理機一致性”,所有的寫操作是遵循程序序的,不會越過前面的讀寫操作。但是,由于Intel CPU系列可能會在將來采用更弱的內(nèi)存一致性模型并且其他體系結(jié)構(gòu)可能采用其他放松的一致性模型,仍然在內(nèi)核里必須適當(dāng)?shù)夭迦雡mb()保證內(nèi)存事件的正確次序。
見頭文件include/asm/system.h
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
#define rmb() mb()
#define wmb() __asm__ __volatile__ ("": : :"memory")
此外,barrier實際上也是內(nèi)存屏障。
include/linux/kernel.h:
#define barrier() __asm__ __volatile__("": : :"memory")
內(nèi)存屏障也是一種避免鎖的技術(shù)。
它在進程上下文中將一個元素插入一個單向鏈表:
new->next=i->next;
wmb();
i->next=new;
同時,如果不加鎖地遍歷這個單向鏈表。或者在遍歷鏈表時已經(jīng)可以看到new,或者new還不在該鏈表中。Alan Cox書寫這段代碼時就注意到了這一點,兩個內(nèi)存寫事件的順序必須按照程序順序進行。否則可能new的next指針將指向一個無效地址,就很可能出現(xiàn) OOPS!
不論是gcc編譯器的優(yōu)化還是處理器本身采用的大量優(yōu)化,如Write buffer, Lock-up free, Non-blocking reading, Register allocation, Dynamic scheduling, Multiple issues等,都可能使得實際執(zhí)行可能違反程序序,因此,引入wmb內(nèi)存屏障來保證兩個寫事件的執(zhí)行次序嚴格按程序順序來執(zhí)行。
作者說明:原貼子不太清楚,作者作了必要的調(diào)整。
**************************************************************************
作者讀到這里,不懂OOPS便又上網(wǎng)查找OOPS的資料學(xué)習(xí)如下,以期望搞懂OOPS后能更好的理解上面這段話。
------------------------------------------OOPS解釋--------------------------------------------------
1.網(wǎng)上第一個貼子:
--->殊途同歸 發(fā)表于 2005-7-26 16:40:00 :掌握 Linux 調(diào)試技術(shù) 來自中國教育人博客:www.blog.edu.cn/index.html
Oops 分析
Oops(也稱panic,慌張)消息包含系統(tǒng)錯誤的細節(jié),如CPU寄存器的內(nèi)容。在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時發(fā)送到系統(tǒng)控制臺的 Oops消息。一旦您掌握了細節(jié),就可以將消息發(fā)送到ksymoops實用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號。在很多情況下,這些信息就足夠您確定錯誤的可能原因是什么了。請注意,Oops 消息并不包括核心文件。
2.網(wǎng)上第二個貼子:
--->www.plinux.org自由飛鴿 上的貼子:System.map文件的作用 作者:趙炯
gohigh@sh163.net
作者說明:
1.OOPS和System.map文件密切相關(guān)。所以要研討System.map文件。
2.本作者對所引用的文章內(nèi)容進行了整理,刪除了一些次要的部分,插入了一些內(nèi)容,使文章更清晰。再者對一些內(nèi)容進行了擴展說明。
--->符號表:
1.什么是符號(Symbols)?
在編程中,一個符號(symbol)是一個程序的創(chuàng)建塊:它是一個變量名或一個函數(shù)名。如你自己編制的程序一樣,內(nèi)核具有各種符號也是不應(yīng)該感到驚奇的。當(dāng)然,區(qū)別在 于內(nèi)核是一非常復(fù)雜的代碼塊,并且含有許多、許多的全局符號。
2.內(nèi)核符號表(Kernel Symbol Table)是什么東西?
內(nèi)核并不使用符號名。它是通過變量或函數(shù)的地址(指針)來使用變量或函數(shù)的,而 不是使用size_t BytesRead,內(nèi)核更喜歡使用(例如)c0343f20來引用這個變量。
而另一方面,人們并不喜歡象c0343f20這樣的名字。我們跟喜歡使用象 size_t BytesRead這樣的表示。通常,這并不會帶來什么問題。內(nèi)核主要是用C語言寫成的,所以在我們編程時編譯器/連接程序允許我們使用符號名,并且使內(nèi)核在運行時使用地址表示。這樣大家都滿意了。
然而,存在一種情況,此時我們需要知道一個符號的地址(或者一個地址對應(yīng)的 符號)。這是通過符號表來做到的,與gdb能夠從一個地址給出函數(shù)名(或者給出一個函數(shù)名的地址)的情況很相似。符號表是所有符號及其對應(yīng)地址的一個列表。這里是 一個符號表例子:
c03441a0 B dmi_broken
c03441a4 B is_sony_vaio_laptop
c03441c0 b dmi_ident
c0344200 b pci_bios_present
c0344204 b pirq_table
c0344208 b pirq_router
c034420c b pirq_router_dev
c0344220 b ascii_buffer
c0344224 b ascii_buf_bytes
你可以看出名稱為dmi_broken的變量位于內(nèi)核地址c03441a0處。
--->;System.map文件與ksyms:
1.什么是System.map文件?
有兩個文件是用作符號表的:
/proc/ksyms
System.map
這里,你現(xiàn)在可以知道System.map文件是干什么用的了。每當(dāng)你編譯一個新內(nèi)核時,各種符號名的地址定會變化。
/proc/ksyms 是一個 "proc文件" 并且是在內(nèi)核啟動時創(chuàng)建的。實際上它不是一個真實的文件;它只是內(nèi)核數(shù)據(jù)的簡單表示形式,呈現(xiàn)出象一個磁盤文件似的。如果你不相信我,那么就試試找出/proc/ksyms的文件大小來。因此, 對于當(dāng)前運行的內(nèi)核來說,它總是正確的..
然而,System.map卻是文件系統(tǒng)上的一個真實文件。當(dāng)你編譯一個新內(nèi)核時,你原來的System.map中的符號信息就不正確了。隨著每次內(nèi)核的編譯,就會產(chǎn)生一個新的 System.map文件,并且需要用該文件取代原來的文件。
--->OOPS:
1.什么是一個Oops?
在自己編制的程序中最常見的出錯情況是什么?是段出錯(segfault),信號11。
Linux內(nèi)核中最常見的bug是什么?也是段出錯。除此,正如你想象的那樣,段出錯的問題是非常復(fù)雜的,而且也是非常嚴重的。當(dāng)內(nèi)核引用了一個無效指針時,并不稱其為段出錯 -- 而被稱為"oops"。一個oops表明內(nèi)核存在一個bug,應(yīng)該總是提出報告并修正該bug。
2.OOPS與段違例錯的比較:
請注意,一個oops與一個段出錯并不是一回事。你的程序并不能從段出錯中恢復(fù) 過來,當(dāng)出現(xiàn)一個oops時,并不意味著內(nèi)核肯定處于不穩(wěn)定的狀態(tài)。Linux內(nèi)核是非常健壯的;一個oops可能僅殺死了當(dāng)前進程,并使余下的內(nèi)核處于一個良好的、穩(wěn)定的狀態(tài)。
3.OOPS與panic的比較:
一個oops并非是內(nèi)核死循環(huán)(panic)。在內(nèi)核調(diào)用了panic()函數(shù)后,內(nèi)核就不能繼續(xù)運行了;此時系統(tǒng)就處于停頓狀態(tài)并且必須重啟。如果系統(tǒng)中關(guān)鍵部分遭到破壞那么一個oops也可能會導(dǎo)致內(nèi)核進入死循環(huán)(panic)。例如,設(shè)備驅(qū)動程序中 出現(xiàn)的oops就幾乎不會導(dǎo)致系統(tǒng)進行死循環(huán)。
當(dāng)出現(xiàn)一個oops時,系統(tǒng)就會顯示出用于調(diào)試問題的相關(guān)信息,比如所有CPU寄存器中的內(nèi)容以及頁描述符表的位置等,尤其會象下面那樣打印出EIP(指令指針)的內(nèi)容:
EIP: 0010:[<00000000>]
Call Trace: []
4.一個Oops與System.map文件有什么關(guān)系呢?
我想你也會認為EIP和Call Trace所給出的信息并不多,但是重要的是,對于內(nèi)核開發(fā)人員來說這些信息也是不夠的。由于一個符號并沒有固定的地址, c010b860可以指向任何地方。
為了幫助我們使用oops含糊的輸出,Linux使用了一個稱為klogd(內(nèi)核日志后臺程序)的后臺程序,klogd會截取內(nèi)核oops并且使用syslogd將其記錄下來,并將某些象c010b860信息轉(zhuǎn)換成我們可以識別和使用的信息。換句話說,klogd是一個內(nèi)核消息記錄器 (logger),它可以進行名字-地址之間的解析。一旦klogd開始轉(zhuǎn)換內(nèi)核消息,它就使用手頭的記錄器,將整個系統(tǒng)的消息記錄下來,通常是使用 syslogd記錄器。
為了進行名字-地址解析,klogd就要用到System.map文件。我想你現(xiàn)在知道一個oops與System.map的關(guān)系了。
---------------------
作者補充圖:
System.map文件
^
|
|
syslogd記錄------->klogd解析名字-地址
^
|
|
內(nèi)核出錯----->OOPS
-----------------------
深入說明: klogd會執(zhí)行兩類地址解析活動:
1.靜態(tài)轉(zhuǎn)換,將使用System.map文件。 所以得知System.map文件只用于名字-地址的靜態(tài)轉(zhuǎn)換。
2.Klogd動態(tài)轉(zhuǎn)換
動態(tài)轉(zhuǎn)換,該方式用于可加載模塊,不使用System.map,因此與本討論沒有關(guān)系,但我仍然對其加以簡單說明。假設(shè)你加載了一個產(chǎn)生oops 的內(nèi)核模塊。于是就會產(chǎn)生一個oops消息,klogd就會截獲它,并發(fā)現(xiàn)該oops發(fā)生在d00cf810處。由于該地址屬于動態(tài)加載模塊,因此在 System.map文件中沒有對應(yīng)條目。klogd將會在其中尋找并會毫無所獲,于是斷定是一個可加載模塊產(chǎn)生了oops。此時klogd就會向內(nèi)核查詢該可加載模塊輸出的符號。即使該模塊的編制者沒有輸出其符號,klogd也起碼會知道是哪個模塊產(chǎn)生了oops,這總比對一個oops一無所知要好。
還有其它的軟件會使用System.map,我將在后面作一說明。
--------------
System.map應(yīng)該位于什么地方?
System.map應(yīng)該位于使用它的軟件能夠?qū)ふ业降牡胤剑簿褪钦f,klogd會在什么地方尋找它。在系統(tǒng)啟動時,如果沒有以一個參數(shù)的形式為klogd給出System.map的位置,則klogd將會在三個地方搜尋System.map。依次為:
/boot/System.map
/System.map
/usr/src/linux/System.map
System.map 同樣也含有版本信息,并且klogd能夠智能化地搜索正確的map文件。例如,假設(shè)你正在運行內(nèi)核2.4.18并且相應(yīng)的map文件位于 /boot/System.map。現(xiàn)在你在目錄/usr/src/linux中編譯一個新內(nèi)核2.5.1。在編譯期間,文件 /usr/src/linux/System.map就會被創(chuàng)建。當(dāng)你啟動該新內(nèi)核時,klogd將首先查詢/boot/System.map,確認它不是啟動內(nèi)核正確的map文件,就會查詢/usr/src/linux/System.map, 確定該文件是啟動內(nèi)核正確的map文件并開始讀取其中的符號信息。
幾個注意點:
1.klogd未公開的特性:
在2.5.x系列內(nèi)核的某個版本,Linux內(nèi)核會開始untar成linux-version,而非只是linux(請舉手表決--有多少人一直等待著這樣做?)。我不知道klogd是否已經(jīng)修改為在/usr/src/linux-version/System.map中搜索。TODO:查看 klogd源代碼。
在線手冊上對此也沒有完整描述,請看:
# strace -f /sbin/klogd | grep 'System.map'
31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2
顯然,不僅klogd在三個搜索目錄中尋找正確版本的map文件,klogd也同樣知道尋找名字為 "System.map" 后加"-內(nèi)核版本",象 System.map-2.4.18. 這是klogd未公開的特性。
2.驅(qū)動程序與System.map文件的關(guān)系:
有一些驅(qū)動程序?qū)⑹褂肧ystem.map來解析符號(因為它們與內(nèi)核頭連接而非glibc庫等),如果沒有System.map文件,它們將不能正確地工作。這與一個模塊由于內(nèi)核版本不匹配而沒有得到加載是兩碼事。模塊加載是與內(nèi)核版本有關(guān),而與即使是同一版本內(nèi)核其符號表也會變化的編譯后內(nèi)核無關(guān)。
3.還有誰使用了System.map?
不要認為System.map文件僅對內(nèi)核oops有用。盡管內(nèi)核本身實際上不使用System.map,其它程序,象klogd,lsof,
satan# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23
ps,
satan# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6
以及其它許多軟件,象dosemu,需要有一個正確的System.map文件。
4.如果我沒有一個好的System.map,會發(fā)生什么問題?
假設(shè)你在同一臺機器上有多個內(nèi)核。則每個內(nèi)核都需要一個獨立的System.map文件!如果所啟動的內(nèi)核沒有對應(yīng)的System.map文件,那么你將定期地看到這樣一條信息:
System.map does not match actual kernel (System.map與實際內(nèi)核不匹配)
不是一個致命錯誤,但是每當(dāng)你執(zhí)行ps ax時都會惱人地出現(xiàn)。有些軟件,比如dosemu,可能不會正常工作。最后,當(dāng)出現(xiàn)一個內(nèi)核oops時,klogd或ksymoops的輸出可能會不可靠。
5.我如何對上述情況進行補救?
方法是將你所有的System.map文件放在目錄/boot下,并使用內(nèi)核版本號重新對它們進行命名。
5-1.假設(shè)你有以下多個內(nèi)核:
/boot/vmlinuz-2.2.14
/boot/vmlinuz-2.2.13
那么,只需對應(yīng)各內(nèi)核版本對map文件進行改名,并放在/boot下,如:
/boot/System.map-2.2.14
/boot/System.map-2.2.13
5-2.如果你有同一個內(nèi)核的兩個拷貝怎么辦?
例如:
/boot/vmlinuz-2.2.14
/boot/vmlinuz-2.2.14.nosound
最佳解決方案將是所有軟件能夠查找下列文件:
/boot/System.map-2.2.14
/boot/System.map-2.2.14.nosound
但是說實在的,我并不知道這是否是最佳情況。我曾經(jīng)見到搜尋"System.map-kernelversion",但是對于搜索 "System.map-kernelversion.othertext"的情況呢?我不太清楚。此時我所能做的就是利用這樣一個事實: /usr/src/linux是標準map文件的搜索路徑,所以你的map文件將放在:
/boot/System.map-2.2.14
/usr/src/linux/System.map (對于nosound版本)
你也可以使用符號連接:
System.map-2.2.14
System.map-2.2.14.sound
System.map -> System.map-2.2.14.sound
------------------------------------------------OOPS解釋完畢----------------------------------------------
學(xué)習(xí)到這里,OOPS和system.map文件,已經(jīng)有了較深刻的認識。回過頭來繼續(xù)對內(nèi)存屏障的學(xué)習(xí)。
******************************************************************************
4.www.21icbbs.com上的貼子
為了防止編譯器對有特定時續(xù)要求的的硬件操作進行優(yōu)化,系統(tǒng)提供了相應(yīng)的辦法:
1,對于由于數(shù)據(jù)緩沖(比如延時讀寫,CACHE)所引起的問題,可以把相應(yīng)的I/O區(qū)設(shè)成禁用緩沖。
2,對于編譯優(yōu)化,可以用內(nèi)存屏障來解決。如:void rmb(void),void wmb(void),void mb(void),分別是讀,寫,讀寫 屏障。和void barrier(void).
5.自己分析:
作者查閱了內(nèi)核注釋如下:
-----------------------------------------------asm-i386/system.h--------------------------------------
內(nèi)核注釋:
/*
* Force strict CPU ordering.
* And yes, this is required on UP too when we're talking
* to devices.
*
* For now, "wmb()" doesn't actually do anything, as all
* Intel CPU's follow what Intel calls a *Processor Order*,
* in which all writes are seen in the program order even
* outside the CPU.
*
* I expect future Intel CPU's to have a weaker ordering,
* but I'd also expect them to finally get their act together
* and add some real memory barriers if so.
*
* Some non intel clones support out of order store. wmb() ceases to be a
* nop for these.
*/
自己分析認為:
1.Intel CPU 有嚴格的“processor Order”,已經(jīng)確保內(nèi)存按序?qū)懀@里的wmb()所以定義的為空操作。
2.內(nèi)核人員希望Intel CPU今后能采用弱排序技術(shù),采用真正的內(nèi)存屏障技術(shù)。
3.在非intel的cpu上,wmb()就不再為空操作了。
-----------------------------------------內(nèi)核2.6.14完整的源代碼----------------------------------
下面的源代碼來自于Linux Kernel 2.6.14,開始對其進行一一的全面的分析:
-------------------------------------------/include/asm-i386/system.h----------------------------------
-----------------------------------------------------alternative()-----------------------------------------
/*
* Alternative instructions for different CPU types or capabilities.
*
* This allows to use optimized instructions even on generic binary kernels.
*
* length of oldinstr must be longer or equal the length of newinstr
* It can be padded with nops as needed.
*
* For non barrier like inlines please define new variants
* without volatile and memory clobber.
*/
#define alternative(oldinstr, newinstr, feature) /
asm volatile ("661:/n/t" oldinstr "/n662:/n" /
".section .altinstructions,/"a/"/n" /
" .align 4/n" /
" .long 661b/n" /* label */ /
" .long 663f/n" /* new instruction */ /
" .byte %c0/n" /* feature bit */ /
" .byte 662b-661b/n" /* sourcelen */ /
" .byte 664f-663f/n" /* replacementlen */ /
".previous/n" /
".section .altinstr_replacement,/"ax/"/n" /
"663:/n/t" newinstr "/n664:/n" /* replacement */ /
".previous" :: "i" (feature) : "memory")
自己分析:
1.alternative()宏用于在不同的cpu上優(yōu)化指令。oldinstr為舊指令,newinstr為新指令,feature為cpu特征位。
2.oldinstr的長度必須>=newinstr的長度。不夠?qū)⑻畛淇詹僮鞣?br />----------------------------------------------------------------------
/*
* Force strict CPU ordering.
* And yes, this is required on UP too when we're talking
* to devices.
*
* For now, "wmb()" doesn't actually do anything, as all
* Intel CPU's follow what Intel calls a *Processor Order*,
* in which all writes are seen in the program order even
* outside the CPU.
*
* I expect future Intel CPU's to have a weaker ordering,
* but I'd also expect them to finally get their act together
* and add some real memory barriers if so.
*
* Some non intel clones support out of order store. wmb() ceases * to be a nop for these.
*/
/*
* Actually only lfence would be needed for mb() because all stores done by the kernel should be already ordered. But keep a full barrier for now.
*/
自己分析:
這里的內(nèi)核中的注釋,在前面已經(jīng)作了講解,主要就是intel cpu采用Processor Order,對wmb()保證其的執(zhí)行順序按照程序順序執(zhí)行,所以wmb()定義為空操作。如果是對于對于非intel的cpu,這時wmb()就不能再是空操作了。
---------------------------mb()--rmb()--read_barrier_depends()--wmb()------------------
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
#define read_barrier_depends() do { } while(0)
#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE,but make it already an possibility. */
作者附注:(對內(nèi)核注釋中的名詞的解釋)
-->OOO:Out of Order,亂序執(zhí)行。
-->SSE:SSE是英特爾提出的即MMX之后新一代(當(dāng)然是幾年前了)CPU指令集,最早應(yīng)用在PIII系列CPU上。
本小段內(nèi)核注釋意即:亂序存儲的cpu還沒有問世,故CONFIG_X86_OOSTORE也就仍未定義的,wmb()當(dāng)為后面空宏(在__volatile__作用下,阻止編譯器重排順序優(yōu)化)。
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb() __asm__ __volatile__ ("": : :"memory")
#endif
--------------------------
自己分析:
1.lock, addl $0,0(%%esp)在本文開始處已經(jīng)解決。
lock前綴表示將后面這句匯編語句:"addl $0,0(%%esp)"作為cpu的一個內(nèi)存屏障。addl $0,0(%%esp)表示將數(shù)值0加到esp寄存器中,而該寄存器指向棧頂?shù)膬?nèi)存單元。加上一個0,esp寄存器的數(shù)值依然不變。即這是一條無用的匯編指令。在此利用這條無價值的匯編指令來配合lock指令,用作cpu的內(nèi)存屏障。
2.mfence保證系統(tǒng)在后面的memory訪問之前,先前的memory訪問都已經(jīng)結(jié)束。這是mfence是X86cpu家族中的新指令。詳見后面。
3.新舊指令對比:
-------------------------------
以前的源代碼:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
__asm__用于指示編譯器在此插入?yún)R編語句
__volatile__用于告訴編譯器,嚴禁將此處的匯編語句與其它的語句重組合優(yōu)化。即:原原本本按原來的樣子處理這這里的匯編。
-------------------
現(xiàn)在的源代碼:
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
--------------------------
兩者比較:
比起以前的源代碼來少了__asm__和__volatile__。增加了alternative()宏和mfence指令。
-------------------------
而SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon處理器中引入)提供了某些特殊類型內(nèi)存操作的排序和串行化功能。sfence,lfence,mfence指令是在后繼的cpu中新出現(xiàn)的的指令。
SFENCE,LFENCE,MFENCE指令提供了高效的方式來保證讀寫內(nèi)存的排序,這種操作發(fā)生在產(chǎn)生弱排序數(shù)據(jù)的程序和讀取這個數(shù)據(jù)的程序之間。
SFENCE——串行化發(fā)生在SFENCE指令之前的寫操作但是不影響讀操作。
LFENCE——串行化發(fā)生在SFENCE指令之前的讀操作但是不影響寫操作。
MFENCE——串行化發(fā)生在MFENCE指令之前的讀寫操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更靈活有效的控制內(nèi)存排序的方式。
sfence:在sfence指令前的寫操作當(dāng)必須在sfence指令后的寫操作前完成。
lfence:在lfence指令前的讀操作當(dāng)必須在lfence指令后的讀操作前完成。
mfence:在mfence指令前的讀寫操作當(dāng)必須在mfence指令后的讀寫操作前完成。
其實這里是用mfence新指令來替換老的指令串:__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")。
mfence的執(zhí)行效果就等效于__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")的執(zhí)行效果。只不過,__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")是在以前的cpu平臺上所設(shè)計的,借助于編譯器__asm__,__volatile__,lock這些指令來實現(xiàn)內(nèi)存屏障。而在 Pentium 4和Intel Xeon處理器中由于已經(jīng)引入了mfence指令,無須再用這一套指令,直接調(diào)用這一條指令即ok。而alternative()宏就是用于這個優(yōu)化指令的替換,用新的指令來替換老的指令串。
4.intel cpu已保證wmb()的順序完成。wmb()此處定義為空操作。
5.X86_FEATURE_XMM的解釋:
--------------------------------------asm-i386/cpufeature.h----------------------------------------
#define X86_FEATURE_XMM (0*32+25) /* Streaming SIMD Extensions */
************************************************************************
下面對SIMD進行解釋:
--------------《計算機系統(tǒng)結(jié)構(gòu)》--鄭緯民編--清華大學(xué)出版社---------
1).指令流:(instruction stream)機器執(zhí)行的指令序列
2).數(shù)據(jù)流:(data stream)指令調(diào)用的數(shù)據(jù)序列,包括輸入數(shù)據(jù)和中間結(jié)果。
3)Flynn分類法:
(1)SISD(Single Instrution stream Single Datastream)
單指令流單數(shù)據(jù)流,對應(yīng)為傳統(tǒng)的順序處理計算機。
(2)SIMD(Single Instrution stream Multiple Datastream)
單指令流多數(shù)據(jù)流,對應(yīng)陣列處理機或并行處理機。
(3)MISD(Multiple Instrution stream Single Datastream)
多指令流單數(shù)據(jù)流,對應(yīng)流水線處理機。
(4)MIMD(Multiple Instrution stream Multiple Datastream)
多指令流多數(shù)據(jù)流,對應(yīng)多處理機。
*************************************************************************
由于以上幾個指令牽涉到多處理器的管理,要徹底弄懂這些代碼的原理,必須深入挖掘之,既然遇到了,就一口氣吃掉。追根問底,清楚其來龍去脈。
***********************************************************************
----->來自Baidu快照,原網(wǎng)頁打不開了:多處理器管理
說明:作者對此文進行了參考,由于文章太長,太專業(yè)化,作者對其進行了改動處理:
------------------------------------------------------------------------------------------------
1.IA-32體系的機制:總線加鎖、cache一致性管理、串行化指令、高級可編程中斷控制器、二級緩存、超線程技術(shù):IA-32體系提供了幾種機制來管理和提升連接到同一系統(tǒng)總線的多個處理器的性能。這些機制包括:
1)總線加鎖、cache一致性管理以實現(xiàn)對系統(tǒng)內(nèi)存的原子操作、串行化指令(serializing instructions。這些指令僅對pentium4,Intel Xeon, P6,Pentium處理器有效)。
2)處理器芯片內(nèi)置的高級可編程中斷控制器(APIC)。APIC是在Pentium處理器中被引入IA-32體系的。
3)二級緩存(level 2, L2)。對于Pentium4,Intel Xeon, P6處理器,L2 cache已經(jīng)緊密的封裝到了處理器中。而Pentium,Intel486提供了用于支持外部L2 cache的管腳。
4)超線程技術(shù)。這個技術(shù)是IA-32體系的擴展,它能夠讓一個處理器內(nèi)核并發(fā)的執(zhí)行兩個或兩個以上的指令流。
這些機制在對稱多處理系統(tǒng)(symmetric-multiprocessing, SMP)中是極其有用的。然而,在一個IA-32處理器和一個專用處理器(例如通信,圖形,視頻處理器)共享系統(tǒng)總線的應(yīng)用中,這些機制也是適用的。
-------------------------
2.多處理機制的設(shè)計目標是:
1)保持系統(tǒng)內(nèi)存的完整性(coherency):
當(dāng)兩個或多個處理器試圖同時訪問系統(tǒng)內(nèi)存的同一地址時,必須有某種通信機制或內(nèi)存訪問協(xié)議來提升數(shù)據(jù)的完整性,以及在某些情況下,允許一個處理器臨時鎖定某個內(nèi)存區(qū)域。
2)保持高速緩存的一致性:
當(dāng)一個處理器訪問另一個處理器緩存中的數(shù)據(jù)時,必須要得到正確的數(shù)據(jù)。如果這個處理器修改了數(shù)據(jù),那么所有的訪問這個數(shù)據(jù)的處理器都要收到被修改后的數(shù)據(jù)。
3)允許以可預(yù)知的順序?qū)憙?nèi)存:
在某些情況下,從外部觀察到的寫內(nèi)存順序必須要和編程時指定的寫內(nèi)存順序相一致。
4)在一組處理器中派發(fā)中斷處理:
當(dāng)幾個處理器正在并行的工作在一個系統(tǒng)中時,有一個集中的機制是必要的,這個機制可以用來接收中斷以及把他們派發(fā)到某一個適當(dāng)?shù)奶幚砥鳌?br />
5)采用現(xiàn)代操作系統(tǒng)和應(yīng)用程序都具有的多線程和多進程的特性來提升系統(tǒng)的性能。
---------------------------
根據(jù)本文的需要,將重點討論內(nèi)存加鎖,串行(serializing instructions)指令,內(nèi)存排序,加鎖的原子操作(locked atomic operations)。
3.系統(tǒng)內(nèi)存加鎖的原子操作:
32位IA-32處理器支持對系統(tǒng)內(nèi)存加鎖的原子操作。這些操作常用來管理共享的數(shù)據(jù)結(jié)構(gòu)(例如信號量,段描述符,系統(tǒng)段頁表)。兩個或多個處理器可能會同時的修改這些數(shù)據(jù)結(jié)構(gòu)中的同一數(shù)據(jù)域或標志。
處理器應(yīng)用三個相互依賴的機制來實現(xiàn)加鎖的原子操作:
1)可靠的原子操作(guaranteed atomic operations)。
2)總線加鎖,使用LOCK#信號和LOCK指令前綴。
3)緩存完整性協(xié)議,保證原子操作能夠?qū)彺嬷械臄?shù)據(jù)結(jié)構(gòu)執(zhí)行;這個機制出現(xiàn)在Pentium4,IntelXeon,P6系列處理器中,這些機制以下面的形式相互依賴。
--->某些基本的內(nèi)存事務(wù)(memory transaction)例如讀寫系統(tǒng)內(nèi)存的一個字節(jié))被保證是原子的。也就是說,一旦開始,處理器會保證這個操作會在另一個處理器或總線代理(bus agent)訪問相同的內(nèi)存區(qū)域之前結(jié)束。
--->處理器還支持總線加鎖以實現(xiàn)所選的內(nèi)存操作(例如在共享內(nèi)存中的讀-改-寫操作),這些操作需要自動的處理,但又不能以上面的方式處理。因為頻繁使用的內(nèi)存數(shù)據(jù)經(jīng)常被緩存在處理器的L1,L2高速緩存里,原子操作通常是在處理器緩存內(nèi)部進行的,并不需要聲明總線加鎖。這里的處理器緩存完整性協(xié)議保證了在緩沖內(nèi)存上執(zhí)行原子操作時其他緩存了相同內(nèi)存區(qū)域的處理器被正確管理。
注意到這些處理加鎖的原子操作的機制已經(jīng)像IA-32處理器一樣發(fā)展的越來越復(fù)雜。于是,最近的IA-32處理器(例如Pentium 4, Intel Xeon, P6系列處理器)提供了一種比早期IA-32處理器更為精簡的機制。
------------------------------------------------保證原子操作的情況------------------------------------
4.保證原子操作的情況
Pentium 4, Intel Xeon,P6系列,Pentium,以及Intel486處理器保證下面的基本內(nèi)存操作總被自動的執(zhí)行:
1)讀或?qū)懸粋€字節(jié)
2)讀或?qū)懸粋€在16位邊界對齊的字
3)讀或?qū)懸粋€在32位邊界對齊的雙字
Pentium 4, Intel Xeon,P6系列以及Pentium處理器還保證下列內(nèi)存操作總是被自動執(zhí)行:
1)讀或?qū)懸粋€在64位邊界對齊的四字(quadword)
2)對32位數(shù)據(jù)總線可以容納的未緩存的內(nèi)存位置進行16位方式訪問
(16-bit accesses to uncached memory locations that fit within a 32-bit data bus)
P6系列處理器還保證下列內(nèi)存操作被自動執(zhí)行:
對32位緩沖線(cache line)可以容納的緩存中的數(shù)據(jù)進行非對齊的16位,32位,64位訪問.
對于可以被緩存的但是卻被總線寬度,緩沖線,頁邊界所分割的內(nèi)存區(qū)域,Pentium 4, Intel Xeon, P6 family,Pentium以及Intel486處理器都不保證訪問操作是原子的。Pentium 4, Intel Xeon,P6系列處理器提供了總線控制信號來允許外部的內(nèi)存子系統(tǒng)完成對分割內(nèi)存的原子性訪問;但是,對于非對齊內(nèi)存的訪問會嚴重影響處理器的性能,因此應(yīng)該盡量避免。
--------------------------------------------------------------總線加鎖------------------------------------------
5.總線加鎖(Bus Locking)
1.Lock信號的作用:
IA-32處理器提供了LOCK#信號。這個信號會在某些內(nèi)存操作過程中被自動發(fā)出。當(dāng)這個輸出信號發(fā)出的時候,來自其他處理器或總線代理的總線控制請求將被阻塞。軟件能夠利用在指令前面添加LOCK前綴來指定在其他情況下的也需要LOCK語義(LOCK semantics)。
在Intel386,Intel486,Pentium處理器中,直接調(diào)用加鎖的指令會導(dǎo)致LOCK#信號的產(chǎn)生。硬件的設(shè)計者需要保證系統(tǒng)硬件中LOCK#信號的有效性,以控制多個處理對內(nèi)存的訪問。
--->注意:
對于Pentium 4, Intel Xeon,以及P6系列處理器,如果被訪問的內(nèi)存區(qū)域存在于處理器內(nèi)部的高速緩存中,那么LOCK#信號通常不被發(fā)出;但是處理器的緩存卻要被鎖定。
--------------------------------------------------自動加鎖(Automatic Locking)------- -------------------
6.自動加鎖(Automatic Locking)
1.下面的操作會自動的帶有LOCK語義:
1)執(zhí)行引用內(nèi)存的XCHG指令。
2)設(shè)置TSS描述符的B(busy忙)標志。在進行任務(wù)切換時,處理器檢查并設(shè)置TSS描述符的busy標志。為了保證兩個處理器不會同時切換到同一個任務(wù)。處理器會在檢查和設(shè)置這個標志的時遵循LOCK語義。
3)更新段描述符時。在裝入一個段描述符時,如果段描述符的訪問標志被清除,處理器會設(shè)置這個標志。在進行這個操作時,處理器會遵循LOCK語義,因此這個描述符不會在更新時被其他的處理器修改。為了使這個動作能夠有效,更新描述符的操作系統(tǒng)過程應(yīng)該采用下面的方法:
(1)使用加鎖的操作修改訪問權(quán)字節(jié)(access-rights byte),來表明這個段描述符已經(jīng)不存在,同時設(shè)置類型變量,表明這個描述符正在被更新。
(2)更新段描述符的內(nèi)容。這個操作可能需要多個內(nèi)存訪問;因此不能使用加鎖指令。
(3)使用加鎖操作來修改訪問權(quán)字節(jié)(access-rights byte),來表明這個段描述符存在并且有效。
注意,Intel386處理器總是更新段描述符的訪問標志,無論這個標志是否被清除。Pentium 4, Intel Xeon,P6系列,Pentium以及Intel486處理器僅在該標志被清除時才設(shè)置這個標志。
4)更新頁目錄(page-directory)和頁表(page-table)的條目。在更新頁目錄和頁表的條目時,處理器使用加鎖的周期(locked cycles)來設(shè)置訪問標志和臟標志(dirty flag)。
5)響應(yīng)中斷。發(fā)生中斷后,中斷控制器可能會使用數(shù)據(jù)總線給處理器傳送中斷向量。處理器必須遵循LOCK語義來保證傳送中斷向量時數(shù)據(jù)總線上沒有其他數(shù)據(jù)。
-------------------------------------------------軟件控制的總線加鎖----------------------------------------
7.軟件控制的總線加鎖
1)總述:
如果想強制執(zhí)行LOCK語義,軟件可以在下面的指令前使用LOCK前綴。當(dāng)LOCK前綴被置于其他的指令之前或者指令沒有對內(nèi)存進行寫操作(也就是說目標操作數(shù)在寄存器中)時,一個非法操作碼(invalid-opcode)異常會被拋出。
2)可以使用LOCK前綴的指令:
1)位測試和修改指令(BTS, BTR, BTC)
2)交換指令(XADD, CMPXCHG, CMPXCHG8B)
3)XCHG指令自動使用LOCK前綴
4)單操作數(shù)算術(shù)和邏輯指令:INC, DEC, NOT, NEG
5)雙操作數(shù)算術(shù)和邏輯指令:ADD, ADC, SUB, SBB, AND, OR, XOR
3)注意:
(1)一個加鎖的指令會保證對目標操作數(shù)所在的內(nèi)存區(qū)域加鎖,但是系統(tǒng)可能會將鎖定區(qū)域解釋得稍大一些。
(2)軟件應(yīng)該使用相同的地址和操作數(shù)長度來訪問信號量(一個用作處理器之間信號傳遞用的共享內(nèi)存)。例如,如果一個處理器使用一個字來訪問信號量,其他的處理器就不應(yīng)該使用一個字節(jié)來訪問這個信號量。
(3)總線加鎖的完整性不受內(nèi)存區(qū)域?qū)R的影響。在所有更新操作數(shù)的總線周期內(nèi),加鎖語義一直持續(xù)。但是建議加鎖訪問能夠在自然邊界對齊,這樣可以提升系統(tǒng)性能:
任何邊界的8位訪問(加鎖或不加鎖)
16位邊界的加鎖字訪問。
32位邊界的加鎖雙字訪問。
64位邊界的加鎖四字訪問。
(4)對所有的內(nèi)存操作和可見的外部事件來說,加鎖的操作是原子的。只有取指令和頁表操作能夠越過加鎖的指令。
(5)加鎖的指令能用于同步數(shù)據(jù),這個數(shù)據(jù)被一個處理器寫而被其他處理器讀。
對于P6系列處理器來說,加鎖的操作使所有未完成的讀寫操作串行化(serialize)(也就是等待它們執(zhí)行完畢)。這條規(guī)則同樣適用于Pentium4和Intel Xeon處理器,但有一個例外:對弱排序的內(nèi)存類型的讀入操作可能不會被串行化。
加鎖的指令不應(yīng)該用來保證寫的數(shù)據(jù)可以作為指令取回。
--------------->自修改代碼(self-modifying code)
(6)加鎖的指令對于Pentium 4, Intel Xeon, P6 family, Pentium, and Intel486處理器,允許寫的數(shù)據(jù)可以作為指令取回。但是Intel建議需要使用自修改代碼(self-modifying code)的開發(fā)者使用另外一種同步機制。
處理自修改和交叉修改代碼(handling self- and cross-modifying code)
處理器將數(shù)據(jù)寫入當(dāng)前的代碼段以實現(xiàn)將該數(shù)據(jù)作為代碼來執(zhí)行的目的,這個動作稱為自修改代碼。IA-32處理器在執(zhí)行自修改代碼時采用特定模式的行為,具體依賴于被修改的代碼與當(dāng)前執(zhí)行位置之間的距離。由于處理器的體系結(jié)構(gòu)變得越來越復(fù)雜,而且可以在引退點(retirement point)之前推測性地執(zhí)行接下來的代碼(如:P4, Intel Xeon, P6系列處理器),如何判斷應(yīng)該執(zhí)行哪段代碼,是修改前地還是修改后的,就變得模糊不清。要想寫出于現(xiàn)在的和將來的IA-32體系相兼容的自修改代碼,必須選擇下面的兩種方式之一:
(方式1)
將代碼作為數(shù)據(jù)寫入代碼段;
跳轉(zhuǎn)到新的代碼位置或某個中間位置;
執(zhí)行新的代碼;
(方式2)
將代碼作為數(shù)據(jù)寫入代碼段;
執(zhí)行一條串行化指令;(如:CPUID指令)
執(zhí)行新的代碼;
(在Pentium或486處理器上運行的程序不需要以上面的方式書寫,但是為了與Pentium 4, Intel Xeon, P6系列處理器兼容,建議采用上面的方式。)
需要注意的是自修改代碼將會比非自修改代碼的運行效率要低。性能損失的程度依賴于修改的頻率以及代碼本身的特性。
--------------->交叉修改代碼(cross-modifying code)
處理器將數(shù)據(jù)寫入另外一個處理器的代碼段以使得哪個處理器將該數(shù)據(jù)作為代碼執(zhí)行,這稱為交叉修改代碼(cross-modifying code)。像自修改代碼一樣,IA-32處理器采用特定模式的行為執(zhí)行交叉修改代碼,具體依賴于被修改的代碼與當(dāng)前執(zhí)行位置之間的距離。要想寫出于現(xiàn)在的和將來的IA-32體系相兼容的自修改代碼,下面的處理器同步算法必須被實現(xiàn):
;修改的處理器
Memory_Flag ← 0; (* Set Memory_Flag to value other than 1 *)
將代碼作為數(shù)據(jù)寫入代碼段;
Memory_Flag ← 1;
;執(zhí)行的處理器
WHILE (Memory_Flag ≠ 1)
等待代碼更新;
ELIHW;
執(zhí)行串行化指令; (* 例如, CPUID instruction *)
開始執(zhí)行修改后的代碼;
(在Pentium或486處理器上運行的程序不需要以上面的方式書寫,但是為了與Pentium 4, Intel Xeon, P6系列處理器兼容,建議采用上面的方式。)
像自修改代碼一樣,交叉修改代碼將會比非交叉修改代碼的運行效率要低。性能損失的程度依賴于修改的頻率以及代碼本身的特性。
說明:作者讀到這里時,也是對自修改代碼和交叉修改代碼稍懂一點,再要深入,也備感艱難。
-------------------------------------------------------緩存加鎖--------------------------------------------
8.緩存加鎖
1)加鎖操作對處理器內(nèi)部緩存的影響:
(1)對于Intel486和Pentium處理器,在進行加鎖操作時,LOCK#信號總是在總線上發(fā)出,甚至鎖定的內(nèi)存區(qū)域已經(jīng)緩存在處理器cache中的時候,LOCK#信號也從總線上發(fā)出。
(2)對于Pentium 4, Intel Xeon,P6系列處理器,如果加鎖的內(nèi)存區(qū)域已經(jīng)緩存在處理器cache中,處理器可能并不對總線發(fā)出LOCK#信號,而是僅僅修改cache緩存中的數(shù)據(jù),然后依賴cache緩存一致性機制來保證加鎖操作的自動執(zhí)行。這個操作稱為"緩存加鎖"。緩存一致性機制會自動阻止兩個或多個緩存了同一區(qū)域內(nèi)存的處理器同時修改數(shù)據(jù)。
-----------------------------------------------訪存排序(memory ordering)-------- ---------------------
9.訪存排序(memory ordering)
(1)編程排序(program ordering):
訪存排序指的是處理器如何安排通過系統(tǒng)總線對系統(tǒng)內(nèi)存訪問的順序。IA-32體系支持幾種訪存排序模型,具體依賴于體系的實現(xiàn)。例如, Intel386處理器強制執(zhí)行"編程排序(program ordering)"(又稱為強排序),在任何情況下,訪存的順序與它們出現(xiàn)在代碼流中的順序一致。
(2)處理器排序(processor ordering):
為了允許代碼優(yōu)化,IA-32體系在Pentium 4, Intel Xeon,P6系列處理器中允許強排序之外的另外一種模型——處理器排序(processor ordering)。這種排序模型允許讀操作越過帶緩存的寫操作來提升性能。這個模型的目標是在多處理器系統(tǒng)中,在保持內(nèi)存一致性的前提下,提高指令執(zhí)行速度。
-----------------------------
10.Pentium和Intel 486處理器的訪存排序:
1)普遍情況:
Pentium和Intel 486處理器遵循處理器排序訪存模型;但是,在大多數(shù)情況下,訪存操作還是強排序,讀寫操作都是以編程時指定的順序出現(xiàn)在系統(tǒng)總線上。除了在下面的情況時,未命中的讀操作可以越過帶緩沖的寫操作:
--->當(dāng)所有的帶緩沖的寫操作都在cache緩存中命中,因此也就不會與未命中的讀操作訪問相同的內(nèi)存地址。
2)I/O操作訪存:
在執(zhí)行I/O操作時,讀操作和寫操作總是以編程時指定的順序執(zhí)行。在"處理器排序"處理器(例如,Pentium 4, Intel Xeon,P6系列處理器)上運行的軟件不能依賴Pentium或Intel486處理器的強排序。軟件應(yīng)該保證對共享變量的訪問能夠遵守編程順序,這種編程順序是通過使用加鎖或序列化指令來完成的。
3)Pentium 4, Intel Xeon, P6系列處理器的訪存排序
Pentium 4, Intel Xeon, P6系列處理器也是使用"處理器排序"的訪存模型,這種模型可以被進一步定義為"帶有存儲緩沖轉(zhuǎn)發(fā)的寫排序"(write ordered with store-buffer forwarding)。這種模型有下面的特點:
---------單處理器系統(tǒng)中的排序規(guī)則
(1)在一個單處理器系統(tǒng)中,對于定義為回寫可緩沖(write-back cacheable)的內(nèi)存區(qū)域,下面的排序規(guī)則將被應(yīng)用:
a.讀能夠被任意順序執(zhí)行。
b.讀可以越過緩沖寫,但是處理器必須保證數(shù)據(jù)完整性(self-consistent)。
c.對內(nèi)存的寫操作總是以編程順序執(zhí)行,除非寫操作執(zhí)行了CLFUSH指令以及利用非瞬時的移動指令(MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, MOVNTPD)來執(zhí)行流存儲操作(streamint stores)。
作者認為:CLFUSH--->CFLUSH,streamint--->streaming???是否原文有誤。
d.寫可以被緩沖。寫不能夠預(yù)先執(zhí)行;它們只能等到其他指令執(zhí)行完畢。
e.在處理器中,來自于緩沖寫的數(shù)據(jù)可以直接被發(fā)送到正在等待的讀操作。
f.讀寫操作都不能跨越I/O指令,加鎖指令,或者序列化指令。
g.讀操作不能越過LFENCE和MFENCE指令。
h.`寫操作不能越過SFECE和MFENCE指令。
第二條規(guī)則(b)允許一個讀操作越過寫操作。然而如果寫操作和讀操作都是訪問同一個內(nèi)存區(qū)域,那么處理器內(nèi)部的監(jiān)視機制將會檢測到?jīng)_突并且在處理器使用錯誤的數(shù)據(jù)執(zhí)行指令之前更新已經(jīng)緩存的讀操作。
第六條規(guī)則(f)構(gòu)成了一個例外,否則整個模型就是一個寫排序模型(write ordered model)。
注意"帶有存儲緩沖轉(zhuǎn)發(fā)的寫排序"(在本節(jié)開始的時候介紹)指的是第2條規(guī)則和第6條規(guī)則的組合之后產(chǎn)生的效果。
---------------多處理器系統(tǒng)中的排序規(guī)則
(2)在一個多處理器系統(tǒng)中,下面的排序規(guī)則將被應(yīng)用:
a.每個處理器使用同單處理器系統(tǒng)一樣的排序規(guī)則。
b.所有處理器所觀察到的某個處理器的寫操作順序是相同的。
c.每個處理器的寫操作并不與其它處理器之間進行排序。
例如:在一個三處理器的系統(tǒng)中,每個處理器執(zhí)行三個寫操作,分別對三個地址A, B,C。每個處理器以編程的順序執(zhí)行操作,但是由于總線仲裁和其他的內(nèi)存訪問機制,三個處理器執(zhí)行寫操作的順序可能每次都不相同。最終的A, B, C的值會因每次執(zhí)行的順序而改變。
-------------------
(3)本節(jié)介紹的處理器排序模型與Pentium Intel486處理器使用的模型是一樣的。唯一在Pentium 4, Intel Xeon,P6系列處理器中得到加強的是:
a.對于預(yù)先執(zhí)行讀操作的支持。
b.存儲緩沖轉(zhuǎn)發(fā),當(dāng)一個讀操作越過一個訪問相同地址的寫操作。
c.對于長串的存儲和移動的無次序操作(out-of-Order Stores)Pentium 4,
--------------------
(4)快速串:
Intel Xeon, P6處理器對于串操作的無次序存儲(Out-of-Order Stores)
Pentium 4, Intel
Xeon,P6處理器在進行串存儲的操作(以MOVS和STOS指令開始)時,修改了處理器的動作,以提升處理性能。一旦"快速串"的條件滿足了 (將在下面介紹),處理器將會在緩沖線(cache line)上以緩沖線模式進行操作。這會導(dǎo)致處理器在循環(huán)過程中發(fā)出對源地址的緩沖線讀請求,以及在外部總線上發(fā)出對目標地址的寫請求,并且已知了目標地址內(nèi)的數(shù)據(jù)串一定要被修改。在這種模式下,處理器僅僅在緩沖線邊界時才會相應(yīng)中斷。因此,目標數(shù)據(jù)的失效和存儲可能會以不規(guī)則的順序出現(xiàn)在外部總線上。
按順序存儲串的代碼不應(yīng)該使用串操作指令。數(shù)據(jù)和信號量應(yīng)該分開。依賴順序的代碼應(yīng)該在每次串操作時使用信號量來保證存儲數(shù)據(jù)的順序在所有處理器看來是一致的。
"快速串"的初始條件是:
在Pentium III 處理器中,EDI和ESI必須是8位對齊的。在Pentium4中,EDI必須是8位對齊的。
串操作必須是按地址增加的方向進行的。
初始操作計數(shù)器(ECX)必須大于等于64。
源和目的內(nèi)存的重合區(qū)域一定不能小于一個緩沖線的大小(Pentium 4和Intel Xeon 處理器是64字節(jié);P6 和Pentium處理器是 32字節(jié))。
源地址和目的地址的內(nèi)存類型必須是WB或WC。
----------------
11.加強和削弱訪存排序模型(Strengthening or Weakening the Memory Ordering Model)
IA-32體系提供了幾種機制用來加強和削弱訪存排序模型以處理特殊的編程場合。這些機制包括:
1)I/O指令,加鎖指令,LOCK前綴,以及序列化指令來強制執(zhí)行"強排序"。
2)SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon處理器中引入)提供了某些特殊類型內(nèi)存操作的排序和串行化功能。
3)內(nèi)存類型范圍寄存器(memory type range registers (MTRRs))可以被用來加強和削弱物理內(nèi)存中特定區(qū)域的訪存排序模型。MTRRs只存在于Pentium 4, Intel Xeon, P6系列處理器。
4)頁屬性表可以被用來加強某個頁或一組頁的訪存排序("頁屬性表"Page Attribute Table(PAT))。PAT只存在于Pentium 4, Intel Xeon,P6系列處理器。
這些機制可以通過下面的方式使用:
1)內(nèi)存映射和其他I/O設(shè)備通常對緩沖區(qū)寫操作的順序很敏感。I/O指令(IN,OUT)以下面的方式對這種訪問執(zhí)行強排序。在執(zhí)行一條I/O 指令之前,處理器等待之前的所有指令執(zhí)行完畢以及所有的緩沖區(qū)都被寫入了內(nèi)存。只有取指令操作和頁表查詢(page table walk)能夠越過I/O指令。后續(xù)指令要等到I/O指令執(zhí)行完畢才開始執(zhí)行。
2)一個多處理器的系統(tǒng)中的同步機制可能會依賴"強排序"模型。這里,一個程序使用加鎖指令,例如XCHG或者LOCK前綴,來保證讀-改-寫操作是自動進行的。加鎖操作像I/O指令一樣等待所有之前的指令執(zhí)行完畢以及緩沖區(qū)都被寫入了內(nèi)存。
3)程序同步可以通過序列化指令來實現(xiàn)。這些指令通常用于臨界過程或者任務(wù)邊界來保證之前所有的指令在跳轉(zhuǎn)到新的代碼區(qū)或上下文切換之前執(zhí)行完畢。像I/O加鎖指令一樣,處理器等待之前所有的指令執(zhí)行完畢以及所有的緩沖區(qū)寫入內(nèi)存后才開始執(zhí)行序列化指令。
4)SFENCE,LFENCE,MFENCE指令提供了高效的方式來保證讀寫內(nèi)存的排序,這種操作發(fā)生在產(chǎn)生弱排序數(shù)據(jù)的程序和讀取這個數(shù)據(jù)的程序之間。
SFENCE——串行化發(fā)生在SFENCE指令之前的寫操作但是不影響讀操作。
LFENCE——串行化發(fā)生在SFENCE指令之前的讀操作但是不影響寫操作。
MFENCE——串行化發(fā)生在MFENCE指令之前的讀寫操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更靈活有效的控制內(nèi)存排序的方式。
5)MTRRs在P6系列處理器中引入,用來定義物理內(nèi)存的特定區(qū)域的高速緩存特性。下面的兩個例子是利用MTRRs設(shè)置的內(nèi)存類型如何來加強和削弱Pentium 4, Intel Xeon, P6系列處理器的訪存排序:
(1)強不可緩沖(strong uncached,UC)內(nèi)存類型實行內(nèi)存訪問的強排序模型:
這里,所有對UC內(nèi)存區(qū)域的讀寫都出現(xiàn)在總線上,并且不能夠被亂序或預(yù)先執(zhí)行。這種內(nèi)存類型可以應(yīng)用于映射成I/O設(shè)備的內(nèi)存區(qū)域來強制執(zhí)行訪存強排序。
(2)對于可以容忍弱排序訪問的內(nèi)存區(qū)域,可以選擇回寫(write back, WB)內(nèi)存類型:
這里,讀操作可以預(yù)先的被執(zhí)行,寫操作可以被緩沖和組合(combined)。對于這種類型的內(nèi)存,鎖定高速緩存是通過一個加鎖的原子操作實現(xiàn)的,這個操作不會分割緩沖線,因此會減少典型的同步指令(如,XCHG在整個讀-改-寫操作周期要鎖定數(shù)據(jù)總線)所帶來的性能損失。對于WB內(nèi)存,如果訪問的數(shù)據(jù)已經(jīng)存在于緩存cache中,XCHG指令會鎖定高速緩存而不是數(shù)據(jù)總線。
(3)PAT在Pentium III中引入,用來增強用于存儲內(nèi)存頁的緩存性能。PAT機制通常被用來與MTRRs一起來加強頁級別的高速緩存性能。在Pentium 4, Intel Xeon,P6系列處理器上運行的軟件最好假定是 "處理器排序"模型或者是更弱的訪存排序模型。
Pentium 4, Intel Xeon,P6系列處理器沒有實現(xiàn)強訪存排序模型,除了對于UC內(nèi)存類型。盡管Pentium 4, Intel Xeon,P6系列處理器支持處理器排序模型,Intel并沒有保證將來的處理器會支持這種模型。為了使軟件兼容將來的處理器,操作系統(tǒng)最好提供臨界區(qū) (critical region)和資源控制構(gòu)建以及基于I/O,加鎖,序列化指令的API,用于同步多處理器系統(tǒng)對共享內(nèi)存區(qū)的訪問。同時,軟件不應(yīng)該依賴處理器排序模型,因為也許系統(tǒng)硬件不支持這種訪存模型。
(4)向多個處理器廣播頁表和頁目錄條目的改變:
在一個多處理器系統(tǒng)中,當(dāng)一個處理器改變了一個頁表或頁目錄的條目,這個改變必須要通知所有其它的處理器。這個過程通常稱為"TLB shootdown"。廣播頁表或頁目錄條目的改變可以通過基于內(nèi)存的信號量或者處理器間中斷(interprocessor interrupts, IPI)。
例如一個簡單的,但是算法上是正確的TLB shootdown序列可能是下面的樣子:
a.開始屏障(begin barrier)——除了一個處理器外停止所有處理器;讓他們執(zhí)行HALT指令或者空循環(huán)。
b.讓那個沒有停止的處理器改變PTE or PDE。
c.讓所有處理器在他們各自TLB中修改的PTE, PDE失效。
d.結(jié)束屏障(end barrier)——恢復(fù)所有的處理器執(zhí)行。
(5)串行化指令(serializing instructions):
IA-32體系定義了幾個串行化指令(SERIALIZING INSTRUCTIONS)。這些指令強制處理器完成先前指令對標志,寄存器以及內(nèi)存的修改,并且在執(zhí)行下一條指令之前將所有緩沖區(qū)里的數(shù)據(jù)寫入內(nèi)存。
===>串行化指令應(yīng)用一:開啟保護模式時的應(yīng)用
例如:當(dāng)MOV指令將一個操作數(shù)裝入CR0寄存器以開啟保護模式時,處理器必須在進入保護模式之前執(zhí)行一個串行化操作。這個串行化操作保證所有在實地址模式下開始執(zhí)行的指令在切換到保護模式之前都執(zhí)行完畢。
-------------
串行化指令的概念在Pentium處理器中被引入IA-32體系。這種指令對于Intel486或更早的處理器是沒有意義的,因為它們并沒有實現(xiàn)并行指令執(zhí)行。
非常值得注意的是,在Pentium 4, Intel Xeon,P6系列處理器上執(zhí)行串行化指令會抑制指令的預(yù)執(zhí)行(speculative execution),因為預(yù)執(zhí)行的結(jié)果會被放棄掉。
-------------
下面的指令是串行化指令:
1.--->特權(quán)串行化指令——MOV(目標操作數(shù)為控制寄存器),MOV(目標操作數(shù)為調(diào)試存器),WRMSR, INVD, INVLPG, WBINVD, LGDT, LLDT, LIDT, LTR。
-------------------------作者補充------------------------------
作者:如果上述指令不熟,可以參考《80X86匯編語言程序設(shè)計教程》楊季文編,清華大學(xué)出版社。下面作些簡單的介紹:以下作者對匯編指令的說明均參考引用了該書。
---->INVLPG指令:
使TLB(轉(zhuǎn)換后援緩沖器:用于存放最常使用的物理頁的頁碼)項無效。該指令是特權(quán)指令,只有在實方式和保護方式的特權(quán)級0下,才可執(zhí)行該指令。
---------------------------------------------------------------
2.--->非特權(quán)串行化指令——CPUID, IRET, RSM。
3.--->非特權(quán)訪存排序指令——SFENCE, LFENCE, MFENCE。
當(dāng)處理器執(zhí)行串行化指令的時候,它保證在執(zhí)行下一條指令之前,所有未完成的內(nèi)存事務(wù)都被完成,包括寫緩沖中的數(shù)據(jù)。任何指令不能越過串行化指令,串行化指令也不能越過其他指令(讀,寫, 取指令, I/O)。
CPUID指令可以在任何特權(quán)級下執(zhí)行串行化操作而不影響程序執(zhí)行流(program flow),除非EAX, EBX, ECX, EDX寄存器被修改了。
SFENCE,LFENCE,MFENCE指令為控制串行化讀寫內(nèi)存提供了更多的粒度。
在使用串行化指令時,最好注意下面的額外信息:
處理器在執(zhí)行串行化指令的時候并不將高速緩存中已經(jīng)被修改的數(shù)據(jù)寫回到內(nèi)存中。軟件可以通過WBINVD串行化指令強制修改的數(shù)據(jù)寫回到內(nèi)存中。但是頻繁的使用WVINVD(作者注:當(dāng)為WBINVD,原文此處有誤)指令會嚴重的降低系統(tǒng)的性能。
----------------作者補充:對WBINVAD的解釋-----------------------
----->INVD指令:
INVD指令使片上的高速緩存無效,即:清洗片上的超高速緩存。但該指令并不把片上的超高速緩存中的內(nèi)容寫回主存。該指令是特權(quán)指令,只有在實方式和保護方式的特權(quán)級0下,才可執(zhí)行該指令。
---->WBINVD指令:
WBINVD指令使片上的超高速緩存無效即:清洗片上的超高速緩存。但該指令將把片上的超高速緩存中更改的內(nèi)容寫回主存。該指令是特權(quán)指令,只有在實方式和保護方式的特權(quán)級0下,才可執(zhí)行該指令。
****************************************************************
===>串行化指令應(yīng)用二:改變了控制寄存器CR0的PG標志的應(yīng)用
當(dāng)一條會影響分頁設(shè)置(也就是改變了控制寄存器CR0的PG標志)的指令執(zhí)行時,這條指令后面應(yīng)該是一條跳轉(zhuǎn)指令。跳轉(zhuǎn)目標應(yīng)該以新的PG標志 (開啟或關(guān)閉分頁)來進行取指令操作,但跳轉(zhuǎn)指令本身還是按先前的設(shè)置執(zhí)行。Pentium 4, Intel Xeon,P6系列處理器不需要在設(shè)置CR0處理器之后放置跳轉(zhuǎn)指令(因為任何對CR0進行操作的MOV指令都是串行化的)。但是為了與其他IA-32處理器向前和向后兼容,最好是放置一條跳轉(zhuǎn)指令。
=========
作者說明:CR0的第31位為PG標志,PG=1:啟用分頁管理機制,此時線性地址經(jīng)過分頁管理機制后轉(zhuǎn)換為物理地址;PG=0:禁用分頁管理機制,此時線性地址直接作為物理地址使用。
****************************************************************
在允許分頁的情況下,當(dāng)一條指令會改變CR3的內(nèi)容時,下一條指令會根據(jù)新的CR3內(nèi)容所設(shè)置的轉(zhuǎn)換表進行取指令操作。因此下一條以及之后的指令應(yīng)該根據(jù)新的CR3內(nèi)容建立映射。
=========
作者說明:CR3用于保存頁目錄表的起始物理地址,由于目錄表是責(zé)對齊的,所以僅高20位有效,低12位無效。所以如果向CR3中裝入新值,其低 12位當(dāng)為0;每當(dāng)用mov指令重置CR3的值時候,TLB中的內(nèi)容會無效。CR3在實方式下也可以設(shè)置,以使分頁機制初始化。在任務(wù)切換時候,CR3要被改變。但要是新任務(wù)的CR3的值==舊任務(wù)的CR3的值,則TLB的內(nèi)容仍有效,不被刷新。
******************************************************************************
以上通過這篇文章資料對cpu的工作機制有了更深刻的了解,從而對我們的Linux Kernel的學(xué)習(xí)有極大的幫助。由此對加鎖,各類排序,串行化,sfence,mfence,lfence指令的出現(xiàn)有了清楚的認識。再回頭來讀讀源代碼有更深刻的認識。
*****************************************************************************
------------------------------------------smp_mb()---smp_rmb()---smp_wmb()-------------------------
#ifdef CONFIG_SMP
#define smp_mb() mb()
#define smp_rmb() rmb()
#define smp_wmb() wmb()
#define smp_read_barrier_depends() read_barrier_depends()
#define set_mb(var, value) do { xchg(&var, value); } while (0)
#else
#define smp_mb() barrier()
#define smp_rmb() barrier()
#define smp_wmb() barrier()
#define smp_read_barrier_depends() do { } while(0)
#define set_mb(var, value) do { var = value; barrier(); } while (0)
#endif
#define set_wmb(var, value) do { var = value; wmb(); } while (0)
-----------------------------------------------/linux/compiler-gcc.h--------------------------------------
------------------------------------------------------barrier()-------------------------------------------------
/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")
自己分析:
1.如果定義的了CONFIG_SMP,也就是系統(tǒng)為對稱多處理器系統(tǒng)。smp_mb(),smp_rmb(),smp_wmb()就是mb(),rmb(),wmb()。
由此可見,多處理器上的內(nèi)存屏障與單處理器原理一樣。
2.barrier()函數(shù)并無什么難點,與前面代碼一樣。
3.如果沒有定義CONFIG_SMP,則smp_mb(), smp_rmb(), smp_wmb(), smp_read_barrier_depends( 都是空宏。
**************************************************************************
在本文的代碼中有不少下劃線的關(guān)鍵字,特此作一研究:
--------------------------------------------------------雙下劃線的解釋--------------------------------------
--->摘自gcc手冊
Alternate Keywords ‘-ansi’ and the various ‘-std’ options disable certain keywords。 This causes trouble when you want to use GNU C extensions, or a general-purpose header file that should be usable by all programs, including ISO C programs。 The keywords asm, typeof and inline are not available in programs compiled with ‘-ansi’ or ‘-std’ (although inline can be used in a program compiled with ‘-std=c99’)。 The ISO C99 keyword restrict is only available when ‘-std=gnu99’ (which will eventually be the default) or ‘-std=c99’ (or the equivalent ‘-std=iso9899:1999’) is used。The way to solve these problems is to put ‘__’ at the beginning and end of each problematical keyword。 For example, use __asm__ instead of asm, and __inline__ instead of inline。
Other C compilers won’t accept these alternative keywords; if you want to compile with another compiler, you can define the alternate keywords as macros to replace them with the customary keywords。 It looks like this:
#ifndef __GNUC__
#define __asm__ asm
#endif
‘-pedantic’(pedantic選項解釋見下面) and other options cause warnings for many GNU C extensions。 You can prevent such warnings within one expression by writing __extension__ before the expression。__extension__ has no effect aside from this。
自己分析:
1。我們在程序中使用了很多的gnu風(fēng)格,也就是GNU C extensions 或其他的通用的頭文件。但是如果程序用'-ansi'或各種'-std'選項編譯時候,一些關(guān)鍵字,比如:asm、typeof、inline就不能再用了,在這個編譯選項下,這此關(guān)鍵字被關(guān)閉。所以用有雙下劃線的關(guān)鍵字,如:__asm__、__typeof__、__inline__,這些編譯器通常支持這些帶有雙下劃線的宏。這能替換這些會產(chǎn)生編譯問題的關(guān)鍵字,使程序能正常通過編譯。
2。如果是用其他的編譯器,可能不認這些帶有雙下劃線的宏,就用以下宏來轉(zhuǎn)換:
#ifndef __GNUC__
#define __asm__ asm
#endif
這樣的話,這些其他的編譯器沒有定義__GUNUC__,也不支持__asm__,__inline__,__typeof__等宏,所以必會,執(zhí)行#define __asm__ asm等。這樣,用__asm__,__inline__,__typeof__所編寫的程序代碼,仍能宏展開為asm,inline,typeof,而這此關(guān)鍵字這些其他的編譯器支持。所以程序能正常編譯。
-----------------------------------------------pedantic選項的解釋----------------------------------
--->摘自gcc手冊Download from www。gnu。org
Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions, and some other programs that do not follow ISO C and ISO C++。 For ISO C, follows the version of the ISO C standard specified by any ‘-std’ option used。 Valid ISO C and ISO C++ programs should compile properly with or without this option (though a rare few will require ‘-ansi’ or a ‘-std’ option specifying the required version of ISO C)。 However, without this option, certain GNU extensions and traditional C and C++ features are supported as well。 With this
option, they are rejected。 ‘-pedantic’ does not cause warning messages for use of the alternate keywords whose names begin and end with ‘__’。 Pedantic warnings are also disabled in the expression that follows __extension__。 However, only system header files should use these escape routes; application programs should avoid them。 See Section 5。38 [Alternate Keywords], page 271。
Some users try to use ‘-pedantic’ to check programs for strict ISO C conformance。They soon find that it does not do quite what they want: it finds some non-ISO practices, but not all—only those for which ISO C requires a diagnostic, and some others for which diagnostics have been added。 A feature to report any failure to conform to ISO C might be useful in some instances, but would require considerable additional work and would be quite different from ‘-pedantic’。 We don’t have plans to support such a feature in the near future。
-----------------------------------------------------------------------------------------------------------------