2013年7月8日
#
在Linux系統(tǒng)中,有兩種不同的上下文:進程上下文、中斷上下文。
在中斷中沒有進程上下文,而具有中斷上下文,因此在中斷上下文中不能發(fā)生睡眠,也就是不能發(fā)生進程切換。
這就決定了在在中斷上下文中不能采用同步原語(信號量,管程、同步變量等)和長期互斥原語(這會導(dǎo)致進程睡眠), 而只能采用短期互斥原語(例如自旋鎖)。
曾經(jīng),中斷處理程序并不具有自己的棧。相反,它們共享所中斷進程的內(nèi)核棧。內(nèi)核棧的大小是兩頁,具體地說,在32位體系結(jié)構(gòu)上是8KB,在64位體系結(jié)構(gòu)上是16KB.
現(xiàn)在。中斷處理程序有了自己的棧,每個處理器一個,大小為一頁。這個棧就稱為中斷棧,盡管中斷棧的大小是原先共享棧的一半,但平均可用棧空間大得多,因為中斷處理
程序把這一整頁占為己有。
UP(單CPU系統(tǒng))上的中斷處理
互斥
如果一個中斷處理程序的代碼訪問或者更新了由非中斷的代碼(通常稱為基準代碼)使用的同一數(shù)據(jù)結(jié)構(gòu),那么就會出現(xiàn)競爭條件。
幸運的是,得到允許的以內(nèi)核態(tài)執(zhí)行的進程會臨時禁止中斷。因此,只要基準代碼要更新一個與中斷處理程序共享的數(shù)據(jù)結(jié)構(gòu),那么就
首先禁止中斷,執(zhí)行臨界段,然后再重新允許中斷。禁止和允許中斷的動作就實現(xiàn)了互斥。
在采取中斷互斥時,必須使用函數(shù)顯示地把編碼寫入算法中。
MP(多CPU系統(tǒng))上
在MP系統(tǒng)上,中斷可以在任何處理器上出現(xiàn)。從最低限度上來說,每個進程會接收時鐘中斷,但也可能接收I/O中斷。在MP系統(tǒng)上,例程
SPL(禁止中斷)所提供的保護并不充分,因為它們執(zhí)行影響執(zhí)行它們的處理器上的中斷優(yōu)先級。中斷可能會在另一個處理器上出現(xiàn),如果設(shè)備驅(qū)動程序
正在別處運行,那么會造成一個競爭條件。因為中斷處理程序代表另一個進入內(nèi)核的入口點。
當(dāng)基準驅(qū)動程序代碼和中斷處理程序之間共享數(shù)據(jù)結(jié)構(gòu)時,UP可以通過屏蔽中斷來防止出現(xiàn)競爭條件的技術(shù),在多線程MP內(nèi)核中還不充分。
臨界段要在一個處理器上執(zhí)行,執(zhí)行屏蔽中斷例程只會屏蔽在那個處理器上出現(xiàn)的中斷。如果在別的處理器上出現(xiàn)中斷,那么立即就會有
兩個處理器同時訪問、而且可能更新臨界資源。既然這些臨界段需要短期互斥,那么可以使用自旋鎖來對數(shù)據(jù)進行保護。
如果不考慮中斷處理程序和基準代碼之間的互斥,則Linux中的中斷處理程序是無須重入的。當(dāng)一個給定的中斷處理程序正在執(zhí)行時,相應(yīng)的中斷線
在所有處理器上都會被屏蔽掉,以防止同一中斷線上接收另一個新的中斷。通常情況下,所有的其他中斷都是打開的,所以這些不同中斷線上的其他中斷
都能處理,但當(dāng)前中斷線總是被禁止的。由此可以看出,同一個中斷處理程序絕不會被同時調(diào)用以處理嵌套的中斷。這極大地簡化了中斷程序的編寫。
2013年6月20日
#
MP(多CPU)同步原語代碼示例
----引自《現(xiàn)代體系結(jié)構(gòu)上的UNIX系統(tǒng)》
為了便于對示例的展開,我們首先假定一個場景:
內(nèi)核中把檢測到的錯誤信息記錄到一個磁盤文件中的情形。出錯信息通過內(nèi)存中的一個隊列來傳遞給日志進程(logging process)的。
當(dāng)出現(xiàn)一個錯誤時,就在隊列中加入一項,并且通過調(diào)用函數(shù)log_error 通知日志進程。出錯日志進程接著把隊列中的項寫到磁盤上。
這就使得碰到錯誤的進程不必等候I/O完成或者或得為了向文件執(zhí)行I/O而可能需要的任何鎖,并且避免了任何可能的上鎖次序問題。
代碼1, 采用事件計數(shù)的出錯日志通知機制
日志進程
log_error(error) |
{ | for(next=1; ; next++) {
lock(&err_queue); | await(&err_event, next);
把出錯信息加入到隊列 | lock(&err_queue);
unlock(&err_queue); | 從隊列中刪除項
advance(&err_event); | unlock(&err_queue);
} | write error to disk
| }
隊列本身由一個自旋鎖來保護。在本例中,事件計數(shù)只用于同步的目的,并不提供互斥。
在試用事件計數(shù)的時候,advance操作會永久性地改變事件計數(shù)的狀態(tài)。advance和await操作的相對時序沒有關(guān)系。
代碼2, 采用同步變量的出錯日志通知機制
日志進程
log_error(error) |
{ | for(;;){
lock(&err_queue); | lock(&err_queue);
把出錯信息加入到隊列 | if (queue_empty){
SV_SIGNAL(&err_syncvar, 0); | SV_WAIT(&err_syncvar, PRI, &err_queue);
unlock(&err_queue); | lock(&err_queue);
} | }
| 從隊列中刪除項
| unlock(&err_queue);
| 把錯誤寫入磁盤
| }
因為同步變量自身沒有保留狀態(tài),所以當(dāng)日志進程測試隊列的狀態(tài)并決定是等待一項還是從隊列中刪除一項的時候,必須占有自旋鎖。類似地,log_error在
發(fā)送信號時也必須占有自旋鎖。注,SV_WAIT將釋放自旋鎖,并且阻塞日志進程,SV_SIGNAL到后從阻塞處繼續(xù)執(zhí)行。
代碼3, 采用管程的出錯日志通知機制
日志進程
log_error(error) | for(;;){
{ | mon_enter(&err_mon);
mon_enter(&err_mon); | if (queue empty)
把出錯信息加入到隊列 | mon_wait(&err_mon, NEWENTRY);
|
mon_signal(&err_mon, NEWENTRY); | 從隊列中刪除項
mon_exit(&err_mon); | mon_exit(&err_mon);
} | 把錯誤寫入磁盤
| }
代碼4, 采用信號量的出錯日志通知機制
日志進程
log_error(error) | for(;;){
{ | P(&err_sema);
lock(&err_queue); | lock(&err_queue);
把出錯信息加入到隊列 | 從隊列中刪除項
unlock(err_queue); | unlock(&err_queue);
V(&err_sema); | 把錯誤寫入磁盤
} | }
我們把異步環(huán)境下的一組并發(fā)進程因直接制約而互相發(fā)送消息、進行互相合作、互相等待,使得各進程按一定的速度執(zhí)行的過程稱為進程間的同步。
具有同步關(guān)系的一組并發(fā)進程稱為合作進程,合作進程間互相發(fā)送的信號稱為消息或事件。 如果我們對一個消息或事件賦以唯一的消息名,則我們
可用過程 wait (消息名) 表示進程等待合作進程發(fā)來的消息,而用過程 signal (消息名) 表示向合作進程發(fā)送消息。
(引自百度百科)
進程間的同步方式:
為了實現(xiàn)進程互斥地進入自己的臨界區(qū),操作系統(tǒng)中設(shè)置專門的同步機制來協(xié)調(diào)各進程間的運行。所有的同步機制都應(yīng)遵循下書四條準則:
1)空閑讓進
2)忙則等待
3)有限等待
4)讓權(quán)原則。當(dāng)進程不能進入自己的臨界區(qū)時,應(yīng)立即釋放處理機,以免進程陷入“忙等”狀態(tài)。
1.單CPU (UP)機器利用sleep/wakeup函數(shù)對實現(xiàn)同步機制。
函數(shù)sleep是一個內(nèi)部的內(nèi)核例程,它掛起調(diào)用它的進程,直到指定的事件發(fā)生為止。這是一個以內(nèi)核態(tài)運行的進程自愿出讓控制權(quán),允許自己被搶占。
函數(shù)wakeup用于發(fā)出一個特定事件已經(jīng)出現(xiàn)的信號,它使得所有等待該事件的進程被喚醒,并放回到運行隊列中。事件用一個整數(shù)值來表示,它往往
是該事件相關(guān)的內(nèi)核數(shù)據(jù)結(jié)構(gòu)的地址。
void lock_object( char *flag_ptr)
{
lock(&object_locking); //自旋鎖
while (*flag_ptr)
sleep(flag_ptr);
*flag_ptr = 1;
unlock(&object_locking);
}
void unlock_object( char *flag_ptr)
{
lock( &object_locking );
*flag_ptr = 0;
wakeup( flag_ptr);
unlock( &object_locking );
}
應(yīng)為wakeup操作沒有記憶,所以wakeup函數(shù)必須喚醒在同一事件上睡眠的所有進程。在多CPU系統(tǒng)上,即MP上sleep/wakeup機制不起作用。
2.SVR4.2 MP 提供了單獨的執(zhí)行進程同步的原語:同步變量。
因為同步變量不包含狀態(tài),所以可以把它們想成是sleep/wakeup的一種MP變形。相反,所需的任何狀態(tài)信息都保存在外部標志或者計數(shù)器中。
同步變量的設(shè)計要同自旋鎖配合工作。
同步變量被聲明為sv_t類型,采用下面的函數(shù)可以給它分配空間和進行初始化:
sv_t *SV_ALLOC( int slpflag);
slpflag指定,如果需要為同步變量分配內(nèi)存,那么是否能阻塞進程。
回收同步變量可以調(diào)用
void SV_DEALLOC( sv_t *svp );
內(nèi)核希望單獨等候的每一個事件都用一個不同的同步變量來表示,這就好比配合sleep如何使用唯一的事件參數(shù)。
void SV_WAIT( sv_t *svp, int pri, lock_t *lockp );
要觸發(fā)在同步變量上的事件,可以使用下面的函數(shù):
void SV_SIGNAL( sv_t *svp, int flags);
SV_SIGNAL與wakeup的相似之處在于,如果沒有正在睡眠的進程,那么就對過去曾經(jīng)執(zhí)行過的操作沒有記憶,調(diào)用什么也不做。
SV_SIGNAL只喚醒一個進程。如果要喚醒在事件上睡眠的所有進程,可以用同步變量的下列函數(shù)來實現(xiàn):
void SV_BROADCAST( sv_t *svp, int flags);
如果在事件被觸發(fā)之前出現(xiàn)了一個UNIX信號,那么下面的SV_WAIT變形會喚醒進程:
bool_t SV_WAIT_SIG( sv_t *svp, int pri, lock_t *lkp );
返回的代碼表明發(fā)生了什么樣的事件:如果出現(xiàn)了一個UNIX信號,那么它返回FALSE,如果出現(xiàn)了SV_SIGNAL或SV_BROADCAST,那么它返回TRUE.
3.采用信號量的同步
將信號量的值初始化為0,就可以用于進程同步,這樣允許通過使用P操作讓一個進程等待某個事件發(fā)生。既然信號量被初始化為0,那么進程將立即阻塞。
另一個進程使用V操作能夠發(fā)出信號,表明事件已經(jīng)結(jié)束。V操作導(dǎo)致正等待事件的進程被喚醒,并繼續(xù)進行。因為即使在信號量上沒有阻塞進程,
V操作也會給信號量加1,所以在前一個進程能夠執(zhí)行P操作之前出發(fā)事件會導(dǎo)致進程繼續(xù)進行,不必等待。這是一種受歡迎的情形,因為它不需要額外的
協(xié)調(diào)工作,就能夠處理在等候事件的進程同發(fā)信號表明該事件完成的進程之間本來就有的競爭條件。
進程1 進程2
p(s) /*等待事件*/ .
.
.
V(s) /*觸發(fā)事件*/
4.利用管程進行同步
管程為臨界資源以及訪問或者修改該資源的所有臨界段提供了互斥機制,它還提供了在使用管程的諸進程之間進行同步的手段。一個管程可以想成是一個裝有
資源的隔間。進程要訪問資源,它必須首先進入隔間。通過一次只允許一個進程進入隔間,就做到了互斥。如果在管程已經(jīng)投入使用的時候,別的進程試圖進
入它,那就會被阻塞,直到使用管程的進程退出管程為止,或者在與管程關(guān)聯(lián)的事件上等待。每個管程都可能有一個或者更多的事件,若干進程能夠在這些事
件上等待。進程被阻塞這些事件上,直到在管程內(nèi)執(zhí)行的其他進程觸發(fā)事件為止。根據(jù)定義,觸發(fā)操作只能從管程內(nèi)部完成。
5.利用事件計數(shù)進行同步
事件計數(shù)是一個非遞減的正整數(shù),在這個數(shù)值上定義了3種操作。操作advance(E)將事件計數(shù)E加1,這叫做出發(fā)事件。
操作await(E,V)致使調(diào)用進程被阻塞,指導(dǎo)事件計數(shù)E的值達到V為止。如果在調(diào)用await的時候,事件計數(shù)的值大于或等于V,那么進程繼續(xù)執(zhí)行,而不會阻塞,
因為事件是以前觸發(fā)的。事件計數(shù)的當(dāng)前值可以用read(E)來讀取。在創(chuàng)建事件計數(shù)的時候,它被初始化為0,而且在數(shù)值上永遠不會減少。假定保存事件計數(shù)值
得存儲器位置足夠大,于是事件計數(shù)在其整個生命期中,一直都不會溢出(通常一個32位的無符號整數(shù)就夠了)。
有關(guān)代碼示例,請參見后面的隨筆。
2013年6月8日
#
在多CPU系統(tǒng),即MP系統(tǒng)中,存在總線仲裁。
1. 原子操作
從CPU或者I/O設(shè)備到主存儲器的單次讀或者寫操作為原子操作。
這樣的操作一旦開始,就不能被系統(tǒng)上來自CPU或者I/O設(shè)備的任何其他存儲操作所中斷,或者受到他們的干擾。
原子變量
2. 自旋鎖 (自旋鎖用于短期互斥)
自旋鎖得名于這樣一個事實,一個進程在等候另一個進程正在使用的鎖時會處于忙等待(busy-wait,在一個循環(huán)中自旋)狀態(tài)。
typedef int lock_t;
void initlock( volatile lock_t * lock_status)
{
*lock_status = 0;
}
int
test_and_set(volatile int *addr)
{
int old_value;
old_value = swap_atomic(addr, 1);
if (old_value == 0)
return 0;
return 1;
}
void lock(volatile lock_t *lock_status)
{
while (test_and_set( lock_status) == 1) //被鎖定時,進程在此自旋。
;
}
// test_and_set 如果前面的狀態(tài)不為0就返回1,否則返回0.
//如果鎖的狀態(tài)已經(jīng)是1(鎖已經(jīng)被占用),那么test_and_set函數(shù)返回1,并且處理器在循環(huán)中自旋,直到該鎖被釋放為止。只要把鎖的狀態(tài)設(shè)置為0,就可以釋放鎖了。
void
unlock(volatile lock_t * lock_status)
{
*lock_status =0;
}
減少對鎖的爭用可以采用兩種辦法:
第一、內(nèi)核針對不同的臨界資源使用不同的自旋鎖,防止處理器在沒有競爭條件威脅的時候被另一個處理器掛起。
第二、增強lock和unlock函數(shù),在上鎖的時候屏蔽中斷。
在平時的應(yīng)用設(shè)計中,由于出現(xiàn)多線程(進程)的并發(fā)應(yīng)用,以及分布式應(yīng)用,比較容易出現(xiàn)死鎖現(xiàn)象。
下面來看一個簡單的實例:
有兩個獨立的鏈表,假定使用兩個獨立的鎖來保護他們,以便能夠獨立的訪問。再假定某個操作要求遍歷一個列表所檢索到的元素必須從兩個列表中斷開連接,
而且必須以一次
原子操作來完成。
線程1 | 線程2
lock(&lock_a); | lock(&lock_b);
find element to unlink on list a | find element to unlink on list b
lock(&lock_b); | lock(&lock_a);
unlink element from both lists | unlink element from both lists
unlock(&lock_b); | unlock(&lock_a);
unlock(&lock_a); | unlock(&lock_b);
可能出現(xiàn)死鎖的情況。
這要求在消除一個元素是必須同時擁有兩個列表的鎖。對第一個線程要先或得鎖lock_a, 然后要或得鎖lock_b.
第2個線程正好相反,它先或得鎖lock_b,然后再獲取鎖lock_a.
如果某一時刻,第一個線程執(zhí)行到了 find element to unlink on list a, 而第二個線程此時執(zhí)行到了find element to unlink on list b, 則這兩個線程將發(fā)生死鎖。
這種死鎖稱為AB-BA死鎖。 (注,死鎖的發(fā)生是跟兩個線程的執(zhí)行時序相關(guān)的,例如,第一個線程執(zhí)行完了所有的這部分代碼,線程2才開始執(zhí)行此段代碼,則不會發(fā)生死鎖。)
如果某個鎖不是遞歸鎖,例如lock_a, 而線程1在應(yīng)用中對它進行多個調(diào)用,而沒有調(diào)用解鎖操作,也會發(fā)生死鎖。
代碼示例如下:
lock(&lock_a);
other logic code
lock(&lock_a);
other logic code
unlock(&lock_a);
unlock(&lock_a);
防止死鎖的辦法: 為了防止發(fā)生這類死鎖,所有線程必須以相同的次序獲得嵌套鎖,即以相同的次序獲得且同時占有鎖。
可以把上面的代碼改成如下代碼來避免死鎖:
lock(&lock_a);
lock(&lock_b);
find element to unlink on list a or b
unlink element from both lists
unlock(&lock_b);
unlock(&lock_a);
當(dāng)涉及到3個或者更多鎖的時候也是如此:只要各線程在獲得和釋放鎖的時候保持相同的次序,那么就不會出現(xiàn)死鎖。
2009年3月20日
#
實體(語言內(nèi)建的數(shù)據(jù)類型,開發(fā)者定義的類和方法等)的定義與聲明,實體和指針跟程序設(shè)計帶來了不同影響.
對于實體或定義,編譯器要知道實體真實的物理內(nèi)存布局,因此讓編譯器知道這些信息,并且在程序編譯完畢后不能更改.要想更改必須重新編譯程序.因此如果在系統(tǒng)設(shè)計者程序庫中運用了inline函數(shù),并且如果應(yīng)用開發(fā)者在應(yīng)用中用了這個inline函數(shù),則當(dāng)后來要對inline進行修改時,有可能要導(dǎo)致應(yīng)用被重新編譯.
對于指針,它的大小在特定的機器上是固定的(在32位機器上,它的大小是32位;在64位機器上,它的大小是64位).因此可以改變它的值,而不需要重新編譯應(yīng)用,就可以改變應(yīng)用的功能.
在面向?qū)ο笾校梢酝ㄟ^虛函數(shù)指針來延遲特定函數(shù)的決策,即調(diào)用子類的函數(shù).
在C語言中,我們可以通過函數(shù)指針來對函數(shù)的功能進行推遲決策.
在C++中,我們也可以通過函數(shù)指針(函數(shù)對象)、對象指針來推遲決策,從而使程序的功能更有彈性。例如,在設(shè)計模式中的strategy模式中,就是通過在contex中包含一個指向strategy的指針來實現(xiàn)的。我們可以定義一個抽象的strategy接口,然后由各個具體的strategy實現(xiàn)這些接口,從而在保證應(yīng)用架構(gòu)幾乎不做任何調(diào)整下,實現(xiàn)不同的功能。當(dāng)然在這種實現(xiàn)方式中,我們應(yīng)該加入strategy的決議者,由它來裁決采用哪一種策略方式。決議者可以采用配置文件、應(yīng)用的輸入等作為決議的依據(jù)。
熟悉symbian的人,很快就會發(fā)現(xiàn):它與symbian中的ECOM架構(gòu)很相似。它要求各種strategy的實現(xiàn)方式被包含在共享的DLL中,并由RLibrary::Loard()調(diào)用動態(tài)載入.
使用定義抽象接口,然后在各DLL中定義具體的實現(xiàn),并且動態(tài)載入,我們可以比較容易地實現(xiàn)所謂的插件(plugin)。插件的載入取決于配置,或相應(yīng)的輸入檢測。
下面給出在linux和windows上從動態(tài)庫中查找和載入的例子:
#ifdef WIN32
HINSTANCE hDll;
if(!(hDll = LoadLibrary(VOCALSIP_DLLPATH)))
adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)GetProcAddress( hDll, "InitVocalSipStack");
adapter.m_createFunc = (CREATE_CHANNEL_FUNC)GetProcAddress( hDll, "CreateVocalSipGCChannel");
adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)GetProcAddress( hDll, "CleanupVocalSipStack");
#else
void* h_dl = dlopen(VOCALSIP_DLLPATH,RTLD_NOW | RTLD_GLOBAL);
adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)dlsym( h_dl, "InitVocalSipStack");
adapter.m_createFunc = (CREATE_CHANNEL_FUNC)dlsym( h_dl, "CreateVocalSipGCChannel");
adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)dlsym( h_dl, "CleanupVocalSipStack");
#endif
2009年3月18日
#
"互斥(mutext)和旗語(semaphore)之間有什么不同?"這樣的問題簡短而有力,但要回答卻相當(dāng)困難.即使有經(jīng)驗的實時操作系統(tǒng)(RTOS)用戶在區(qū)別如何正確使用mutex和semaphore時也存在著困難.
但這一點很不幸而且很危險,因為無任這兩種原生RTOS中的哪一種被錯誤使用,都會導(dǎo)致嵌入式系統(tǒng)出現(xiàn)意想不到的錯誤,特別是這些系統(tǒng)為有關(guān)生命安全的產(chǎn)品時.
有關(guān)mutex和semaphore的荒誕說法是它們是相似的,甚至是可以互換的.正確的事實是盡管mutex和semaphore在它們的執(zhí)行上有相似之處,但是我們還是應(yīng)該在使用它們時加以區(qū)別對待.
最普遍(但也是不正確)的答案是:mutex和semphore非常相似,它們只有一個區(qū)別,那就是semaphores的計數(shù)可以超過1. 差不多所有的工程師都能正確的理解:mutex是一個二進制標志,可以通過它來確保執(zhí)行流在代碼關(guān)鍵區(qū)(critical section of code)互相排斥,從而對共享資源加一保護.但當(dāng)他們被要求進一步回答如何使用"計算方法semaphore"的方式時,大部分工程師的回答就如同教科書書一般的刻板---semaphore用于保護多重同類資源.
通過類比辦法,我們很容易解釋為什么"多重資源"場景是有缺陷的.如果你認為一個mutex是由操作系統(tǒng)擁有的關(guān)鍵值的話,我們可以很容易地將個別的mutex比喻是城市咖啡店中一間浴室的鑰匙.如果你想使用浴室,卻找不到鑰匙,你就必須在一個隊列中等候.同樣地,mutex則協(xié)串行化多項任務(wù),以取得全域資源的共享,并且為等待隊列中的任務(wù)分配一個靜候其循序漸進的位置.
但這種簡單的資源保護協(xié)議并不使用于兩間相同浴室的情況.如果把一個semaphore概括為一個mutex,使其能保護兩個或更多相同的資源,那么在我們的比喻中,它就象是放著兩把相同鑰匙的藍子,你可以用任何一把打開任何一扇浴室的門.
因此,semaphore本身并不能解決多個相同資源的問題.咖啡店中的客人可能只知道有一把鑰匙,但并不知道哪間浴室可用.如果你試圖以此方式使用semaphore,你將會發(fā)現(xiàn)需要更多的狀態(tài)信息---它們通常是由不同的mutex所保護的共享資源.
正確使用semaphore是為了使信號從一項任務(wù)傳至另一項任務(wù).mutex意味著取得與釋放,使用受保護共享資源的每一次任務(wù)都是以這樣的順序進行.相比之下,使用semaphore的任務(wù)通常不是發(fā)送信號,就是進入等待狀態(tài),不可能同時發(fā)生.
例如,任務(wù)1可能包含程序代碼,當(dāng)按下"電源"(power)按鈕時,即可提出(如發(fā)送信號或增量)一個特別的semaphore; 任務(wù)2則依據(jù)相同的semaphore而用于喚醒顯示器. 在這種情況下,其中一項任務(wù)是信號的生產(chǎn)者,另一項任務(wù)是信號的消費者.
用一個例子來做總結(jié),首先展示如何使用mutex:
/* Task 1 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);
/* Task 2 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);
相應(yīng)地,你總是采用下列方法使用semaphore:
/* Task 1 - Producer */
semPost(sem_power_btn); // Send the signal
/* Task 2 - Consumer */
semPend(sem_power_btn); // Wait for signal
重要的是,semaphores可以被interrupt service routine(ISR)中斷服務(wù)程序用來向task發(fā)送信號.發(fā)送一個semaphore是一個非阻塞的RTOS行為,并且ISR安全.因為這種技術(shù)排除了在task級別的為了是中斷不使能而引起的錯誤的可能性,從ISR中發(fā)出信號是一種使嵌入式軟件更加可靠的設(shè)計方式.
http://www.embedded.com/columns/guest/210605040?printable=true
http://www.eettaiwan.com/ART_8800557420_676964_NT_a22f6436.HTM
2009年3月17日
#
摘要: 前一段時間,讀symbian文檔資料,和Darwin,ICE等開源代碼,經(jīng)常碰到定時器和定時器事件.故對定時器的實現(xiàn)進行了一些整理,作為讀書筆記,以防以后忘記.
閱讀全文
2009年3月11日
#
在以前的multi-process程序中,process之間通過共享內(nèi)存、操作系統(tǒng)提供的消息對列,命名管道等不同方式進行數(shù)據(jù)傳遞。為了減少內(nèi)存,以及進程切換時引發(fā)的上下文切換的開銷,現(xiàn)在的系統(tǒng)一般采用multi-thread 工作方式。
各process都有各自獨立的地址空間,并且是專有的,因此一個process不能通過指針訪問另一個process中的地址空間。而屬于同一process的各thread,它們共享同一process的地址空間,因此一個thread可以通過指針訪問另一個thread中的地址。這樣我們可以在應(yīng)用中自己定義消息隊列,并對消息隊列的訪問進行管理,簡化程序并提高性能。
在multi-thread應(yīng)用中,我們可以使用消息對列來在不同thread之間進行消息傳遞,并降低各thread之間的藕合。
它們之間的關(guān)系如下:
生產(chǎn)者:向消息隊列中放數(shù)據(jù)的線程。
消費者:從消息隊列中取出數(shù)據(jù)的線程。
生產(chǎn)者 ----> 消息隊列 ----->消費者
如果應(yīng)用規(guī)定消息隊列的最大長度。在對隊列進行控制時,應(yīng)該當(dāng)消息隊列滿時,可能要讓生產(chǎn)者進行等待,直到消息隊列中有新的位置可以放入新的消息。當(dāng)消息隊列為空時,應(yīng)該讓消費者進行等待,知道有新的消息被加到消息隊列中。
在該模型中有兩點需要注意:
1.不同生產(chǎn)者,消費者同時并發(fā)操作消息隊列時,對各操作的串行化。這個問題可以通過對隊列的操作進行加鎖來實現(xiàn)。它的實現(xiàn)可以參考另一篇隨筆《C++同步鎖管理的一種方法》。
2.在消息隊列為空或滿時,應(yīng)用程序的處理。
這可以在C++中采用Monitor.在Monitor中對某種條件進行監(jiān)控。
Monitor 對象可以采取的動作:
(1)、等待操作 wait(long timeout = INFINITE), 缺省情況下無限等待下去。
(2)、發(fā)信號操作 signal(long count = 1), 缺省情況下激活一個正在消息隊列上進行等代的線程。
對于Monitor的實現(xiàn),可以很簡單地用下列兩種方式實現(xiàn):
1、采用ACE中的ACE_Event, ACE_Thread_Semphore實現(xiàn)。
2、采用 ICE的Cond實現(xiàn), 在Linux上,Cond實際上是通過pthread_cond_t來實現(xiàn)的。
2009年3月9日
#
前段時間,寫了一點關(guān)于智能指針的東西,有讀者反映沒有代碼比較難懂.現(xiàn)給出源碼,并稍微加以解釋.
智能指針類用到的基類的定義:
template<typename T>
class HandleBase
{
public:
typedef T element_type;
T* get() const
{
return _ptr;
}
//重載->操作符,返回所指對象的指針.
T* operator->() const
{
if(!_ptr)
{
//
// We don't throw directly NullHandleException here to
// keep the code size of this method to a minimun (the
// assembly code for throwing an exception is much bigger
// than just a function call). This maximises the chances
// of inlining by compiler optimization.
//
throwNullHandleException(__FILE__, __LINE__);
}
return _ptr;
}
// 通過智能指針獲取所指對象的引用.
T& operator*() const
{
if(!_ptr)
{
//
// We don't throw directly NullHandleException here to
// keep the code size of this method to a minimun (the
// assembly code for throwing an exception is much bigger
// than just a function call). This maximises the chances
// of inlining by compiler optimization.
//
throwNullHandleException(__FILE__, __LINE__);
}
return *_ptr;
}
operator bool() const
{
return _ptr ? true : false;
}
void swap(HandleBase& other)
{
std::swap(_ptr, other._ptr);
}
T* _ptr;
private:
void throwNullHandleException(const char *, int) const;
};
......
// 智能指針類定義
template<typename T>
class Handle : public HandleBase<T>
{
public:
Handle(T* p = 0) //智能指針的構(gòu)造函數(shù)
{
this->_ptr = p;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構(gòu)造函數(shù)中增加所指對象的引用計數(shù)
}
}
template<typename Y> //拷貝構(gòu)造函數(shù)
Handle(const Handle<Y>& r)
{
this->_ptr = r._ptr;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構(gòu)造函數(shù)中增加所指對象的引用計數(shù)
}
}
Handle(const Handle& r) //拷貝構(gòu)造函數(shù)
{
this->_ptr = r._ptr;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構(gòu)造函數(shù)中增加所指對象的引用計數(shù)
}
}
~Handle()
{
if(this->_ptr)
{
this->_ptr->__decRef(); //在析構(gòu)函數(shù)中減少所指對象的引用計數(shù)
}
}
// 重載=操作符, 要注意所有權(quán) (即,對原實例的處理).
Handle& operator=(T* p)
{
if(this->_ptr != p)
{
if(p)
{
p->__incRef(); //增加新指對象的引用計數(shù)
}
T* ptr = this->_ptr;
this->_ptr = p;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數(shù)
}
}
return *this;
}
template<typename Y>
Handle& operator=(const Handle<Y>& r)
{
if(this->_ptr != r._ptr)
{
if(r._ptr)
{
r._ptr->__incRef(); //增加新指對象的引用計數(shù)
}
T* ptr = this->_ptr;
this->_ptr = r._ptr;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數(shù)
}
}
return *this;
}
Handle& operator=(const Handle& r)
{
if(this->_ptr != r._ptr)
{
if(r._ptr)
{
r._ptr->__incRef(); //增加新指對象的引用計數(shù)
}
T* ptr = this->_ptr;
this->_ptr = r._ptr;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數(shù)
}
}
return *this;
}
跟智能指針配合使用的對象.要能夠跟指針智能配合使用,這些對象應(yīng)該是從下列類的派生類的實例.
class SimpleShared
{
public:
SimpleShared();
SimpleShared(const SimpleShared&);
virtual ~SimpleShared()
{
}
SimpleShared& operator=(const SimpleShared&)
{
return *this;
}
void __incRef()
{
assert(_ref >= 0);
++_ref;
}
void __decRef()
{
assert(_ref > 0);
if(--_ref == 0) // 如果引用計數(shù)為0,則摧毀對象本身.
{
if(!_noDelete)
{
_noDelete = true;
delete this;
}
}
}
int __getRef() const
{
return _ref;
}
void __setNoDelete(bool b)
{
_noDelete = b;
}
private:
int _ref;
bool _noDelete;
};
class Shared
{
public:
Shared();
Shared(const Shared&);
virtual ~Shared()
{
}
Shared& operator=(const Shared&)
{
return *this;
}
virtual void __incRef();
virtual void __decRef();
virtual int __getRef() const;
virtual void __setNoDelete(bool);
protected:
#if defined(_WIN32)
LONG _ref;
#elif defined(ICE_HAS_ATOMIC_FUNCTIONS)
volatile int _ref;
#else
int _ref;
Mutex _mutex;
#endif
bool _noDelete;
};