如果你認爲本系列文章對你有所幫助,請大家有錢的捧個錢場,點擊此處贊助,贊助額0.1元起步,多少隨意
聲明:本文只用於個人學習交流,若不慎造成侵權,請及時聯繫我,立即予以改正
鋒影
email:[email protected]
學習目的:
1.qnx微內核怎樣操作硬件中斷?
2.我們怎樣用代碼操作中斷?
3.不同的中斷操作策略
學習概要
1.中斷的概念
2.中斷服務子程序中的進程間通信
3.程序架構
1.中斷的概念
可搶佔中斷調度:
不可搶佔中斷調度:
中斷有下特性:
1.它的優先級比任何的線程都高
2.可被高優先級中斷搶佔(需要平臺支持)
3.能被用戶空間程序操作(驅動被動態加載,而不是存在內核空間)
4.被內核調用(有內核特權)
中斷潛在時間:
影響因素:
1.中斷失能花費的時間
2.相同或更高優先級中斷服務程序花費時間
3.中斷其他的服務程序花費時間
4.特定屏蔽中斷花費時間(通常是共享中斷和InterruptAttachEvent())
調度潛在時間:
影響因素:
1.花費在其他中斷服務程序的時間
2.被調度的線程優先級高低
3.花費在已經處於READY狀態的更高優先級線程的時間
因爲中斷的優先級比線程高,所以,花費在中斷服務程序的時間直接影響到線程調度。
中斷調度和中斷操作策略
中斷相關函數調用:
id = InterruptAttach (int intr,
struct sigevent *(*handler)(void *, int),
void *area, int size, unsigned flags);
id = InterruptAttachEvent (int intr, struct sigevent *event,
unsigned flags);
InterruptDetach (int id);
InterruptWait (int flags, uint64_t *reserved);
InterruptMask (int intr, int id);
InterruptUnmask (int intr, int id);
InterruptLock (struct intrspin *spinlock);
InterruptUnlock (struct intrspin *spinlock);
注:在調用以上函數之前,必須要先調用ThreadCtl(_NTO_TCTL_IO, 0),以取得I/O特權。
InterruptEnable (void);
InterruptDisable (void);
以上兩個函數只用於non-SMP系統,所以並不通用,我們以該使用先兩個函數:
InterruptLock();
InterruptUnlock();
一個簡單的例子
struct sigevent event;
main ()
{
ThreadCtl (_NTO_TCTL_IO, 0);
SIGEV_INTR_INIT (&event);
id = InterruptAttachEvent (intnum, &event, ...);
for (;;)
{
InterruptWait (0, NULL);
// do the interrupt work here, at thread priority
InterruptUnmask (intnum, id);
}
}
注:當內核控制時, 它將屏蔽掉中斷,並且使用event來處理適當的調度。在本例中,因爲使用的是SIGEV_INTR,所以 InterruptWait()將不會阻塞。
怎樣將中斷和event相聯繫:
一旦一個event和中斷相關聯後,內核將自動對中斷解除屏蔽。
傳遞給 InterruptAttachEvent()的參數是返回的真實event。
當中斷產生以及內核調用時,內核將在線程調度之前自動屏蔽中斷,以保證它在的安全性。
例子:
struct sigevent event; //創建一個event
const struct sigevent *
handler (void *not_used, int id) //返回一個實際的event
{
if (check_status_register()) // place-holder for hw code
{
return (&event);
}
else
{
return (NULL);
}
}
main ()
{
ThreadCtl (_NTO_TCTL_IO, 0);
SIGEV_INTR_INIT (&event);
id = InterruptAttach (intnum, handler, NULL, 0, ...);
for (;;)
{
InterruptWait (0, NULL);
// do some or all of the work here
}
}
注:在本例中,這是一個用戶提供的中斷程序。將有一個work必須在timing reasons中斷優先級被完成。中斷程序和線程將共享這個work。中斷將完成時間關鍵任務(the time-critical work),然後線程將被喚醒去做非時間關鍵任務(the non-time-critical work)(數據分析,並傳遞給其他線程)。///參考linux的底半部,頂半部
在以上的中斷程序中, check_status_register() 是去檢查硬件的各種狀態寄存器,檢查硬件是否產生了中斷,或者清除中斷源。這些在level-sensitive architectures上都是需要的,因爲中斷將被共享,並且內核將在中斷鏈的最後產生一個EOI(外部中斷的中斷結束命令),所以我們必須在返回之前清除中斷。這也就是爲什麼在使用InterruptAttachEvent()時,內核要在線程調度前進行中斷屏蔽。
怎樣將中斷服務程序handle和中斷向量相聯繫?
注:一旦一個handler和中斷相關聯,內核就將對中斷解除屏蔽。
area的好處是它可以被傳遞給你的中斷服務程序的第一個參數。它讓一箇中斷程序處理多箇中斷。
並且它避免了使用全局變量。
logical interrupt中斷號是:
在啓動時就定義了,在板子的buildfile中。bulid file在QNX-TATGET/cpu/boot/build中。
例如:
# Interrupt Assignments
# ---------------------
#
# vector: 0 (PPC800_INTR_IRQ0)
# trigger: falling edge
# device: unassigned
...
# vector: 15 (PPC800_INTR_LVL7)
# trigger: N/A
# device: Programmable Interval Timer interrupt (system timer)
...
# vector: 0x8001001e (PPC800_INTR_CPMSCC1)
# trigger: N/A
# device: CPM SCC1
interruptAttachEvent and InterruptAttach的flag參數:
1._NTO_INTR_FLAGS_END
內核包含了給每個中斷的處理函數和events的隊列。這個參數表示新的處理函數和event應該被添加在隊列的結尾而不是開頭。
2._NTO_INTR_FLAGS_PROCESS
將處理程序/event與進程向關聯。通常 它們將與綁定的線程相關聯。如果這個線程死了,這個處理程序也就取消綁定。但是如果有這個參數,即使線程死了,處理程序也不會被取消綁定。
你應該使用進程中的event,如pulse或signal
注:如果你使用了這個標誌,你的中斷handler將不會返回event.sigev_notify of _SIGEV_INTR,因爲它將去綁定的可能不再存在的線程。
3._NTO_INTR_FLAGS_TRK_MSK
這個參數指定了當應用app與中斷斷開綁定時,內核將使用合適的數字來取消中斷屏蔽,以保證中斷函數正常。通常使用這個flag。
控制中斷:
1.使用InterruptMask() and InterruptUnmask() 來對中斷進行屏蔽和解屏蔽。
2.爲了對一箇中斷解屏蔽,你必須使用與屏蔽相同的數字來操作。
3.使用 InterruptLock() and InterruptUnlock() 來失能和使能中斷。在SMP中使用自旋鎖來同步中斷
注:Mask/unmask是在PIC(Programmable Interrupt Controller)中進行
Disable/enable 是在CPU中進行。
綁定和解除綁定Attach & Detach?
註解:在開始時,內核屏蔽了所有中斷,來避免軟件不關心的硬件中斷開銷。
當一箇中斷髮生時,內核將控制和決定是哪一個中斷。它將遍歷這個中斷的所有handlers和event。對於enent,它將event入隊。對於handler,它將準備MMU來啓動進程,然後調用handler。如果handler返回一個event,它將入隊。在所有的event和handler都完成時,內核將給PIC一個EOI。一旦所用中斷都完成時,內核將遍歷所有event隊列,然後發生調度,然後返回到最高的READY狀態的線程。
註解:這個例子展示的內容對於支持共享中斷的bus(PCI)來說很有用。
這兒有兩個主要的硬件中斷架構:edge-sensitive and level-sensitive.
在edge-sensitive架構中,不論硬件中斷線狀態合適改變,中斷都將被註冊。存在問題:如果一個設備觸發一箇中斷,中斷服務服務程序(ISR)將被調用。如果另一個設備也觸發了這個中斷,在中斷服務程序復位了第一個中斷前,非額外中斷將被生成給系統,因爲當總線正在被第一個設備驅動時,這兒沒有第二個edge了。
在level-sensitive架構中,當中斷線是激活狀態時,中斷也被當做激活。當內核產生一個EOI,如果還存在激活狀態的中斷線時,ISR將被立即再次調用來服務下一個設備。因此在此架構中,ISR必須要清理中斷源,或者在返回之前,屏蔽它。
這種清理clear在process level不會被執行。屏蔽mask在InterruptAttachEvent()中是自動執行的。
應該綁定一個handler還是event?
1.對於qnx系統,內核是一個單一故障點。
綁定handler會增加單一故障點,event不會。
2 . event的debug更簡單。(ISR不能單步debug)
3 . 在線程中執行h/w操作時,有完整的系統功能。
4.event比handler有更小的系統開銷。
(使用event時,不需要MMU工作來取得進程空間操作權)
5 . 爲每個中斷調度線程會產生更多的系統開銷
6 . handler比線程調度有更小的latency。
2.中斷服務子程序中的進程間通信
方法:
1.SIGEV_INTR/InterruptWait()
最簡單,最快速;必須服務一個線程;queue深度只有1
2.pulse
在頻道中能有多個線程來等待接收消息;能入隊;最複雜
3.Signal
使用一個signal handler是最費資源的,但是在使用sigwaitinfo()時,比pulse更快一點;能入隊
等待中斷髮生最簡單的方法:
InterruptWait (reserved, reserved);
1
等待線程必須是一開始綁定handler的線程。
使用pulse來通知:
#define INTR_PULSE _PULSE_CODE_MINAVAIL
struct sigevent event;
main ()
{
...
chid = ChannelCreate( 0 );
coid = ConnectAttach( ND_LOCAL_NODE, 0, chid, _NTO_SIDE_CHANNEL, 0 );
SIGEV_PULSE_INIT( &event, coid, MyPriority, INTR_PULSE, 0 );
InterruptAttach( intnum, handler, NULL, 0, _NTO_INTR_FLAGS_TRK_MSK );
for (;;)
{
rcvid = MsgReceive( chid, ... );
if (rcvid == 0)
{
// we got a pulse
}
}
}
const struct sigevent *
handler (void *area, int id)
{
// do whatever work is required
return (&event); // wake up main thread
}
或者:
const struct sigevent *
intHandler (void *not_used, int id)
{
...
if (nothing_to_report)
{
return (NULL);
}
else
{
if (low_priority_event)
{
return (&lowpri_event);
}
else
{
return (&highpri_event);
}
}
...
}