從API開始理解QNX

消息傳遞

QNX是個微內(nèi)核結(jié)構(gòu)的操作系統(tǒng),靠的是進(jìn)程間通訊來實現(xiàn)整個系統(tǒng)功能的稽荧。那么具體到寫一個程序的時候姨丈,到底這個通訊是如何完成的呢构挤?這章就是具體介紿最底層的消息傳遞API的筋现。消息傳遞是通過內(nèi)核進(jìn)行的矾飞,所以所謂的API洒沦,實際也就是最底層的內(nèi)核調(diào)用了申眼。需要指出的是括尸,真正在QNX上寫程序的時候濒翻,很少會直接用到這些API有送,而是利用更高層的API雀摘,不過届宠,知道這些底層的API對于將來理解建立在這些API上的界面豌注,應(yīng)該會有幫助的灯萍。


頻道(Channel)與連接(Connect)

消息傳遞是基于服務(wù)器與客戶端的模式來進(jìn)行的旦棉,那么客戶端怎樣才能與服務(wù)器端通訊呢绑洛?最簡單的真屯,當(dāng)然是指定對方的進(jìn)程號。要發(fā)送的一方泵额,將消息加一個頭嫁盲,告訴內(nèi)核“把這個消息發(fā)給 pid 12345"就行了羞秤。其實這也是QNX4時候的做法锥腻。但QNX6開始完整支持POSIX線程后瘦黑,這種方法似乎就不太適合了幸斥。如果服務(wù)器甲葬,有兩個線程经窖,分別進(jìn)行不同的服務(wù)画侣,那該怎么辦呢配乱?或者你會說“把這個消息發(fā)給pid 12345 tid 3"就行了皮迟》幔可是爆阶,如果某一個服務(wù),不是由單一線程來進(jìn)行服務(wù)的芭碍,而是有一組線程進(jìn)行的窖壕,那又怎么辦呢瞻讽?

為此速勇,QNX6抽象出了”頻道“(Channel)這個概念坎拐。一個頻道哼勇,就是一個服務(wù)的入口积担;至于這個頻道到底具體有多少線程為其服務(wù)帝璧,那都是服務(wù)器端自己的事情的烁。
一個服務(wù)器如果有多個服務(wù)撮躁,它也可以開多個頻道把曼。而客戶端嗤军,在向“頻道”發(fā)送消息前叙赚,需要先建立連接(Connection)震叮,然后將消息在連接上發(fā)出去苇瓣。這樣同一個客戶端击罪,如果需要媳禁,可以與同一個頻道建立多個連接。所以囱怕,大致上通訊的準(zhǔn)備過程是這樣的:

服務(wù)代碼:

  ChannelId = ChannelCreate(Flags); 

客戶端代碼:

ConnectionId = ConnectAttach(Node, Pid, Chid, Index, Flag);

服務(wù)器端就不用解釋了光涂,客戶端要建立連接的話忘闻,它需要Node齐佳,這個就是機(jī)器號炼吴。如果過網(wǎng)絡(luò)(透明分布處理)時這個值決定了哪一臺機(jī)器硅蹦;如果客戶端與服務(wù)器在同一臺機(jī)器里時童芹,這個數(shù)字是0假褪,或者說ND_LOCAL_NODE生音;pid是服備器的進(jìn)程號;而chid就是服務(wù)器調(diào)用 ChannelCreate()后得到的頻道號了慕匠。Index與Flag以后再討論台谊∏嗌耍基本上客戶端就是同"Node這臺機(jī)器里的狠角,Pid這個進(jìn)程的丰歌,Chid頻道"做一個連接立帖。有了連接以后晓勇,就可以進(jìn)行消息傳遞了绑咱。

連接的終止是ConnectDetach()描融,而頻道的結(jié)束則是ChannelDestroy()了窿克。不過年叮,一般服務(wù)器都是長久存在的谋右,不大有需要ChannelDestroy()的時候改执。


