?Ingo Molnar 的實時補(bǔ)丁
這是本系列文章(分兩部分)的第 2 部分,詳細(xì)分析了一個典型的實時實現(xiàn)(Ingo's RT patch)。第 1 部分闡述了實時的概念、衡量實時性的指標(biāo),詳細(xì)地分析了嵌入式系統(tǒng)對 Linux 實時性的需求以及 Linux 在實時性方面的不足,然后簡單地描述了三個著名的 Linux 實時實現(xiàn)。
一、簡介
Ingo Molnar 的實時補(bǔ)丁是完全開源的,它采用的實時實現(xiàn)技術(shù)完全類似于Timesys Linux,而且中斷線程化的代碼是基于TimeSys Linux的中斷線程化代碼的。這些實時實現(xiàn)技術(shù)包括:中斷線程化(包括IRQ和softirq)、用Mutex取代spinlock、優(yōu)先級繼承和死鎖 檢測、等待隊列優(yōu)先級化、大內(nèi)核鎖(BKL-Big Kernel Lock)可搶占等。
該實時實現(xiàn)包含了以前的VP補(bǔ)丁(在內(nèi)核郵件列表這么稱呼,即Voluntary Preemption),VP補(bǔ)丁由針對2.4內(nèi)核的低延遲補(bǔ)丁(low latency patch)演進(jìn)而來,它使用兩種方法來實現(xiàn)低延遲:
一種就是鎖分解,即把大循環(huán)中保持的鎖分解為每一輪循環(huán)中都獲得鎖和釋放鎖,典型的代碼結(jié)構(gòu)示例如下:鎖分解前:
spin_lock(&x_lock);
for (…) {
? ? some operations;
? ? …
}
spin_unlock(&x_lock);
鎖分解后:
for (…) {
? ? spin_lock(&x_lock);
? ? some operations;
? ? …
? ? spin_unlock(&x_lock);
}
另一種是增加搶占點,即自愿被搶占,下面是一個鼠標(biāo)驅(qū)動的例子:
未增加搶占點以前在文件driver/char/tty_io.c中的一段代碼:
? ?? ???/* Do the write .. */
? ?? ???for (;;) {
? ?? ?? ?? ?? ? size_t size = count;
? ?? ?? ?? ?? ? if (size > chunk)
? ?? ?? ?? ?? ?? ?? ?? ?size = chunk;
? ?? ?? ?? ?? ? ret = -EFAULT;
? ?? ?? ?? ?? ? if (copy_from_user(tty->write_buf, buf, size))
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? lock_kernel();
? ?? ?? ?? ?? ? ret = write(tty, file, tty->write_buf, size);
? ?? ?? ?? ?? ? unlock_kernel();
? ?? ?? ?? ?? ? if (ret <= 0)
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? written += ret;
? ?? ?? ?? ?? ? buf += ret;
? ?? ?? ?? ?? ? count -= ret;
? ?? ?? ?? ?? ? if (!count)
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? ret = -ERESTARTSYS;
? ?? ?? ?? ?? ? if (signal_pending(current))
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ???}
???
增加搶占點之后:
? ?? ?? ?? ?? ? /* Do the write .. */
? ?? ???for (;;) {
? ?? ?? ?? ?? ? size_t size = count;
? ?? ?? ?? ?? ? if (size > chunk)
? ?? ?? ?? ?? ?? ?? ?? ?size = chunk;
? ?? ?? ?? ?? ? ret = -EFAULT;
? ?? ?? ?? ?? ? if (copy_from_user(tty->write_buf, buf, size))
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? lock_kernel();
? ?? ?? ?? ?? ? ret = write(tty, file, tty->write_buf, size);
? ?? ?? ?? ?? ? unlock_kernel();
? ?? ?? ?? ?? ? if (ret <= 0)
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? written += ret;
? ?? ?? ?? ?? ? buf += ret;
? ?? ?? ?? ?? ? count -= ret;
? ?? ?? ?? ?? ? if (!count)
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? ret = -ERESTARTSYS;
? ?? ?? ?? ?? ? if (signal_pending(current))
? ?? ?? ?? ?? ?? ?? ?? ?break;
? ?? ?? ?? ?? ? cond_resched();
? ?? ???}
語句cond_resched()將判斷是否有進(jìn)程需要搶占當(dāng)前進(jìn)程,如果是將立即發(fā)生調(diào)度,這就是增加的強(qiáng)占點。
為了能并入主流內(nèi)核,Ingo Molnar的實時補(bǔ)丁也采用了非常靈活的策略,它支持四種搶占模式:
1.No Forced Preemption (Server),這種模式等同于沒有使能搶占選項的標(biāo)準(zhǔn)內(nèi)核,主要適用于科學(xué)計算等服務(wù)器環(huán)境。
2.Voluntary Kernel Preemption (Desktop),這種模式使能了自愿搶占,但仍然失效搶占內(nèi)核選項,它通過增加搶占點縮減了搶占延遲,因此適用于一些需要較好的響應(yīng)性的環(huán)境,如桌面環(huán)境,當(dāng)然這種好的響應(yīng)性是以犧牲一些吞吐率為代價的。
3.Preemptible Kernel (Low-Latency Desktop),這種模式既包含了自愿搶占,又使能了可搶占內(nèi)核選項,因此有很好的響應(yīng)延遲,實際上在一定程度上已經(jīng)達(dá)到了軟實時性。它主要適用于桌面和一些嵌入式系統(tǒng),但是吞吐率比模式2更低。
4.Complete Preemption (Real-Time),這種模式使能了所有實時功能,因此完全能夠滿足軟實時需求,它適用于延遲要求為100微秒或稍低的實時系統(tǒng)。
實現(xiàn)實時是以犧牲系統(tǒng)的吞吐率為代價的,因此實時性越好,系統(tǒng)吞吐率就越低。
在寫本文時最新的實時實現(xiàn)補(bǔ)丁是:
http://people.redhat.com/~mingo/ ... 6.12-rc4-V0.7.47-03
它自2004年10月發(fā)布以來一直更新很頻繁,幾乎每天都有新版本發(fā)布,直到最近才比較穩(wěn)定。它的很多代碼部分已經(jīng)并入到標(biāo)準(zhǔn)的2.6內(nèi)核源碼數(shù),包括 IRQ子系統(tǒng),那為中斷線程化提供了很好的基礎(chǔ);自愿搶占;大內(nèi)核鎖可搶占;這些已經(jīng)包含在2.6.11中。作者預(yù)期,其余的代碼部分也將很快進(jìn)入到主流 內(nèi)核,可能是2.6.12或以后的某個版本。
因此,本文專門對這個實時實現(xiàn)進(jìn)行詳細(xì)的實現(xiàn)分析將有重要意義。
二、中斷線程化
中斷線程化是實現(xiàn)Linux實時性的一個重要步驟,在Linux標(biāo)準(zhǔn)內(nèi)核中,中斷是最高優(yōu)先級的執(zhí)行單元,不管內(nèi)核當(dāng)時處理什么,只要有中斷事件,系統(tǒng)將 立即響應(yīng)該事件并執(zhí)行相應(yīng)的中斷處理代碼,除非當(dāng)時中斷關(guān)閉(即使用local_irq_disable失效了IRQ)。因此,如果系統(tǒng)有嚴(yán)重的網(wǎng)絡(luò)或 I/O負(fù)載,中斷將非常頻繁,實時任務(wù)將很難有機(jī)會運(yùn)行,也就是說,毫無實時性可言。中斷線程化之后,中斷將作為內(nèi)核線程運(yùn)行而且賦予不同的實時優(yōu)先級, 實時任務(wù)可以有比中斷線程更高的優(yōu)先級,這樣,實時任務(wù)就可以作為最高優(yōu)先級的執(zhí)行單元來運(yùn)行,即使在嚴(yán)重負(fù)載下仍有實時性保證。
中斷線程化的另一個重要原因是spinlock被mutex取代。中斷處理代碼中大量地使用了spinlock,當(dāng)spinlock被mutex取代之 后,中斷處理代碼就有可能因為得不到鎖而需要被掛到等待隊列上,但是只有可調(diào)度的進(jìn)程才可以這么做,如果中斷處理代碼仍然使用原來的 spinlock,則spinlock取代mutex的努力將大打折扣,因此為了滿足這一要求,中斷必須被線程化,包括IRQ和softirq。
在Ingo Molnar的實時補(bǔ)丁中,中斷線程化的實現(xiàn)方法是:
對于IRQ,在內(nèi)核初始化階段init(該函數(shù)在內(nèi)核源碼樹的文件init/main.c中定義)調(diào)用init_hardirqs(該函數(shù)在內(nèi)核源碼樹的 文件kernel/irq/manage.c中定義)來為每一個IRQ創(chuàng)建一個內(nèi)核線程,IRQ號為0的中斷賦予實時優(yōu)先級49,IRQ號為1的賦予實時 優(yōu)先級48,依次類推直到25,因此任何IRQ線程的最低實時優(yōu)先級為25。原來的 do_IRQ 被分解成兩部分,架構(gòu)相關(guān)的放在類似于arch/*/kernel/irq.c的文件中,名稱仍然為do_IRQ,而架構(gòu)獨立的部分被放在IRQ子系統(tǒng)的 位置kernel/irq/handle.c中,名稱為__do_IRQ。當(dāng)發(fā)生中斷時,CPU將執(zhí)行do_IRQ來處理相應(yīng)的中斷,do_IRQ將做了 必要的架構(gòu)相關(guān)的處理后調(diào)用__do_IRQ。函數(shù)__do_IRQ將判斷該中斷是否已經(jīng)被線程化(如果中斷描述符的狀態(tài)字段不包含SA_NODELAY 標(biāo)志說明中斷被線程化了),如果是將喚醒相應(yīng)的處理線程,否則將直接調(diào)用handle_IRQ_event(在IRQ子系統(tǒng)位置的 kernel/irq/handle.c文件中)來處理。對于已經(jīng)線程化的情況,中斷處理線程被喚醒并開始運(yùn)行后,將調(diào)用do_hardirq(在源碼樹 的IRQ子系統(tǒng)位置的文件kernel/irq/manage.c中定義)來處理相應(yīng)的中斷,該函數(shù)將判斷是否有中斷需要被處理(中斷描述符的狀態(tài)標(biāo)志 IRQ_INPROGRESS),如果有就調(diào)用handle_IRQ_event來處理。handle_IRQ_event將直接調(diào)用相應(yīng)的中斷處理句柄 來完成中斷處理。
如果某個中斷需要被實時處理,它可以用SA_NODELAY標(biāo)志來聲明自己非線程化,例如:
系統(tǒng)的時鐘中斷就是,因為它被用來維護(hù)系統(tǒng)時間以及定時器等,所以不應(yīng)當(dāng)被線程化。
static struct irqaction irq0??=
{ timer_interrupt, SA_INTERRUPT | SA_NODELAY, CPU_MASK_NONE, "timer", NULL, NULL};
這是在靜態(tài)聲明時指定不要線程化,也可以在調(diào)用request_irq時指定,如:
request_irq (HIGHWIRE_SMI_IRQ,? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?
highwire_smi_interrupt, SA_NODELAY, "System Management Switch", NULL))
對于softirq,標(biāo)準(zhǔn)Linux內(nèi)核已經(jīng)使用內(nèi)核線程的方式來處理,只是Ingo Molnar的實時補(bǔ)丁做了修改使其易于被搶占,改進(jìn)了實時性,具體的修改包括:
把ksoftirqd的優(yōu)先級設(shè)置為nice值為-10,即它的優(yōu)先級高于普通的用戶態(tài)進(jìn)程和內(nèi)核態(tài)線程,但它不是實時線程,因此這樣一來softirq對實時性的影響將顯著減小。
在處理軟中斷期間,搶占是使能的,這使得實時性更進(jìn)一步地增強(qiáng)。
在處理軟中斷的函數(shù)___do_softirq中,每次處理完一個待處理的軟中斷后,都將調(diào)用cond_resched_all(),這顯著地增加了調(diào)度點數(shù),提高了整個系統(tǒng)的實時性。
增加了兩個函數(shù)_do_softirq和___do_softirq,其中___do_softirq就是原來的__do_softirq,只是增加了調(diào) 度點。__do_softirq則是對___do_softirq的包裝,_do_softirq是對do_softirq的替代,但保留 do_softirq用于一些特殊需要。
三、spinlock轉(zhuǎn)換成mutex
spinlock是一個高效的共享資源同步機(jī)制,在SMP(對稱多處理器Symmetric Multiple Proocessors)的情況下,它用于保護(hù)共享資源,如全局的數(shù)據(jù)結(jié)構(gòu)或一個只能獨占的硬件資源。但是spinlock保持期間將使搶占失效,用 spinlock保護(hù)的區(qū)域稱為臨界區(qū)(Critical Section),在內(nèi)核中大量地使用了spinlock,有大量的臨界區(qū)存在,因此它們將嚴(yán)重地影響著系統(tǒng)的實時性。Ingo Molnar的實時補(bǔ)丁使用mutex來替換spinlock,它的意圖是讓spinlock可搶占,但是可搶占后將產(chǎn)生很多后續(xù)影響。
Spinlock失效搶占的目的是避免死鎖。Spinlock如果可搶占了,一個spinlock的競爭者將可能搶占該spinlock的保持者來運(yùn)行, 但是由于得不到spinlock將自旋在那里,如果競爭者的優(yōu)先級高于保持者的優(yōu)先級,將形成一種死鎖的局面,因為保持者無法得到運(yùn)行而永遠(yuǎn)不能釋放 spinlock,而競爭者由于不能得到一個不可能釋放的spinlock而永遠(yuǎn)自旋在那里。
由于中斷處理函數(shù)也可以使用spinlock,如果它使用的spinlock已經(jīng)被一個進(jìn)程保持,中斷處理函數(shù)將無法繼續(xù)進(jìn)行,從而形成死鎖,這樣的 spinlock在使用時應(yīng)當(dāng)中斷失效來避免這種死鎖的情況發(fā)生。標(biāo)準(zhǔn)linux內(nèi)核就是這么做的,中斷線程化之后,中斷失效就沒有必要,因為遇到這種狀 況后,中斷線程將掛在等待隊列上并放棄CPU讓別的線程或進(jìn)程來運(yùn)行。
等待隊列就是解決這種死鎖僵局的方法,在Ingo Molnar的實時補(bǔ)丁中,每個spinlock都有一個等待隊列,該等待隊列是按進(jìn)程或線程的優(yōu)先級排隊的。如果一個進(jìn)程或線程競爭的spinlock 已經(jīng)被另一個線程保持,它將把自己掛在該spinlock的優(yōu)先級化的等待隊列上,然后發(fā)生調(diào)度把CPU讓給別的進(jìn)程或線程。
需要特別注意,對于非線程化的中斷,必須使用原來的spinlock,原因前面已經(jīng)講得很清楚。
原來的spinlock結(jié)構(gòu)如下:
typedef struct {
? ?? ???volatile unsigned long lock;
# ifdef CONFIG_DEBUG_SPINLOCK
? ?? ???unsigned int magic;
# endif
# ifdef CONFIG_PREEMPT
? ?? ???unsigned int break_lock;
# endif
} spinlock_t;
它非常簡潔,替換成mutex之后,它的結(jié)構(gòu)為:
typedef struct {
? ?? ???struct rt_mutex lock;
? ?? ???unsigned int break_lock;
} spinlock_t;
其中struct rt_mutex結(jié)構(gòu)如下:
struct rt_mutex {
? ?? ???raw_spinlock_t? ?? ?? ? wait_lock;
? ?? ???struct plist? ?? ?? ?? ?wait_list;
? ?? ???struct task_struct? ?? ?*owner;
? ?? ???int? ?? ?? ?? ?? ?? ?? ?owner_prio;
# ifdef CONFIG_RT_DEADLOCK_DETECT
? ?? ???int? ?? ?? ?? ?? ?? ?? ?save_state;
? ?? ???struct list_head? ?? ???held_list;
? ?? ???unsigned long? ?? ?? ???acquire_eip;
? ?? ???char? ?? ?? ?? ?? ?? ???*name, *file;
? ?? ???int? ?? ?? ?? ?? ?? ?? ?line;
# endif
};
類型raw_spinlock_t就是原來的spinlock_t。在結(jié)構(gòu)struct rt_mutex中的wait_list字段就是優(yōu)先級化的等待隊列。
原來的rwlock_t結(jié)構(gòu)如下:
typedef struct { volatile unsigned long lock; # ifdef CONFIG_DEBUG_SPINLOCK unsigned magic; # endif # ifdef CONFIG_PREEMPT unsigned int break_lock; # endif } rwlock_t;
被mutex化的rwlock結(jié)構(gòu)如下:
typedef struct { struct rw_semaphore lock; unsigned int break_lock; } rwlock_t;
其中rw_semaphore結(jié)構(gòu)為:
struct rw_semaphore { struct rt_mutex lock; int read_depth; };
rwlock_t和spinlock_t沒有本質(zhì)的不同,只是rwlock_t只能有一個寫者,但可以有多個讀者,因此使用了字段read_depth,其他都等同于spinlock_t。
如果必須使用原來的spinlock,可以把它聲明為raw_spinlock_t,如果必須使用原來的rwlock_t,可以把它聲明為 raw_rwlock_t,但是對其進(jìn)行鎖或解鎖操作時仍然使用同樣的函數(shù),靜態(tài)初始化時必須分別使用RAW_SPIN_LOCK_UNLOCKED和 RAW_RWLOCK_UNLOCKED。為什么不同的變量類型可以使用同樣的函數(shù)操作呢?關(guān)鍵在于使用了gcc的內(nèi)嵌函數(shù) __builtin_types_compatible_p,下面以spin_lock為例來說明其中的奧妙:
#define spin_lock(lock)? ?? ?? ?PICK_OP(raw_spinlock_t, spin, _lock, lock)
PICK_OP的定義為:
#define PICK_OP(type, optype, op, lock)? ?? ?? ?? ?? ?? ?? ?? ? \
do {? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?\
? ?? ???if (TYPE_EQUAL((lock), type))? ?? ?? ?? ?? ?? ?? ?? ?? ?\
? ?? ?? ?? ?? ? _raw_##optype##op((type *)(lock));? ?? ?? ?? ???\
? ?? ???else if (TYPE_EQUAL(lock, spinlock_t))? ?? ?? ?? ?? ?? ?\
? ?? ?? ?? ?? ? _spin##op((spinlock_t *)(lock));? ?? ?? ?? ?? ? \
? ?? ???else __bad_spinlock_type();? ?? ?? ?? ?? ?? ?? ?? ?? ???\
} while (0)
TYPE_EQUAL的定義為:
? ?? ?? ?? ?? ? #define TYPE_EQUAL(lock, type) \
? ?? ?? ?? ?? ? __builtin_types_compatible_p(typeof(lock), type *)
gcc內(nèi)嵌函數(shù)__builtin_types_compatible_p用于判斷一個變量的類型是否為某指定的類型,如果是就返回1,否則返回0。
因此,如果一個spinlock的類型如果是spinlock_t,宏spin_lock的預(yù)處理結(jié)果將是:
do {
? ? if (0)
? ?? ???_raw_spin_lock((raw_spinlock_t *)(lock));
? ? else if (1)
? ?? ???_spin_lock((spinlock_t *)(lock));
? ? else __bad_spinlock_type;
} while (0)
如果一個spinlock的類型為raw_spinlock_t,宏spin_lock的預(yù)處理結(jié)果將是:
do {
? ? if (1)
? ?? ???_raw_spin_lock((raw_spinlock_t *)(lock));
? ? else if (0)
? ?? ???_spin_lock((spinlock_t *)(lock));
? ? else __bad_spinlock_type;
} while (0)
很明顯,如果類型為spinlock_t,將運(yùn)行函數(shù)_spin_lock,而如果類型為raw_spinlock_t,將運(yùn)行函數(shù)_raw_spin_lock。
_spin_lock是新的spinlock的鎖實現(xiàn)函數(shù),而_raw_spin_lock就是原來的spinlock的鎖實現(xiàn)函數(shù)。
等待隊列優(yōu)先級化的目的是為了更好地改善實時性,因為優(yōu)先級化后,每次當(dāng)spinlock保持者釋放鎖時總是喚醒排在最前面的優(yōu)先級最高的進(jìn)程或線程,而喚醒的時間復(fù)雜度為O(1)。
四、優(yōu)先級繼承和死鎖檢測
spinlock被mutex化后會產(chǎn)生優(yōu)先級逆轉(zhuǎn)(Priority Inversion)現(xiàn)象。所謂優(yōu)先級逆轉(zhuǎn),就是優(yōu)先級高的進(jìn)程由于優(yōu)先級低的進(jìn)程保持了競爭資源被迫等待,而讓中間優(yōu)先級的進(jìn)程運(yùn)行,優(yōu)先級逆轉(zhuǎn)將導(dǎo)致 高優(yōu)先級進(jìn)程的搶占延遲增大,中間優(yōu)先級的進(jìn)程的執(zhí)行時間的不確定性導(dǎo)致了高優(yōu)先級進(jìn)程搶占延遲的不確定性,因此為了保證實時性,必須消除優(yōu)先級逆轉(zhuǎn)現(xiàn) 象。
優(yōu)先級繼承協(xié)議(Priority Inheritance Protocol)和優(yōu)先級頂棚協(xié)議(Priority Ceiling Protocol)就是專門針對優(yōu)先級逆轉(zhuǎn)問題提出的解決辦法。
所謂優(yōu)先級繼承,就是spinlock的保持者將繼承高優(yōu)先級的競爭者進(jìn)程的優(yōu)先級,從而能先于中間優(yōu)先級進(jìn)程運(yùn)行,盡可能快地釋放鎖,這樣高優(yōu)先級進(jìn)程就能很快得到競爭的spinlock,使得搶占延遲更確定,更短。
所謂優(yōu)先級頂棚,就是根據(jù)靜態(tài)分析確定一個spinlock的可能擁有者的最高優(yōu)先級,然后把spinlock的優(yōu)先級頂棚設(shè)置為該確定的值,每次當(dāng)進(jìn)程獲得該spinlock后,就將該進(jìn)程的優(yōu)先級設(shè)置為spinlock的優(yōu)先級頂棚值。
Ingo Molnar的實時補(bǔ)丁實現(xiàn)了優(yōu)先級繼承協(xié)議,但沒有實現(xiàn)優(yōu)先級頂棚協(xié)議。
Spinlock被mutex化后引入的另一個問題就是死鎖,典型的死鎖有兩種:
一種為自鎖,即一個spinlock保持者試圖獲得它已經(jīng)保持的鎖,很顯然,這會導(dǎo)致該進(jìn)程無法運(yùn)行而死鎖。
另一種為非順序鎖而導(dǎo)致的,即進(jìn)程 P1已經(jīng)保持了spinlock LOCKA但是要獲得進(jìn)程P2已經(jīng)保持的spinlock LOCKB,而進(jìn)程P2要獲得進(jìn)程P1已經(jīng)保持的spinlock LOCKA,這樣進(jìn)程P1和P2都將因為需要得到對方擁有的但永遠(yuǎn)不可能釋放的spinlock而死鎖。
Ingo Molnar的實時補(bǔ)丁對這兩種情況進(jìn)行了檢測,一旦發(fā)生這種死鎖,內(nèi)核將輸出死鎖執(zhí)行路徑并panic。
五、大內(nèi)核鎖可搶占
大內(nèi)核鎖(BKL---Big Kernel Lock)實質(zhì)上也是spinlock,只是它一般用于保護(hù)整個內(nèi)核,該鎖的保持時間比較長,因此它對整個系統(tǒng)的實時性影響是非常大的,在Ingo Molnar的實時補(bǔ)丁中,大內(nèi)核鎖使用了semaphore來實現(xiàn),如果內(nèi)核配置為前面三種搶占模式,struct semaphore是架構(gòu)相關(guān)的,如對于x86,結(jié)構(gòu)定義如下:
struct semaphore {
? ?? ???atomic_t count;
? ?? ???int sleepers;
? ?? ???wait_queue_head_t wait;
};
但對于第四種搶占模式,其結(jié)構(gòu)為:
struct semaphore {
? ?? ???atomic_t count;
? ?? ???struct rt_mutex lock;
};
注意新的spinlock定義也包含字段struct rt_mutex lock,因此可搶占大內(nèi)核鎖和新的spinlock共用了低層的處理代碼。使用semaphore之后,大內(nèi)核鎖就可搶占了。
六、架構(gòu)支持和一些移植以及驅(qū)動注意事項
Ingo Molnar的實時補(bǔ)丁支持的架構(gòu)包括i386、x86_64、ppc和mips,基本上含蓋了主流的架構(gòu),對于其他的架構(gòu),移植起來也是非常容易的。
架構(gòu)移植主要涉及到以下幾個方面:
1.中斷線程化
中斷線程化有兩種做法,一種是利用IRQ子系統(tǒng)的代碼,另一種是在架構(gòu)相關(guān)的子樹實現(xiàn),前一種方法利用的是已有的中斷線程化代碼,因此移植時幾乎不需要做 什么工作,但是對一些架構(gòu),這種方法缺乏靈活性,尤其是一些架構(gòu)中斷處理比較特別時,可能會是IRQ子系統(tǒng)的中斷線程化代碼部分變的越來越丑陋,因此對于 這種架構(gòu),后一種方法就有明顯優(yōu)勢,當(dāng)然在后一種方法中仍然可以拷貝IRQ子系統(tǒng)內(nèi)的大部分線程化處理代碼。
中斷線程化要求一些spinlock或rwlock必須是raw_*類型的,而且一些IRQ必須是非線程化的,如時鐘中斷、級聯(lián)中斷等。這些是中斷線程化的必要前提。
2.一些架構(gòu)相關(guān)的代碼
有一些變量定義在架構(gòu)相關(guān)的子樹下,如hardirq_preemption等,還有就是需要對entry.S做一些修改,因為增加了一個新的調(diào)用 preempt_schedule_irq,它要求在調(diào)用之前失效中斷。還有就是一些調(diào)試代碼支持,那是完全架構(gòu)相關(guān)的必須重新實現(xiàn),如mcount。
3.架構(gòu)相關(guān)的semaphore定義必須在第四種搶占模式下失效
前面已經(jīng)講過,如果使能第四種搶占模式,將使用新定義的semaphore,它是架構(gòu)無關(guān)的,相應(yīng)的處理代碼也是架構(gòu)無關(guān)的,因此原來的架構(gòu)相關(guān)的定義和處理代碼必須失效,這需要修改相應(yīng)的.h、.c和Makefile。
4.一些spinlock必須聲明為raw_*類型的
在架構(gòu)相關(guān)的子樹中,一些spinlock必須聲明為raw_*類型的,靜態(tài)初始化也必須修改為RAW_*,一些外部聲名也得做相應(yīng)的改動。
5.在打開第四種搶占模式或中斷線程化使能之后,一些編程邏輯要求已經(jīng)發(fā)生了變化。
中斷線程化后,在中斷處理函數(shù)中失效中斷不在需要,因為如果中斷處理線程在中斷失效后想得到spinlock時,將可能發(fā)生上下文切換,新的實時實現(xiàn)認(rèn)為這種狀況不應(yīng)當(dāng)發(fā)生將輸出警告信息。
原來用中斷失效保護(hù)共享資源,現(xiàn)在完全可以用搶占失效來替代,因此不是萬不得已,建議不使用中斷失效。在網(wǎng)卡驅(qū)動的發(fā)送處理函數(shù)中不能失效中斷,因此原來顯式得失效中斷的函數(shù)應(yīng)當(dāng)被替換,如:
local_irq_save應(yīng)當(dāng)變成為local_irq_save_nort
local_irq_restore應(yīng)當(dāng)變成為local_irq_restore_nort
網(wǎng)絡(luò)的核心代碼將主動檢測這種情況,如果中斷失效了,將重新打開中斷,但是將輸出警告信息。
在保持了raw_spinlock之后不能在試圖獲得新的spinlock類型的鎖,因為raw_spinlock是搶占失效的,但是新的spinlock卻能夠?qū)е逻M(jìn)程睡眠或發(fā)生搶占。
對于新的semaphore,必須要求執(zhí)行down和up操作的是同一個進(jìn)程,否則優(yōu)先級繼承和死鎖檢測將無法實現(xiàn)。而且代碼本身也將操作失敗。
評論
查看更多