VxWorksSMP多核編程指南_第1頁(yè)
VxWorksSMP多核編程指南_第2頁(yè)
VxWorksSMP多核編程指南_第3頁(yè)
VxWorksSMP多核編程指南_第4頁(yè)
VxWorksSMP多核編程指南_第5頁(yè)
已閱讀5頁(yè),還剩24頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

./VxWorksSMP多核編程指南本文摘自vxworks_kernel_programmers_guide_6.8第24章介紹VxWorksSMP是風(fēng)河公司為VxWorks設(shè)計(jì)的symmetricmultiprocessing〔SMP系統(tǒng)。它與風(fēng)河公司的uniporcessor〔UP系統(tǒng)一樣,具備實(shí)時(shí)操作系統(tǒng)的特性。本章節(jié)介紹了風(fēng)河VxWorksSMP系統(tǒng)的特點(diǎn)。介紹了VxWorksSMP的配置過(guò)程、它與UP編程的區(qū)別,還有就是如何將UP代碼移植為SMP代碼。關(guān)于VxWorksSMP多核系統(tǒng)指的是一個(gè)系統(tǒng)中包含兩個(gè)或兩個(gè)以上的處理單元。SMP是多核技巧中的一個(gè),它的主要特點(diǎn)是一個(gè)OS運(yùn)行在多個(gè)處理單元上,并且內(nèi)存是共享的。另一種多核技巧是asymmetricmultiprocessing〔AMP系統(tǒng),即多個(gè)處理單元上運(yùn)行多個(gè)OS。技術(shù)特點(diǎn)關(guān)于CPU與處理器的概念在很多計(jì)算機(jī)相關(guān)書籍里有所介紹。但是,在此我們?nèi)砸獙?duì)這二者在SMP系統(tǒng)中的區(qū)別進(jìn)行詳細(xì)說(shuō)明。CPU:一個(gè)CPU通常使用CPUID、物理CPU索引、邏輯CPU索引進(jìn)行標(biāo)示。一個(gè)CPUID通常由系統(tǒng)固件和硬件決定。物理CPU索引從0開始,系統(tǒng)從CPU0開始啟動(dòng),隨著CPU個(gè)數(shù)的增加,物理CPU索引也會(huì)增加。邏輯CPU索引指的是OS實(shí)例。例如,UP系統(tǒng)中邏輯CPU的索引永遠(yuǎn)是0;對(duì)于一個(gè)4個(gè)CPU的SMP系統(tǒng)而言,它的CPU邏輯索引永遠(yuǎn)是0到3,無(wú)論硬件系統(tǒng)中CPU的個(gè)數(shù)。處理器<processor>:是一個(gè)包含一個(gè)CPU或多個(gè)CPU的硅晶體單元。多處理器<multiprocessor>:在一個(gè)獨(dú)立的硬件環(huán)境中包含兩個(gè)以上的處理器。單核處理器<uniprocessor>:一個(gè)包含了一個(gè)CPU的硅晶體單元。例如:adual-coreMPC8641D指的是一個(gè)處理器上有兩個(gè)CPU;aquad-coreBroadcom1480指的是一個(gè)處理器上有四個(gè)CPU。在SMP系統(tǒng)上運(yùn)行UP代碼總會(huì)遇到問(wèn)題,即使將UP代碼進(jìn)行了更新,也很難保證代碼很好的利用了SMP系統(tǒng)的特性。對(duì)于在SMP上運(yùn)行的代碼,我們分為兩個(gè)級(jí)別:SMP-ready:雖然可以正常的運(yùn)行在SMP系統(tǒng)上,但是并沒有很充分的利用SMP系統(tǒng)的特點(diǎn),即沒有利用到多核處理器的優(yōu)勢(shì);SMP-optimized:不僅可以正常的運(yùn)行在SMP系統(tǒng)上,而且還能很好的利用SMP系統(tǒng)的特點(diǎn),使用多個(gè)CPU使多個(gè)任務(wù)可以同時(shí)執(zhí)行,提高系統(tǒng)的效率,比UP系統(tǒng)的效果更加明顯。VxWorksSMPOS特點(diǎn)VxWorks單核編程〔UP與SMP編程在多數(shù)情況下是一樣的。類似的,多數(shù)API在UP和SMP編程中是通用的。一些少數(shù)UP編程中的API不能在SMP中使用。與此同時(shí),SMP中的一些API在UP中使用時(shí)表現(xiàn)的不是SMP中的效果,而是默認(rèn)UP的效果,或者壓根就不能使用〔例如,taskspinlock默認(rèn)表現(xiàn)為tasklock。本小節(jié)將簡(jiǎn)短介紹一下VxWorks的對(duì)稱多處理器的一些特點(diǎn):多任務(wù):對(duì)于傳統(tǒng)的UP系統(tǒng)而言,處理多任務(wù)的方法是通過(guò)任務(wù)優(yōu)先級(jí)對(duì)CPU資源進(jìn)行搶占式處理的。而SMP系統(tǒng)則改變了這種方法,它是實(shí)實(shí)在在的任務(wù)、中斷的同時(shí)執(zhí)行。實(shí)現(xiàn)同時(shí)執(zhí)行的關(guān)鍵是多個(gè)任務(wù)可以在不同的CPU上執(zhí)行,當(dāng)然這需要OS的協(xié)調(diào)控制。對(duì)于UP系統(tǒng)中多任務(wù)所謂的同時(shí)執(zhí)行,其實(shí)只不過(guò)是CPU的快速切換,占有CPU的任務(wù)由一個(gè)快速切換到另一個(gè)。在SMP系統(tǒng)中,同時(shí)執(zhí)行不是幻想而是實(shí)實(shí)在在存在的。任務(wù)調(diào)度機(jī)制:VxWorksSMP系統(tǒng)中的任務(wù)調(diào)度機(jī)制與UP中的類似,都是基于優(yōu)先級(jí)的。不同的是,當(dāng)不同的任務(wù)運(yùn)行在不同的CPU上時(shí),可以實(shí)現(xiàn)兩個(gè)任務(wù)的同時(shí)執(zhí)行?;コ猓河捎赟MP系統(tǒng)允許任務(wù)同時(shí)運(yùn)行的情況存在,因此,在UP系統(tǒng)中通過(guò)關(guān)中斷、鎖任務(wù)調(diào)度等這些保護(hù)臨界資源的手段在SMP系統(tǒng)中將不再適用。這種在所有CPU上通過(guò)強(qiáng)行關(guān)閉中斷、鎖任務(wù)調(diào)度的方法會(huì)影響到SMP系統(tǒng)發(fā)揮它的特點(diǎn),將SMP系統(tǒng)帶回到UP系統(tǒng)的模式。VxWorksSMP提供一套特殊的任務(wù)間、中斷間同步/互斥的方法——即UP中的taskLock<>和intLock<>等將會(huì)被VxWorksSMP提供的spinlock,原子操作以及CPU-specific等機(jī)制替代。CPU-Affinity:默認(rèn)情況下,任意任務(wù)可以運(yùn)行在任意CPU上。VxWorksSMP提供了一種叫做CPU-Affinity的機(jī)制,即可以分配任務(wù)到指定CPU〔CPU邏輯索引上執(zhí)行。VxWorksSMP硬件特點(diǎn)VxWorksSMP系統(tǒng)要求硬件必須具備對(duì)稱多處理器。這些處理器必須是一樣的,處理器可以共享內(nèi)存、可以平等的訪問(wèn)所有設(shè)備。VxWorksSMP必須遵循uniformmemoryaccess<UMA>結(jié)構(gòu)。圖1顯示了一個(gè)雙CPU的SMP系統(tǒng)圖SEQ圖\*ARABIC1SMP硬件結(jié)構(gòu)無(wú)論SMP系統(tǒng)中CPU的個(gè)數(shù)是多少,它們的重要特點(diǎn)是一樣的:內(nèi)存對(duì)所有CPU可見,不存在"只屬于某個(gè)CPU的內(nèi)存"的情況。即任意CPU可以在任意內(nèi)存中執(zhí)行代碼;每個(gè)CPU都有MemoryManagementUnit<MMU>。MMU可以使任務(wù)在不同的虛擬內(nèi)存中同時(shí)運(yùn)行。例如,RTP1的一個(gè)任務(wù)可以在CPU0上運(yùn)行,與此同時(shí),RTP2的一個(gè)任務(wù)可以在CPU1上運(yùn)行;每個(gè)CPU可以訪問(wèn)所有設(shè)備。設(shè)備產(chǎn)生的中斷可以通過(guò)可編程中斷控制器發(fā)送到任意CPU上執(zhí)行;通過(guò)多CPU,任務(wù)和ISR可以實(shí)現(xiàn)同步;通過(guò)spinlock,任務(wù)和ISR可以實(shí)現(xiàn)互斥;Snoopbus的作用是使CPU之間的datacache總是保持前后一致性。VxWorksSMP與AMP的對(duì)比關(guān)于SMP與AMP系統(tǒng)中對(duì)內(nèi)存訪問(wèn)的對(duì)比如圖2所示:圖SEQ圖\*ARABIC2SMP系統(tǒng)對(duì)內(nèi)存的占用情況在SMP系統(tǒng)中,所有物理內(nèi)存被所有CPU共享。內(nèi)存空間可以用來(lái)保存VxWorksSMP鏡像、Real-TimeProcess<RTP>等。所有CPU可以讀、寫、運(yùn)行所有內(nèi)存。內(nèi)核任務(wù)、用戶任務(wù)可以在任意CPU中執(zhí)行。在SMP系統(tǒng)中,所有內(nèi)存、設(shè)備被所有CPU共享,CPU之間的主要通訊是如何防止"同時(shí)訪問(wèn)共享資源"的情況發(fā)生。圖SEQ圖\*ARABIC3AMP系統(tǒng)對(duì)內(nèi)存的占用情況在AMP系統(tǒng)中,每個(gè)CPU對(duì)應(yīng)一個(gè)VxWorks鏡像的拷貝,它們只能被對(duì)應(yīng)的CPU訪問(wèn)。因此,CPU1中執(zhí)行的內(nèi)核任務(wù)不可能在CPU0的內(nèi)存中執(zhí)行,反之亦然。對(duì)于RTP也是一樣的。在AMP系統(tǒng)中,一些內(nèi)存是共享的,但是在這些共享內(nèi)存中讀寫數(shù)據(jù)是嚴(yán)格受到控制的。例如,在兩個(gè)VxWorks鏡像中傳遞數(shù)據(jù)等。硬件資源根據(jù)OS被劃分,因此CPU之間的通信只有在訪問(wèn)共享內(nèi)存時(shí)才會(huì)發(fā)生。VxWorksSMP配置說(shuō)明Spinlock的調(diào)式版本組件INCLUDE_SPINLOCK_DEBUG提供了spinlock的版本,這對(duì)調(diào)試SMPAPP有幫助。在包含INCLUDE_SPINLOCK_DEBUG的同時(shí),最好要加入INCLUDE_EDR_ERRLOG組件,它可以記錄spinlock的錯(cuò)誤信息。CPU配置參數(shù)組件INCLUDE_KERNEL組件中包含了一些對(duì)VxWorksSMP參數(shù)的配置,包括:VX_SMP_NUM_CPUS代表VxWorksSMP的使能CPU個(gè)數(shù)。所有體系結(jié)構(gòu)的最大使能CPU個(gè)數(shù)如下:ARM=4,IA32=8,MIPS=32,PowerPC=8,VxWorksSimulator=32。ENABLE_ALL_CPUS默認(rèn)是TRUE,代表所有已配置的CPU使能。這個(gè)參數(shù)也可以設(shè)置為FALSE,一般出于調(diào)試目的,此時(shí)只有邏輯CPU0是使能的,只有通過(guò)kernelCpuEnable<>才可以使能指定的CPU。VX_ENABLE_CPU_TIMEOUT代表CPU使能超時(shí)時(shí)長(zhǎng),當(dāng)ENABLE_ALL_CPUS是TRUE時(shí),該值表示所有CPU的使能時(shí)長(zhǎng),當(dāng)ENABLE_ALL_CPUS是FALSE時(shí),在kernelCpuEnable<>被調(diào)用時(shí),它用來(lái)表示CPU的啟動(dòng)時(shí)長(zhǎng)。VX_SMP_CPU_EXPLICIT_RESERVE表示將指定CPU排除在"可使用CPU-Affinity屬性的CPU池"之外。它是一個(gè)字符串,若填寫"237",則代表CPU2,3,7不能使用CPU-Affinity屬性。即不能通過(guò)taskCpuAffinitySet<>分配任務(wù)到這些CPU上運(yùn)行。當(dāng)某個(gè)CPU被VX_SMP_CPU_EXPLICIT_RESERVE包含,唯一能夠使他們恢復(fù)預(yù)留屬性的方法是調(diào)用vxCpuReserve<>。在多核AMP系統(tǒng)上配置VxWorksSMP略啟動(dòng)VxWorksSMP在WorkBench開啟后會(huì)有一個(gè)默認(rèn)的SMP的simulator,如圖4所示:圖SEQ圖\*ARABIC4WR自帶的SMP虛擬機(jī)點(diǎn)擊連接后啟動(dòng),啟動(dòng)過(guò)程如圖5所示,代表目前已經(jīng)進(jìn)入VxWorksSMP系統(tǒng)以及當(dāng)前CPU的個(gè)數(shù)。圖SEQ圖\*ARABIC5SMP虛擬機(jī)啟動(dòng)過(guò)程啟動(dòng)后在SHELL中輸入i可以查看系統(tǒng)目前運(yùn)行的任務(wù),你會(huì)發(fā)現(xiàn)兩個(gè)idle任務(wù),它們分別運(yùn)行在兩個(gè)不同的CPU上。如圖6所示。圖SEQ圖\*ARABIC6SMP系統(tǒng)任務(wù)運(yùn)行情況VxWorksSMP編程VxWorks單核編程〔UP與SMP編程在多數(shù)情況下是一樣的。類似的,多數(shù)API在UP和SMP編程中是通用的。一些少數(shù)UP編程中的API不能在SMP中使用。與此同時(shí),SMP中的一些API在UP中使用時(shí)表現(xiàn)的不是SMP中的效果,而是默認(rèn)UP的效果,或者壓根就不能使用〔例如,taskspinlock默認(rèn)表現(xiàn)為tasklock。由于SMP系統(tǒng)的特殊性,因此SMP編程需要特別注意,尤其是在互斥/同步機(jī)制上,在使用的時(shí)候需要充分考慮如何提高系統(tǒng)的性能。在VxWorksSMP系統(tǒng)中針對(duì)每個(gè)CPU都有一個(gè)idle任務(wù),這在UP中是沒有的。Idle任務(wù)是最低優(yōu)先級(jí)〔用戶級(jí)任務(wù)是不能達(dá)到這么低優(yōu)先級(jí)的。當(dāng)CPU進(jìn)出idle狀態(tài)時(shí),idle任務(wù)會(huì)提供任務(wù)上下文,這可以用來(lái)監(jiān)視CPU的利用率情況。當(dāng)CPU無(wú)事可做時(shí),Idle任務(wù)的存在不會(huì)影響CPU進(jìn)入睡眠狀態(tài)〔當(dāng)電源管理開啟時(shí)??梢允褂胟ernelIsCpuIdle<>或者kernelIsSystemIdle<>這兩個(gè)API查看一個(gè)指定CPU是否執(zhí)行了idle任務(wù)或者所有CPU是否執(zhí)行了idle任務(wù)。[注意]不要對(duì)idle任務(wù)進(jìn)行掛起、關(guān)閉、跟蹤、改變優(yōu)先級(jí)等一系列操作。SMP的互斥/同步機(jī)制SMP編程與UP編程最大的一個(gè)不同就是互斥/同步API的使用。有一些API在這兩種編程中都可以使用,而有一些則不同。此外,UP編程中的一些隱式同步技巧〔例如使用任務(wù)優(yōu)先級(jí)替代顯示同步鎖等在SMP中是不能用的。與UP系統(tǒng)不同,SMP系統(tǒng)允許真正意義上的同時(shí)執(zhí)行。即多個(gè)任務(wù)或多個(gè)中斷可以同時(shí)執(zhí)行。在絕大多數(shù)情況下,UP系統(tǒng)中與SMP系統(tǒng)中的互斥/同步機(jī)制〔例如,信號(hào)量、消息隊(duì)列等是一樣的。但是,UP中的一些機(jī)制〔例如,關(guān)中斷、掛起任務(wù)搶占機(jī)制以此來(lái)保護(hù)臨界資源等在SMP中是不適用的。這是因?yàn)檫@些機(jī)制阻礙了同時(shí)執(zhí)行的理念,降低了CPU的利用率,是的SMP系統(tǒng)向UP系統(tǒng)的回溯。SMP編程與UP編程的一點(diǎn)不同是關(guān)于taskLock<>和intLock<>的使用上。SMP提供了以下互斥/同步鎖機(jī)制進(jìn)行替代:任務(wù)級(jí)、中斷級(jí)的spinlock;任務(wù)級(jí)、中斷級(jí)的CPU-specific;原子操作;內(nèi)存障礙〔memorybarrierspinlock互斥/同步機(jī)制在UP〔單核編程中通過(guò)信號(hào)量的方法可以實(shí)現(xiàn)task的互斥與同步,在SMP系統(tǒng)中可以繼續(xù)沿用信號(hào)量的機(jī)制,而spinlock則用于替換UP編程中使用taskLock<>和intLock<>的地方。簡(jiǎn)介taskLock<>和intLock<>通過(guò)taskLock<>可以關(guān)閉系統(tǒng)的任務(wù)調(diào)度機(jī)制,調(diào)用taskLock<>的任務(wù)將是唯一獲得CPU運(yùn)行資源的任務(wù),直到這個(gè)任務(wù)調(diào)用taskUnlock<>為止。intLock<>與taskLock<>類似,intLock<>用于關(guān)閉中斷,使得中斷IRS無(wú)法執(zhí)行,直到調(diào)用者調(diào)用了intUnlock<>。Spinlock具有"滿內(nèi)存障礙"屬性VxWorksspinlock的獲取與釋放操作具備"滿內(nèi)存障礙"屬性。"滿內(nèi)存障礙"屬性可以使讀、寫內(nèi)存操作按照嚴(yán)格的順序執(zhí)行而不受到多CPU的影響。因此,在申請(qǐng)與釋放spinlock之間進(jìn)行更新的數(shù)據(jù)可以保證"更新順序"。Spinlock的種類Spinlock分為兩種:中斷級(jí)spinlock和任務(wù)級(jí)spinlock:中斷級(jí)spinlock:可用于關(guān)閉本地CPU的中斷。當(dāng)任務(wù)調(diào)用中斷級(jí)spinlock時(shí),將會(huì)關(guān)閉本CPU的任務(wù)搶占機(jī)制;任務(wù)級(jí)spinlock:用于關(guān)閉本地CPU的任務(wù)搶占機(jī)制?!脖镜贑PU指的是調(diào)用這些API的CPUSpinlock的作用以及使用說(shuō)明與信號(hào)量不同的是,當(dāng)一個(gè)任務(wù)試圖申請(qǐng)一個(gè)已被另一個(gè)任務(wù)占用的spinlock時(shí),該任務(wù)并不會(huì)進(jìn)入阻塞狀態(tài)〔pend,而是可以繼續(xù)運(yùn)行,它會(huì)進(jìn)入一個(gè)簡(jiǎn)單的、緊湊的循環(huán)直到spinlock得到釋放。這種等待spinlock釋放的狀態(tài)可以用’spinning’和’busywaiting’來(lái)描述。在此,我們可以看出spinlock的優(yōu)點(diǎn)和缺點(diǎn)。優(yōu)點(diǎn)是:由于任務(wù)〔或ISR在等待spinlock的時(shí)候沒有進(jìn)入pend狀態(tài)而是繼續(xù)執(zhí)行〔一個(gè)簡(jiǎn)單的循環(huán)用于獲取spinlock,這就避免了任務(wù)調(diào)用度以及上下文切換的消耗。缺點(diǎn)是:循環(huán)操作沒有實(shí)際意義,會(huì)占用CPU資源。因此,只有在必要時(shí)才使用spinlock。即占用spinlock的時(shí)間越短,spinlock的優(yōu)勢(shì)發(fā)揮的越明顯〔例如UP中的taskLock<>和intLock<>。否則,如果占用spinlock較長(zhǎng)的時(shí)間,在UP編程中的缺陷〔增加了任務(wù)和中斷的響應(yīng)時(shí)間同樣也會(huì)在多核編程中出現(xiàn)。在一個(gè)CPU上獲取spinlock,并不會(huì)影響另一個(gè)CPU上任務(wù)和中斷的調(diào)度機(jī)制。當(dāng)一個(gè)任務(wù)在持有spinlock的時(shí)候,該任務(wù)不能被刪除。中斷級(jí)spinlock任務(wù)和中斷都可以獲使用中斷級(jí)spinlock。有兩種中斷級(jí)spinlock:確定性的和非確定性的。[注意]在UP系統(tǒng)中,中斷級(jí)spinlock與intLock<>和intUnlock<>的效果是一樣的。確定性中斷級(jí)spinlock確定性中斷級(jí)spinlock的最大特點(diǎn)是:公平、確定性。Spinlock會(huì)分給第一個(gè)申請(qǐng)的中斷或任務(wù)。申請(qǐng)的spinlock會(huì)屏蔽掉本地CPU的其他中斷。如果是一個(gè)任務(wù)申請(qǐng)了中斷用spinlock,本地CPU的任務(wù)調(diào)度機(jī)制將被停止直到該任務(wù)釋放spinlock。Spinlock確保了任務(wù)可以獨(dú)占CPU完成一些操作。其他CPU上的中斷和任務(wù)不會(huì)受到干擾。確定性中斷級(jí)spinlock的API全部包含在spinLockLib中,API如表1所示。表SEQ表\*ARABIC1確定性中斷級(jí)spinlock的APIAPI描述voidspinLockIsrInit<spinlockIsr_t*pLock,/*pointertoISR-callablespinlock*/intflags /*spinlockattributes*/>初始化確定性中斷級(jí)spinlockvoidspinLockIsrTake<spinlockIsr_t*pLock/*pointertoISR-callablespinlock*/>獲取確定性中斷級(jí)spinlockvoidspinLockIsrGive<spinlockIsr_t*pLock/*pointertoISR-callablespinlock*/>釋放確定性中斷級(jí)spinlock非確定性中斷級(jí)spinlock非確定性中斷級(jí)spinlock提供了更高的性能,但是當(dāng)多個(gè)CPU試圖同時(shí)申請(qǐng)一個(gè)spinlock時(shí),它并不保證公平性和確定性。即非確定性中斷級(jí)spinlock并不一定會(huì)把spinlock分配給第一個(gè)申請(qǐng)者。它的優(yōu)勢(shì)在于中斷響應(yīng)時(shí)間較短,即當(dāng)CPU等待獲取spinlock的時(shí)候,中斷不會(huì)被屏蔽。API如表2所示。表SEQ表\*ARABIC2非確定性中斷級(jí)spinlock的APIAPI描述voidspinLockIsrNdInit<spinlockIsrNd_t*spin/*pointertospinlock*/>初始化非確定性中斷級(jí)spinlockintspinLockIsrNdTake<spinlockIsrNd_t*spin/*pointertospinlock*/>獲取非確定性中斷級(jí)spinlockvoidspinLockIsrNdGive<spinlockIsrNd_t*spin,intkey/*returnvalueofspinLockIsrNdTake*/>釋放非確定性中斷級(jí)spinlock任務(wù)級(jí)spinlock任務(wù)級(jí)spinlock〔中斷不可調(diào)用該spinlock可以關(guān)掉本地CPU的任務(wù)切換機(jī)制,使持有spinlock的任務(wù)獨(dú)占CPU完成一些操作。同時(shí),它不會(huì)對(duì)其他CPU上的任務(wù)調(diào)度機(jī)制產(chǎn)生影響。[注意]SMP中任務(wù)級(jí)spinlock等同于UP編程中的taskLock<>和taskUnlock<>API如表3所示。表SEQ表\*ARABIC3任務(wù)級(jí)spinlock的APIAPI描述voidspinLockTaskInit<spinlockTask_t*pLock,/*pointertotask-onlyspinlock*/intflags /*spinlockattributes*/>初始化任務(wù)級(jí)spinlockvoidspinLockTaskTake<spinlockTask_t*pLock/*pointertotask-onlyspinlock*/>獲取任務(wù)級(jí)spinlockvoidspinLockTaskGive<spinlockTask_t*pLock/*pointertotask-onlyspinlock*/>釋放任務(wù)級(jí)spinlockSpinlock的使用注意事項(xiàng)由于SMP系統(tǒng)允許任務(wù)的同時(shí)運(yùn)行,因此在使用spinlock的時(shí)候需要注意以下事宜:spinlock最好用于短時(shí)間占用的情況;任務(wù)〔或中斷一次只能申請(qǐng)一個(gè)spinlock。當(dāng)一個(gè)已申請(qǐng)了spinlock的實(shí)體再一次申請(qǐng)了另一個(gè)spinlock時(shí),很有可能會(huì)造成死鎖;任務(wù)〔或中斷不能申請(qǐng)它已經(jīng)持有的spinlock。這可能會(huì)造成死鎖;持有spinlock的任務(wù)〔或中斷不能再調(diào)用一些特殊函數(shù)〔尤其是內(nèi)核函數(shù),由于這些特殊函數(shù)本身持有spinlock,這種操作可能會(huì)導(dǎo)致死鎖。Spinlock的調(diào)式版本Spinlock的調(diào)試版本可以運(yùn)行那些開發(fā)中使用了spinlock的程序?qū)pinlock的情況進(jìn)行調(diào)試。這需要添加INCLUDE_SPINLOCK_DEBUG組件。如果添加了INCLUDE_EDR_ERRLOG組件,則當(dāng)由使用spinlock造成的系統(tǒng)異常進(jìn)而重啟后,相關(guān)信息會(huì)被記錄下來(lái)。會(huì)產(chǎn)生錯(cuò)誤信息的情況如表4所示。表SEQ表\*ARABIC4使用spinlock會(huì)出現(xiàn)錯(cuò)誤的情況使用的API錯(cuò)誤信息spinLockTaskTake<>一個(gè)中斷任務(wù)使用了該API申請(qǐng)了已持有的spinlock嵌套申請(qǐng)spinlockspinLockTaskGive<>一個(gè)中斷任務(wù)使用了該API試圖釋放一個(gè)沒有申請(qǐng)過(guò)的spinlockspinLockIsrTake<>申請(qǐng)了已持有的spinlock嵌套申請(qǐng)spinlockspinLockIsrGive<>試圖釋放一個(gè)沒有申請(qǐng)過(guò)的spinlockSpinlock中限制使用的系統(tǒng)API當(dāng)任務(wù)〔或中斷持有spinlock時(shí),一些系統(tǒng)API不能被調(diào)用〔具體原因見Spinlock的使用注意事項(xiàng)。這樣做為的是防止持有spinlock的任務(wù)或ISR進(jìn)入內(nèi)核臨界區(qū),這可能會(huì)導(dǎo)致死鎖的發(fā)生。這種限制對(duì)于intCpuLock<>也是適用的。這是因?yàn)橛行﹥?nèi)核API需要中斷操作。這些限制看起來(lái)好像使spinlock的運(yùn)用受到影響,但是它們卻是有必要的。Spinlock適用于進(jìn)程間很快的同步/互斥情況。若將spinlock用在會(huì)進(jìn)行大量操作——包括內(nèi)核API調(diào)用等——的情況時(shí),則會(huì)導(dǎo)致SMP性能的下降。這是因?yàn)楫?dāng)使用spinlock時(shí),任務(wù)搶占機(jī)制以及中斷都將會(huì)被關(guān)閉。圖7列出了在使用spinlock和CPUlock時(shí)限制使用的系統(tǒng)API。圖SEQ圖\*ARABIC7spinlock中限制使用的系統(tǒng)APICPU-specific互斥機(jī)制VxWorksSMP提供了一種基于CPU-specific的互斥機(jī)制,它可以嚴(yán)格限定互斥操作的范圍在調(diào)用該操作的CPU〔本地CPU上執(zhí)行。通過(guò)設(shè)計(jì)CPU-specific使得將UP代碼轉(zhuǎn)到SMP系統(tǒng)上變得容易。中斷級(jí)CPU-specific中斷級(jí)CPU-specific可以關(guān)閉本地CPU上的中斷。例如,當(dāng)任務(wù)A在CPU-0上運(yùn)行一個(gè)本地CPU的中斷鎖操作,則該CPU將不再允許其他中斷執(zhí)行,直到任務(wù)A釋放這個(gè)鎖。SMP系統(tǒng)中其他的CPU將不會(huì)受到影響。對(duì)于那些想要使用CPU-specific互斥機(jī)制的任務(wù)和ISR,必須使用CPU-Affinity將它們指定運(yùn)行在本地CPU上,只有這樣CPU-specific互斥才會(huì)有意義。與spinlock一樣,在執(zhí)行中斷鎖的任務(wù)中有些系統(tǒng)API不能被使用〔詳見圖7。中斷級(jí)CPU-specific的API如表5所示。[注意]在UP中,它們默認(rèn)的操作與intLock<>和intUnlock<>一樣。表SEQ表\*ARABIC5中斷級(jí)CPU-specific互斥APIAPI描述intintCpuLock<void>;當(dāng)CPU-0上的任務(wù)或ISR調(diào)用了該函數(shù)后,則禁止在CPU-0上的一切中斷調(diào)用。voidintCpuUnlock<intlockKey /*lock-outkeyreturnedbyprecedingintCpuLock<>*/>恢復(fù)在CPU-0上的中斷調(diào)用。任務(wù)級(jí)CPU-specific任務(wù)級(jí)CPU-specific可以關(guān)閉調(diào)用該API的CPU上的任務(wù)搶占機(jī)制。例如,當(dāng)運(yùn)行在CPU-0上的任務(wù)A調(diào)用了任務(wù)鎖操作,則該CPU上將禁止任務(wù)切換,即該CPU上其他任務(wù)將不能得到運(yùn)行,直到任務(wù)A釋放了這個(gè)鎖或執(zhí)行了一個(gè)阻塞操作。[注意]調(diào)用該操作的任務(wù)是不能被移交到另外的CPU上運(yùn)行的,直到這個(gè)鎖被釋放。SMP系統(tǒng)中其他的CPU將不會(huì)受到影響。對(duì)于那些想要使用CPU-specific互斥機(jī)制的任務(wù)和ISR,必須使用CPU-Affinity將它們指定運(yùn)行在本地CPU上,只有這樣CPU-specific互斥才會(huì)有意義。任務(wù)級(jí)CPU-specific的API如表6所示。[注意]在UP編程中,他們默認(rèn)的操作與taskLock<>和taskUnlock<>類似。表SEQ表\*ARABIC6任務(wù)級(jí)CPU-specific互斥APIAPI描述taskCpuLock<>當(dāng)CPU-0上的任務(wù)或ISR調(diào)用了該函數(shù)后,則禁止在CPU-0上的一切任務(wù)切換。taskCpuUnlock<>恢復(fù)在CPU-0上的任務(wù)切換。MemoryBarrier在現(xiàn)代多核體系中,CPU需要對(duì)讀、寫操作完成重排序,為的是提高系統(tǒng)的整體性能。而在單核CPU中,這種重排序完全是透明的,因?yàn)闊o(wú)論系統(tǒng)如何對(duì)讀、寫操作進(jìn)行排序,CPU都能確保任何讀操作獲取的數(shù)據(jù)都是之前已寫入的數(shù)據(jù)。在多核體系中,當(dāng)一個(gè)CPU執(zhí)行了一系列寫內(nèi)存操作時(shí),這些寫操作將會(huì)在CPU執(zhí)行操作寫到內(nèi)存之前被排序。CPU可以將這些寫內(nèi)存的操作按任意順序排列,無(wú)論是哪條指令先到達(dá)的CPU。類似的,CPU可以將多個(gè)讀操作并行處理。由于這種重排序的存在,兩個(gè)有共享數(shù)據(jù)的任務(wù)不能保證:一個(gè)任務(wù)在CPU0上執(zhí)行讀、寫操作的順序與另一個(gè)任務(wù)在CPU1獲取對(duì)應(yīng)數(shù)據(jù)的順序是一致的。關(guān)于重排序問(wèn)題有一個(gè)經(jīng)典的例子:在一個(gè)雙核CPU系統(tǒng)中,一個(gè)CPU正在準(zhǔn)備工作,當(dāng)設(shè)置一個(gè)bool變量為true時(shí),告知另一個(gè)CPU這個(gè)工作準(zhǔn)備就緒,在此之前,另一個(gè)CPU一直處于等待狀態(tài)。這個(gè)程序的代碼就像下面一樣:/*CPU0–announcetheavailabilityofwork*/pWork=&work_item; /*storepointertoworkitemtobeperformed*/workAvailable=true;/*CPU1–waitforworktobeperformed*/while<!workAvailable>;doWork<pWork>; /*error–pWorkmightnotbevisibletothisCPUyet*/這個(gè)程序的結(jié)果很有可能是CPU1使用的pWork指針指向了不正確的數(shù)據(jù),這是因?yàn)镃PU0會(huì)重排序它的寫內(nèi)存操作,這就會(huì)導(dǎo)致CPU1在觀察到workAvailable改變的時(shí)候而pWork還未被更新。為了解決內(nèi)存操作排序問(wèn)題,VxWorks提供了一系列的"memorybarrier"操作。這些操作的唯一目的就是提供一種方法可以確保CPU間操作順序的一致性。memorybarrier分為三個(gè)主要方面:讀memorybarrier,寫memorybarrier,滿〔讀寫memorybarrier。[注意]VxWorksSMP提供了一系列同步原語(yǔ)來(lái)保護(hù)共享資源。這些原語(yǔ)包括:信號(hào)量、消息隊(duì)列、spinlock等。這些原語(yǔ)中已經(jīng)包括了滿memorybarrier功能,不用再添加其他的memorybarrier操作來(lái)保護(hù)共享資源。[注意]memorybarrier不能用在用戶模式的RTPapp中。讀memorybarrierVX_MEM_BARRIER_R<>宏定義提供讀memorybarrier。VX_MEM_BARRIER_R<>會(huì)強(qiáng)制所有讀操作進(jìn)行排序。如果沒有barrier,CPU會(huì)隨意的為這些讀操作進(jìn)行排序。對(duì)于一個(gè)單核CPU而言不受影響。例如,CPU可以隨意重排序一下讀操作的順序:a=*pAvalue; /*讀可能發(fā)生在讀pBvalue之后*/b=*pBvalue; /*讀可能發(fā)生在讀pAvalue之前*/通過(guò)在讀操作間加入memorybarrier,可以保證讀的順序,例如:a=*pAvalue; /*讀發(fā)生在讀pBvalue之前*/VX_MEM_BARRIER_R<>;b=*pBvalue; /*讀發(fā)生在讀pAvalue之后*/在使用VX_MEM_BARRIER_R<>后可以確保讀數(shù)據(jù)的順序是正確的。但是,這種保證只有在"寫數(shù)據(jù)"能夠保證順序正確的前提下才能有效。即VX_MEM_BARRIER_R<>和VX_MEM_BARRIER_W<>宏定義必須一起使用。寫MemoryBarrierVX_MEM_BARRIER_W<>宏定義提供寫memorybarrier。VX_MEM_BARRIER_W<>會(huì)強(qiáng)制所有寫操作進(jìn)行排序。以下程序片段來(lái)自前面的代碼,通過(guò)加入寫memorybarrier后對(duì)代碼進(jìn)行了改進(jìn):pWork=&work_item;VX_MEM_BARRIER_W<>;workAvailable=true;通過(guò)加入barrier可以確保pWork的更新一定先于workAvailable.[注意]VX_MEM_BARRIER_W<>并不是強(qiáng)迫將變量寫入內(nèi)存,而是指定了寫的順序[注意]VX_MEM_BARRIER_W<>必須與VX_MEM_BARRIER_R<>一起使用。讀寫〔滿MemoryBarrierVX_MEM_BARRIER_RW<>宏定義提供讀/寫〔滿memorybarrier。VX_MEM_BARRIER_RW<>包括了VX_MEM_BARRIER_R<>和VX_MEM_BARRIER_W<>的功能。使用VX_MEM_BARRIER_RW<>的代價(jià)要高于VX_MEM_BARRIER_R<>或VX_MEM_BARRIER_W<>的使用代價(jià)。WindRiver不推薦使用VX_MEM_BARRIER_RW<>。原子的內(nèi)存操作〔原子操作原子操作是利用了CPU支持原子訪問(wèn)內(nèi)存的特點(diǎn)。原子操作是一些不能被中斷的操作的集合。原子操作為一組操作提供了互斥性〔例如變量的自增、自減操作。使用原子操作更新一個(gè)數(shù)據(jù),可以省去使用鎖的步驟。例如,你想更新一個(gè)鏈表元素的next指針從NULL到非NULL,當(dāng)你使用原子操作時(shí),這個(gè)過(guò)程就不用使用中斷鎖了,這樣可以使算法變得簡(jiǎn)單。在調(diào)用者使用原子操作的時(shí)候,必須保證該操作所在的內(nèi)存是可以訪問(wèn)的。若訪問(wèn)了一個(gè)不可訪問(wèn)的內(nèi)存,會(huì)產(chǎn)生一個(gè)異常。在vxAtmicLib庫(kù)中提供了許多原子操作。如表7所示。需要注意的是vxAtmicLib還提供了這些原子操作的inline版本。例如,vxAtomicAdd_inline<>。還提供了兼容SMP和AMP的版本。例如vxAtomic32Add<>。原子操作可以在用戶〔RTPAPP、內(nèi)核空間中使用。表SEQ表\*ARABIC7原子操作APIAPI描述atomicVal_tvxAtomicAdd<atomic_t* target, /*memorylocationtoaddto*/atomicVal_t value /*valuetoadd*/>將兩個(gè)值相加。atomicVal_tvxAtomicSub<atomic_t* target, /*memorylocationtosubtractfrom*/atomicVal_t value /*valuetosub*/>將兩個(gè)值相減atomicVal_tvxAtomicInc<atomic_t* target /*memorylocationtoincrement*/>將值增加1atomicVal_tvxAtomicDec<atomic_t* target /*memorylocationtodecrement*/>將值減1atomicVal_tvxAtomicOr<atomic_t* target, /*memorylocationtoOR*/atomicVal_t value /*ORwiththisvalue*/>將兩個(gè)值進(jìn)行位或操作atomicVal_tvxAtomicXor<atomic_t* target, /*memorylocationtoXOR*/atomicVal_t value /*XORwiththisvalue*/>將兩個(gè)值進(jìn)行位異或操作atomicVal_tvxAtomicAnd<atomic_t* target, /*memorylocationtoAND*/atomicVal_t value /*ANDwiththisvalue*/>將兩個(gè)值進(jìn)行位與操作,atomicVal_tvxAtomicNand<atomic_t* target, /*memorylocationtoNAND*/atomicVal_t value /*NANDwiththisvalue*/>將兩個(gè)值進(jìn)行位非與操作atomicVal_tvxAtomicSet<atomic_t* target, /*memorylocationtoset*/atomicVal_t value /*setwiththisvalue*/>將一個(gè)值設(shè)定為另一個(gè)值atomicVal_tvxAtomicClear<atomic_t* target /*memorylocationtoclear*/>將一個(gè)值清空BOOLvxCas<atomic_t* target, /*memorylocationtocompare-and-swap*/atomicVal_t oldValue, /*comparetothisvalue*/atomicVal_t newValue /*swapwiththisvalue*/>對(duì)比或交換內(nèi)存中的值。CPUAffinityVxWorksSMP提供了CPUAffinity這種機(jī)制。通過(guò)這種機(jī)制可以將中斷或者任務(wù)分配給指定的CPU執(zhí)行。任務(wù)級(jí)CPUAffinityVxWorksSMP具有將任務(wù)分配給指定CPU執(zhí)行的能力。從另一個(gè)角度來(lái)說(shuō),即將指定CPU預(yù)留給指定任務(wù)。SMP的默認(rèn)操作——任何任務(wù)可以運(yùn)行在任何CPU上——這會(huì)根據(jù)系統(tǒng)的整體性能而定。但是有些時(shí)候?qū)⒅付ㄈ蝿?wù)分配給指定的CPU對(duì)系統(tǒng)性能是有幫助的。例如一個(gè)CPU上只運(yùn)行一個(gè)單獨(dú)的任務(wù)而不做其他事情,則這塊CPU的cache中就只保存了這個(gè)任務(wù)所需要的數(shù)據(jù)和代碼。這樣做節(jié)省了任務(wù)在CPU之間切換的消耗。還有個(gè)例子就是當(dāng)多個(gè)任務(wù)爭(zhēng)奪一個(gè)spinlock時(shí),如果這些任務(wù)運(yùn)行在不同的CPU上,則會(huì)有大量的時(shí)間被浪費(fèi)在等待spinlock上。若將爭(zhēng)奪同一個(gè)spinlock的任務(wù)指定在同一個(gè)CPU上運(yùn)行,則這會(huì)給另一塊CPU上執(zhí)行其他程序帶來(lái)便利。關(guān)于任務(wù)級(jí)CPUaffinity的使用方法如下:一個(gè)任務(wù)可以通過(guò)調(diào)用taskCpuAffinitySet<>設(shè)置自己的CPUaffinity,也可以設(shè)置其他任務(wù)的CPUaffinity;子任務(wù)會(huì)繼承父任務(wù)的CPUaffinity。一個(gè)任務(wù)中調(diào)用如下API就會(huì)自動(dòng)繼承CPUaffinity:taskSpawn<>,taskCreate<>,taskInit<>,taskOpen<>,taskInitExcStk<>。任務(wù)級(jí)CPUaffinity的API如表8所示。表SEQ表\*ARABIC8任務(wù)級(jí)CPUAffinity的APIAPI描述STATUStaskCpuAffinitySet<inttid,/*taskID*/cpuset_tnewAffinity/*newaffinityset*/>分配一個(gè)任務(wù)在指定CPU上執(zhí)行。STATUStaskCpuAffinityGet<inttid,/*taskID*/cpuset_t*pAffinity/*addresstostoretask'saffinity*/>獲得指定任務(wù)在哪個(gè)CPU上執(zhí)行。taskCpuAffinitySet<>和taskCpuAffinityGet<>都使用cpuset_t結(jié)構(gòu)對(duì)CPU信息進(jìn)行標(biāo)示。前者用于分配任務(wù)到指定CPU;后者獲取指定任務(wù)的cpu_set_t。CPUSET_ZERO<>宏定義用于將cpuset_t清0〔類似FD_ZERO,它必須被最先調(diào)用。CPUSET_SET<>宏定義在CPUSET_ZERO<>之后使用。RTP任務(wù)與CPUAffinity默認(rèn)情況下,RTP任務(wù)會(huì)繼承父任務(wù)的CPUAffinity屬性。如果父任務(wù)沒有CPUAffinity屬性,則RTP任務(wù)也沒有CPUAffinity屬性。如果父任務(wù)有CPUAffinity屬性,則RTP任務(wù)也繼承該CPUAffinity屬性并僅運(yùn)行在對(duì)應(yīng)的CPU上。在使用rtpSpawn<>時(shí),RTP_CPU_AFFINITY_NONE選項(xiàng)表示創(chuàng)建RTP時(shí)不繼承CPUAffinity屬性,即使父任務(wù)具有CPUAffinity屬性。任務(wù)級(jí)CPUAffinity示例:以下代碼說(shuō)明了創(chuàng)建一個(gè)任務(wù),并將該任務(wù)分配給CPU1執(zhí)行的全過(guò)程:[藍(lán)色部分表示調(diào)用的關(guān)鍵API]STATUSaffinitySetExample<void>{cpuset_taffinity;inttid;/*Createthetaskbutonlyactivateitaftersettingitsaffinity*/Tid=taskCreate<"myCpu1Task",100,0,5000,printf,<int>"myCpu1TaskexecutedonCPU1!",0,0,0,0,0,0,0,0,0>;if<tid==NULL>returnERROR;/*CleartheaffinityCPUsetandsetindexforCPU1*/CPUSET_ZERO<affinity>;CPUSET_SET<affinity,1>;if<taskCpuAffinitySet<tid,affinity>==ERROR>{ taskDelete<tid>; returnERROR;}/*NowletthetaskrunonCPU1*/taskActivate<tid>;returnOK;}下面這個(gè)例子是一個(gè)任務(wù)如何刪除它的CPUaffinity:{cpuset_taffinity;CPUSET_ZERO<affinity>;/*passingatidequaltozerocausesanaffinitytobesetforthecallingtask*/taskCpuAffinitySet<0,affinity>;}中斷級(jí)CPUAffinitySMP硬件需要可編程中斷控制設(shè)備。VxWorksSMP利用這些硬件可以分配中斷到指定CPU。默認(rèn)情況下,中斷是在VxWorks的CPU0中觸發(fā)的。通過(guò)中斷級(jí)CPUAffinity,可以將中斷合理平均的分配到不同CPU上〔而不是在一個(gè)CPU上存在很多中斷。運(yùn)行時(shí)刻分配中斷到指定CPU是在啟動(dòng)時(shí)發(fā)生的,當(dāng)系統(tǒng)啟動(dòng)從BSP中讀取中斷配置信息的時(shí)候。然后,中斷控制器收到一條命令,該命令用于指示一條中斷運(yùn)行在指定的CPU上。將CPU預(yù)留給使用了CPUAffinity的任務(wù)〔CPU預(yù)留機(jī)制VxWorksSMP提供了一種機(jī)制可以將CPU預(yù)留給那些已經(jīng)使用了CPUAffinity的任務(wù)。這種機(jī)制可以防止其他任務(wù)與使用了CPU預(yù)留機(jī)制的任務(wù)搶占CPU資源,因此它提升了系統(tǒng)效率。CPU預(yù)留機(jī)制的API如表9所示。表SEQ表\*ARABIC9CPU預(yù)留機(jī)制APIAPI描述STATUSvxCpuReservedGet<cpuset_t*pCpuSet>獲取可預(yù)留CPU的集合STATUSvxCpuReserve<cpuset_tcpus, /*CPUstobereserved*/cpuset_t*pReservedCpus /*CPUsreserved*/>預(yù)留CPU集合cpus,返回CPU預(yù)留的結(jié)果pReservedCpusSTATUSvxCpuUnreserve<cpuset_tcpus>解除某個(gè)CPU的預(yù)留機(jī)制。默認(rèn)情況下,當(dāng)CPU沒有使用vxCpuReserve<>時(shí),所有CPU都是可以被預(yù)留的??梢酝ㄟ^(guò)配置VX_SMP_CPU_EXPLICIT_RESERVE參數(shù),將指定CPU排除在CPU預(yù)留池之外。只有在CPU池中的CPU才可以被預(yù)留。taskCpuAffinitySet<>與vxCpuReserve<>沒有明確的調(diào)用順序。前者是把任務(wù)分配給指定的CPU,這樣可以防止該任務(wù)運(yùn)行在其他CPU上。而后者限定了CPU上可以運(yùn)行哪些任務(wù)??梢愿鶕?jù)具體情況分先后調(diào)用這兩者。[注意]如果一個(gè)任務(wù)使用了CPUAffinity,則它的子任務(wù)將會(huì)繼承CPU-Affinity屬性;如果一個(gè)CPU預(yù)留給了使用CPUAffinity的任務(wù),則這些任務(wù)的子任務(wù)將會(huì)在這個(gè)CPU上運(yùn)行。CPU預(yù)留與任務(wù)級(jí)CPUAffinity的示例以下程序片段展示了如何預(yù)留一個(gè)CPU以及設(shè)置一個(gè)任務(wù)級(jí)CPUaffinity在預(yù)留CPU上執(zhí)行的過(guò)程:[藍(lán)色部分表示調(diào)用的關(guān)鍵API]voidmyRtn<void>{ cpuset_tcpuset; /*InputargumenttovxCpuReserve<>*/ cpuset_tresCpuSet; /*ReturnargumentfromvxCpuReserve<>*/ /*PassinganemptycpusetasinputreservesanarbitraryCPU*/CPUSET_ZERO<cpuset>;if<vxCpuReserve<cpuset,&resCpuSet>==OK>{ /*setaffinityforcurrenttask*/ if<taskCpuAffinitySet<0,resCpuSet>!=OK> /*handleerror*/}else{ /*handleerror*/}}以下代碼片段展示了如何預(yù)留一個(gè)或多個(gè)指定CPU并且為多個(gè)任務(wù)設(shè)置CPUaffinity:voidmyRtn<void>{ externinttids[3]; /*sometaskIds*/ intcpuIx[]={1,2,4}; /*CPUindicestoreserve*/ cpuset_t cpuSet; cpuset_t tmpCpuSet; inti; /*InitializecpuSetwiththedesiredCPUindices*/CPUSET_ZERO<cpuSet>;CPUSET_SET<cpuSet,cpuIx[0]>;CPUSET_SET<cpuSet,cpuIx[1]>;CPUSET_SET<cpuSet,cpuIx[2]>;/*ReservethespecifiedCPUs*/if<vxCpuReserve<cpuSet,NULL>==OK>{ for<i=0;i<3;++i> { tmpCpuSet=CPUSET_FIRST_SET<cpuSet>; if<taskCpuAffinitySet<tids[i],tmpCpuSet>!=OK> /*handleerror*/CPUSET_SUB<cpuSet,tmpCpuSet>; }}else{ /*handleerror*/}}CPU信息及管理VxWorksSMP提供了一些API和宏定義用于獲取及操作CPU的信息。CPU的信息及管理APIkernelLib和vxCpuLib庫(kù)提供了用于獲取CPU信息以及管理CPU的相關(guān)API。kernelLib中的CPUAPI如表10所示。表SEQ表\*ARABIC10CPU內(nèi)核信息APIAPI描述BOOLkernelIsCpuIdle<unsignedintcpu /*CPUtoquerystatusof*/>查看指定CPU是否為空閑狀態(tài)。返回TRUE表示CPU為空閑狀態(tài)。BOOLkernelIsSystemIdle<void>查看所有可用CPU是否為空閑狀態(tài)。返回TRUE表示為空閑狀態(tài)。STATUSkernelCpuEnable<unsignedintcpuToEnable/*logicalindexofCPUtoenable*/>通過(guò)輸入index參數(shù)使能CPUkernelCpuEnable<>可以通過(guò)輸入index參數(shù)來(lái)使能指定的CPU。一旦CPU使能,任務(wù)調(diào)度機(jī)制開始在該CPU上分配任務(wù)。所有CPU在默認(rèn)情況下是使能狀態(tài),可以將組件ENABLE_ALL_CPUS設(shè)置為FALSE,這樣VxWorksSMP系統(tǒng)啟動(dòng)后只有CPU0為使能狀態(tài)。然后,再通過(guò)kernelCpuEnable<>可以使能指定的CPU。vxCpuLib中的CPUAPI如表11所示。表SEQ表\*ARABIC11CPU信息APIAPI描述unsignedintvxCpuConfiguredGet<void>返回在SMP系統(tǒng)中已配置的CPU個(gè)數(shù)cpuset_tvxCpuEnabledGet<void>返回使能CPU的個(gè)數(shù)unsignedintvxCpuIndexGet<void>返回當(dāng)前CPU的索引〔邏輯編號(hào)cpuid_tvxCpuIdGet<void>返回當(dāng)前CPU的ID〔有體系結(jié)構(gòu)變量定義的,非OS定義的邏輯編號(hào)使用vxCpuConfiguredGet<>返回的是配置在BSP中的VxWorksSMP系統(tǒng)的CPU個(gè)數(shù)。這個(gè)值可能與硬件實(shí)際存在的CPU個(gè)數(shù)不一致。使用vxCpuEnabledGet<>返回的是系統(tǒng)中運(yùn)行CPU的個(gè)數(shù)。這個(gè)值可能與vxCpuConfiguredGet<>返回的值不一致,也可能與硬件中實(shí)際存在的CPU個(gè)數(shù)不一致vxCpuEnabledGet<>的返回值類型為cpuset_t,因此我們需要注意:再給返回值賦值之前,我們必須使用CPUSET_ZERO<>將cpuset_t變量清0.vxCpuIndexGet<>返回的是當(dāng)前調(diào)用任務(wù)使用的CPU索引〔邏輯編號(hào)。該編號(hào)在0和N-1之間〔N是vxCpuConfiguredGet<>的返回值。需要注意的是:默認(rèn)情況下,任務(wù)可以從一個(gè)CPU跑到另一個(gè)CPU上執(zhí)行,所以不能保證任務(wù)結(jié)束后所在的CPU索引與剛才使用vxCpuIndexGet<>返回的值是一致的。除非該任務(wù)是分配運(yùn)行在指定CPU上的,或者使用了taskCpuLock<>或者intCpuLock<>。CPU相關(guān)變量以及宏定義VxWorksSMP提供了一組變量和宏定義,通過(guò)設(shè)置這些值可以對(duì)CPU的配置進(jìn)行控制。例如cpuset_t,它用于標(biāo)識(shí)配置在VxWorksSMP系統(tǒng)中的CPU。Cpuset_t的位值標(biāo)識(shí)了CPU的邏輯索引,Cpuset_t的第一位標(biāo)識(shí)了CPU0,第二位標(biāo)識(shí)了CPU1,第三位標(biāo)識(shí)了CPU2,以此類推〔它與CPU在硬件中的物理位置無(wú)關(guān)。例如,有8個(gè)CPU的硬件系統(tǒng),在BSP中為VxWorksSMP配置了4塊CPU,通過(guò)CPUSET_ZERO<>可以講cpuset_t中的位值清0。調(diào)用vxCpuIndexGet<>,它的返回值只會(huì)設(shè)置前四位。CPU宏定義用于設(shè)置和清除CPU索引〔通過(guò)改變cpuset_t的值。這些宏定義如圖12所示。表SEQ表\*ARABIC12操控CPU信息的宏定義API描述CPUSET_SET<cpuset,n>設(shè)置CPU的索引〔只針對(duì)一個(gè)CPU進(jìn)行設(shè)置CPUSET_SETALL<cpuset>設(shè)置CPU的索引〔針對(duì)所有CPU進(jìn)行設(shè)置CPUSET_SETALL_BUT_SELF<cpuset>設(shè)置CPU的索引<除了調(diào)用該宏的CPU之外的所有CPU>CPUSET_CLR<cpuset,n>清除一個(gè)指定的CPU索引〔只針對(duì)一個(gè)CPU進(jìn)行設(shè)置CPUSET_ZERO<cpuset>清除所有CPU索引〔針對(duì)所有CPU進(jìn)行設(shè)置CPUSET_ISSET<cpuset,n>當(dāng)指定索引存在于cpuset_t中時(shí),返回TRUE,CPUSET_ISZERO<cpuset>當(dāng)cpuset_t中沒有索引時(shí),返回TRUE,CPUSET_ATOMICSET<cpuset,n>原子地設(shè)置CPU的索引〔只針對(duì)一個(gè)CPU進(jìn)行設(shè)置CPUSET_ATOMICCLR<cpuset,n>原子地清除CPU的索引〔只針對(duì)一個(gè)CPU進(jìn)行設(shè)置[注意]不要直接對(duì)cpuset_t進(jìn)行操作,而是要通過(guò)上面的宏定義間接的對(duì)cpuset_t進(jìn)行操作。查看任務(wù)性能API通過(guò)checkStack<>可以查看所有任務(wù)棧的使用情況。在shell下輸入checkStack就可以得到下面的信息。如圖8所示。圖SEQ圖\*ARABIC8通過(guò)checkStack檢測(cè)任務(wù)棧情況SIZE表示任務(wù)棧的大小,CUR表示當(dāng)前使用任務(wù)棧的大小,HIGH表示使用任務(wù)棧的峰值,MARGIN表示從沒有使用過(guò)的任務(wù)棧大小〔其中MARGIN=SIZE-HIGH。通過(guò)spy<>可以上報(bào)任務(wù)在內(nèi)核空間、中斷、idle中的tick使用情況。在shell下輸入spy就可以得到下面的信息。如圖9所示。圖SEQ圖\*ARABIC9通過(guò)spy查看CPU使用率情況Spy開啟了tSpyTask用于監(jiān)控系統(tǒng)任務(wù)的使用情況。IDLE表示空閑任務(wù)的CPU占用情況。SMP性能優(yōu)化SMP的目的就是提高系統(tǒng)的性能。如果僅僅是簡(jiǎn)單的使用SMP的代碼,并不能完全發(fā)揮出SMP的潛能。因此,在SMP代碼的基礎(chǔ)上還需要進(jìn)行優(yōu)化。SMP算法是否能夠提高系統(tǒng)性能很大程度上取決于算法并行性的程度以及多線程獨(dú)立的程度。有些算法是高可并行性的,并且很好的利用了多CPU。一個(gè)很好的例子:圖形壓縮器可以在獨(dú)立的線程中分別壓縮一整塊數(shù)據(jù)中的一小塊。如果SMP算法不好的話,那么同時(shí)執(zhí)行兩個(gè)線程的消耗將會(huì)抵消掉多個(gè)CPU所帶來(lái)的好處。類似的,如果存在很多共享數(shù)據(jù),即多個(gè)CPU需要爭(zhēng)奪的數(shù)據(jù),那么系統(tǒng)將會(huì)增大爭(zhēng)奪、等待數(shù)據(jù)的消耗。不好的算法會(huì)導(dǎo)致更糟糕的情況,即SMP系統(tǒng)反而不如UP系統(tǒng)運(yùn)行的快。最好的情況是使運(yùn)算速度提高一倍。線程化線程化包括將一個(gè)單線程的APP通過(guò)任務(wù)復(fù)制的方式變成多線程。一個(gè)典型的例子是:?jiǎn)拘岩粋€(gè)"工人"任務(wù),這個(gè)任務(wù)的工作是從一個(gè)隊(duì)列中獲取工作,還有一個(gè)任務(wù)或ISR負(fù)責(zé)往這個(gè)隊(duì)列中填充工作。假設(shè)瓶頸出現(xiàn)在"工人"任務(wù)中,我們可以通過(guò)復(fù)制"工人"任務(wù)的方式提高系統(tǒng)的性能。線程化不是一個(gè)新的概念了,在出現(xiàn)多線程OS的時(shí)候我們就已經(jīng)知道這個(gè)詞了。但是在一個(gè)UP系統(tǒng)中,線程化只能增加任務(wù)的吞吐量,即雖然線程增加了,但它們卻在等待資源。到頭來(lái)我們發(fā)現(xiàn)瓶頸是在CPU本身,而線程化并不能提高性能。例如,在一個(gè)UP系統(tǒng)上計(jì)算密集的APP,線程化不能幫上什么忙。但是在SMP系統(tǒng)上情況有所不同,線程化可以有效地提高系統(tǒng)性能,這是因?yàn)镾MP系統(tǒng)解決了CPU的瓶頸問(wèn)題。使用Spinlock使用spinlock會(huì)潛移默化的影響中斷和任務(wù)搶占機(jī)制。因此必須謹(jǐn)慎的使用spinlock,使用的話也必須是在很短的周期內(nèi)使用和釋放。使用浮點(diǎn)數(shù)和其他協(xié)處理器出于效率的考慮,關(guān)于使用協(xié)處理器的任務(wù)創(chuàng)建選項(xiàng)〔VP_FP_TASK必須被謹(jǐn)慎使用。只有當(dāng)任務(wù)確實(shí)需要使用時(shí)再添加。當(dāng)一個(gè)任務(wù)在創(chuàng)建時(shí)開啟了協(xié)處理器選項(xiàng)時(shí),協(xié)處理器的狀態(tài)將被保存,同時(shí),系統(tǒng)也會(huì)保存每一次的上下文切換。這對(duì)于那些雖然開啟了協(xié)處理器但是卻沒有使用它的任務(wù)而言顯得沒有必要了。使用vmBaseLibvmBaseLib庫(kù)是VxWorksMMU的管理庫(kù),它允許內(nèi)核APP和驅(qū)動(dòng)管理MMU。SMPOS的一個(gè)重要任務(wù)是保證MMU的后備內(nèi)存〔TLB的一致性。例如,CPUMPC8641D中有硬件資源可以保證TLB的一致性。其他CPU,例如MIPS體系結(jié)構(gòu)家族,就沒有這個(gè)能力。這時(shí)候就需要OS進(jìn)行對(duì)MMU一致性的保護(hù)了。任務(wù)和中斷的CPU-Affinity對(duì)于一些APP和系統(tǒng),分配指定任務(wù)或中斷到指定CPU上執(zhí)行可以提高系統(tǒng)的效率。簡(jiǎn)單例子VxWorksSMP提供了一些測(cè)試程序,用來(lái)測(cè)試SMP的特點(diǎn)和性能。以下程序測(cè)試了I/O功能和系統(tǒng)調(diào)用功能:philDemosmpLockDemo以下測(cè)試了計(jì)算能力:primesDemorawPerf可以通過(guò)配置VxWorks內(nèi)核的方式〔添加INCLUDE_SMP_DEMO組件,將這些測(cè)試程序鏈接到VxWorksSMP內(nèi)核鏡像中。測(cè)試源代碼在installDir/vxworks-6.x/target/src/demo/smp中。在此以installDir/vxworks-6.x/target/src/demo/smp/smpLockDemo.c為例來(lái)說(shuō)明關(guān)于SMP編程的一些注意事項(xiàng):/*smpLockDemo.c用于測(cè)試VxWorksSMP的同步/互斥機(jī)制* 描述:* 這個(gè)demo描述了VxWorksSMP中的同步機(jī)制。在SMP系統(tǒng)中任務(wù)和ISR可以同時(shí)運(yùn)* 行在不同的CPU上,這就涉及到同時(shí)訪問(wèn)共享數(shù)據(jù)的問(wèn)題,對(duì)此,VxWorksSMP提供* 了一系列機(jī)制:* 信號(hào)量〔Semaphore:可以使用信號(hào)量實(shí)現(xiàn)任務(wù)間的同步機(jī)制。例如,使用一個(gè)任務(wù)或* ISR"喚醒"另一個(gè)任務(wù)。* VxWorks事件〔VxWorksEvents:同信號(hào)量。* 原子操作〔AtomicOperator:能夠安全的讀、寫內(nèi)存。例如,完成一些類型的全局的* 自增操作。* Spinlock:它可以用在任務(wù)間、ISR間、任務(wù)與ISR間的同步。它一般用來(lái)對(duì)較多的共* 享數(shù)據(jù)和臨界資源進(jìn)行保護(hù)。** 本demo對(duì)以上同步機(jī)制進(jìn)行了對(duì)比。通過(guò)SMP系統(tǒng)中多個(gè)任務(wù)對(duì)一個(gè)共享int型變量* 操作的實(shí)例,來(lái)對(duì)比這些同步機(jī)制。** DEMO執(zhí)行* 本demo中包含了兩個(gè)優(yōu)先級(jí)一樣的任務(wù),每個(gè)任務(wù)都重復(fù)地〔循環(huán)增加一個(gè)共享計(jì)* 數(shù)器。這個(gè)共享計(jì)數(shù)器是用戶定義的一個(gè)累加值〔即被更新次數(shù)。每個(gè)任務(wù)還有一個(gè)* 自己的計(jì)數(shù)器,在增加共享計(jì)數(shù)器的同時(shí)也增加自己的計(jì)數(shù)器。假設(shè)任務(wù)自己的計(jì)數(shù)器* 不會(huì)出錯(cuò),我們要看看使用不同同步機(jī)制的不同效果,即共享數(shù)據(jù)的值是否與兩個(gè)任務(wù)* 自身計(jì)數(shù)器的值之和一致。* * 以上過(guò)程重復(fù)5次,每次使用不同的同步機(jī)制:* 1.不使用同步機(jī)制:任務(wù)訪問(wèn)共享數(shù)據(jù)時(shí)不使用同步機(jī)制。* 2.Spinlock:當(dāng)任務(wù)累加共享數(shù)據(jù)的時(shí)候申請(qǐng)spinlock,然后再釋放spinlock。* 3.vxAtomicInc<>原子操作:任務(wù)使用原子操作vxAtomicInc<>對(duì)這個(gè)共享數(shù)據(jù)進(jìn)行累加。* 4.vxTas<>原子操作:在任務(wù)中通過(guò)vxTas<>設(shè)置/清除一個(gè)flag,將這個(gè)flag當(dāng)做一個(gè)普* 通信號(hào)量使用。當(dāng)這個(gè)flag被清除時(shí)表示信號(hào)量不可用,當(dāng)被設(shè)置時(shí)表示可用。任務(wù)* 需要使用這個(gè)信號(hào)量對(duì)共享數(shù)據(jù)進(jìn)行累加,然后再釋放信號(hào)量。* 5.vxAtomicAdd<>原子操作:在任務(wù)中使用vxAtomicAdd<>原子操作對(duì)共享數(shù)據(jù)進(jìn)行累* 加。** 調(diào)用任務(wù)* 在SHELL中輸入smpLockDemo3* 參數(shù)3表示任務(wù)更新共享數(shù)據(jù)和自己數(shù)據(jù)的時(shí)長(zhǎng)〔以秒為單位。如果不填寫參數(shù),則* 已2秒為默認(rèn)值。** 執(zhí)行結(jié)果:* METHODTASK0TASK1SUM<COUNTS>GLOBALRESULT** no-lock0x253d8ae0x253b31c0x4a78bca0x470327dFailed* spinLocks0x782f430x78265a0xf0559d0xf0559dPassed* atomicInc0x20b600d0x20b46230x416a6300x416a630Passed* test-and-set0x1509e720x150a6280x2a1449a0x2a1449aPassed* atomicAdd0x1e25b2a0x1e26d2a0x3c4c8540x3c4c854Passed** METHOD欄表示任務(wù)為了更新共享數(shù)據(jù)而使用的同步機(jī)制。TASK0任務(wù)欄表示TASK0* 自己的計(jì)數(shù)器值,TASK1任務(wù)欄表示TASK1自己的計(jì)數(shù)器值。計(jì)數(shù)器的值越大,說(shuō)明* 同步機(jī)制越好。SUM<COUNTS>欄表示前面兩個(gè)欄之和〔TASK0和TASK1欄的和。* GLOBAL欄表示共享數(shù)據(jù)的值。RESULT欄表示了SUM值是否與GLOBAL的值一致。** 從上面的測(cè)試結(jié)果可以看出,不使用同步機(jī)制是不行的,即使對(duì)共享數(shù)據(jù)僅僅是一個(gè)小* 小的自增操作,不使用同步機(jī)制也會(huì)造成錯(cuò)誤。結(jié)果還可以看出,使用vxAtomicInc<>* 原子操作不僅安全,而且其效率比vxAtomicAdd<>要高,而vxAtomicAdd<>的效率比使* 用Spinlock<>要高。原子操作效率高于spinlock的原因在于它們就是為簡(jiǎn)單原子讀寫操* 作設(shè)計(jì)的,而spinlock則是對(duì)相對(duì)復(fù)雜的同步機(jī)制設(shè)計(jì)的。test-and-set方法通過(guò)使用* vxTas<>執(zhí)行一個(gè)信號(hào)量,這種方法比原子操作慢,比spinlock快。但是,在數(shù)據(jù)需要極* 端小心操作時(shí),還是使用spinlock這種鎖機(jī)制比較好。Spinlock是普通信號(hào)量更安全的* 替代品。* * 下面是同樣的程序、同樣的周期在一個(gè)UP系統(tǒng)上運(yùn)行的結(jié)果。通過(guò)結(jié)果我們看到,這* 里沒有沖突、沒有錯(cuò)誤,這是因?yàn)闆]有真正意義的同時(shí)執(zhí)行存在。但是,從計(jì)數(shù)的結(jié)果* 來(lái)看,性能還是要比前者好的。這是因?yàn)?由于任務(wù)不能同時(shí)執(zhí)行,很少有對(duì)共享數(shù)據(jù)* 的競(jìng)爭(zhēng)存在。不過(guò),這個(gè)例子從另一個(gè)方面說(shuō)明:一個(gè)APP雖然有"同時(shí)執(zhí)行"* 的能力,但是過(guò)多的"競(jìng)爭(zhēng)"使得這種并行的好處大打折扣。* * ->smpLockDemo* METHODTASK0TASK1SUM<COUNTS>GLOBALRESULT* * no-lock0x265d7940x2675ee80x4cd367c0x4cd367cPassed* spinLocks0xaae6780xaaefaf0x155d6270x155d627Passed* atomicInc0x23c51350x23c70180x478c14d0x478c14dPassed* test-and-set0x1a84dd60x1a864c00x350b2960x350b296Passed* atomicAdd0x20b9c800x20bb8430x41754c30x41754c3Passed*/[注意]在此只對(duì)關(guān)鍵部分進(jìn)行說(shuō)明,因此部分代碼就不在寫了,大家可以查看它的源文件進(jìn)行對(duì)照。[藍(lán)色的是本文介紹的關(guān)于SMP的一些API]頭文件略;LOCALvolatileBOOLlockDemoWorkersReady=FALSE;LOCALatomic_ttasVar=0;/*variablefortest-and-setmethod*//*smpLockDemosmpLockDemo入口函數(shù)〔也是SHELL命令*->smpLockDemo<numberofsecs>,<numberoftasks>,<[TRUE,FALSE];<affinity>*這個(gè)函數(shù)會(huì)開啟兩個(gè)任務(wù)用于同時(shí)更新一個(gè)共享數(shù)據(jù),與此同時(shí),更新自己的一個(gè)數(shù)據(jù)。* <secs>參數(shù)表示使用不同同步機(jī)制進(jìn)行測(cè)試的時(shí)間〔默認(rèn)為2s,即每種算法會(huì)給<secs>* 進(jìn)行測(cè)試。*/STATUSsmpLockDemo<unsignedint secs, /*Theminimumlifetimeofaworkertask*/unsignedint reqNumOfTasks,/*numberoftasks*/BOOL setAff/*dotaskhaveaffinity*/>{unsignedint availCpus=vxCpuConfiguredGet<>; //獲取已配置CPU個(gè)數(shù)unsignedintnumOfTasks;unsignedint eventsToWait=0;LOCKS_DEMO_TYPE lockMethod=methodLockNone;//這是一個(gè)枚舉,表示不同機(jī)制if<reqNumOfTasks==0>numOfTasks=availCpus;elsenumOfTasks=reqNumOfTasks;if<secs==0>secs=5;/*Create,setaffinityandactivitedtasks*/if<locksDemoTasksInit<secs,numOfTasks,setAff,&eventsToWait,lockMethod>!=OK>{ returnERROR;}lockDemoWorkersReady=TRUE;/*Waituntilallworkertasksaredone*/if<eventReceive<eventsToWait,EVENTS_WAIT_ALL,WAIT_FOREVER,NULL>!=OK>{ returnERROR;}}/*locksDemoTasksInit初始化、激活、設(shè)置CPU-Affinity任務(wù)。默認(rèn)情況下,任務(wù)的* CPU-Affinity屬性是關(guān)閉的。locksDemoTasksInit創(chuàng)建了數(shù)個(gè)任務(wù)。*/LOCALSTATUSlocksDemoTasksInit<int secs, /*howlongtorun */unsignedint numOfTasks, /*numberoftasksevolved */BOOL affIsOn,unsignedint* eventsToWait, /*Eventstowaitmask */LOCKS_DEMO_TYPE lockMethod /*Methodofglobalcountlock */>{cpuset_t taskAffinity;unsignedinteventWorkerDone;int i=0;workerTid[i++]=taskIdSelf<>;//workerTid是一個(gè)數(shù)組,用于記錄任務(wù)IDwhile<i<<numOfTasks+1>> {/*Task'snumberisthebittosendaneventon*/eventWorkerDone=1<<i; /*AddeventtothereceivelistfortheMaintasksreceives*/*eventsToWait|=eventWorkerDone;/*Createthespecifiednumberoftasks*/if<<workerTid[i]=taskCreate<"worker",WORKER_TASKS_PRIORITY,0, 5000,<FUNCPTR>workerEntry,secs,taskIdSelf<>,numOfTasks, eventWorkerDone,i,lockMethod,0,0,0,0>>==ERROR> { returnERROR; }/* *Setaffinityforeachtaskifrequestedandthatnumberoftasks *thenumberofCPU'savailable. */if<affIsOn==TRUE> { taskAffinity=1<<i;/*Setaffinitythetaskaffinitywiththe

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論