QNX相關(guān)歷史文章:
Unblocking Clients and Handling Interrupts
這篇文章主要描述資源管理器對(duì)客戶端的解阻塞屋厘,以及中斷的處理谈火。
1. Handling client unblocking due to signals or timeouts
資源管理器提供的另一個(gè)方便的服務(wù)就是解除阻塞香府。
當(dāng)一個(gè)客戶端發(fā)起請(qǐng)求绅喉,比如調(diào)用read()
,會(huì)轉(zhuǎn)變成MsgSend()
向資源管理器發(fā)送消息症脂,這是一個(gè)阻塞調(diào)用朴摊。如果客戶端在MsgSend()
未完成期間收到一個(gè)信號(hào),資源管理器需要給出一些指示型檀,以便它可以中止請(qǐng)求。
因?yàn)閹?kù)在調(diào)用ChannelCreate()
時(shí)設(shè)置了_NTO_CHF_UNBLOCK
標(biāo)志听盖,所以當(dāng)客戶端試圖從MsgSend()
(并且已經(jīng)通過(guò)MsgReceive()
收到消息)中解除阻塞時(shí)胀溺,都會(huì)收到一個(gè)脈沖。之前也講過(guò)當(dāng)客戶端發(fā)送消息給資源管理器時(shí)皆看,可能處在兩種阻塞狀態(tài):
-
SEND-blocked
仓坞,此時(shí)資源管理器還沒(méi)收到; -
REPLY-blocked
腰吟,此時(shí)資源管理器還沒(méi)有調(diào)用MsgReply()
回復(fù)无埃;
當(dāng)產(chǎn)生脈沖時(shí),資源管理器庫(kù)會(huì)處理這個(gè)脈沖消息毛雇,并合成一個(gè)_IO_UNBLOCK
消息嫉称。
在resmgr_io_funcs_t
和resmgr_connect_funcs_t
結(jié)構(gòu)體中,有兩個(gè)unblock message
的處理函數(shù):一個(gè)在I/O函數(shù)中灵疮,一個(gè)在連接函數(shù)中织阅。有兩個(gè)處理函數(shù)的原因是因?yàn)榭赡茉谏线呥@兩種情況中中止。
一旦執(zhí)行了_IO_CONNECT
消息的處理震捣,I/O函數(shù)中的unblock
成員將用于處理unblock pulse
荔棉。因此在提供自己的io_open
處理程序時(shí)闹炉,先確保在調(diào)用resmgr_open_bind()
之前設(shè)置好了OCB中所有相關(guān)字段,否則I/O函數(shù)中的unblock
處理程序調(diào)用時(shí)可能會(huì)用到OCB中無(wú)效數(shù)據(jù)润樱。(注意渣触,只有在資源管理器中有多個(gè)線程在運(yùn)行時(shí),才會(huì)出現(xiàn)消息處理期間脈沖中止的問(wèn)題祥国,如果只有一個(gè)線程昵观,那么消息將由庫(kù)的MsgReceive()
函數(shù)序列化)
當(dāng)客戶端處于SEND-blocked
狀態(tài)時(shí),服務(wù)器不需要知道用戶正在中止請(qǐng)求舌稀,因?yàn)樗€沒(méi)收到消息啊犬。只有收到后并對(duì)該請(qǐng)求執(zhí)行處理的情況下,才需要知道客戶端的中止需求壁查。
如果要覆蓋默認(rèn)的unblock
處理函數(shù)iofunc_unblock_default()
觉至,應(yīng)該首先調(diào)用這個(gè)默認(rèn)的處理函數(shù),保證能處理通用的情況睡腿。這可以確保在資源管理器列表中的客戶端能被解阻塞语御,需要一些方法來(lái)遍歷阻塞的客戶端rcvid表,找到匹配的然后進(jìn)行阻塞解除席怪。
例程應(yīng)該通過(guò)調(diào)用MsgInfo()
檢查_NTO_MI_UNBLOCK_REQ
標(biāo)志來(lái)確認(rèn)unblock
仍然處于掛起狀態(tài)(避免出現(xiàn)競(jìng)爭(zhēng)條件)应闯。如果找不到匹配的客戶端,可以通過(guò)返回_RESMGR_NOREPLY
來(lái)忽略unblock
請(qǐng)求挂捻。
/* Check if rcvid is still valid and still has an unblock
request pending. */
if (MsgInfo(ctp->rcvid, &info) == -1 ||
!(info.flags & _NTO_MI_UNBLOCK_REQ)) {
return _RESMGR_NOREPLY;
}
如果沒(méi)有提供unblock
處理程序碉纺,可以讓客戶端處在REPLY-blocked
狀態(tài),當(dāng)客戶端終止時(shí)刻撒,服務(wù)器必須有機(jī)會(huì)來(lái)清理客戶端數(shù)據(jù)結(jié)構(gòu)骨田。
2. Unblocking if someone closes a file descriptor
假設(shè)有以下情況:
- 客戶端中一個(gè)線程打開(kāi)文件描述符,并調(diào)用
read()
函數(shù)声怔; - 資源管理器在
io_read
處理函數(shù)中沒(méi)有回復(fù)态贤,客戶端處于阻塞狀態(tài); - 客戶端中的另一個(gè)線程關(guān)閉同一個(gè)文件描述符醋火;
如果資源管理器不處理這種情況悠汽,則第一個(gè)線程會(huì)無(wú)限阻塞在read()
操作中。在其他阻塞操作中胎撇,比如write()
介粘、devctl()
等中也可能出現(xiàn)這種情況。
為了避免這種情況出現(xiàn)晚树,資源管理器需要為阻塞的客戶端維護(hù)一個(gè)列表,當(dāng)有客戶端阻塞時(shí)便添加到該列表中雅采,解除阻塞時(shí)則移除爵憎。
在執(zhí)行close()
時(shí)慨亲,會(huì)調(diào)用資源管理器框架提供的io_close_dup()
的處理函數(shù),在這個(gè)io_close_dup()
函數(shù)中宝鼓,應(yīng)該遍歷列表刑棵,并根據(jù)server connection ID (scoid)
和connection ID (coid)
來(lái)定位哪個(gè)客戶端阻塞在該文件描述符上,并對(duì)它們進(jìn)行回復(fù)以便解除阻塞愚铡。比如:
int io_close_dup (resmgr_context_t *ctp, io_close_t *msg, RESMGR_OCB_T *ocb)
{
// unblock any clients blocked on the file descriptor being closed
blocked_client_t *client, *prev;
prev = NULL;
client = blocked_clients;
while ( client != NULL )
{
if ( (client->coid == ctp->info.coid) && (client->scoid == ctp->info.scoid) )
{
MsgError( client -> rcvid, EBADF );
if (prev != NULL) // anywhere but the head of the list
{
prev->next = client->next;
free(client);
client = prev->next;
}
else // head of the list case
{
blocked_clients = client->next;
free(client);
client = blocked_clients;
}
}
else // no match, move to the next entry and track previous entry
{
prev = client;
client = client->next;
}
}
// do rest of the regular close handling
return iofunc_close_dup_default(ctp, msg, ocb );
}
3. Handling interrupts
管理硬件的資源管理器需要處理硬件的中斷蛉签。當(dāng)中斷處理函數(shù)中發(fā)生了重要事件時(shí),處理程序需要通知資源管理器的線程沥寥,這通常通過(guò)一個(gè)脈沖來(lái)完成碍舍,也可以使用SIGEV_INTR
事件通知類(lèi)型來(lái)完成。
當(dāng)資源管理器啟動(dòng)時(shí)邑雅,它將控制權(quán)轉(zhuǎn)交給thread_pool_start()
片橡,這個(gè)函數(shù)可能返回,也可能不返回淮野,這個(gè)取決于傳遞給thread_pool_create()
的標(biāo)志(如果沒(méi)有傳遞標(biāo)志捧书,函數(shù)在創(chuàng)建線程池之后返回)。這意味著需要在啟動(dòng)線程池之前進(jìn)行設(shè)置骤星。
如果使用SIGEV_INTR
事件通知類(lèi)型经瓷,就會(huì)遇到一個(gè)問(wèn)題,那就是連接中斷的線程(通過(guò)InterruptAttach()
或InterruptAttachEvent()
)必須與調(diào)用InterruptWait()
的線程是同一個(gè)洞难。
下邊是一個(gè)關(guān)于中斷的示例代碼:
#define INTNUM 0
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>
static resmgr_connect_funcs_t connect_funcs;
static resmgr_io_funcs_t io_funcs;
static iofunc_attr_t attr;
void *
interrupt_thread (void * data)
{
struct sigevent event;
int id;
/* fill in "event" structure */
memset(&event, 0, sizeof(event));
event.sigev_notify = SIGEV_INTR;
/* Obtain I/O privileges */
ThreadCtl( _NTO_TCTL_IO, 0 );
/* intNum is the desired interrupt level */
id = InterruptAttachEvent (INTNUM, &event, 0);
/*... insert your code here ... */
while (1) {
InterruptWait (NULL, NULL);
/* do something about the interrupt,
* perhaps updating some shared
* structures in the resource manager
*
* unmask the interrupt when done
*/
InterruptUnmask(INTNUM, id);
}
}
int
main(int argc, char **argv) {
thread_pool_attr_t pool_attr;
resmgr_attr_t resmgr_attr;
dispatch_t *dpp;
thread_pool_t *tpp;
int id;
if((dpp = dispatch_create()) == NULL) {
fprintf(stderr,
"%s: Unable to allocate dispatch handle.\n",
argv[0]);
return EXIT_FAILURE;
}
memset(&pool_attr, 0, sizeof pool_attr);
pool_attr.handle = dpp;
pool_attr.context_alloc = dispatch_context_alloc;
pool_attr.block_func = dispatch_block;
pool_attr.unblock_func = dispatch_unblock;
pool_attr.handler_func = dispatch_handler;
pool_attr.context_free = dispatch_context_free;
pool_attr.lo_water = 2;
pool_attr.hi_water = 4;
pool_attr.increment = 1;
pool_attr.maximum = 50;
if((tpp = thread_pool_create(&pool_attr,
POOL_FLAG_EXIT_SELF)) == NULL) {
fprintf(stderr, "%s: Unable to initialize thread pool.\n",
argv[0]);
return EXIT_FAILURE;
}
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);
memset(&resmgr_attr, 0, sizeof resmgr_attr);
resmgr_attr.nparts_max = 1;
resmgr_attr.msg_max_size = 2048;
if((id = resmgr_attach(dpp, &resmgr_attr, "/dev/sample",
_FTYPE_ANY, 0,
&connect_funcs, &io_funcs, &attr)) == -1) {
fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
return EXIT_FAILURE;
}
/* Start the thread that will handle interrupt events. */
pthread_create (NULL, NULL, interrupt_thread, NULL);
/* Never returns */
thread_pool_start(tpp);
}
這里的interrupt_thread()
函數(shù)使用了InterruptAttachEvent()
將中斷源(intNum
)綁定到事件上舆吮,然后等待事件的發(fā)生。
這個(gè)方法相對(duì)于使用脈沖有一個(gè)優(yōu)勢(shì)廊营,脈沖作為消息傳遞給資源管理器歪泳,意味著如果資源管理器的消息處理線程正在忙著處理請(qǐng)求,那么這個(gè)脈沖就需要排隊(duì)露筒,直到一個(gè)線程執(zhí)行MsgReceive()
呐伞。使用InterruptWait()
方法,如果執(zhí)行InterruptWait()
的線程具有足夠的優(yōu)先級(jí)慎式,那么它在生成SIGEV_INTR
之后立即解除阻塞并運(yùn)行伶氢。