版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
Linux源代碼閱讀SMP結(jié)構(gòu)中的中斷機制和進程調(diào)度張飛Linux源代碼閱讀SMP結(jié)構(gòu)中的中斷1概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制2分布式中斷處理APIC簡介SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)全局APIC本地APIC分布式中斷處理3高級可編程中斷控制器APIC為了充分利用smp體系結(jié)構(gòu)的并行性,要求動態(tài)分配中斷請求,也就是說可以向任意cpu發(fā)出中斷請求.傳統(tǒng)的i386處理器都采用8259A中斷控制器,其作用是提供多個外部中斷源與單一cpu之間的連接.如果在SMP結(jié)構(gòu)中還是采用8259A中斷控制器,那就只能靜態(tài)的把所有的外部中斷源劃分成若干組,分別把每一組都連接到一個8259A,而8259A則與cpu有一對一的連接.這樣就達不到動態(tài)分配中斷請求的目的.為了更好的支持smp結(jié)構(gòu),從Pentium開始,Intel設(shè)計了一種更為通用的中斷控制器,稱為高級可編程中斷控制器APIC(AdvancedProgrammableInterruptController).高級可編程中斷控制器APIC為了充分利用smp體系結(jié)構(gòu)的并行4SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu1本地APIC本地APICcpun全局APIC本地中斷請求
本地中斷請求本地中斷請求ICC(中斷控制器通信)總線外部中斷請求SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu5分布式中斷處理硬件機制概述兩種APIC:本地APIC和全局APIC,通過中斷控制器通信(InterruptControllerCommunication,ICC)總線相連.本地APIC集成在cpu內(nèi)部,通過內(nèi)部APIC可以向其他cpu發(fā)送中斷請求.全局APIC負責(zé)把來自外部設(shè)備的中斷請求提交和分配給系統(tǒng)中各個cpu的任務(wù).分布式中斷處理硬件機制概述兩種APIC:本地APIC和全局A6全局APIC組成全局APIC由一組IRQ線路,一個有24個表項的中斷重定向表(InterruptRedirectionTable),一個可編程寄存器和一個用來發(fā)送和接受經(jīng)過ICC總線的APIC消息的消息單元組成.和8259A的IRQ引腳不同,中斷優(yōu)先級和引腳號無關(guān),重定向表中的每個表項都可以被單獨編程來說明中斷向量和優(yōu)先級,目標(biāo)處理器以及如何選定處理器.重定向表中的消息用來把外部IRQ信號轉(zhuǎn)換成通過ICC總線發(fā)往一個或多個本地APIC單元的消息.全局APIC組成7全局APIC 工作模式固定模式把IRQ信號發(fā)送到相應(yīng)的重定向表表項所列出的本地APIC上.最低優(yōu)先級模式把IRQ信號發(fā)送到正在執(zhí)行優(yōu)先級最低的進程的處理器的本地APIC上.所有的本地APIC都有一個可編程任務(wù)優(yōu)先級寄存器(taskpriorityregister),它包含了當(dāng)前正在運行的進程的優(yōu)先級.在每次任務(wù)切換時這個寄存器的值必須由內(nèi)核進行修改.全局APIC 工作模式8本地APIC組成每個本地APIC都有幾個32位的寄存器,一個內(nèi)部時鐘,一個定時器設(shè)備,240個不同的中斷向量(從0x20~0xff,0~0x1f用于cpu本身的陷阱)以及兩條為局部中斷保留的IRQ線路,這兩條線路用于重啟系統(tǒng).本地APIC的一個重要功能是實現(xiàn)處理器間中斷IPI當(dāng)一個cpu想要向其他cpu發(fā)送中斷時,將中斷向量和目標(biāo)處理器的本地apic標(biāo)志符保存到自己本地apic的中斷命令寄存器中,然后通過ICC總線向目標(biāo)處理器的本地apic發(fā)送一條消息,目標(biāo)處理器的本地apic就向自己的cpu發(fā)出相應(yīng)的中斷.本地APIC組成9SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu1本地APIC本地APICcpun全局APIC本地中斷請求
本地中斷請求本地中斷請求ICC(中斷控制器通信)總線外部中斷請求SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu10概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制11中斷初始化smp相關(guān)的幾個主要中斷向量設(shè)置中斷門中斷響應(yīng)程序的建立相關(guān)中斷處理程序代碼smp_reschedule_interrupt()smp_call_function_interrupt()中斷初始化12smp相關(guān)的幾個主要中斷向量smp結(jié)構(gòu)專用的幾個IRQ向量定義在include/asm-i386/apic.h中
#defineSPURIOUS_APIC_VECTOR 0xff
#defineERROR_APIC_VECTOR 0xfe
#defineINVALIDATE_TLB_VECTOR0xfd
#defineRESCHEDULE_VECTOR 0xfc
#defineCALL_FUNCTION_VECTOR 0xfb
#defineLOCAL_TIMER_VECTOR 0xef其他不常用的向量合并到CALL_FUNCTION_VECTOR中以節(jié)省向量空間,使用比較頻繁的是TLB、reschedule和localAPIC中斷向量.smp相關(guān)的幾個主要中斷向量smp結(jié)構(gòu)專用的幾個IRQ向量定13設(shè)置中斷門void
__init
init_IRQ(void){
…
for(i=
0;i<
NR_IRQS;i++){
intvector=
FIRST_EXTERNAL_VECTOR
+i;
if(vector!=
SYSCALL_VECTOR)
set_intr_gate(vector,interrupt[i]); }#ifdefCONFIG_SMP
set_intr_gate(FIRST_DEVICE_VECTOR,interrupt[0]);
set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt);
set_intr_gate(INVALIDATE_TLB_VECTOR,invalidate_interrupt);
set_intr_gate(CALL_FUNCTION_VECTOR,call_function_interrupt);#endif
…}設(shè)置中斷門void__initinit_IRQ(void14設(shè)置中斷門for循環(huán)設(shè)置了除SYSCALL_VECTOR外從FIRST_EXTERNAL_VECTOR開始的NR_IRQS個中斷門向量,在smp結(jié)構(gòu)中覆蓋了其中的4個,其他的中斷向量基本上沒有什么變化,還與采用8259A時大致相同,不同的是現(xiàn)在由全局APIC取代8259A將外部中斷請求送達各個cpu.為中斷向量FIRST_DEVICE_VECTOR設(shè)置的中斷響應(yīng)入口程序是interrupt[0].中斷向量RESCHEDULE_VECTOR的中斷響應(yīng)入口程序是reschedule_interrupt.中斷向量INVALIDATE_TLB_VECTOR的中斷響應(yīng)入口程序設(shè)置為invalidate_interrupt.中斷向量CALL_FUNCTION_VECTOR對應(yīng)call_function_interrupt程序.設(shè)置中斷門for循環(huán)設(shè)置了除SYSCALL_VECTOR外從15中斷響應(yīng)程序的建立上述幾個主要中斷向量的實際中斷處理程序由下面一組宏語句建立:#ifdefCONFIG_SMPBUILD_SMP_INTERRUPT(reschedule_interrupt,RESCHEDULE_VECTOR)BUILD_SMP_INTERRUPT(invalidate_interrupt,INVALIDATE_TLB_VECTOR)BUILD_SMP_INTERRUPT(call_function_interrupt,CALL_FUNCTION_VECTOR)#endif中斷響應(yīng)程序的建立上述幾個主要中斷向量的實際中斷處理程序由下16中斷響應(yīng)程序的建立其中BUILD_SMP_INTERRUPT宏定義如下:#define
BUILD_SMP_INTERRUPT(x,v)XBUILD_SMP_INTERRUPT(x,v)#define
XBUILD_SMP_INTERRUPT(x,v)\asmlinkage
voidx(void);\asmlinkage
voidcall_##x(void);\__asm__(\"\n"__ALIGN_STR"\n"\SYMBOL_NAME_STR(x)":\n\t"\"pushl$"#v"\n\t"\SAVE_ALL\SYMBOL_NAME_STR(call_##x)":\n\t"\"call"SYMBOL_NAME_STR(smp_##x)"\n\t"\"jmpret_from_intr\n");中斷響應(yīng)程序的建立其中BUILD_SMP_INTERRUPT17中斷響應(yīng)程序的建立BUILD_SMP_INTERRUPT(reschedule_interrupt,RESCHEDULE_VECTOR)宏展開如下:asmlinkage
void
reschedule_interrupt(void);\asmlinkage
void
call_reschedule_interrupt(void);\__asm__(
reschedule_interrupt: pushl$RESCHEDULE_VECTOR#中斷號存進核心棧
SAVE_ALL#保存各個寄存器值
call_smp_reschedule_interrupt: callsmp_reschedule_interrupt jmpret_from_intr)中斷響應(yīng)程序的建立BUILD_SMP_INTERRUPT(r18中斷響應(yīng)程序的建立至此,結(jié)合前面說的中斷門的初始化,smp專有的主要中斷向量及其響應(yīng)機制建立起來:當(dāng)發(fā)生RESCHEDULE_VECTOR中斷時,響應(yīng)程序的入口是reschedule_interrupt(),實際負責(zé)中斷處理程序的函數(shù)為smp_reschedule_interrupt().同理,與INVALIDATE_TLB_VECTOR相對應(yīng)的入口程序是invalidate_interrupt(),實際中斷處理程序的則是smp_invalidate_interrupt();與CALL_FUNCTION_VECTOR相對應(yīng)的入口程序是call_function_interrupt(),而實際處理中斷請求的是smp_call_function_interrupt().中斷響應(yīng)程序的建立至此,結(jié)合前面說的中斷門的初始化,smp專19具體中斷處理程序smp_reschedule_interrupt()asmlinkage
void
smp_reschedule_interrupt(void){
ack_APIC_irq();//發(fā)送中斷請求確認}externinlinevoid
ack_APIC_irq(void){
apic_write_around(APIC_EOI,0);/*向本地APIC的控制寄存器寫入0,表示已經(jīng)收到了中斷請求*/}具體中斷處理程序smp_reschedule_interru20具體中斷處理程序smp_reschedule_interrupt()該函數(shù)應(yīng)其他cpu的請求進行一次進程調(diào)度,但是從程序代碼上看,該函數(shù)僅僅發(fā)回一個中斷確認.實際上,對中斷請求的服務(wù)隱藏在內(nèi)核對中斷處理的公共部分:內(nèi)核在針對特定中斷請求的服務(wù)完成后都要檢查(本cpu)是否需要進行進程調(diào)度,這正是smp_reschedule_interrupt()的所要達到的目的—引發(fā)目標(biāo)CPU一次中斷,以便檢查是否需要重新調(diào)度.具體中斷處理程序smp_reschedule_interru21具體中斷處理程序smp_call_function_interrupt()該函數(shù)響應(yīng)CALL_FUNCTION_VECTOR,用于請求目標(biāo)CPU執(zhí)行一個指定的函數(shù).發(fā)送者先設(shè)置好一個全局的call_data_struct數(shù)據(jù)結(jié)構(gòu),然后向目標(biāo)CPU發(fā)出請求,目標(biāo)CPU收到中斷向量后,調(diào)用該函數(shù)進行處理staticspinlock_tcall_lock=SPIN_LOCK_UNLOCKED;struct
call_data_struct{
void(*func)(void*info);//指向要求對方執(zhí)行的函數(shù)
void
*info;//函數(shù)參數(shù)
atomic_tstarted;
atomic_tfinished;
intwait;};static
struct
call_data_struct*call_data;具體中斷處理程序smp_call_function_inte22具體中斷處理程序smp_call_function_interrupt()asmlinkage
void
smp_call_function_interrupt(void){
void(*func)(void*info)=call_data->func;//取出函數(shù)指針
void
*info=call_data->info;
intwait=call_data->wait;
ack_APIC_irq();//先發(fā)回中斷確認
atomic_inc(&call_data->started); (*func)(info);//調(diào)用指定函數(shù)
if(wait)//指定動作完成后,wait設(shè)置為1
atomic_inc(&call_data->finished);}具體中斷處理程序smp_call_function_inte23具體中斷處理程序smp_call_function_interrupt()當(dāng)然,一般的函數(shù)是沒有必要請其他CPU來執(zhí)行的,因為系統(tǒng)中所有的CPU都可共享同樣的代碼和數(shù)據(jù).之所以要請求其他CPU執(zhí)行,是因為某個函數(shù)必須由特定的CPU來執(zhí)行.例如,pentium處理器有一條cpuid指令,通過這條指令可以查詢本CPU的型號、版本以及是否支持一些特殊的功能、當(dāng)前的功能設(shè)置等等信息.但是這條指令只能由具體的CPU本身執(zhí)行,而不能由別的CPU代替.這樣,如果要知道系統(tǒng)中某一個CPU的有關(guān)情況,就只能請求該CPU來執(zhí)行cpuid指令.具體中斷處理程序smp_call_function_inte24概述SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概述SMP結(jié)構(gòu)中的中斷機制25處理器間中斷IPIIPI概述IPI中斷向量IPI中斷請求函數(shù)處理器間中斷IPI26IPI概述IPI(InterprocessorInterrupt)稱為處理機間中斷.實際上,前面介紹的RESCHEDULE_VECTOR、INVALIDATE_TLB_VECTOR以及CALL_FUNCTION_VECTOR都屬于處理器間中斷IPI.從前一部分可以看出,與單cpu系統(tǒng)的中斷處理機制相比,smp系統(tǒng)僅僅作了少量修改,并且這些修改又大部分集中在處理器間中斷IPI這一部分,也許我們可以從另一個角度來看待smp系統(tǒng)的中斷機制-smp中斷機制和單CPU系統(tǒng)中斷機制在某種程度上是等價的.IPI概述IPI(InterprocessorInter27全局APICCPU0CPU1CPUn本地APIC本地APIC本地APIC外部中斷外部中斷CPU8259A單個“超級”處理器SMP結(jié)構(gòu)與單CPU系統(tǒng)中斷機制的類比全局APICCPU0CPU1CPUn本地APIC本地A28IPI中斷向量本地APIC可以識別6種消息,這些消息是由接收消息的CPU作為不同中斷向量來解釋的.(之所以不同于一般中斷向量,可能是因為這些中斷向量并不對應(yīng)著實際的中斷請求引腳).SPURIOUS_APIC_VECTOR(0xff)入口程序spurious_interrupt(),實際中斷處理程序smp_spurious_interrupt().ERROR_APIC_VECTOR(0xfe)出錯計數(shù)器溢出中斷,入口程序error_interrupt(),中斷處理程序smp_error_interrupt().INVALIDATE_TLB_VECTOR(0xfd)RESCHEDULE_VECTOR(0xfc)CALL_FUNCTION_VECTOR(0xfb)LOCAL_TIMER_VECTOR(0xef)I/OAPIC把定時中斷自動發(fā)給所有的CPU.相應(yīng)的入口程序是apic_timer_interrupt(),實際中斷服務(wù)程序為smp_apic_timer_interrupt().IPI中斷向量本地APIC可以識別6種消息,這些消息是由接收29中斷請求函數(shù)中斷請求函數(shù)用于向目標(biāo)CPU發(fā)送指定的IPI請求向量staticinlinevoidsend_IPI_allbutself(intvector)向除自己以外的所有CPU發(fā)送一個IPI.staticinlinevoidsend_IPI_all(intvector)向所有的CPU(包括自己)發(fā)送一個IPI.voidsend_IPI_self(intvector)向自己發(fā)送一個IPI.staticinlinevoidsend_IPI_mask(intmask,intvector)向由mask指定的一個或多個CPU發(fā)送一個IPI.這幾個函數(shù)功能大同小異,實現(xiàn)代碼也都比較簡單.其中以send_IPI_mask函數(shù)最為靈活,因而也稍為復(fù)雜一些.中斷請求函數(shù)中斷請求函數(shù)用于向目標(biāo)CPU發(fā)送指定的IPI請求30send_IPI_mask函數(shù)staticinlinevoid
send_IPI_mask(intmask,intvector){
unsignedlongcfg;
unsignedlongflags; __save_flags(flags);//中斷前狀態(tài)信息保存在flags中 __cli();//關(guān)中斷
apic_wait_icr_idle();//確認或等待APIC_ICR處于空閑狀態(tài)
/*ICR2、ICR是本地APIC的2個控制寄存器*/
cfg=__prepare_ICR2(mask);//根據(jù)中斷請求的目標(biāo)CPU準(zhǔn)備將要
apic_write_around(APIC_ICR2,cfg)//寫入寄存器APIC_ICR2的值 cfg=__prepare_ICR(0,vector); //根據(jù)要發(fā)送的中斷向量準(zhǔn)備將要
apic_write_around(APIC_ICR,cfg);//寫入寄存器APIC_ICR的值/*對APIC_ICR的寫入操作引發(fā)并完成將中斷向量發(fā)送至目標(biāo)cpu的工作*/ __restore_flags(flags);//恢復(fù)標(biāo)志}staticinlineint__prepare_ICR2(unsignedintmask){returnSET_APIC_DEST_FIELD(mask);}staticinlineint__prepare_ICR(unsignedintshortcut,intvector){returnAPIC_DM_FIXED|shortcut|vector|APIC_DEST_LOGICAL;}send_IPI_mask函數(shù)staticinlinev31其他相關(guān)發(fā)送函數(shù)再看一個較為簡單的函數(shù)send_IPI_allstaticinlinevoid
send_IPI_all(intvector){ __send_IPI_shortcut(APIC_DEST_ALLINC,vector);}staticinlinevoid__send_IPI_shortcut(unsignedintshortcut,intvector){
unsignedintcfg;
apic_wait_icr_idle();//確認或等待ICR空閑 cfg=__prepare_ICR(shortcut,vector);.//對ICR進行編程
apic_write_around(APIC_ICR,cfg);}/*與send_IPI_reschedule相比,少了耗時的開/關(guān)中斷動作,并且只對APIC編程一次*/除send_IPI_mask()之外的幾個發(fā)送函數(shù)實際上都是簡單的調(diào)用__send_IPI_shortcut函數(shù),只不過傳給這個函數(shù)的參數(shù)不同而已.在這些函數(shù)基礎(chǔ)上,還定義了一些功能更為專一、明確的函數(shù).比如當(dāng)一個CPU需要另一個CPU進行一次進程調(diào)度時,可以調(diào)用下面的函數(shù):void
smp_send_reschedule(intcpu){
send_IPI_mask(1<<cpu,RESCHEDULE_VECTOR);}其他相關(guān)發(fā)送函數(shù)再看一個較為簡單的函數(shù)send_IPI_al32概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制33相關(guān)數(shù)據(jù)結(jié)構(gòu)單CPU系統(tǒng)中,任一時刻只有當(dāng)前進程是在運行中的,但在SMP系統(tǒng)中同時有好幾個進程在運行,因此在task_struct結(jié)構(gòu)中引入兩個字段:一個是has_cpu,為1時說明進程正在運行,為0則表示不在運行;另一個字段是processor,當(dāng)has_cpu為1時指出進程是在哪一個cpu上運行.一個相關(guān)的宏操作can_schedule():
#ifdef
CONFIG_SMP
#define
idle_task(cpu)(init_tasks[cpu_number_map(cpu)])
#define
can_schedule(p,cpu)((!(p)->has_cpu)&&\ ((p)->cpus_allowed&(1<<cpu)))
#else
#define
idle_task(cpu)(&init_task)
#define
can_schedule(p,cpu)(1)
#endif相關(guān)數(shù)據(jù)結(jié)構(gòu)單CPU系統(tǒng)中,任一時刻只有當(dāng)前進程是在運行中的34調(diào)度函數(shù)Schedule()中相關(guān)代碼當(dāng)一個CPU通過schedule()從系統(tǒng)的就緒隊列中挑選了一個進程作為運行的下一個進程next,即從當(dāng)前進程prev切換到這個進程時,就將其task_struct結(jié)構(gòu)中的has_cpu字段設(shè)置成1,并將processor設(shè)置成該CPU的邏輯編號.asmlinkage
void
schedule(void){…this_cpu=prev->processor;//取得當(dāng)前CPU邏輯號…#ifdefCONFIG_SMP
next->has_cpu=1;//將要上臺進程的has_cpu設(shè)置為1
next->processor=this_cpu;//設(shè)置processor字段#endif
spin_unlock_irq(&runqueue_lock);
if(prev==next) gotosame_process;…switch_to(prev,next,prev);//從prev切換到next進程__schedule_tail(prev);//處理下臺的prev進程,看能否在其他cpu上重新調(diào)度…}調(diào)度函數(shù)Schedule()中相關(guān)代碼當(dāng)一個CPU通過sch35staticinlinevoid__schedule_tail(structtask_struct
*prev)函數(shù)保存先前進程調(diào)度策略并清零prev->policy中SCHED_YIELD標(biāo)志位將prev的has_cpu字段清零下臺前運行態(tài)?“空轉(zhuǎn)”進程或自動下臺?之前是運行態(tài)?嘗試重新調(diào)度prev進程開始返回policy=prev->policy;
prev->policy=policy&~SCHED_YIELD;wmb();task_lock(prev);prev->has_cpu=0;mb();NYYNNYif(prev->state==TASK_RUNNING)if((prev==idle_task(smp_processor_id()))||(policy&SCHED_YIELD))if(prev->state==TASK_RUNNING)reschedule_idle(prev);staticinlinevoid__schedule_36staticvoid
reschedule_idle(structtask_struct
*p)函數(shù)staticvoid
reschedule_idle(structtask_struct
*p){#ifdefCONFIG_SMP
intthis_cpu=smp_processor_id();//取得當(dāng)前CPU邏輯號
struct
task_struct*tsk,*target_tsk;
intcpu,best_cpu,i,max_prio;
cycles_toldest_idle; best_cpu=p->processor;//最好可以在原來的CPU上重新運行
if(can_schedule(p,best_cpu)){//可以在原來的CPU上調(diào)度么 tsk=idle_task(best_cpu);//取得該CPU的”空轉(zhuǎn)”進程
if(cpu_curr(best_cpu)==tsk){//判斷目的CPU是否空閑
intneed_resched;send_now_idle: need_resched=tsk->need_resched;//保存原先的need_resched
tsk->need_resched=1;//設(shè)置need_resched標(biāo)志
/*如果need_resched為-1,則沒有必要發(fā)送IPI,idle進程會自動監(jiān)視該變量*/
if((best_cpu
!=this_cpu)&&!need_resched) //需要發(fā)送IPI?
smp_send_reschedule(best_cpu);//發(fā)送RESCHEDULE_VECTOR
return; } }staticvoidreschedule_idle(st37oldest_idle=(cycles_t)-1;target_tsk=NULL;max_prio=1;
/*查找所有可用CPU,看看能否重新調(diào)度進程p*/for(i=0;i<smp_num_cpus;i++){//smp_num_cpus:系統(tǒng)中活動CPU個數(shù) cpu=cpu_logical_map(i);//取得該CPU的邏輯號
if(!can_schedule(p,cpu))
continue; tsk=cpu_curr(cpu);
if(tsk==idle_task(cpu)){//存在空閑CPU,選擇運行時間最長的進程來剝奪
if(last_schedule(cpu)<oldest_idle){ oldest_idle=last_schedule(cpu); target_tsk=tsk; } }else{if(oldest_idle==-1ULL){//若沒有空閑CPU
intprio=preemption_goodness(tsk,p,cpu);
if(prio>max_prio){//尋找一個運行資格較p最低的進程來剝奪 max_prio=prio; target_tsk=tsk; } } }}(接上)oldest_idle=(cycles_t)-1;(接38tsk=target_tsk;
if(tsk){//如果存在可以剝奪的進程
if(oldest_idle
!=-1ULL){//如果存在空閑CPU best_cpu=tsk->processor; gotosend_now_idle; }
tsk->need_resched=1;//置need_resched標(biāo)志
if(tsk->processor
!=this_cpu)
smp_send_reschedule(tsk->processor);//發(fā)送IPI }
return; #else/*ifundefCONFIG_SMP*/
intthis_cpu=smp_processor_id();
struct
task_struct
*tsk; tsk=cpu_curr(this_cpu);//取得當(dāng)前進程
if(preemption_goodness(tsk,p,this_cpu)>1)//運行資格高于當(dāng)前進程
tsk->need_resched=1;#endif}(接上)tsk=target_tsk;(接上)39謝謝大家!
謝謝大家!
40Linux源代碼閱讀SMP結(jié)構(gòu)中的中斷機制和進程調(diào)度張飛Linux源代碼閱讀SMP結(jié)構(gòu)中的中斷41概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制42分布式中斷處理APIC簡介SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)全局APIC本地APIC分布式中斷處理43高級可編程中斷控制器APIC為了充分利用smp體系結(jié)構(gòu)的并行性,要求動態(tài)分配中斷請求,也就是說可以向任意cpu發(fā)出中斷請求.傳統(tǒng)的i386處理器都采用8259A中斷控制器,其作用是提供多個外部中斷源與單一cpu之間的連接.如果在SMP結(jié)構(gòu)中還是采用8259A中斷控制器,那就只能靜態(tài)的把所有的外部中斷源劃分成若干組,分別把每一組都連接到一個8259A,而8259A則與cpu有一對一的連接.這樣就達不到動態(tài)分配中斷請求的目的.為了更好的支持smp結(jié)構(gòu),從Pentium開始,Intel設(shè)計了一種更為通用的中斷控制器,稱為高級可編程中斷控制器APIC(AdvancedProgrammableInterruptController).高級可編程中斷控制器APIC為了充分利用smp體系結(jié)構(gòu)的并行44SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu1本地APIC本地APICcpun全局APIC本地中斷請求
本地中斷請求本地中斷請求ICC(中斷控制器通信)總線外部中斷請求SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu45分布式中斷處理硬件機制概述兩種APIC:本地APIC和全局APIC,通過中斷控制器通信(InterruptControllerCommunication,ICC)總線相連.本地APIC集成在cpu內(nèi)部,通過內(nèi)部APIC可以向其他cpu發(fā)送中斷請求.全局APIC負責(zé)把來自外部設(shè)備的中斷請求提交和分配給系統(tǒng)中各個cpu的任務(wù).分布式中斷處理硬件機制概述兩種APIC:本地APIC和全局A46全局APIC組成全局APIC由一組IRQ線路,一個有24個表項的中斷重定向表(InterruptRedirectionTable),一個可編程寄存器和一個用來發(fā)送和接受經(jīng)過ICC總線的APIC消息的消息單元組成.和8259A的IRQ引腳不同,中斷優(yōu)先級和引腳號無關(guān),重定向表中的每個表項都可以被單獨編程來說明中斷向量和優(yōu)先級,目標(biāo)處理器以及如何選定處理器.重定向表中的消息用來把外部IRQ信號轉(zhuǎn)換成通過ICC總線發(fā)往一個或多個本地APIC單元的消息.全局APIC組成47全局APIC 工作模式固定模式把IRQ信號發(fā)送到相應(yīng)的重定向表表項所列出的本地APIC上.最低優(yōu)先級模式把IRQ信號發(fā)送到正在執(zhí)行優(yōu)先級最低的進程的處理器的本地APIC上.所有的本地APIC都有一個可編程任務(wù)優(yōu)先級寄存器(taskpriorityregister),它包含了當(dāng)前正在運行的進程的優(yōu)先級.在每次任務(wù)切換時這個寄存器的值必須由內(nèi)核進行修改.全局APIC 工作模式48本地APIC組成每個本地APIC都有幾個32位的寄存器,一個內(nèi)部時鐘,一個定時器設(shè)備,240個不同的中斷向量(從0x20~0xff,0~0x1f用于cpu本身的陷阱)以及兩條為局部中斷保留的IRQ線路,這兩條線路用于重啟系統(tǒng).本地APIC的一個重要功能是實現(xiàn)處理器間中斷IPI當(dāng)一個cpu想要向其他cpu發(fā)送中斷時,將中斷向量和目標(biāo)處理器的本地apic標(biāo)志符保存到自己本地apic的中斷命令寄存器中,然后通過ICC總線向目標(biāo)處理器的本地apic發(fā)送一條消息,目標(biāo)處理器的本地apic就向自己的cpu發(fā)出相應(yīng)的中斷.本地APIC組成49SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu1本地APIC本地APICcpun全局APIC本地中斷請求
本地中斷請求本地中斷請求ICC(中斷控制器通信)總線外部中斷請求SMP結(jié)構(gòu)中的中斷控制硬件機構(gòu)cpu0本地APICcpu50概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制51中斷初始化smp相關(guān)的幾個主要中斷向量設(shè)置中斷門中斷響應(yīng)程序的建立相關(guān)中斷處理程序代碼smp_reschedule_interrupt()smp_call_function_interrupt()中斷初始化52smp相關(guān)的幾個主要中斷向量smp結(jié)構(gòu)專用的幾個IRQ向量定義在include/asm-i386/apic.h中
#defineSPURIOUS_APIC_VECTOR 0xff
#defineERROR_APIC_VECTOR 0xfe
#defineINVALIDATE_TLB_VECTOR0xfd
#defineRESCHEDULE_VECTOR 0xfc
#defineCALL_FUNCTION_VECTOR 0xfb
#defineLOCAL_TIMER_VECTOR 0xef其他不常用的向量合并到CALL_FUNCTION_VECTOR中以節(jié)省向量空間,使用比較頻繁的是TLB、reschedule和localAPIC中斷向量.smp相關(guān)的幾個主要中斷向量smp結(jié)構(gòu)專用的幾個IRQ向量定53設(shè)置中斷門void
__init
init_IRQ(void){
…
for(i=
0;i<
NR_IRQS;i++){
intvector=
FIRST_EXTERNAL_VECTOR
+i;
if(vector!=
SYSCALL_VECTOR)
set_intr_gate(vector,interrupt[i]); }#ifdefCONFIG_SMP
set_intr_gate(FIRST_DEVICE_VECTOR,interrupt[0]);
set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt);
set_intr_gate(INVALIDATE_TLB_VECTOR,invalidate_interrupt);
set_intr_gate(CALL_FUNCTION_VECTOR,call_function_interrupt);#endif
…}設(shè)置中斷門void__initinit_IRQ(void54設(shè)置中斷門for循環(huán)設(shè)置了除SYSCALL_VECTOR外從FIRST_EXTERNAL_VECTOR開始的NR_IRQS個中斷門向量,在smp結(jié)構(gòu)中覆蓋了其中的4個,其他的中斷向量基本上沒有什么變化,還與采用8259A時大致相同,不同的是現(xiàn)在由全局APIC取代8259A將外部中斷請求送達各個cpu.為中斷向量FIRST_DEVICE_VECTOR設(shè)置的中斷響應(yīng)入口程序是interrupt[0].中斷向量RESCHEDULE_VECTOR的中斷響應(yīng)入口程序是reschedule_interrupt.中斷向量INVALIDATE_TLB_VECTOR的中斷響應(yīng)入口程序設(shè)置為invalidate_interrupt.中斷向量CALL_FUNCTION_VECTOR對應(yīng)call_function_interrupt程序.設(shè)置中斷門for循環(huán)設(shè)置了除SYSCALL_VECTOR外從55中斷響應(yīng)程序的建立上述幾個主要中斷向量的實際中斷處理程序由下面一組宏語句建立:#ifdefCONFIG_SMPBUILD_SMP_INTERRUPT(reschedule_interrupt,RESCHEDULE_VECTOR)BUILD_SMP_INTERRUPT(invalidate_interrupt,INVALIDATE_TLB_VECTOR)BUILD_SMP_INTERRUPT(call_function_interrupt,CALL_FUNCTION_VECTOR)#endif中斷響應(yīng)程序的建立上述幾個主要中斷向量的實際中斷處理程序由下56中斷響應(yīng)程序的建立其中BUILD_SMP_INTERRUPT宏定義如下:#define
BUILD_SMP_INTERRUPT(x,v)XBUILD_SMP_INTERRUPT(x,v)#define
XBUILD_SMP_INTERRUPT(x,v)\asmlinkage
voidx(void);\asmlinkage
voidcall_##x(void);\__asm__(\"\n"__ALIGN_STR"\n"\SYMBOL_NAME_STR(x)":\n\t"\"pushl$"#v"\n\t"\SAVE_ALL\SYMBOL_NAME_STR(call_##x)":\n\t"\"call"SYMBOL_NAME_STR(smp_##x)"\n\t"\"jmpret_from_intr\n");中斷響應(yīng)程序的建立其中BUILD_SMP_INTERRUPT57中斷響應(yīng)程序的建立BUILD_SMP_INTERRUPT(reschedule_interrupt,RESCHEDULE_VECTOR)宏展開如下:asmlinkage
void
reschedule_interrupt(void);\asmlinkage
void
call_reschedule_interrupt(void);\__asm__(
reschedule_interrupt: pushl$RESCHEDULE_VECTOR#中斷號存進核心棧
SAVE_ALL#保存各個寄存器值
call_smp_reschedule_interrupt: callsmp_reschedule_interrupt jmpret_from_intr)中斷響應(yīng)程序的建立BUILD_SMP_INTERRUPT(r58中斷響應(yīng)程序的建立至此,結(jié)合前面說的中斷門的初始化,smp專有的主要中斷向量及其響應(yīng)機制建立起來:當(dāng)發(fā)生RESCHEDULE_VECTOR中斷時,響應(yīng)程序的入口是reschedule_interrupt(),實際負責(zé)中斷處理程序的函數(shù)為smp_reschedule_interrupt().同理,與INVALIDATE_TLB_VECTOR相對應(yīng)的入口程序是invalidate_interrupt(),實際中斷處理程序的則是smp_invalidate_interrupt();與CALL_FUNCTION_VECTOR相對應(yīng)的入口程序是call_function_interrupt(),而實際處理中斷請求的是smp_call_function_interrupt().中斷響應(yīng)程序的建立至此,結(jié)合前面說的中斷門的初始化,smp專59具體中斷處理程序smp_reschedule_interrupt()asmlinkage
void
smp_reschedule_interrupt(void){
ack_APIC_irq();//發(fā)送中斷請求確認}externinlinevoid
ack_APIC_irq(void){
apic_write_around(APIC_EOI,0);/*向本地APIC的控制寄存器寫入0,表示已經(jīng)收到了中斷請求*/}具體中斷處理程序smp_reschedule_interru60具體中斷處理程序smp_reschedule_interrupt()該函數(shù)應(yīng)其他cpu的請求進行一次進程調(diào)度,但是從程序代碼上看,該函數(shù)僅僅發(fā)回一個中斷確認.實際上,對中斷請求的服務(wù)隱藏在內(nèi)核對中斷處理的公共部分:內(nèi)核在針對特定中斷請求的服務(wù)完成后都要檢查(本cpu)是否需要進行進程調(diào)度,這正是smp_reschedule_interrupt()的所要達到的目的—引發(fā)目標(biāo)CPU一次中斷,以便檢查是否需要重新調(diào)度.具體中斷處理程序smp_reschedule_interru61具體中斷處理程序smp_call_function_interrupt()該函數(shù)響應(yīng)CALL_FUNCTION_VECTOR,用于請求目標(biāo)CPU執(zhí)行一個指定的函數(shù).發(fā)送者先設(shè)置好一個全局的call_data_struct數(shù)據(jù)結(jié)構(gòu),然后向目標(biāo)CPU發(fā)出請求,目標(biāo)CPU收到中斷向量后,調(diào)用該函數(shù)進行處理staticspinlock_tcall_lock=SPIN_LOCK_UNLOCKED;struct
call_data_struct{
void(*func)(void*info);//指向要求對方執(zhí)行的函數(shù)
void
*info;//函數(shù)參數(shù)
atomic_tstarted;
atomic_tfinished;
intwait;};static
struct
call_data_struct*call_data;具體中斷處理程序smp_call_function_inte62具體中斷處理程序smp_call_function_interrupt()asmlinkage
void
smp_call_function_interrupt(void){
void(*func)(void*info)=call_data->func;//取出函數(shù)指針
void
*info=call_data->info;
intwait=call_data->wait;
ack_APIC_irq();//先發(fā)回中斷確認
atomic_inc(&call_data->started); (*func)(info);//調(diào)用指定函數(shù)
if(wait)//指定動作完成后,wait設(shè)置為1
atomic_inc(&call_data->finished);}具體中斷處理程序smp_call_function_inte63具體中斷處理程序smp_call_function_interrupt()當(dāng)然,一般的函數(shù)是沒有必要請其他CPU來執(zhí)行的,因為系統(tǒng)中所有的CPU都可共享同樣的代碼和數(shù)據(jù).之所以要請求其他CPU執(zhí)行,是因為某個函數(shù)必須由特定的CPU來執(zhí)行.例如,pentium處理器有一條cpuid指令,通過這條指令可以查詢本CPU的型號、版本以及是否支持一些特殊的功能、當(dāng)前的功能設(shè)置等等信息.但是這條指令只能由具體的CPU本身執(zhí)行,而不能由別的CPU代替.這樣,如果要知道系統(tǒng)中某一個CPU的有關(guān)情況,就只能請求該CPU來執(zhí)行cpuid指令.具體中斷處理程序smp_call_function_inte64概述SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概述SMP結(jié)構(gòu)中的中斷機制65處理器間中斷IPIIPI概述IPI中斷向量IPI中斷請求函數(shù)處理器間中斷IPI66IPI概述IPI(InterprocessorInterrupt)稱為處理機間中斷.實際上,前面介紹的RESCHEDULE_VECTOR、INVALIDATE_TLB_VECTOR以及CALL_FUNCTION_VECTOR都屬于處理器間中斷IPI.從前一部分可以看出,與單cpu系統(tǒng)的中斷處理機制相比,smp系統(tǒng)僅僅作了少量修改,并且這些修改又大部分集中在處理器間中斷IPI這一部分,也許我們可以從另一個角度來看待smp系統(tǒng)的中斷機制-smp中斷機制和單CPU系統(tǒng)中斷機制在某種程度上是等價的.IPI概述IPI(InterprocessorInter67全局APICCPU0CPU1CPUn本地APIC本地APIC本地APIC外部中斷外部中斷CPU8259A單個“超級”處理器SMP結(jié)構(gòu)與單CPU系統(tǒng)中斷機制的類比全局APICCPU0CPU1CPUn本地APIC本地A68IPI中斷向量本地APIC可以識別6種消息,這些消息是由接收消息的CPU作為不同中斷向量來解釋的.(之所以不同于一般中斷向量,可能是因為這些中斷向量并不對應(yīng)著實際的中斷請求引腳).SPURIOUS_APIC_VECTOR(0xff)入口程序spurious_interrupt(),實際中斷處理程序smp_spurious_interrupt().ERROR_APIC_VECTOR(0xfe)出錯計數(shù)器溢出中斷,入口程序error_interrupt(),中斷處理程序smp_error_interrupt().INVALIDATE_TLB_VECTOR(0xfd)RESCHEDULE_VECTOR(0xfc)CALL_FUNCTION_VECTOR(0xfb)LOCAL_TIMER_VECTOR(0xef)I/OAPIC把定時中斷自動發(fā)給所有的CPU.相應(yīng)的入口程序是apic_timer_interrupt(),實際中斷服務(wù)程序為smp_apic_timer_interrupt().IPI中斷向量本地APIC可以識別6種消息,這些消息是由接收69中斷請求函數(shù)中斷請求函數(shù)用于向目標(biāo)CPU發(fā)送指定的IPI請求向量staticinlinevoidsend_IPI_allbutself(intvector)向除自己以外的所有CPU發(fā)送一個IPI.staticinlinevoidsend_IPI_all(intvector)向所有的CPU(包括自己)發(fā)送一個IPI.voidsend_IPI_self(intvector)向自己發(fā)送一個IPI.staticinlinevoidsend_IPI_mask(intmask,intvector)向由mask指定的一個或多個CPU發(fā)送一個IPI.這幾個函數(shù)功能大同小異,實現(xiàn)代碼也都比較簡單.其中以send_IPI_mask函數(shù)最為靈活,因而也稍為復(fù)雜一些.中斷請求函數(shù)中斷請求函數(shù)用于向目標(biāo)CPU發(fā)送指定的IPI請求70send_IPI_mask函數(shù)staticinlinevoid
send_IPI_mask(intmask,intvector){
unsignedlongcfg;
unsignedlongflags; __save_flags(flags);//中斷前狀態(tài)信息保存在flags中 __cli();//關(guān)中斷
apic_wait_icr_idle();//確認或等待APIC_ICR處于空閑狀態(tài)
/*ICR2、ICR是本地APIC的2個控制寄存器*/
cfg=__prepare_ICR2(mask);//根據(jù)中斷請求的目標(biāo)CPU準(zhǔn)備將要
apic_write_around(APIC_ICR2,cfg)//寫入寄存器APIC_ICR2的值 cfg=__prepare_ICR(0,vector); //根據(jù)要發(fā)送的中斷向量準(zhǔn)備將要
apic_write_around(APIC_ICR,cfg);//寫入寄存器APIC_ICR的值/*對APIC_ICR的寫入操作引發(fā)并完成將中斷向量發(fā)送至目標(biāo)cpu的工作*/ __restore_flags(flags);//恢復(fù)標(biāo)志}staticinlineint__prepare_ICR2(unsignedintmask){returnSET_APIC_DEST_FIELD(mask);}staticinlineint__prepare_ICR(unsignedintshortcut,intvector){returnAPIC_DM_FIXED|shortcut|vector|APIC_DEST_LOGICAL;}send_IPI_mask函數(shù)staticinlinev71其他相關(guān)發(fā)送函數(shù)再看一個較為簡單的函數(shù)send_IPI_allstaticinlinevoid
send_IPI_all(intvector){ __send_IPI_shortcut(APIC_DEST_ALLINC,vector);}staticinlinevoid__send_IPI_shortcut(unsignedintshortcut,intvector){
unsignedintcfg;
apic_wait_icr_idle();//確認或等待ICR空閑 cfg=__prepare_ICR(shortcut,vector);.//對ICR進行編程
apic_write_around(APIC_ICR,cfg);}/*與send_IPI_reschedule相比,少了耗時的開/關(guān)中斷動作,并且只對APIC編程一次*/除send_IPI_mask()之外的幾個發(fā)送函數(shù)實際上都是簡單的調(diào)用__send_IPI_shortcut函數(shù),只不過傳給這個函數(shù)的參數(shù)不同而已.在這些函數(shù)基礎(chǔ)上,還定義了一些功能更為專一、明確的函數(shù).比如當(dāng)一個CPU需要另一個CPU進行一次進程調(diào)度時,可以調(diào)用下面的函數(shù):void
smp_send_reschedule(intcpu){
send_IPI_mask(1<<cpu,RESCHEDULE_VECTOR);}其他相關(guān)發(fā)送函數(shù)再看一個較為簡單的函數(shù)send_IPI_al72概要SMP結(jié)構(gòu)中的中斷機制分布式中斷處理中斷初始化處理器間中斷IPISMP結(jié)構(gòu)中的進程調(diào)度概要SMP結(jié)構(gòu)中的中斷機制73相關(guān)數(shù)據(jù)結(jié)構(gòu)單CPU系統(tǒng)中,任一時刻只有當(dāng)前進程是在運行中的,但在SMP系統(tǒng)中同時有好幾個進程在運行,因此在task_struct結(jié)構(gòu)中引入兩個字段:一個是has_cpu,為1時說明進程正在運行,為0則表示不在運行;另一個字段是processor,當(dāng)has_cpu為1時指出進程是在哪一個cpu上運行.一個相關(guān)的宏操作can_schedule():
#ifdef
CONFIG_SMP
#define
idle_task(cpu)(init_tasks[cpu_number_map(cpu)])
#define
can_schedule(p,cpu)((!(p)->has_cpu)&&\ ((p)->cpus_allowed&(1<<cpu)))
#else
#define
idle_task(cpu)(&init_task)
#define
can_schedule(p,cpu)(1)
#endif相關(guān)數(shù)據(jù)結(jié)構(gòu)單CPU系統(tǒng)中,任一時刻只有當(dāng)前進程是在運行中的74調(diào)度函數(shù)Schedule()中相關(guān)代碼當(dāng)一個CPU通過schedule()從系統(tǒng)的就緒隊列中挑選了一個進程作為運行的下一個進程next,即從當(dāng)前進程prev切換到這個進程時,就將其task_struct結(jié)構(gòu)中的has_cpu字段設(shè)置成1,并將processor設(shè)置成該CPU的邏輯編號.asmlinkage
void
schedule(void){…this_cpu=prev->processor;//取得當(dāng)前CPU邏輯號…#ifdefCONFIG_SMP
next->has_cpu=1;//將要上臺進程的has_cpu設(shè)置為1
next->processor=this_cpu;//設(shè)置processor字段#endif
spin_unlock_irq(&runqueue_lock);
if(prev==next) gotosame_process;…switch_to(prev,next,prev);//從prev切換到next進程__schedule_tail(prev);//處理下臺的prev進程,看能否在其他cpu上重新調(diào)度…}調(diào)度函數(shù)Schedule()中相關(guān)代碼當(dāng)一個CPU通過sch75staticinlinevoid__schedule_tail(structtask_struct
*prev)函數(shù)保存先前進程調(diào)度策略并清零prev->policy中SCHED_YIELD標(biāo)志位將prev的has_cpu字段清零下臺前運行態(tài)?“空轉(zhuǎn)”進程或自動下臺?之前是運行態(tài)?嘗試重新調(diào)度prev進程開始返回policy=prev->policy;
prev->policy=policy&~SCHED_YIELD;wmb();task_
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 企業(yè)借款擔(dān)保人還款協(xié)議
- 《保險的分類》課件
- 關(guān)于去工廠實習(xí)報告范文錦集六篇
- 創(chuàng)客活動策劃案
- 牙體牙髓病常見疾病概述
- 小學(xué)生交通安全比賽
- 2024年店鋪資產(chǎn)分割與離婚協(xié)議
- 《說明方法及作用》課件
- 漢字聽寫大賽感言(3篇)
- 早戀感想200字(14篇)
- 紅色古色綠色文化教育活動策劃方案
- 《Monsters 怪獸》中英對照歌詞
- 《正交分解法》導(dǎo)學(xué)案
- 建筑材料知識點匯總
- 平面構(gòu)成作品欣賞
- 英語管道專業(yè)術(shù)語
- 淺談?wù)Z文課程內(nèi)容的橫向聯(lián)系
- 社會工作畢業(yè)論文(優(yōu)秀范文8篇)
- 五篇500字左右的短劇劇本
- 新形勢下如何加強醫(yī)院新聞宣傳工作
- 第十一章總集與別集(杜澤遜版)
評論
0/150
提交評論