存屏障機(jī)制及內(nèi)核相關(guān)源代碼分析
分析人:余旭
分析版本:Linux Kernel 2.6.14 來自于:www.kernel.org
分析開始時(shí)間:2005-11-17-20:45:56
分析結(jié)束時(shí)間:2005-11-21-20:07:32
編號(hào):2-1 類別:進(jìn)程管理-準(zhǔn)備工作1-內(nèi)存屏障
Email:yuxu9710108@163.com
版權(quán)聲明:版權(quán)保留。本文用作其他用途當(dāng)經(jīng)作者本人同意,轉(zhuǎn)載請(qǐng)注明作者姓名
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中常要遇到的問題,這里專門來對(duì)其進(jìn)行研究。一者查閱網(wǎng)上現(xiàn)有資料,進(jìn)行整理匯集;二者翻閱Linux內(nèi)核方面的指導(dǎo)書,從中提煉觀點(diǎn);最后,自己加以綜合分析,提出自己的看法。下面將對(duì)個(gè)問題進(jìn)行專題分析。
*****************************************************************************
------------------------------------------------------ 專題研究:內(nèi)存屏障--------------------------------
---------------------------------------------------------論壇眾人資料匯集分析---------------------------
set_current_state(),__set_current_state(),set_task_state(),__set_task_state(),rmb(),wmb(),mb()的源代碼中的相關(guān)疑難問題及眾人的論壇觀點(diǎ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)前進(jìn)程狀態(tài)的代碼,但我搞不清楚這兩種用法的不同?有哪位大蝦指點(diǎn)一二,必將感謝不盡!
------------------
2.---> chyyuu(chenyu-tmlinux@hpclab.cs.tsinghua.edu.cn) 在www.linuxforum.net的Linux內(nèi)核技術(shù)上發(fā)貼問:
在kernel.h中有一個(gè)define
/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")
在內(nèi)核許多地方被調(diào)用,不知到底是生成什么匯編指令????
請(qǐng)教!!!
--------------------
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的時(shí)候發(fā)現(xiàn)有一個(gè)古怪的函數(shù),就是barrier,俺愣是不知道它是干嘛用的,幫幫我這菜鳥吧,感謝感謝!
還有就是下面這句中的("":::"memory")是什么意思呀,我苦!
# define barrier() _asm__volatile_("": : :"memory")
***********************************眾人的觀點(diǎn)*******************************
ANSWER:
1.jkl Reply:這就是所謂的內(nèi)存屏障,前段時(shí)間曾經(jīng)討論過。CPU越過內(nèi)存屏障后,將刷新自已對(duì)存儲(chǔ)器的緩沖狀態(tài)。這條語句實(shí)際上不生成任何代碼,但可使gcc在barrier()之后刷新寄存器對(duì)變量的分配。
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()帶有一個(gè)memory barrier,__set_task_state()則沒有,當(dāng)狀態(tài)state是RUNNING時(shí),因?yàn)閟cheduler可能訪問這個(gè)state,因此此時(shí)要變成其他狀態(tài)(如INTERRUPTIBLE),就要用set_task_state()而當(dāng)state不是RUNNING時(shí),因?yàn)闆]有其他人會(huì)訪問這個(gè)state,因此可以用__set_task_state()反正用set_task_state()肯定是安全的,但 __set_task_state()可能會(huì)快些。
自己分析:
wheelz講解很清楚,尤其是指出了__set_task_state()速度會(huì)快于set_task_state()。這一點(diǎn),很多貼子忽略了,這里有獨(dú)到之處。在此,作者專門強(qiáng)調(diào)之。
3.自己分析:
1)set_mb(),mb(),barrier()函數(shù)追蹤到底,就是__asm__ __volatile__("":::"memory"),而這行代碼就是內(nèi)存屏障。
2)__asm__用于指示編譯器在此插入?yún)R編語句
3)__volatile__用于告訴編譯器,嚴(yán)禁將此處的匯編語句與其它的語句重組合優(yōu)化。即:原原本本按原來的樣子處理這這里的匯編。
4)memory強(qiáng)制gcc編譯器假設(shè)RAM所有內(nèi)存單元均被匯編指令修改,這樣cpu中的registers和cache中已緩存的內(nèi)存單元中的數(shù)據(jù)將作廢。cpu將不得不在需要的時(shí)候重新讀取內(nèi)存中的數(shù)據(jù)。這就阻止了cpu又將registers,cache中的數(shù)據(jù)用于去優(yōu)化指令,而避免去訪問內(nèi)存。
5)"":::表示這是個(gè)空指令。barrier()不用在此插入一條串行化匯編指令。在后文將討論什么叫串行化指令。
6)__asm__,__volatile__,memory在前面已經(jīng)解釋
7)lock前綴表示將后面這句匯編語句:"addl $0,0(%%esp)"作為cpu的一個(gè)內(nèi)存屏障。
8)addl $0,0(%%esp)表示將數(shù)值0加到esp寄存器中,而該寄存器指向棧頂?shù)膬?nèi)存單元。加上一個(gè)0,esp寄存器的數(shù)值依然不變。即這是一條無用的匯編指令。在此利用這條無價(jià)值的匯編指令來配合lock指令,在__asm__,__volatile__,memory的作用下,用作cpu的內(nèi)存屏障。
9)set_current_state()和__set_current_state()區(qū)別就不難看出。
10)至于barrier()就很易懂了。
11)作者注明:作者在回答這個(gè)問題時(shí)候,參考了《深入理解LINUX內(nèi)核》一書,陳莉君譯,中國電力出版社,P174
4.xshell 發(fā)貼指出:
#include <asm/system.h>
"void rmb(void);"
"void wmb(void);"
"void mb(void);"
這些函數(shù)在已編譯的指令流中插入硬件內(nèi)存屏障;具體的插入方法是平臺(tái)相關(guān)的。rmb(讀內(nèi)存屏障)保證了屏障之前的讀操作一定會(huì)在后來的讀操作執(zhí)行之前完成。wmb 保證寫操作不會(huì)亂序,mb 指令保證了兩者都不會(huì)。這些函數(shù)都是 barrier函數(shù)的超集。解釋一下:編譯器或現(xiàn)在的處理器常會(huì)自作聰明地對(duì)指令序列進(jìn)行一些處理,比如數(shù)據(jù)緩存,讀寫指令亂序執(zhí)行等等。如果優(yōu)化對(duì)象是普通內(nèi)存,那么一般會(huì)提升性能而且不會(huì)產(chǎn)生邏輯錯(cuò)誤。但如果對(duì)I/O操作進(jìn)行類似優(yōu)化很可能造成致命錯(cuò)誤。所以要使用內(nèi)存屏障,以強(qiáng)制該語句前后的指令以正確的次序完成。其實(shí)在指令序列中放一個(gè)wmb的效果是使得指令執(zhí)行到該處時(shí),把所有緩存的數(shù)據(jù)寫到該寫的地方,同時(shí)使得wmb前面的寫指令一定會(huì)在wmb的寫指令之前執(zhí)行。
5.Nazarite發(fā)貼指出:
__volatitle__是防止編譯器移動(dòng)該指令的位置或者把它優(yōu)化掉。"memory",是提示編譯器該指令對(duì)內(nèi)存修改,防止使用某個(gè)寄存器中已經(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前綴會(huì)在執(zhí)行這條指令前先去拉這根pin,持續(xù)到這個(gè)指令結(jié)束時(shí)放開#HLOCK pin,在這期間,北橋會(huì)屏蔽掉一切外設(shè)以及AGP的內(nèi)存操作。也就保證了這條指令的atomic。
7.coldwind 發(fā)貼指出:
"memory",是提示編譯器該指令對(duì)內(nèi)存修改,防止使用某個(gè)寄存器中已經(jīng)load的內(nèi)存的值,應(yīng)該是告訴CPU內(nèi)存已經(jīng)被修改過,讓CPU invalidate所有的cache。
通過以上眾人的貼子的分析,自己綜合一下,這4個(gè)宏set_current_state(),__set_current_state(), set_task_state(),__set_task_state()和3個(gè)函數(shù)rmb(),wmb(),mb()的源代碼中的疑難大都被解決。此處只是匯集眾人精彩觀點(diǎn),只用來解決代碼中的疑難,具體有序系統(tǒng)的源代碼將在后面給出。
--------------------------------------------------------------------------------------------------------------
mfence,mb(),wmb(),OOPS的疑難問題的突破
--------------------------------------------------------------------------------------------------------------
1.--->puppy love (zhou_ict@hotmail.com )在www.linuxforum.net CPU 與 編譯器 問: 在linux核心當(dāng)中, mb(x86-64)的實(shí)現(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的手冊(cè)。mfence保證系統(tǒng)在后面的memory訪問之前,先前的memory訪問都已經(jīng)結(jié)束。由于這條指令可能引起memory任意地址上內(nèi)容的改變,所以需要用“memory” clobber告訴gcc這一點(diǎn)。這樣gcc就需要重新從memory中l(wèi)oad寄存器來保證同一變量在寄存器和memory中的內(nèi)容一致。
------------------
3.cool_bird Reply:
內(nèi)存屏障
MB(memory barrier,內(nèi)存屏障) :x86采用PC(處理機(jī))內(nèi)存一致性模型,使用MB強(qiáng)加的嚴(yán)格的CPU內(nèi)存事件次序,保證程序的執(zhí)行看上去象是遵循順序一致性(SC)模型,當(dāng)然,即使對(duì)于UP,由于內(nèi)存和設(shè)備見仍有一致性問題,這些Mb也是必須的。在當(dāng)前的實(shí)現(xiàn)中,wmb()實(shí)際上是一個(gè)空操作,這是因?yàn)槟壳癐ntel的CPU系列都遵循“處理機(jī)一致性”,所有的寫操作是遵循程序序的,不會(huì)越過前面的讀寫操作。但是,由于Intel CPU系列可能會(huì)在將來采用更弱的內(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實(shí)際上也是內(nèi)存屏障。
include/linux/kernel.h:
#define barrier() __asm__ __volatile__("": : :"memory")
內(nèi)存屏障也是一種避免鎖的技術(shù)。
它在進(jìn)程上下文中將一個(gè)元素插入一個(gè)單向鏈表:
new->next=i->next;
wmb();
i->next=new;
同時(shí),如果不加鎖地遍歷這個(gè)單向鏈表。或者在遍歷鏈表時(shí)已經(jīng)可以看到new,或者new還不在該鏈表中。Alan Cox書寫這段代碼時(shí)就注意到了這一點(diǎn),兩個(gè)內(nèi)存寫事件的順序必須按照程序順序進(jìn)行。否則可能new的next指針將指向一個(gè)無效地址,就很可能出現(xiàn) OOPS!
不論是gcc編譯器的優(yōu)化還是處理器本身采用的大量優(yōu)化,如Write buffer, Lock-up free, Non-blocking reading, Register allocation, Dynamic scheduling, Multiple issues等,都可能使得實(shí)際執(zhí)行可能違反程序序,因此,引入wmb內(nèi)存屏障來保證兩個(gè)寫事件的執(zhí)行次序嚴(yán)格按程序順序來執(zhí)行。
作者說明:原貼子不太清楚,作者作了必要的調(diào)整。
**************************************************************************
作者讀到這里,不懂OOPS便又上網(wǎng)查找OOPS的資料學(xué)習(xí)如下,以期望搞懂OOPS后能更好的理解上面這段話。
------------------------------------------OOPS解釋--------------------------------------------------
1.網(wǎng)上第一個(gè)貼子:
--->殊途同歸 發(fā)表于 2005-7-26 16:40:00 :掌握 Linux 調(diào)試技術(shù) 來自中國教育人博客:www.blog.edu.cn/index.html
Oops 分析
Oops(也稱panic,慌張)消息包含系統(tǒng)錯(cuò)誤的細(xì)節(jié),如CPU寄存器的內(nèi)容。在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時(shí)發(fā)送到系統(tǒng)控制臺(tái)的 Oops消息。一旦您掌握了細(xì)節(jié),就可以將消息發(fā)送到ksymoops實(shí)用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號(hào)。在很多情況下,這些信息就足夠您確定錯(cuò)誤的可能原因是什么了。請(qǐng)注意,Oops 消息并不包括核心文件。
2.網(wǎng)上第二個(gè)貼子:
--->www.plinux.org自由飛鴿 上的貼子:System.map文件的作用 作者:趙炯
gohigh@sh163.net
作者說明:
1.OOPS和System.map文件密切相關(guān)。所以要研討System.map文件。
2.本作者對(duì)所引用的文章內(nèi)容進(jìn)行了整理,刪除了一些次要的部分,插入了一些內(nèi)容,使文章更清晰。再者對(duì)一些內(nèi)容進(jìn)行了擴(kuò)展說明。
--->符號(hào)表:
1.什么是符號(hào)(Symbols)?
在編程中,一個(gè)符號(hào)(symbol)是一個(gè)程序的創(chuàng)建塊:它是一個(gè)變量名或一個(gè)函數(shù)名。如你自己編制的程序一樣,內(nèi)核具有各種符號(hào)也是不應(yīng)該感到驚奇的。當(dāng)然,區(qū)別在 于內(nèi)核是一非常復(fù)雜的代碼塊,并且含有許多、許多的全局符號(hào)。
2.內(nèi)核符號(hào)表(Kernel Symbol Table)是什么東西?
內(nèi)核并不使用符號(hào)名。它是通過變量或函數(shù)的地址(指針)來使用變量或函數(shù)的,而 不是使用size_t BytesRead,內(nèi)核更喜歡使用(例如)c0343f20來引用這個(gè)變量。
而另一方面,人們并不喜歡象c0343f20這樣的名字。我們跟喜歡使用象 size_t BytesRead這樣的表示。通常,這并不會(huì)帶來什么問題。內(nèi)核主要是用C語言寫成的,所以在我們編程時(shí)編譯器/連接程序允許我們使用符號(hào)名,并且使內(nèi)核在運(yùn)行時(shí)使用地址表示。這樣大家都滿意了。
然而,存在一種情況,此時(shí)我們需要知道一個(gè)符號(hào)的地址(或者一個(gè)地址對(duì)應(yīng)的 符號(hào))。這是通過符號(hào)表來做到的,與gdb能夠從一個(gè)地址給出函數(shù)名(或者給出一個(gè)函數(shù)名的地址)的情況很相似。符號(hào)表是所有符號(hào)及其對(duì)應(yīng)地址的一個(gè)列表。這里是 一個(gè)符號(hào)表例子:
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文件?
有兩個(gè)文件是用作符號(hào)表的:
/proc/ksyms
System.map
這里,你現(xiàn)在可以知道System.map文件是干什么用的了。每當(dāng)你編譯一個(gè)新內(nèi)核時(shí),各種符號(hào)名的地址定會(huì)變化。
/proc/ksyms 是一個(gè) "proc文件" 并且是在內(nèi)核啟動(dòng)時(shí)創(chuàng)建的。實(shí)際上它不是一個(gè)真實(shí)的文件;它只是內(nèi)核數(shù)據(jù)的簡單表示形式,呈現(xiàn)出象一個(gè)磁盤文件似的。如果你不相信我,那么就試試找出/proc/ksyms的文件大小來。因此, 對(duì)于當(dāng)前運(yùn)行的內(nèi)核來說,它總是正確的..
然而,System.map卻是文件系統(tǒng)上的一個(gè)真實(shí)文件。當(dāng)你編譯一個(gè)新內(nèi)核時(shí),你原來的System.map中的符號(hào)信息就不正確了。隨著每次內(nèi)核的編譯,就會(huì)產(chǎn)生一個(gè)新的 System.map文件,并且需要用該文件取代原來的文件。
--->OOPS:
1.什么是一個(gè)Oops?
在自己編制的程序中最常見的出錯(cuò)情況是什么?是段出錯(cuò)(segfault),信號(hào)11。
Linux內(nèi)核中最常見的bug是什么?也是段出錯(cuò)。除此,正如你想象的那樣,段出錯(cuò)的問題是非常復(fù)雜的,而且也是非常嚴(yán)重的。當(dāng)內(nèi)核引用了一個(gè)無效指針時(shí),并不稱其為段出錯(cuò) -- 而被稱為"oops"。一個(gè)oops表明內(nèi)核存在一個(gè)bug,應(yīng)該總是提出報(bào)告并修正該bug。
2.OOPS與段違例錯(cuò)的比較:
請(qǐng)注意,一個(gè)oops與一個(gè)段出錯(cuò)并不是一回事。你的程序并不能從段出錯(cuò)中恢復(fù) 過來,當(dāng)出現(xiàn)一個(gè)oops時(shí),并不意味著內(nèi)核肯定處于不穩(wěn)定的狀態(tài)。Linux內(nèi)核是非常健壯的;一個(gè)oops可能僅殺死了當(dāng)前進(jìn)程,并使余下的內(nèi)核處于一個(gè)良好的、穩(wěn)定的狀態(tài)。
3.OOPS與panic的比較:
一個(gè)oops并非是內(nèi)核死循環(huán)(panic)。在內(nèi)核調(diào)用了panic()函數(shù)后,內(nèi)核就不能繼續(xù)運(yùn)行了;此時(shí)系統(tǒng)就處于停頓狀態(tài)并且必須重啟。如果系統(tǒng)中關(guān)鍵部分遭到破壞那么一個(gè)oops也可能會(huì)導(dǎo)致內(nèi)核進(jìn)入死循環(huán)(panic)。例如,設(shè)備驅(qū)動(dòng)程序中 出現(xiàn)的oops就幾乎不會(huì)導(dǎo)致系統(tǒng)進(jìn)行死循環(huán)。
當(dāng)出現(xiàn)一個(gè)oops時(shí),系統(tǒng)就會(huì)顯示出用于調(diào)試問題的相關(guān)信息,比如所有CPU寄存器中的內(nèi)容以及頁描述符表的位置等,尤其會(huì)象下面那樣打印出EIP(指令指針)的內(nèi)容:
EIP: 0010:[<00000000>]
Call Trace: []
4.一個(gè)Oops與System.map文件有什么關(guān)系呢?
我想你也會(huì)認(rèn)為EIP和Call Trace所給出的信息并不多,但是重要的是,對(duì)于內(nèi)核開發(fā)人員來說這些信息也是不夠的。由于一個(gè)符號(hào)并沒有固定的地址, c010b860可以指向任何地方。
為了幫助我們使用oops含糊的輸出,Linux使用了一個(gè)稱為klogd(內(nèi)核日志后臺(tái)程序)的后臺(tái)程序,klogd會(huì)截取內(nèi)核oops并且使用syslogd將其記錄下來,并將某些象c010b860信息轉(zhuǎn)換成我們可以識(shí)別和使用的信息。換句話說,klogd是一個(gè)內(nèi)核消息記錄器 (logger),它可以進(jìn)行名字-地址之間的解析。一旦klogd開始轉(zhuǎn)換內(nèi)核消息,它就使用手頭的記錄器,將整個(gè)系統(tǒng)的消息記錄下來,通常是使用 syslogd記錄器。
為了進(jìn)行名字-地址解析,klogd就要用到System.map文件。我想你現(xiàn)在知道一個(gè)oops與System.map的關(guān)系了。
---------------------
作者補(bǔ)充圖:
System.map文件
^
|
|
syslogd記錄------->klogd解析名字-地址
^
|
|
內(nèi)核出錯(cuò)----->OOPS
-----------------------
深入說明: klogd會(huì)執(zhí)行兩類地址解析活動(dòng):
1.靜態(tài)轉(zhuǎn)換,將使用System.map文件。 所以得知System.map文件只用于名字-地址的靜態(tài)轉(zhuǎn)換。
2.Klogd動(dòng)態(tài)轉(zhuǎn)換
動(dòng)態(tài)轉(zhuǎn)換,該方式用于可加載模塊,不使用System.map,因此與本討論沒有關(guān)系,但我仍然對(duì)其加以簡單說明。假設(shè)你加載了一個(gè)產(chǎn)生oops 的內(nèi)核模塊。于是就會(huì)產(chǎn)生一個(gè)oops消息,klogd就會(huì)截獲它,并發(fā)現(xiàn)該oops發(fā)生在d00cf810處。由于該地址屬于動(dòng)態(tài)加載模塊,因此在 System.map文件中沒有對(duì)應(yīng)條目。klogd將會(huì)在其中尋找并會(huì)毫無所獲,于是斷定是一個(gè)可加載模塊產(chǎn)生了oops。此時(shí)klogd就會(huì)向內(nèi)核查詢?cè)摽杉虞d模塊輸出的符號(hào)。即使該模塊的編制者沒有輸出其符號(hào),klogd也起碼會(huì)知道是哪個(gè)模塊產(chǎn)生了oops,這總比對(duì)一個(gè)oops一無所知要好。
還有其它的軟件會(huì)使用System.map,我將在后面作一說明。
--------------
System.map應(yīng)該位于什么地方?
System.map應(yīng)該位于使用它的軟件能夠?qū)ふ业降牡胤剑簿褪钦f,klogd會(huì)在什么地方尋找它。在系統(tǒng)啟動(dòng)時(shí),如果沒有以一個(gè)參數(shù)的形式為klogd給出System.map的位置,則klogd將會(huì)在三個(gè)地方搜尋System.map。依次為:
/boot/System.map
/System.map
/usr/src/linux/System.map
System.map 同樣也含有版本信息,并且klogd能夠智能化地搜索正確的map文件。例如,假設(shè)你正在運(yùn)行內(nèi)核2.4.18并且相應(yīng)的map文件位于 /boot/System.map。現(xiàn)在你在目錄/usr/src/linux中編譯一個(gè)新內(nèi)核2.5.1。在編譯期間,文件 /usr/src/linux/System.map就會(huì)被創(chuàng)建。當(dāng)你啟動(dòng)該新內(nèi)核時(shí),klogd將首先查詢/boot/System.map,確認(rèn)它不是啟動(dòng)內(nèi)核正確的map文件,就會(huì)查詢/usr/src/linux/System.map, 確定該文件是啟動(dòng)內(nèi)核正確的map文件并開始讀取其中的符號(hào)信息。
幾個(gè)注意點(diǎn):
1.klogd未公開的特性:
在2.5.x系列內(nèi)核的某個(gè)版本,Linux內(nèi)核會(huì)開始untar成linux-version,而非只是linux(請(qǐng)舉手表決--有多少人一直等待著這樣做?)。我不知道klogd是否已經(jīng)修改為在/usr/src/linux-version/System.map中搜索。TODO:查看 klogd源代碼。
在線手冊(cè)上對(duì)此也沒有完整描述,請(qǐng)看:
# strace -f /sbin/klogd | grep 'System.map'
31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2
顯然,不僅klogd在三個(gè)搜索目錄中尋找正確版本的map文件,klogd也同樣知道尋找名字為 "System.map" 后加"-內(nèi)核版本",象 System.map-2.4.18. 這是klogd未公開的特性。
2.驅(qū)動(dòng)程序與System.map文件的關(guān)系:
有一些驅(qū)動(dòng)程序?qū)⑹褂肧ystem.map來解析符號(hào)(因?yàn)樗鼈兣c內(nèi)核頭連接而非glibc庫等),如果沒有System.map文件,它們將不能正確地工作。這與一個(gè)模塊由于內(nèi)核版本不匹配而沒有得到加載是兩碼事。模塊加載是與內(nèi)核版本有關(guān),而與即使是同一版本內(nèi)核其符號(hào)表也會(huì)變化的編譯后內(nèi)核無關(guān)。
3.還有誰使用了System.map?
不要認(rèn)為System.map文件僅對(duì)內(nèi)核oops有用。盡管內(nèi)核本身實(shí)際上不使用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,需要有一個(gè)正確的System.map文件。
4.如果我沒有一個(gè)好的System.map,會(huì)發(fā)生什么問題?
假設(shè)你在同一臺(tái)機(jī)器上有多個(gè)內(nèi)核。則每個(gè)內(nèi)核都需要一個(gè)獨(dú)立的System.map文件!如果所啟動(dòng)的內(nèi)核沒有對(duì)應(yīng)的System.map文件,那么你將定期地看到這樣一條信息:
System.map does not match actual kernel (System.map與實(shí)際內(nèi)核不匹配)
不是一個(gè)致命錯(cuò)誤,但是每當(dāng)你執(zhí)行ps ax時(shí)都會(huì)惱人地出現(xiàn)。有些軟件,比如dosemu,可能不會(huì)正常工作。最后,當(dāng)出現(xiàn)一個(gè)內(nèi)核oops時(shí),klogd或ksymoops的輸出可能會(huì)不可靠。
5.我如何對(duì)上述情況進(jìn)行補(bǔ)救?
方法是將你所有的System.map文件放在目錄/boot下,并使用內(nèi)核版本號(hào)重新對(duì)它們進(jìn)行命名。
5-1.假設(shè)你有以下多個(gè)內(nèi)核:
/boot/vmlinuz-2.2.14
/boot/vmlinuz-2.2.13
那么,只需對(duì)應(yīng)各內(nèi)核版本對(duì)map文件進(jìn)行改名,并放在/boot下,如:
/boot/System.map-2.2.14
/boot/System.map-2.2.13
5-2.如果你有同一個(gè)內(nèi)核的兩個(gè)拷貝怎么辦?
例如:
/boot/vmlinuz-2.2.14
/boot/vmlinuz-2.2.14.nosound
最佳解決方案將是所有軟件能夠查找下列文件:
/boot/System.map-2.2.14
/boot/System.map-2.2.14.nosound
但是說實(shí)在的,我并不知道這是否是最佳情況。我曾經(jīng)見到搜尋"System.map-kernelversion",但是對(duì)于搜索 "System.map-kernelversion.othertext"的情況呢?我不太清楚。此時(shí)我所能做的就是利用這樣一個(gè)事實(shí): /usr/src/linux是標(biāo)準(zhǔn)map文件的搜索路徑,所以你的map文件將放在:
/boot/System.map-2.2.14
/usr/src/linux/System.map (對(duì)于nosound版本)
你也可以使用符號(hào)連接:
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)有了較深刻的認(rèn)識(shí)。回過頭來繼續(xù)對(duì)內(nèi)存屏障的學(xué)習(xí)。
******************************************************************************
4.www.21icbbs.com上的貼子
為了防止編譯器對(duì)有特定時(shí)續(xù)要求的的硬件操作進(jìn)行優(yōu)化,系統(tǒng)提供了相應(yīng)的辦法:
1,對(duì)于由于數(shù)據(jù)緩沖(比如延時(shí)讀寫,CACHE)所引起的問題,可以把相應(yīng)的I/O區(qū)設(shè)成禁用緩沖。
2,對(duì)于編譯優(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.
*/
自己分析認(rèn)為:
1.Intel CPU 有嚴(yán)格的“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,開始對(duì)其進(jìn)行一一的全面的分析:
-------------------------------------------/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,對(duì)wmb()保證其的執(zhí)行順序按照程序順序執(zhí)行,所以wmb()定義為空操作。如果是對(duì)于對(duì)于非intel的cpu,這時(shí)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. */
作者附注:(對(duì)內(nèi)核注釋中的名詞的解釋)
-->OOO:Out of Order,亂序執(zhí)行。
-->SSE:SSE是英特爾提出的即MMX之后新一代(當(dāng)然是幾年前了)CPU指令集,最早應(yīng)用在PIII系列CPU上。
本小段內(nèi)核注釋意即:亂序存儲(chǔ)的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的一個(gè)內(nèi)存屏障。addl $0,0(%%esp)表示將數(shù)值0加到esp寄存器中,而該寄存器指向棧頂?shù)膬?nèi)存單元。加上一個(gè)0,esp寄存器的數(shù)值依然不變。即這是一條無用的匯編指令。在此利用這條無價(jià)值的匯編指令來配合lock指令,用作cpu的內(nèi)存屏障。
2.mfence保證系統(tǒng)在后面的memory訪問之前,先前的memory訪問都已經(jīng)結(jié)束。這是mfence是X86cpu家族中的新指令。詳見后面。
3.新舊指令對(duì)比:
-------------------------------
以前的源代碼:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
__asm__用于指示編譯器在此插入?yún)R編語句
__volatile__用于告訴編譯器,嚴(yán)禁將此處的匯編語句與其它的語句重組合優(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ù)的程序和讀取這個(gè)數(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指令后的讀寫操作前完成。
其實(shí)這里是用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平臺(tái)上所設(shè)計(jì)的,借助于編譯器__asm__,__volatile__,lock這些指令來實(shí)現(xiàn)內(nèi)存屏障。而在 Pentium 4和Intel Xeon處理器中由于已經(jīng)引入了mfence指令,無須再用這一套指令,直接調(diào)用這一條指令即ok。而alternative()宏就是用于這個(gè)優(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 */
************************************************************************
下面對(duì)SIMD進(jìn)行解釋:
--------------《計(jì)算機(jī)系統(tǒng)結(jié)構(gòu)》--鄭緯民編--清華大學(xué)出版社---------
1).指令流:(instruction stream)機(jī)器執(zhí)行的指令序列
2).數(shù)據(jù)流:(data stream)指令調(diào)用的數(shù)據(jù)序列,包括輸入數(shù)據(jù)和中間結(jié)果。
3)Flynn分類法:
(1)SISD(Single Instrution stream Single Datastream)
單指令流單數(shù)據(jù)流,對(duì)應(yīng)為傳統(tǒng)的順序處理計(jì)算機(jī)。
(2)SIMD(Single Instrution stream Multiple Datastream)
單指令流多數(shù)據(jù)流,對(duì)應(yīng)陣列處理機(jī)或并行處理機(jī)。
(3)MISD(Multiple Instrution stream Single Datastream)
多指令流單數(shù)據(jù)流,對(duì)應(yīng)流水線處理機(jī)。
(4)MIMD(Multiple Instrution stream Multiple Datastream)
多指令流多數(shù)據(jù)流,對(duì)應(yīng)多處理機(jī)。
*************************************************************************
由于以上幾個(gè)指令牽涉到多處理器的管理,要徹底弄懂這些代碼的原理,必須深入挖掘之,既然遇到了,就一口氣吃掉。追根問底,清楚其來龍去脈。
***********************************************************************
----->來自Baidu快照,原網(wǎng)頁打不開了:多處理器管理
說明:作者對(duì)此文進(jìn)行了參考,由于文章太長,太專業(yè)化,作者對(duì)其進(jìn)行了改動(dòng)處理:
------------------------------------------------------------------------------------------------
1.IA-32體系的機(jī)制:總線加鎖、cache一致性管理、串行化指令、高級(jí)可編程中斷控制器、二級(jí)緩存、超線程技術(shù):IA-32體系提供了幾種機(jī)制來管理和提升連接到同一系統(tǒng)總線的多個(gè)處理器的性能。這些機(jī)制包括:
1)總線加鎖、cache一致性管理以實(shí)現(xiàn)對(duì)系統(tǒng)內(nèi)存的原子操作、串行化指令(serializing instructions。這些指令僅對(duì)pentium4,Intel Xeon, P6,Pentium處理器有效)。
2)處理器芯片內(nèi)置的高級(jí)可編程中斷控制器(APIC)。APIC是在Pentium處理器中被引入IA-32體系的。
3)二級(jí)緩存(level 2, L2)。對(duì)于Pentium4,Intel Xeon, P6處理器,L2 cache已經(jīng)緊密的封裝到了處理器中。而Pentium,Intel486提供了用于支持外部L2 cache的管腳。
4)超線程技術(shù)。這個(gè)技術(shù)是IA-32體系的擴(kuò)展,它能夠讓一個(gè)處理器內(nèi)核并發(fā)的執(zhí)行兩個(gè)或兩個(gè)以上的指令流。
這些機(jī)制在對(duì)稱多處理系統(tǒng)(symmetric-multiprocessing, SMP)中是極其有用的。然而,在一個(gè)IA-32處理器和一個(gè)專用處理器(例如通信,圖形,視頻處理器)共享系統(tǒng)總線的應(yīng)用中,這些機(jī)制也是適用的。
-------------------------
2.多處理機(jī)制的設(shè)計(jì)目標(biāo)是:
1)保持系統(tǒng)內(nèi)存的完整性(coherency):
當(dāng)兩個(gè)或多個(gè)處理器試圖同時(shí)訪問系統(tǒng)內(nèi)存的同一地址時(shí),必須有某種通信機(jī)制或內(nèi)存訪問協(xié)議來提升數(shù)據(jù)的完整性,以及在某些情況下,允許一個(gè)處理器臨時(shí)鎖定某個(gè)內(nèi)存區(qū)域。
2)保持高速緩存的一致性:
當(dāng)一個(gè)處理器訪問另一個(gè)處理器緩存中的數(shù)據(jù)時(shí),必須要得到正確的數(shù)據(jù)。如果這個(gè)處理器修改了數(shù)據(jù),那么所有的訪問這個(gè)數(shù)據(jù)的處理器都要收到被修改后的數(shù)據(jù)。
3)允許以可預(yù)知的順序?qū)憙?nèi)存:
在某些情況下,從外部觀察到的寫內(nèi)存順序必須要和編程時(shí)指定的寫內(nèi)存順序相一致。
4)在一組處理器中派發(fā)中斷處理:
當(dāng)幾個(gè)處理器正在并行的工作在一個(gè)系統(tǒng)中時(shí),有一個(gè)集中的機(jī)制是必要的,這個(gè)機(jī)制可以用來接收中斷以及把他們派發(fā)到某一個(gè)適當(dāng)?shù)奶幚砥鳌?br />
5)采用現(xiàn)代操作系統(tǒng)和應(yīng)用程序都具有的多線程和多進(jìn)程的特性來提升系統(tǒng)的性能。
---------------------------
根據(jù)本文的需要,將重點(diǎn)討論內(nèi)存加鎖,串行(serializing instructions)指令,內(nèi)存排序,加鎖的原子操作(locked atomic operations)。
3.系統(tǒng)內(nèi)存加鎖的原子操作:
32位IA-32處理器支持對(duì)系統(tǒng)內(nèi)存加鎖的原子操作。這些操作常用來管理共享的數(shù)據(jù)結(jié)構(gòu)(例如信號(hào)量,段描述符,系統(tǒng)段頁表)。兩個(gè)或多個(gè)處理器可能會(huì)同時(shí)的修改這些數(shù)據(jù)結(jié)構(gòu)中的同一數(shù)據(jù)域或標(biāo)志。
處理器應(yīng)用三個(gè)相互依賴的機(jī)制來實(shí)現(xiàn)加鎖的原子操作:
1)可靠的原子操作(guaranteed atomic operations)。
2)總線加鎖,使用LOCK#信號(hào)和LOCK指令前綴。
3)緩存完整性協(xié)議,保證原子操作能夠?qū)彺嬷械臄?shù)據(jù)結(jié)構(gòu)執(zhí)行;這個(gè)機(jī)制出現(xiàn)在Pentium4,IntelXeon,P6系列處理器中,這些機(jī)制以下面的形式相互依賴。
--->某些基本的內(nèi)存事務(wù)(memory transaction)例如讀寫系統(tǒng)內(nèi)存的一個(gè)字節(jié))被保證是原子的。也就是說,一旦開始,處理器會(huì)保證這個(gè)操作會(huì)在另一個(gè)處理器或總線代理(bus agent)訪問相同的內(nèi)存區(qū)域之前結(jié)束。
--->處理器還支持總線加鎖以實(shí)現(xiàn)所選的內(nèi)存操作(例如在共享內(nèi)存中的讀-改-寫操作),這些操作需要自動(dòng)的處理,但又不能以上面的方式處理。因?yàn)轭l繁使用的內(nèi)存數(shù)據(jù)經(jīng)常被緩存在處理器的L1,L2高速緩存里,原子操作通常是在處理器緩存內(nèi)部進(jìn)行的,并不需要聲明總線加鎖。這里的處理器緩存完整性協(xié)議保證了在緩沖內(nèi)存上執(zhí)行原子操作時(shí)其他緩存了相同內(nèi)存區(qū)域的處理器被正確管理。
注意到這些處理加鎖的原子操作的機(jī)制已經(jīng)像IA-32處理器一樣發(fā)展的越來越復(fù)雜。于是,最近的IA-32處理器(例如Pentium 4, Intel Xeon, P6系列處理器)提供了一種比早期IA-32處理器更為精簡的機(jī)制。
------------------------------------------------保證原子操作的情況------------------------------------
4.保證原子操作的情況
Pentium 4, Intel Xeon,P6系列,Pentium,以及Intel486處理器保證下面的基本內(nèi)存操作總被自動(dòng)的執(zhí)行:
1)讀或?qū)懸粋€(gè)字節(jié)
2)讀或?qū)懸粋€(gè)在16位邊界對(duì)齊的字
3)讀或?qū)懸粋€(gè)在32位邊界對(duì)齊的雙字
Pentium 4, Intel Xeon,P6系列以及Pentium處理器還保證下列內(nèi)存操作總是被自動(dòng)執(zhí)行:
1)讀或?qū)懸粋€(gè)在64位邊界對(duì)齊的四字(quadword)
2)對(duì)32位數(shù)據(jù)總線可以容納的未緩存的內(nèi)存位置進(jìn)行16位方式訪問
(16-bit accesses to uncached memory locations that fit within a 32-bit data bus)
P6系列處理器還保證下列內(nèi)存操作被自動(dòng)執(zhí)行:
對(duì)32位緩沖線(cache line)可以容納的緩存中的數(shù)據(jù)進(jìn)行非對(duì)齊的16位,32位,64位訪問.
對(duì)于可以被緩存的但是卻被總線寬度,緩沖線,頁邊界所分割的內(nèi)存區(qū)域,Pentium 4, Intel Xeon, P6 family,Pentium以及Intel486處理器都不保證訪問操作是原子的。Pentium 4, Intel Xeon,P6系列處理器提供了總線控制信號(hào)來允許外部的內(nèi)存子系統(tǒng)完成對(duì)分割內(nèi)存的原子性訪問;但是,對(duì)于非對(duì)齊內(nèi)存的訪問會(huì)嚴(yán)重影響處理器的性能,因此應(yīng)該盡量避免。
--------------------------------------------------------------總線加鎖------------------------------------------
5.總線加鎖(Bus Locking)
1.Lock信號(hào)的作用:
IA-32處理器提供了LOCK#信號(hào)。這個(gè)信號(hào)會(huì)在某些內(nèi)存操作過程中被自動(dòng)發(fā)出。當(dāng)這個(gè)輸出信號(hào)發(fā)出的時(shí)候,來自其他處理器或總線代理的總線控制請(qǐng)求將被阻塞。軟件能夠利用在指令前面添加LOCK前綴來指定在其他情況下的也需要LOCK語義(LOCK semantics)。
在Intel386,Intel486,Pentium處理器中,直接調(diào)用加鎖的指令會(huì)導(dǎo)致LOCK#信號(hào)的產(chǎn)生。硬件的設(shè)計(jì)者需要保證系統(tǒng)硬件中LOCK#信號(hào)的有效性,以控制多個(gè)處理對(duì)內(nèi)存的訪問。
--->注意:
對(duì)于Pentium 4, Intel Xeon,以及P6系列處理器,如果被訪問的內(nèi)存區(qū)域存在于處理器內(nèi)部的高速緩存中,那么LOCK#信號(hào)通常不被發(fā)出;但是處理器的緩存卻要被鎖定。
--------------------------------------------------自動(dòng)加鎖(Automatic Locking)------- -------------------
6.自動(dòng)加鎖(Automatic Locking)
1.下面的操作會(huì)自動(dòng)的帶有LOCK語義:
1)執(zhí)行引用內(nèi)存的XCHG指令。
2)設(shè)置TSS描述符的B(busy忙)標(biāo)志。在進(jìn)行任務(wù)切換時(shí),處理器檢查并設(shè)置TSS描述符的busy標(biāo)志。為了保證兩個(gè)處理器不會(huì)同時(shí)切換到同一個(gè)任務(wù)。處理器會(huì)在檢查和設(shè)置這個(gè)標(biāo)志的時(shí)遵循LOCK語義。
3)更新段描述符時(shí)。在裝入一個(gè)段描述符時(shí),如果段描述符的訪問標(biāo)志被清除,處理器會(huì)設(shè)置這個(gè)標(biāo)志。在進(jìn)行這個(gè)操作時(shí),處理器會(huì)遵循LOCK語義,因此這個(gè)描述符不會(huì)在更新時(shí)被其他的處理器修改。為了使這個(gè)動(dòng)作能夠有效,更新描述符的操作系統(tǒng)過程應(yīng)該采用下面的方法:
(1)使用加鎖的操作修改訪問權(quán)字節(jié)(access-rights byte),來表明這個(gè)段描述符已經(jīng)不存在,同時(shí)設(shè)置類型變量,表明這個(gè)描述符正在被更新。
(2)更新段描述符的內(nèi)容。這個(gè)操作可能需要多個(gè)內(nèi)存訪問;因此不能使用加鎖指令。
(3)使用加鎖操作來修改訪問權(quán)字節(jié)(access-rights byte),來表明這個(gè)段描述符存在并且有效。
注意,Intel386處理器總是更新段描述符的訪問標(biāo)志,無論這個(gè)標(biāo)志是否被清除。Pentium 4, Intel Xeon,P6系列,Pentium以及Intel486處理器僅在該標(biāo)志被清除時(shí)才設(shè)置這個(gè)標(biāo)志。
4)更新頁目錄(page-directory)和頁表(page-table)的條目。在更新頁目錄和頁表的條目時(shí),處理器使用加鎖的周期(locked cycles)來設(shè)置訪問標(biāo)志和臟標(biāo)志(dirty flag)。
5)響應(yīng)中斷。發(fā)生中斷后,中斷控制器可能會(huì)使用數(shù)據(jù)總線給處理器傳送中斷向量。處理器必須遵循LOCK語義來保證傳送中斷向量時(shí)數(shù)據(jù)總線上沒有其他數(shù)據(jù)。
-------------------------------------------------軟件控制的總線加鎖----------------------------------------
7.軟件控制的總線加鎖
1)總述:
如果想強(qiáng)制執(zhí)行LOCK語義,軟件可以在下面的指令前使用LOCK前綴。當(dāng)LOCK前綴被置于其他的指令之前或者指令沒有對(duì)內(nèi)存進(jìn)行寫操作(也就是說目標(biāo)操作數(shù)在寄存器中)時(shí),一個(gè)非法操作碼(invalid-opcode)異常會(huì)被拋出。
2)可以使用LOCK前綴的指令:
1)位測試和修改指令(BTS, BTR, BTC)
2)交換指令(XADD, CMPXCHG, CMPXCHG8B)
3)XCHG指令自動(dòng)使用LOCK前綴
4)單操作數(shù)算術(shù)和邏輯指令:INC, DEC, NOT, NEG
5)雙操作數(shù)算術(shù)和邏輯指令:ADD, ADC, SUB, SBB, AND, OR, XOR
3)注意:
(1)一個(gè)加鎖的指令會(huì)保證對(duì)目標(biāo)操作數(shù)所在的內(nèi)存區(qū)域加鎖,但是系統(tǒng)可能會(huì)將鎖定區(qū)域解釋得稍大一些。
(2)軟件應(yīng)該使用相同的地址和操作數(shù)長度來訪問信號(hào)量(一個(gè)用作處理器之間信號(hào)傳遞用的共享內(nèi)存)。例如,如果一個(gè)處理器使用一個(gè)字來訪問信號(hào)量,其他的處理器就不應(yīng)該使用一個(gè)字節(jié)來訪問這個(gè)信號(hào)量。
(3)總線加鎖的完整性不受內(nèi)存區(qū)域?qū)R的影響。在所有更新操作數(shù)的總線周期內(nèi),加鎖語義一直持續(xù)。但是建議加鎖訪問能夠在自然邊界對(duì)齊,這樣可以提升系統(tǒng)性能:
任何邊界的8位訪問(加鎖或不加鎖)
16位邊界的加鎖字訪問。
32位邊界的加鎖雙字訪問。
64位邊界的加鎖四字訪問。
(4)對(duì)所有的內(nèi)存操作和可見的外部事件來說,加鎖的操作是原子的。只有取指令和頁表操作能夠越過加鎖的指令。
(5)加鎖的指令能用于同步數(shù)據(jù),這個(gè)數(shù)據(jù)被一個(gè)處理器寫而被其他處理器讀。
對(duì)于P6系列處理器來說,加鎖的操作使所有未完成的讀寫操作串行化(serialize)(也就是等待它們執(zhí)行完畢)。這條規(guī)則同樣適用于Pentium4和Intel Xeon處理器,但有一個(gè)例外:對(duì)弱排序的內(nèi)存類型的讀入操作可能不會(huì)被串行化。
加鎖的指令不應(yīng)該用來保證寫的數(shù)據(jù)可以作為指令取回。
--------------->自修改代碼(self-modifying code)
(6)加鎖的指令對(duì)于Pentium 4, Intel Xeon, P6 family, Pentium, and Intel486處理器,允許寫的數(shù)據(jù)可以作為指令取回。但是Intel建議需要使用自修改代碼(self-modifying code)的開發(fā)者使用另外一種同步機(jī)制。
處理自修改和交叉修改代碼(handling self- and cross-modifying code)
處理器將數(shù)據(jù)寫入當(dāng)前的代碼段以實(shí)現(xiàn)將該數(shù)據(jù)作為代碼來執(zhí)行的目的,這個(gè)動(dòng)作稱為自修改代碼。IA-32處理器在執(zhí)行自修改代碼時(shí)采用特定模式的行為,具體依賴于被修改的代碼與當(dāng)前執(zhí)行位置之間的距離。由于處理器的體系結(jié)構(gòu)變得越來越復(fù)雜,而且可以在引退點(diǎn)(retirement point)之前推測性地執(zhí)行接下來的代碼(如:P4, Intel Xeon, P6系列處理器),如何判斷應(yīng)該執(zhí)行哪段代碼,是修改前地還是修改后的,就變得模糊不清。要想寫出于現(xiàn)在的和將來的IA-32體系相兼容的自修改代碼,必須選擇下面的兩種方式之一:
(方式1)
將代碼作為數(shù)據(jù)寫入代碼段;
跳轉(zhuǎn)到新的代碼位置或某個(gè)中間位置;
執(zhí)行新的代碼;
(方式2)
將代碼作為數(shù)據(jù)寫入代碼段;
執(zhí)行一條串行化指令;(如:CPUID指令)
執(zhí)行新的代碼;
(在Pentium或486處理器上運(yùn)行的程序不需要以上面的方式書寫,但是為了與Pentium 4, Intel Xeon, P6系列處理器兼容,建議采用上面的方式。)
需要注意的是自修改代碼將會(huì)比非自修改代碼的運(yùn)行效率要低。性能損失的程度依賴于修改的頻率以及代碼本身的特性。
--------------->交叉修改代碼(cross-modifying code)
處理器將數(shù)據(jù)寫入另外一個(gè)處理器的代碼段以使得哪個(gè)處理器將該數(shù)據(jù)作為代碼執(zhí)行,這稱為交叉修改代碼(cross-modifying code)。像自修改代碼一樣,IA-32處理器采用特定模式的行為執(zhí)行交叉修改代碼,具體依賴于被修改的代碼與當(dāng)前執(zhí)行位置之間的距離。要想寫出于現(xiàn)在的和將來的IA-32體系相兼容的自修改代碼,下面的處理器同步算法必須被實(shí)現(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處理器上運(yùn)行的程序不需要以上面的方式書寫,但是為了與Pentium 4, Intel Xeon, P6系列處理器兼容,建議采用上面的方式。)
像自修改代碼一樣,交叉修改代碼將會(huì)比非交叉修改代碼的運(yùn)行效率要低。性能損失的程度依賴于修改的頻率以及代碼本身的特性。
說明:作者讀到這里時(shí),也是對(duì)自修改代碼和交叉修改代碼稍懂一點(diǎn),再要深入,也備感艱難。
-------------------------------------------------------緩存加鎖--------------------------------------------
8.緩存加鎖
1)加鎖操作對(duì)處理器內(nèi)部緩存的影響:
(1)對(duì)于Intel486和Pentium處理器,在進(jìn)行加鎖操作時(shí),LOCK#信號(hào)總是在總線上發(fā)出,甚至鎖定的內(nèi)存區(qū)域已經(jīng)緩存在處理器cache中的時(shí)候,LOCK#信號(hào)也從總線上發(fā)出。
(2)對(duì)于Pentium 4, Intel Xeon,P6系列處理器,如果加鎖的內(nèi)存區(qū)域已經(jīng)緩存在處理器cache中,處理器可能并不對(duì)總線發(fā)出LOCK#信號(hào),而是僅僅修改cache緩存中的數(shù)據(jù),然后依賴cache緩存一致性機(jī)制來保證加鎖操作的自動(dòng)執(zhí)行。這個(gè)操作稱為"緩存加鎖"。緩存一致性機(jī)制會(huì)自動(dòng)阻止兩個(gè)或多個(gè)緩存了同一區(qū)域內(nèi)存的處理器同時(shí)修改數(shù)據(jù)。
-----------------------------------------------訪存排序(memory ordering)-------- ---------------------
9.訪存排序(memory ordering)
(1)編程排序(program ordering):
訪存排序指的是處理器如何安排通過系統(tǒng)總線對(duì)系統(tǒng)內(nèi)存訪問的順序。IA-32體系支持幾種訪存排序模型,具體依賴于體系的實(shí)現(xiàn)。例如, Intel386處理器強(qiáng)制執(zhí)行"編程排序(program ordering)"(又稱為強(qiáng)排序),在任何情況下,訪存的順序與它們出現(xiàn)在代碼流中的順序一致。
(2)處理器排序(processor ordering):
為了允許代碼優(yōu)化,IA-32體系在Pentium 4, Intel Xeon,P6系列處理器中允許強(qiáng)排序之外的另外一種模型——處理器排序(processor ordering)。這種排序模型允許讀操作越過帶緩存的寫操作來提升性能。這個(gè)模型的目標(biāo)是在多處理器系統(tǒng)中,在保持內(nèi)存一致性的前提下,提高指令執(zhí)行速度。
-----------------------------
10.Pentium和Intel 486處理器的訪存排序:
1)普遍情況:
Pentium和Intel 486處理器遵循處理器排序訪存模型;但是,在大多數(shù)情況下,訪存操作還是強(qiáng)排序,讀寫操作都是以編程時(shí)指定的順序出現(xiàn)在系統(tǒng)總線上。除了在下面的情況時(shí),未命中的讀操作可以越過帶緩沖的寫操作:
--->當(dāng)所有的帶緩沖的寫操作都在cache緩存中命中,因此也就不會(huì)與未命中的讀操作訪問相同的內(nèi)存地址。
2)I/O操作訪存:
在執(zhí)行I/O操作時(shí),讀操作和寫操作總是以編程時(shí)指定的順序執(zhí)行。在"處理器排序"處理器(例如,Pentium 4, Intel Xeon,P6系列處理器)上運(yùn)行的軟件不能依賴Pentium或Intel486處理器的強(qiáng)排序。軟件應(yīng)該保證對(duì)共享變量的訪問能夠遵守編程順序,這種編程順序是通過使用加鎖或序列化指令來完成的。
3)Pentium 4, Intel Xeon, P6系列處理器的訪存排序
Pentium 4, Intel Xeon, P6系列處理器也是使用"處理器排序"的訪存模型,這種模型可以被進(jìn)一步定義為"帶有存儲(chǔ)緩沖轉(zhuǎn)發(fā)的寫排序"(write ordered with store-buffer forwarding)。這種模型有下面的特點(diǎn):
---------單處理器系統(tǒng)中的排序規(guī)則
(1)在一個(gè)單處理器系統(tǒng)中,對(duì)于定義為回寫可緩沖(write-back cacheable)的內(nèi)存區(qū)域,下面的排序規(guī)則將被應(yīng)用:
a.讀能夠被任意順序執(zhí)行。
b.讀可以越過緩沖寫,但是處理器必須保證數(shù)據(jù)完整性(self-consistent)。
c.對(duì)內(nèi)存的寫操作總是以編程順序執(zhí)行,除非寫操作執(zhí)行了CLFUSH指令以及利用非瞬時(shí)的移動(dòng)指令(MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, MOVNTPD)來執(zhí)行流存儲(chǔ)操作(streamint stores)。
作者認(rèn)為:CLFUSH--->CFLUSH,streamint--->streaming???是否原文有誤。
d.寫可以被緩沖。寫不能夠預(yù)先執(zhí)行;它們只能等到其他指令執(zhí)行完畢。
e.在處理器中,來自于緩沖寫的數(shù)據(jù)可以直接被發(fā)送到正在等待的讀操作。
f.讀寫操作都不能跨越I/O指令,加鎖指令,或者序列化指令。
g.讀操作不能越過LFENCE和MFENCE指令。
h.`寫操作不能越過SFECE和MFENCE指令。
第二條規(guī)則(b)允許一個(gè)讀操作越過寫操作。然而如果寫操作和讀操作都是訪問同一個(gè)內(nèi)存區(qū)域,那么處理器內(nèi)部的監(jiān)視機(jī)制將會(huì)檢測到?jīng)_突并且在處理器使用錯(cuò)誤的數(shù)據(jù)執(zhí)行指令之前更新已經(jīng)緩存的讀操作。
第六條規(guī)則(f)構(gòu)成了一個(gè)例外,否則整個(gè)模型就是一個(gè)寫排序模型(write ordered model)。
注意"帶有存儲(chǔ)緩沖轉(zhuǎn)發(fā)的寫排序"(在本節(jié)開始的時(shí)候介紹)指的是第2條規(guī)則和第6條規(guī)則的組合之后產(chǎn)生的效果。
---------------多處理器系統(tǒng)中的排序規(guī)則
(2)在一個(gè)多處理器系統(tǒng)中,下面的排序規(guī)則將被應(yīng)用:
a.每個(gè)處理器使用同單處理器系統(tǒng)一樣的排序規(guī)則。
b.所有處理器所觀察到的某個(gè)處理器的寫操作順序是相同的。
c.每個(gè)處理器的寫操作并不與其它處理器之間進(jìn)行排序。
例如:在一個(gè)三處理器的系統(tǒng)中,每個(gè)處理器執(zhí)行三個(gè)寫操作,分別對(duì)三個(gè)地址A, B,C。每個(gè)處理器以編程的順序執(zhí)行操作,但是由于總線仲裁和其他的內(nèi)存訪問機(jī)制,三個(gè)處理器執(zhí)行寫操作的順序可能每次都不相同。最終的A, B, C的值會(huì)因每次執(zhí)行的順序而改變。
-------------------
(3)本節(jié)介紹的處理器排序模型與Pentium Intel486處理器使用的模型是一樣的。唯一在Pentium 4, Intel Xeon,P6系列處理器中得到加強(qiáng)的是:
a.對(duì)于預(yù)先執(zhí)行讀操作的支持。
b.存儲(chǔ)緩沖轉(zhuǎn)發(fā),當(dāng)一個(gè)讀操作越過一個(gè)訪問相同地址的寫操作。
c.對(duì)于長串的存儲(chǔ)和移動(dòng)的無次序操作(out-of-Order Stores)Pentium 4,
--------------------
(4)快速串:
Intel Xeon, P6處理器對(duì)于串操作的無次序存儲(chǔ)(Out-of-Order Stores)
Pentium 4, Intel
Xeon,P6處理器在進(jìn)行串存儲(chǔ)的操作(以MOVS和STOS指令開始)時(shí),修改了處理器的動(dòng)作,以提升處理性能。一旦"快速串"的條件滿足了 (將在下面介紹),處理器將會(huì)在緩沖線(cache line)上以緩沖線模式進(jìn)行操作。這會(huì)導(dǎo)致處理器在循環(huán)過程中發(fā)出對(duì)源地址的緩沖線讀請(qǐng)求,以及在外部總線上發(fā)出對(duì)目標(biāo)地址的寫請(qǐng)求,并且已知了目標(biāo)地址內(nèi)的數(shù)據(jù)串一定要被修改。在這種模式下,處理器僅僅在緩沖線邊界時(shí)才會(huì)相應(yīng)中斷。因此,目標(biāo)數(shù)據(jù)的失效和存儲(chǔ)可能會(huì)以不規(guī)則的順序出現(xiàn)在外部總線上。
按順序存儲(chǔ)串的代碼不應(yīng)該使用串操作指令。數(shù)據(jù)和信號(hào)量應(yīng)該分開。依賴順序的代碼應(yīng)該在每次串操作時(shí)使用信號(hào)量來保證存儲(chǔ)數(shù)據(jù)的順序在所有處理器看來是一致的。
"快速串"的初始條件是:
在Pentium III 處理器中,EDI和ESI必須是8位對(duì)齊的。在Pentium4中,EDI必須是8位對(duì)齊的。
串操作必須是按地址增加的方向進(jìn)行的。
初始操作計(jì)數(shù)器(ECX)必須大于等于64。
源和目的內(nèi)存的重合區(qū)域一定不能小于一個(gè)緩沖線的大小(Pentium 4和Intel Xeon 處理器是64字節(jié);P6 和Pentium處理器是 32字節(jié))。
源地址和目的地址的內(nèi)存類型必須是WB或WC。
----------------
11.加強(qiáng)和削弱訪存排序模型(Strengthening or Weakening the Memory Ordering Model)
IA-32體系提供了幾種機(jī)制用來加強(qiáng)和削弱訪存排序模型以處理特殊的編程場合。這些機(jī)制包括:
1)I/O指令,加鎖指令,LOCK前綴,以及序列化指令來強(qiáng)制執(zhí)行"強(qiáng)排序"。
2)SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon處理器中引入)提供了某些特殊類型內(nèi)存操作的排序和串行化功能。
3)內(nèi)存類型范圍寄存器(memory type range registers (MTRRs))可以被用來加強(qiáng)和削弱物理內(nèi)存中特定區(qū)域的訪存排序模型。MTRRs只存在于Pentium 4, Intel Xeon, P6系列處理器。
4)頁屬性表可以被用來加強(qiáng)某個(gè)頁或一組頁的訪存排序("頁屬性表"Page Attribute Table(PAT))。PAT只存在于Pentium 4, Intel Xeon,P6系列處理器。
這些機(jī)制可以通過下面的方式使用:
1)內(nèi)存映射和其他I/O設(shè)備通常對(duì)緩沖區(qū)寫操作的順序很敏感。I/O指令(IN,OUT)以下面的方式對(duì)這種訪問執(zhí)行強(qiáng)排序。在執(zhí)行一條I/O 指令之前,處理器等待之前的所有指令執(zhí)行完畢以及所有的緩沖區(qū)都被寫入了內(nèi)存。只有取指令操作和頁表查詢(page table walk)能夠越過I/O指令。后續(xù)指令要等到I/O指令執(zhí)行完畢才開始執(zhí)行。
2)一個(gè)多處理器的系統(tǒng)中的同步機(jī)制可能會(huì)依賴"強(qiáng)排序"模型。這里,一個(gè)程序使用加鎖指令,例如XCHG或者LOCK前綴,來保證讀-改-寫操作是自動(dòng)進(jìn)行的。加鎖操作像I/O指令一樣等待所有之前的指令執(zhí)行完畢以及緩沖區(qū)都被寫入了內(nèi)存。
3)程序同步可以通過序列化指令來實(shí)現(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ù)的程序和讀取這個(gè)數(shù)據(jù)的程序之間。
SFENCE——串行化發(fā)生在SFENCE指令之前的寫操作但是不影響讀操作。
LFENCE——串行化發(fā)生在SFENCE指令之前的讀操作但是不影響寫操作。
MFENCE——串行化發(fā)生在MFENCE指令之前的讀寫操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更靈活有效的控制內(nèi)存排序的方式。
5)MTRRs在P6系列處理器中引入,用來定義物理內(nèi)存的特定區(qū)域的高速緩存特性。下面的兩個(gè)例子是利用MTRRs設(shè)置的內(nèi)存類型如何來加強(qiáng)和削弱Pentium 4, Intel Xeon, P6系列處理器的訪存排序:
(1)強(qiáng)不可緩沖(strong uncached,UC)內(nèi)存類型實(shí)行內(nèi)存訪問的強(qiáng)排序模型:
這里,所有對(duì)UC內(nèi)存區(qū)域的讀寫都出現(xiàn)在總線上,并且不能夠被亂序或預(yù)先執(zhí)行。這種內(nèi)存類型可以應(yīng)用于映射成I/O設(shè)備的內(nèi)存區(qū)域來強(qiáng)制執(zhí)行訪存強(qiáng)排序。
(2)對(duì)于可以容忍弱排序訪問的內(nèi)存區(qū)域,可以選擇回寫(write back, WB)內(nèi)存類型:
這里,讀操作可以預(yù)先的被執(zhí)行,寫操作可以被緩沖和組合(combined)。對(duì)于這種類型的內(nèi)存,鎖定高速緩存是通過一個(gè)加鎖的原子操作實(shí)現(xiàn)的,這個(gè)操作不會(huì)分割緩沖線,因此會(huì)減少典型的同步指令(如,XCHG在整個(gè)讀-改-寫操作周期要鎖定數(shù)據(jù)總線)所帶來的性能損失。對(duì)于WB內(nèi)存,如果訪問的數(shù)據(jù)已經(jīng)存在于緩存cache中,XCHG指令會(huì)鎖定高速緩存而不是數(shù)據(jù)總線。
(3)PAT在Pentium III中引入,用來增強(qiáng)用于存儲(chǔ)內(nèi)存頁的緩存性能。PAT機(jī)制通常被用來與MTRRs一起來加強(qiáng)頁級(jí)別的高速緩存性能。在Pentium 4, Intel Xeon,P6系列處理器上運(yùn)行的軟件最好假定是 "處理器排序"模型或者是更弱的訪存排序模型。
Pentium 4, Intel Xeon,P6系列處理器沒有實(shí)現(xiàn)強(qiáng)訪存排序模型,除了對(duì)于UC內(nèi)存類型。盡管Pentium 4, Intel Xeon,P6系列處理器支持處理器排序模型,Intel并沒有保證將來的處理器會(huì)支持這種模型。為了使軟件兼容將來的處理器,操作系統(tǒng)最好提供臨界區(qū) (critical region)和資源控制構(gòu)建以及基于I/O,加鎖,序列化指令的API,用于同步多處理器系統(tǒng)對(duì)共享內(nèi)存區(qū)的訪問。同時(shí),軟件不應(yīng)該依賴處理器排序模型,因?yàn)橐苍S系統(tǒng)硬件不支持這種訪存模型。
(4)向多個(gè)處理器廣播頁表和頁目錄條目的改變:
在一個(gè)多處理器系統(tǒng)中,當(dāng)一個(gè)處理器改變了一個(gè)頁表或頁目錄的條目,這個(gè)改變必須要通知所有其它的處理器。這個(gè)過程通常稱為"TLB shootdown"。廣播頁表或頁目錄條目的改變可以通過基于內(nèi)存的信號(hào)量或者處理器間中斷(interprocessor interrupts, IPI)。
例如一個(gè)簡單的,但是算法上是正確的TLB shootdown序列可能是下面的樣子:
a.開始屏障(begin barrier)——除了一個(gè)處理器外停止所有處理器;讓他們執(zhí)行HALT指令或者空循環(huán)。
b.讓那個(gè)沒有停止的處理器改變PTE or PDE。
c.讓所有處理器在他們各自TLB中修改的PTE, PDE失效。
d.結(jié)束屏障(end barrier)——恢復(fù)所有的處理器執(zhí)行。
(5)串行化指令(serializing instructions):
IA-32體系定義了幾個(gè)串行化指令(SERIALIZING INSTRUCTIONS)。這些指令強(qiáng)制處理器完成先前指令對(duì)標(biāo)志,寄存器以及內(nèi)存的修改,并且在執(zhí)行下一條指令之前將所有緩沖區(qū)里的數(shù)據(jù)寫入內(nèi)存。
===>串行化指令應(yīng)用一:開啟保護(hù)模式時(shí)的應(yīng)用
例如:當(dāng)MOV指令將一個(gè)操作數(shù)裝入CR0寄存器以開啟保護(hù)模式時(shí),處理器必須在進(jìn)入保護(hù)模式之前執(zhí)行一個(gè)串行化操作。這個(gè)串行化操作保證所有在實(shí)地址模式下開始執(zhí)行的指令在切換到保護(hù)模式之前都執(zhí)行完畢。
-------------
串行化指令的概念在Pentium處理器中被引入IA-32體系。這種指令對(duì)于Intel486或更早的處理器是沒有意義的,因?yàn)樗鼈儾]有實(shí)現(xiàn)并行指令執(zhí)行。
非常值得注意的是,在Pentium 4, Intel Xeon,P6系列處理器上執(zhí)行串行化指令會(huì)抑制指令的預(yù)執(zhí)行(speculative execution),因?yàn)轭A(yù)執(zhí)行的結(jié)果會(huì)被放棄掉。
-------------
下面的指令是串行化指令:
1.--->特權(quán)串行化指令——MOV(目標(biāo)操作數(shù)為控制寄存器),MOV(目標(biāo)操作數(shù)為調(diào)試存器),WRMSR, INVD, INVLPG, WBINVD, LGDT, LLDT, LIDT, LTR。
-------------------------作者補(bǔ)充------------------------------
作者:如果上述指令不熟,可以參考《80X86匯編語言程序設(shè)計(jì)教程》楊季文編,清華大學(xué)出版社。下面作些簡單的介紹:以下作者對(duì)匯編指令的說明均參考引用了該書。
---->INVLPG指令:
使TLB(轉(zhuǎn)換后援緩沖器:用于存放最常使用的物理頁的頁碼)項(xiàng)無效。該指令是特權(quán)指令,只有在實(shí)方式和保護(hù)方式的特權(quán)級(jí)0下,才可執(zhí)行該指令。
---------------------------------------------------------------
2.--->非特權(quán)串行化指令——CPUID, IRET, RSM。
3.--->非特權(quán)訪存排序指令——SFENCE, LFENCE, MFENCE。
當(dāng)處理器執(zhí)行串行化指令的時(shí)候,它保證在執(zhí)行下一條指令之前,所有未完成的內(nèi)存事務(wù)都被完成,包括寫緩沖中的數(shù)據(jù)。任何指令不能越過串行化指令,串行化指令也不能越過其他指令(讀,寫, 取指令, I/O)。
CPUID指令可以在任何特權(quán)級(jí)下執(zhí)行串行化操作而不影響程序執(zhí)行流(program flow),除非EAX, EBX, ECX, EDX寄存器被修改了。
SFENCE,LFENCE,MFENCE指令為控制串行化讀寫內(nèi)存提供了更多的粒度。
在使用串行化指令時(shí),最好注意下面的額外信息:
處理器在執(zhí)行串行化指令的時(shí)候并不將高速緩存中已經(jīng)被修改的數(shù)據(jù)寫回到內(nèi)存中。軟件可以通過WBINVD串行化指令強(qiáng)制修改的數(shù)據(jù)寫回到內(nèi)存中。但是頻繁的使用WVINVD(作者注:當(dāng)為WBINVD,原文此處有誤)指令會(huì)嚴(yán)重的降低系統(tǒng)的性能。
----------------作者補(bǔ)充:對(duì)WBINVAD的解釋-----------------------
----->INVD指令:
INVD指令使片上的高速緩存無效,即:清洗片上的超高速緩存。但該指令并不把片上的超高速緩存中的內(nèi)容寫回主存。該指令是特權(quán)指令,只有在實(shí)方式和保護(hù)方式的特權(quán)級(jí)0下,才可執(zhí)行該指令。
---->WBINVD指令:
WBINVD指令使片上的超高速緩存無效即:清洗片上的超高速緩存。但該指令將把片上的超高速緩存中更改的內(nèi)容寫回主存。該指令是特權(quán)指令,只有在實(shí)方式和保護(hù)方式的特權(quán)級(jí)0下,才可執(zhí)行該指令。
****************************************************************
===>串行化指令應(yīng)用二:改變了控制寄存器CR0的PG標(biāo)志的應(yīng)用
當(dāng)一條會(huì)影響分頁設(shè)置(也就是改變了控制寄存器CR0的PG標(biāo)志)的指令執(zhí)行時(shí),這條指令后面應(yīng)該是一條跳轉(zhuǎn)指令。跳轉(zhuǎn)目標(biāo)應(yīng)該以新的PG標(biāo)志 (開啟或關(guān)閉分頁)來進(jìn)行取指令操作,但跳轉(zhuǎn)指令本身還是按先前的設(shè)置執(zhí)行。Pentium 4, Intel Xeon,P6系列處理器不需要在設(shè)置CR0處理器之后放置跳轉(zhuǎn)指令(因?yàn)槿魏螌?duì)CR0進(jìn)行操作的MOV指令都是串行化的)。但是為了與其他IA-32處理器向前和向后兼容,最好是放置一條跳轉(zhuǎn)指令。
=========
作者說明:CR0的第31位為PG標(biāo)志,PG=1:啟用分頁管理機(jī)制,此時(shí)線性地址經(jīng)過分頁管理機(jī)制后轉(zhuǎn)換為物理地址;PG=0:禁用分頁管理機(jī)制,此時(shí)線性地址直接作為物理地址使用。
****************************************************************
在允許分頁的情況下,當(dāng)一條指令會(huì)改變CR3的內(nèi)容時(shí),下一條指令會(huì)根據(jù)新的CR3內(nèi)容所設(shè)置的轉(zhuǎn)換表進(jìn)行取指令操作。因此下一條以及之后的指令應(yīng)該根據(jù)新的CR3內(nèi)容建立映射。
=========
作者說明:CR3用于保存頁目錄表的起始物理地址,由于目錄表是責(zé)對(duì)齊的,所以僅高20位有效,低12位無效。所以如果向CR3中裝入新值,其低 12位當(dāng)為0;每當(dāng)用mov指令重置CR3的值時(shí)候,TLB中的內(nèi)容會(huì)無效。CR3在實(shí)方式下也可以設(shè)置,以使分頁機(jī)制初始化。在任務(wù)切換時(shí)候,CR3要被改變。但要是新任務(wù)的CR3的值==舊任務(wù)的CR3的值,則TLB的內(nèi)容仍有效,不被刷新。
******************************************************************************
以上通過這篇文章資料對(duì)cpu的工作機(jī)制有了更深刻的了解,從而對(duì)我們的Linux Kernel的學(xué)習(xí)有極大的幫助。由此對(duì)加鎖,各類排序,串行化,sfence,mfence,lfence指令的出現(xiàn)有了清楚的認(rèn)識(shí)。再回頭來讀讀源代碼有更深刻的認(rèn)識(shí)。
*****************************************************************************
------------------------------------------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)為對(duì)稱多處理器系統(tǒng)。smp_mb(),smp_rmb(),smp_wmb()就是mb(),rmb(),wmb()。
由此可見,多處理器上的內(nèi)存屏障與單處理器原理一樣。
2.barrier()函數(shù)并無什么難點(diǎn),與前面代碼一樣。
3.如果沒有定義CONFIG_SMP,則smp_mb(), smp_rmb(), smp_wmb(), smp_read_barrier_depends( 都是空宏。
**************************************************************************
在本文的代碼中有不少下劃線的關(guān)鍵字,特此作一研究:
--------------------------------------------------------雙下劃線的解釋--------------------------------------
--->摘自gcc手冊(cè)
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選項(xiàng)解釋見下面) 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。我們?cè)诔绦蛑惺褂昧撕芏嗟膅nu風(fēng)格,也就是GNU C extensions 或其他的通用的頭文件。但是如果程序用'-ansi'或各種'-std'選項(xiàng)編譯時(shí)候,一些關(guān)鍵字,比如:asm、typeof、inline就不能再用了,在這個(gè)編譯選項(xiàng)下,這此關(guān)鍵字被關(guān)閉。所以用有雙下劃線的關(guān)鍵字,如:__asm__、__typeof__、__inline__,這些編譯器通常支持這些帶有雙下劃線的宏。這能替換這些會(huì)產(chǎn)生編譯問題的關(guān)鍵字,使程序能正常通過編譯。
2。如果是用其他的編譯器,可能不認(rèn)這些帶有雙下劃線的宏,就用以下宏來轉(zhuǎn)換:
#ifndef __GNUC__
#define __asm__ asm
#endif
這樣的話,這些其他的編譯器沒有定義__GUNUC__,也不支持__asm__,__inline__,__typeof__等宏,所以必會(huì),執(zhí)行#define __asm__ asm等。這樣,用__asm__,__inline__,__typeof__所編寫的程序代碼,仍能宏展開為asm,inline,typeof,而這此關(guān)鍵字這些其他的編譯器支持。所以程序能正常編譯。
-----------------------------------------------pedantic選項(xiàng)的解釋----------------------------------
--->摘自gcc手冊(cè)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。
-----------------------------------------------------------------------------------------------------------------