• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            內(nèi)存屏障機(jī)制及內(nèi)核相關(guān)源代碼分析

            轉(zhuǎn)載自:http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=linuxK&Number=587989&Search=true&Forum=linuxK&Words=%CE%C4%BC%FE%CF%B5%CD%B3&Match=Entire%20Phrase&Searchpage=0&Limit=25&Old=allposts&Main=587989

            存屏障機(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。
            -----------------------------------------------------------------------------------------------------------------


            posted on 2011-08-30 14:32 楊粼波 閱讀(2033) 評(píng)論(1)  編輯 收藏 引用 所屬分類: C++

            評(píng)論

            # re: 內(nèi)存屏障機(jī)制及內(nèi)核相關(guān)源代碼分析 2011-10-26 16:14 程序員他爺爺!!

            恩,太長沒有看完。
            內(nèi)存屏障這東西,必須要熟悉CPU cache機(jī)制。
            建議讀intel的那幾本免費(fèi)書,不知道現(xiàn)在還發(fā)不發(fā)了。。呵呵
              回復(fù)  更多評(píng)論   

            亚洲国产高清精品线久久| 一级a性色生活片久久无少妇一级婬片免费放| 国产A级毛片久久久精品毛片| 九九久久99综合一区二区| 亚洲国产二区三区久久| 香蕉99久久国产综合精品宅男自 | 亚洲精品无码久久久影院相关影片| 亚洲午夜久久久影院| Xx性欧美肥妇精品久久久久久| 久久夜色精品国产噜噜亚洲a | 精品久久久久久99人妻| 久久久久青草线蕉综合超碰| 国内精品人妻无码久久久影院| 色综合久久中文字幕综合网| 国产精品久久成人影院| 伊人久久精品无码二区麻豆| 亚洲国产综合久久天堂| 国产福利电影一区二区三区久久久久成人精品综合 | 亚洲一区中文字幕久久| 亚洲综合精品香蕉久久网| 91久久成人免费| 久久精品一区二区| 国产精品美女久久久久av爽 | 久久强奷乱码老熟女网站| 青青青青久久精品国产h| 久久精品国产亚洲av影院| 精品少妇人妻av无码久久| 久久最近最新中文字幕大全| 久久精品国产亚洲5555| 久久久久久久久久久免费精品| 久久久久久久久久免免费精品| 久久天天婷婷五月俺也去| 久久精品国产亚洲av麻豆小说| 久久av无码专区亚洲av桃花岛| 久久国产免费观看精品3| 伊人热人久久中文字幕| 一本久久知道综合久久| 亚洲国产精品久久久久| 婷婷久久香蕉五月综合加勒比| 久久久亚洲精品蜜桃臀| 久久综合综合久久狠狠狠97色88|