發(fā)送(Send)辈挂,接收(Receive)和應(yīng)答(Reply)

QNX的消息傳遞终蒂,與我們傳統(tǒng)常見的進(jìn)程間通訊最大的不同拇泣,就是這是一個"同步的"消息傳遞霉翔。一個消息傳遞债朵,都要經(jīng)過發(fā)送序芦,接收和應(yīng)答三個部份谚中,所謂的 SRR過程宪塔。具體來說蝌麸,客戶端在連接上"發(fā)送"消息来吩,一旦發(fā)送弟疆,客戶端會被阻塞怠苔,服務(wù)器端會接收到消息柑司,進(jìn)行處理,最后蟆湖,將處理結(jié)果"應(yīng)答"給客戶端隅津;只有服務(wù)器"應(yīng)答"了以后伦仍,客戶端的阻塞狀態(tài)才會被解除充蓝。這種同步的過程棺克,不但保證的客戶端與服務(wù)器端的時序娜谊,也大大簡化了編程纱皆。具體用API來說派草,就是這樣近迁。

服務(wù)代碼:

 ReceiveId = MsgReceive(ChannelId, ReceiveBuffer, ReceiveBufLength, &MsgInfo);
  (... 檢查Buffer里的消息進(jìn)行處理 ...)
  MsgReply(RceeiveId, ReplyStatus, ReplyBuf, ReplyLen);

客戶端代碼:

 MsgSend(ConnectionId, SendBuf, SendLen, ReplyBuf, ReplyLen);
(... 由OS將這個線程掛起 ...)
(... 當(dāng)服務(wù)器MsgReply()后鉴竭,OS 解除線程的阻塞狀態(tài), 客戶端可以檢查自己的 ReceiveBuf 看看應(yīng)答結(jié)果 ...)

服務(wù)器端在頻道上進(jìn)行接收搏存,處理完后應(yīng)答璧眠;客戶端則是在連接上發(fā)送袁滥,要注意在發(fā)送的同時呻拌,客戶端還提供了接收應(yīng)答用的緩沖睦焕。如果你細(xì)心的話垃喊,或許你會問本谜,服務(wù)器端的MsgReceive()與客戶端的MsgSend()沒有同步乌助,會不會有問題呢他托?比如赏参,如果MsgSend()時把篓,服務(wù)器沒有在 MsgReceive()韧掩,會出什么事呢疗锐?答案是OS依然會把發(fā)送線程掛起窒悔,發(fā)送線程從執(zhí)行狀態(tài)(RUNNING)轉(zhuǎn)入“發(fā)送阻塞”狀態(tài)(SEND BLOCK)简珠,一直等到服務(wù)器來MsgReceive()時聋庵,再將SendBuf里的東西復(fù)制到ReceiveBuffer里去祭玉,同時發(fā)送線程的狀態(tài)變成 “應(yīng)答阻塞”(REPLY BLOCK)脱货。
同樣的,如果服務(wù)器調(diào)用MsgReceive()時臼疫,沒有客戶端烫堤,服務(wù)器線程也會被掛起鸽斟,進(jìn)入“接收阻塞”狀態(tài)(RECEIVE BLOCK)富蓄。

在應(yīng)答時格粪,還可以用MsgError()來告訴發(fā)送方有錯誤發(fā)生了氛改。因為MsgReply()也可以返回一個狀態(tài)胜卤,或許你會問這兩者之間有什么區(qū)別葛躏?MsgReply(rcvid, EINVAL, 0, 0);的結(jié)果是,MsgSend() 這個函數(shù)的返回值是22(EINVAL)败富;而MsgError(rcvid, EINVAL)兽叮;的結(jié)果鹦聪,是MsgSend()返回-1泽本,而errno被設(shè)為EINVAL规丽。


數(shù)據(jù)區(qū)與iov

除了用線性的緩沖區(qū)進(jìn)行消息傳遞以外,為了方便使用造成,還提供了用iov_t來“匯集”數(shù)據(jù)。也就是說缓升,可以一次傳送幾塊數(shù)據(jù)港谊。好象下面的圖這樣子歧寺。雖然在客戶端藍(lán)色的Header同紅色的databuf是兩塊不相鄰的內(nèi)存斜筐,但傳遞到服務(wù)器端的ReceiveBuffer里顷链,就是連續(xù)的了嗤练。也就是說在服務(wù)器端煞抬,要想得到原來databuf里的數(shù)據(jù)构哺,只需要(ReceiveBuffer + sizeof(header))就可以了。(要注意數(shù)據(jù)結(jié)構(gòu)對其)

SETIOV(&iov[0], &header, sizeof(header));
SETIOV(&iov[1], databuf, datalen);
MsgSendvs(ConnectionId, iov, 2, Replybf, ReplyLen);

"header" 與 "databuf" 是不連續(xù)的兩塊數(shù)據(jù)湖笨。
服務(wù)器接收后慈省,"header"與"databuf"被連續(xù)地存在ReceiveBuffer里边败。

ReceiveId = MsgReceive(ChannelId, ReceiveBuffer, ReceiveBufLength, &MsgInfo); 
header = (struct header *)ReceiveBUffer;
databuf = (char *)((char *)header + sizeof(*header));

例子

好了,有了以上這些基本函數(shù)(內(nèi)核調(diào)用)登疗,我們就可以寫一個客戶端和一個服務(wù)器端,進(jìn)行最基本的通信了认罩。
服務(wù)囂:這個服務(wù)器垦垂,準(zhǔn)務(wù)好頻道后劫拗,就從頻道上接收信息杨幼。如果信息是字符串”Hello“的話差购,這個服務(wù)器應(yīng)答一個”World“字符串欲逃。如果收到的信處是字符串“Ni Hao", 那么它會應(yīng)答”Zhong Guo"稳析,其它任何消息都用MsgError()回答一個錯誤诚纸。

$ cat simple_server.c

// Simple server
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>

int main()
{
        int chid, rcvid, status;
        char buf[128];

        if ((chid = ChannelCreate(0)) == -1) {
                perror("ChannelCreate");
                return -1;
        }

        printf("Server is ready, pid = %d, chid = %d\n", getpid(), chid);

        for (;;) {
                if ((rcvid = MsgReceive(chid, buf, sizeof(buf), NULL)) == -1) {
                        perror("MsgReceive");
                        return -1;
                }

                printf("Server: Received '%s'\n", buf);

                /* Based on what we receive, return some message */
                if (strcmp(buf, "Hello") == 0) {
                        MsgReply(rcvid, 0, "World", strlen("World") + 1);
                } else if (strcmp(buf, "Ni Hao") == 0) {
                        MsgReply(rcvid, 0, "Zhong Guo", strlen("Zhong Guo") + 1);
                } else {
                        MsgError(rcvid, EINVAL);
                }
        }

        ChannelDestroy(chid);
        return 0;
}

客戶端:客戶端通過從命令行得到的服務(wù)器的進(jìn)程號與頻道號畦徘,與服務(wù)器建立連接井辆。然后向服務(wù)器發(fā)送三遍"Hello"和”Ni Hao",并檢查返回值萍肆。最后發(fā)一個“unknown"看是不是MsgSend()會得到一個出錯返回肉微。

$ cat simple_client.c

//simple client
#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>

int main(int argc, char **argv)
{
        pid_t spid;
        int chid, coid, i;
        char buf[128];

        if (argc < 3) {
                fprintf(stderr, "Usage: simple_client <pid> <chid>\n");
                return -1;
        }

        spid = atoi(argv[1]);
        chid = atoi(argv[2]);

        if ((coid = ConnectAttach(0, spid, chid, 0, 0)) == -1) {
                perror("ConnectAttach");
                return -1;
        }

        /* sent 3 pairs of "Hello" and "Ni Hao" */
        for (i = 0; i < 3; i++) {
                sprintf(buf, "Hello");
                printf("client: sent '%s'\n", buf);
                if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
                        perror("MsgSend");
                        return -1;
                }
                printf("client: returned '%s'\n", buf);

                sprintf(buf, "Ni Hao");
                printf("client: sent '%s'\n", buf);
                if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
                        perror("MsgSend");
                        return -1;
                }
                printf("client: returned '%s'\n", buf);
        }

        /* sent a bad message, see if we get an error */
        sprintf(buf, "Unknown");
        printf("client: sent '%s'\n", buf);
        if (MsgSend(coid, buf, strlen(buf) + 1, buf, sizeof(buf)) != 0) {
                perror("MsgSend");
                return -1;
        }

        ConnectDetach(coid);

        return 0;
}

執(zhí)行結(jié)果

$ ./simple_server
Server is ready, pid = 36409378, chid = 2
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Hello'
Server: Received 'Ni Hao'
Server: Received 'Unknown'
Server: Received ''
$ ./simple_client 36409378 2
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Hello'
client: returned 'World'
client: sent 'Ni Hao'
client: returned 'Zhong Guo'
client: sent 'Unknown'
MsgSend: Invalid argument

可變消息長度

從上面的程序也可以看出來劳曹,消息傳遞的實質(zhì)是把數(shù)據(jù)從一個緩沖琅摩,復(fù)制到(另一個進(jìn)程的)另一個緩沖里去铁孵。問題是,如何確定緩沖的大小呢房资?上述的例子里蜕劝,服務(wù)器端用了一個128字節(jié)的緩沖,萬一客戶端發(fā)送一個比如說512字節(jié)的消息轰异,是不是消息傳遞就會出錯了呢岖沛?

答案是,傳遞依然成功搭独,但是婴削,只有SendBuffer的最初的128個字節(jié)的數(shù)據(jù)會被復(fù)制牙肝。設(shè)計思想是雹姊,服務(wù)器必須發(fā)現(xiàn)這樣的情形寺酪,并設(shè)法取得完整的數(shù)據(jù)。

在MsgRecieve()時,第四個參數(shù)是一個 struct _msg_info沮协。內(nèi)核會在進(jìn)行消息傳遞的同時晨雳,填充這個結(jié)構(gòu),從而告訴讓你得到一些信息氧吐。在這個結(jié)構(gòu)中豁翎,"msglen"告訴你這次消息傳遞你實際收到了多少字節(jié)(在我們的例子里,就是128),而"srcmsglen"則告訴你發(fā)送方的實際Buffer會有多大(在我們的例子里,是512)。通過比較這兩個值滑燃,服務(wù)器端就可以判斷有沒有收到全部數(shù)據(jù)乐严。

一旦服務(wù)器知道了還有更多的數(shù)據(jù)沒有收到,那該怎么辦呢?QNX提供了 MsgRead()這個特殊函數(shù)磕诊。服務(wù)器端可以用這個函數(shù)莱褒,從發(fā)送緩沖中“讀取”數(shù)據(jù)。MsgRead()基本上就是告訴內(nèi)核,從發(fā)送緩沖的某個指定偏移開始,讀取一定長的數(shù)據(jù)回來枫吧。所以服務(wù)器端這部份的代碼基本上是這樣的。

int rcvid;
struct _msg_info info;
char buf[128], *totalmsg;

...

rcvid = MsgReceive(chid, buf, 128, &info);
...
if (info->srcmsglen > info->msglen) {
    totalmsg = malloc(info->srcmsglen);
    if (!totalmsg) {
        MsgError(rcvid, ENOMEM);
        continue;
    } 
    memcpy(totalmsg, buf , 128);
    if (MsgRead(rcvid, &totalmsg[128], 128, info->srcmsglen - info->msglen) == -1) {
        MsgError(rcvid, EINVAL);
        continue;
    }
} else {
    totalmsg = buf;
}

/* Now totalmsg point to a full message, don't forget to free() it later on,
 * if totalmsg is malloc()'d here
 */

你或者會問涎永,為什么消息接收都已經(jīng)結(jié)束了,服務(wù)器端還能去讀取客戶端的數(shù)據(jù)盯蝴?這是因為從一開始我們就提到的,QNX的消息傳遞是“同步”的黑竞。還記得嗎霞玄?在服務(wù)器端“應(yīng)答”之前,客戶端是被阻塞的;也說是說客戶端的發(fā)送緩沖會一直保留在那里,不會變化端朵。(另外再開個線程去把這個緩沖搞亂甚至free掉?當(dāng)然可以挽懦。不過,這是你客戶端程序的BUG了)

與此相近的,有的時候绍傲,服務(wù)器需要返回大量的數(shù)據(jù)給客戶端(比如說1M)鹏往。服務(wù)器不希望 malloc(1024 * 1024),然后MsgReply()力穗,然后再free()。(在嵌入式程序里,經(jīng)常地進(jìn)行malloc()/free()不是一個很好的習(xí)慣)那么服務(wù)器也可以用一個小的定長緩沖唱遭,比方說16K聋迎,然后把數(shù)據(jù)“一部份一部份地寫回”客戶端的應(yīng)答緩沖里。好象下面的樣子斧抱。要記得最后還是要做一個 MsgReply() 以讓客戶端繼續(xù)運行。

char *buf[16 * 1024];
unsigned offset;

    for  (offset = 0; offset < 1024 * 1024; offset += 16 * 1024) {
        /* moving data into buffer */
        MsgWrite(rcvid, buffer, 16 * 1024, offset);
    }
    /* 1MB returned, Reply() to let client go */
    MsgReply(rcvid, 0, 0, 0);

實例

以下是QNX的C庫中的read()和write()函數(shù)實裝孔飒,有了前面的基礎(chǔ)缀棍,應(yīng)該很好理解了。先不管fd是如何得到的躬络,只要理解fd就是 ConnectAttach()返加的連接號就可以了汪疮。雖然read()是從服務(wù)器取得數(shù)據(jù),而write()是向服務(wù)器輸出數(shù)據(jù)弦撩,但實質(zhì)上,它們都是向服務(wù)器提出一個請求庸蔼,由服務(wù)器來應(yīng)答馒疹。而對于write()來說衔峰,這是一個io_write_t,一個MsgWritev()把請求與要傳遞的數(shù)據(jù)一起發(fā)給服務(wù)器鱼响;而對于read()來說唬滑,請求被封裝在 io_read_t 里归苍,MsgSend()把這請求傳給服務(wù)器,read()的結(jié)果緩沖骑晶,則做為應(yīng)答緩沖,由服務(wù)器MsgReply()時填入。
read():

/*
 * $QNXLicenseC:
 * Copyright 2007, QNX Software Systems. All Rights Reserved.
 * 
 * You must obtain a written license from and pay applicable license fees to QNX 
 * Software Systems before you may reproduce, modify or distribute this software, 
 * or any work that includes all or part of this software. Free development 
 * licenses are available for evaluation and non-commercial purposes. For more 
 * information visit http://licensing.qnx.com or email licensing@qnx.com.
 * 
 * This file may contain contributions from others. Please review this entire 
 * file for other proprietary rights or license notices, as well as the QNX 
 * Development Suite License Guide at http://licensing.qnx.com/license-guide/ 
 * for other information.
 * $
 */




#include <unistd.h>
#include <sys/iomsg.h>

ssize_t read(int fd, void *buff, size_t nbytes) {
    io_read_t   msg;

    msg.i.type = _IO_READ;
    msg.i.combine_len = sizeof msg.i;
    msg.i.nbytes = nbytes;
    msg.i.xtype = _IO_XTYPE_NONE;
    msg.i.zero = 0;
    return MsgSend(fd, &msg.i, sizeof msg.i, buff, nbytes); 
}

/*
 * $QNXLicenseC:
 * Copyright 2007, QNX Software Systems. All Rights Reserved.
 * 
 * You must obtain a written license from and pay applicable license fees to QNX 
 * Software Systems before you may reproduce, modify or distribute this software, 
 * or any work that includes all or part of this software. Free development 
 * licenses are available for evaluation and non-commercial purposes. For more 
 * information visit http://licensing.qnx.com or email licensing@qnx.com.
 * 
 * This file may contain contributions from others. Please review this entire 
 * file for other proprietary rights or license notices, as well as the QNX 
 * Development Suite License Guide at http://licensing.qnx.com/license-guide/ 
 * for other information.
 * $
 */




#include <unistd.h>
#include <sys/iomsg.h>

ssize_t write(int fd, const void *buff, size_t nbytes) {
    io_write_t  msg;
    iov_t   iov[2];

    msg.i.type = _IO_WRITE;
    msg.i.combine_len = sizeof msg.i;
    msg.i.xtype = _IO_XTYPE_NONE;
    msg.i.nbytes = nbytes;
    msg.i.zero = 0;
    SETIOV(iov + 0, &msg.i, sizeof msg.i);
    SETIOV(iov + 1, buff, nbytes);
    return MsgSendv(fd, iov, 2, 0, 0);
}

服務(wù)器端應(yīng)該是怎樣進(jìn)行處理的释涛?想想MsgRead()/MsgWrite(),你應(yīng)該不難想像服務(wù)器端是如何工作的吧烧给。

脈沖(Pulse)

脈沖其實更像一個短消息涧尿,也是在“連接”上發(fā)送的桥言。脈沖最大的特點是它是異步的咏闪。發(fā)送方不必要等接收方應(yīng)答搂擦,直接可以繼續(xù)執(zhí)行。但是哗脖,這種異步性也給脈沖帶來了限制瀑踢。脈沖能攜帶的數(shù)據(jù)量有限,只有一個8位的"code"域用來區(qū)分不同的脈沖才避,和一個32位的“value"域來攜帶數(shù)據(jù)橱夭。脈沖最主要的用途就是用來進(jìn)行“通知”(Notification)。不僅是用戶程序桑逝,內(nèi)核也會生成發(fā)送特殊的“系統(tǒng)脈沖”到用戶程序棘劣,以通知某一特殊情況的發(fā)生。

脈沖的接收比較簡單楞遏,如果你知道頻道上不會有別的消息茬暇,只有脈沖的話,可以用MsgReceivePulse()來只接收脈沖寡喝;如果頻道既可以接收消息糙俗,也可以接收脈沖時,就直接用MsgReceive()预鬓,只要確保接收緩沖(ReveiveBuf)至少可以容下一個脈沖(sizeof struct _pulse)就可以了巧骚。在后一種情況下,如果MsgReceive()返回的rcvid是0格二,就代表接收到了一個脈沖劈彪,反之,則收到了一個消息顶猜。所以沧奴,一個既接收脈沖,又接收消息的服務(wù)器长窄,可以是這樣的扼仲。

union {
    struct _pulse pulse;
    msg_header   header;
} msgs;

...

if ((rcvid = MsgReceive(chid, &msgs, sizeof(msgs), &info)) == -1) {
    perror("MsgReceive");
    continue;
}

if (rcvid == 0) {
    process_pulse(&msgs, &info);
} else {
    process_message(&msgs, &info);
}

脈沖的發(fā)送,最直接的就是MsgSendPulse()抄淑。不過屠凶,這個函數(shù)通常只在一個進(jìn)程中,用在一個線程要通知另一個線程的情形肆资。在跨進(jìn)程的時候矗愧,通常不會用到這個函數(shù),而是用到下面將要提到的 MsgDeliverEvent()。

與消息傳遞相比唉韭,消息傳遞永遠(yuǎn)是在進(jìn)程間進(jìn)行的夜涕。也就是說,不會有一個進(jìn)程向內(nèi)核發(fā)送數(shù)據(jù)的情形属愤。而脈沖就不一樣女器,除了用戶進(jìn)程間可以發(fā)脈沖以外,內(nèi)核也會向用戶進(jìn)程發(fā)送“系統(tǒng)脈沖”來通知某一事件的發(fā)生.

消息傳遞的方向與MsgDeliverEvent()

從一開始就提到住诸,QNX的消息傳遞是客戶驾胆、服務(wù)器型的。也就是說贱呐,總是由客戶端向服務(wù)器端發(fā)送請求丧诺,等待被回復(fù)的。但在現(xiàn)實情況中奄薇,客戶端與服務(wù)器端并不是很容易區(qū)分開來的驳阎。有的服務(wù)器端為了處理客戶端的請求,本身就需要向別的服務(wù)器發(fā)送消息馁蒂;有的客戶端需要從不同的服務(wù)器那里得到服務(wù)呵晚,而不能阻塞在某一特定的服務(wù)器上;還有的時候沫屡,兩個進(jìn)程間的數(shù)據(jù)是互相流動的劣纲,這應(yīng)該怎么辦呢?

也許有人認(rèn)為谁鳍,兩個進(jìn)程互為通訊就可以了。每個進(jìn)程都建立自己的頻道劫瞳,然后都與對方的頻道建一個連接就好了倘潜;這樣,需要的時候志于,就可以直接通過連接向?qū)Ψ桨l(fā)送消息了涮因。就好象管道(pipe)或是socketpair一樣。請注意伺绽,這種設(shè)計在QNX的消息傳遞中是應(yīng)該避免的养泡。因為很容易就造成死鎖。一個常見的情形是這樣的奈应。

進(jìn)程A:MsgSend() 到進(jìn)程B
進(jìn)程B:MsgReceive()接收到消息
進(jìn)程B:處理消息澜掩,然后MsgSend()給進(jìn)程A

因為進(jìn)程A正在阻塞狀態(tài)中,無法接收并處理B的請求杖挣;所以A會在STATE_REPLY里肩榕,而B則會因MsgSend()而進(jìn)入STATE_SEND,兩個進(jìn)程就互為死鎖住了惩妇。當(dāng)然株汉,如果A和B都使用多線程筐乳,專門用一個線程來MsgReceive(),這個情形或許可以避免乔妈;但你要保證 MsgReceive()的線程不會去MsgSend()蝙云,否則一樣會死鎖。在程序簡單的時候或許你還有控制路召,如果程序變得復(fù)雜勃刨,又或者你寫的只是一個程序庫,別人怎么來用你完全沒有控制优训,那么最好還是不要用這種設(shè)計朵你。

在QNX中,正確的方法是這樣的揣非。

客戶端: 準(zhǔn)備一個“通知事件”(Notification Event)抡医,并把這個事件用MsgSend()發(fā)給服務(wù)器端,意思是:“如果xxx情況發(fā)生的話早敬,請用這個事件通知我”忌傻。
服務(wù)器: 收到這個消息后,記錄下當(dāng)時的rcvid搞监,和傳過來的事件水孩,然后應(yīng)答“好的,知道了”琐驴。
客戶端: 因為有了服務(wù)器的應(yīng)答俘种,客戶端不再阻塞,可以去做別的事
......
服務(wù)器: 在某個時刻绝淡,客戶端所要求的“xxx情況”滿足了宙刘,服務(wù)器調(diào)用 MsgDeliverEvent(rcvid, event);以通知客戶端
客戶端: 收到通知,再用MsgSend()發(fā)關(guān)“xxx 情況的數(shù)據(jù)在哪里牢酵?”
服務(wù)器: 用MsgReply()把數(shù)據(jù)返回給客戶端

具體的例子悬包,可以參考MsgDeliverEvent()的文檔說明。

路徑名(Path Name)

現(xiàn)在來回想一下我們最初的例子馍乙,客戶端與服務(wù)器是怎樣取得連接的布近?客戶端需要服務(wù)器的 nd, pid, chid,才能與服務(wù)器正確地建立連接丝格。在我們的例子里撑瞧,我們是讓服務(wù)器顯示這幾個數(shù),然后在客戶端的啟動時显蝌,通過命令行里傳給客戶端季蚂。但是,在一個現(xiàn)實的系統(tǒng)里,進(jìn)程不斷地啟動扭屁、終止算谈;服務(wù)器與客戶端的起動過程也無法控制,這種方法顯然是行不通的料滥。

QNX的解決辦法然眼,是把“路徑名”與上述的“服務(wù)頻道”概念巧妙地結(jié)合起來。讓服務(wù)器進(jìn)程可以注冊一個路徑名葵腹,與服務(wù)頻道的nd, pid, chid關(guān)聯(lián)起來高每。這樣,客戶端就不需要知道服務(wù)器的nd, pid, chid践宴,而只要請求連接版務(wù)器路徑名就可以了鲸匿。具體來說 name_attach()就是用來建立一個頻道,并為頻道注冊一個名字的阻肩;而name_open()則是用來連接注冊過的服務(wù)器頻道带欢;具體的例子,可以在name_attach()的文檔里找到烤惊,這里就不再重復(fù)了乔煞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柒室,隨后出現(xiàn)的幾起案子渡贾,更是在濱河造成了極大的恐慌,老刑警劉巖雄右,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件空骚,死亡現(xiàn)場離奇詭異,居然都是意外死亡擂仍,警方通過查閱死者的電腦和手機(jī)囤屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來防楷,“玉大人,你說我怎么就攤上這事则涯「淳郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵粟判,是天一觀的道長亿昏。 經(jīng)常有香客問我,道長档礁,這世上最難降的妖魔是什么角钩? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上递礼,老公的妹妹穿的比我還像新娘惨险。我一直安慰自己,他們只是感情好脊髓,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布辫愉。 她就那樣靜靜地躺著,像睡著了一般将硝。 火紅的嫁衣襯著肌膚如雪恭朗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天依疼,我揣著相機(jī)與錄音痰腮,去河邊找鬼。 笑死律罢,一個胖子當(dāng)著我的面吹牛膀值,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弟翘,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虫腋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稀余?” 一聲冷哼從身側(cè)響起悦冀,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎睛琳,沒想到半個月后盒蟆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡师骗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年历等,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辟癌。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡寒屯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出黍少,到底是詐尸還是另有隱情寡夹,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布厂置,位于F島的核電站菩掏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昵济。R本人自食惡果不足惜智绸,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一野揪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞧栗,春花似錦斯稳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薄货。三九已至攀隔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脱羡,已是汗流浹背找都。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工唇辨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人能耻。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓赏枚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晓猛。 傳聞我的和親對象是個殘疾皇子饿幅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 系統(tǒng)脈沖 前面提到過,除了用戶自己定義的脈沖外戒职,系統(tǒng)(內(nèi)核)也會向用戶進(jìn)程發(fā)送脈沖栗恩,用來通報某些狀態(tài)的發(fā)生。在這些...
    jackniu_ae28閱讀 444評論 0 0
  • 目錄 1.TCP相關(guān)機(jī)制 2.TCP首部格式 1.TCP三次握手 1.TCP四次揮手 一.TCP相關(guān)機(jī)制 TCP通...
    慕涵盛華閱讀 21,747評論 3 37
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • “你以前是不喜歡貓的啊洪燥】某樱”這是當(dāng)時林瓏唯一和我說的話。 我自然有點震驚捧韵,她怎么會知道李小懶的事市咆?后來她托工作人...
    LeoN6line閱讀 346評論 0 0