QNX学习笔记5 消息传递(1) Messgae Passing

一、消息传递基础 Messaging fundamentals

微内核与消息传递 A small microkernel and message passing

Neutrino 有一个特别的优势就是它具有可缩放性(scalable)所谓缩放性我认为就是在最小单元系统下它可以运行,在大规模的网络工作模式下它依然可以运行,且它们的基本结构相同。 它的基本架构如下图所示
QNX学习笔记5 消息传递(1) Messgae Passing_第1张图片

在这些模块中,你可以决定是否使用它们,你也可以在使用后卸载,卸载后还可以重新安装。

要实现这些就必须用到消息传递机制(message passing)。比如说你打开一个文件并写入一些数据,那么消息就要从应用程序传递到文件系统,依靠的就是消息传递。

二、消息传递与客户端/服务器 Message passing and client/server

应用程序从文件系统读取数据是消息传递的一种,在QNX中,应用程序代表客户端client,文件系统代表服务器端server。

服务器 server 有两个状态(state) READY RECEIVE

客户端 client 有三个状态 READY REPLY SEND 且通常情况下REPLY状态要比SEND状态多。因为正常情况下client发送message后,server接受到message就要给client回复。有两种情况client会处于SEND状态,一是server正忙(跟电话占线一样),二是服务器挂了(有人把电话线拔了,或者电话除了啥毛病。。。)

这里有一个客户端消息传递的例子:

#include 
#include 

int
main (void)
{
    int     fd;

    fd = open ("filename", O_WRONLY);
    write (fd, "This is message passing\n", 24);
    close (fd);

    return (EXIT_SUCCESS);
}

很简单,这完全是一个C程序,需要注意的就是 open write close 代表了三个不同的 message 然后它们被发送给了服务器,有了这个概念,就可以接着往下看了。

messaging passing 是在Neutrino中处于核心地位,理解它的使用和含义可以更有效的使用OS。

messaging passing 具有很好的网络协同性(network-distributed),但是它最大的优势是测试和运行软件,消息传递使得软件可以很完美的模块化且便于管理。

三、多线程 Multiple threads

客户端/服务器模型很容易让人理解,但是更常用的是下面两种模型:

1、multiple threads 多线程

多线程参考之前的学习笔记

2、server/subserver 服务器/子服务器

这个模型进场运用于network-distributed的设计中

也可以将上述两个模型结合使用,在网络多进程任务中,它们的结合使用将异常强大。

server/subserver

QNX学习笔记5 消息传递(1) Messgae Passing_第2张图片
这个模型可以用上面这个图片描述。其中的箭头表示的是发送(send)的方向

subserver发送消息后并不会立即得到回复(reply)直到server获得了来自客户端(client)的请求(request),subserver得到回复后就会处理client的请求。在这个模型中server相当于一个协调者(coordinator)

因此可以说

server/subserver 是 reply-driven 回复驱动 模型

client/server 是 send-driven 发送驱动 模型

四、使用消息传递 using message passing

使用消息传递有以下函数:

ChannelCreate(), ChannelDestroy() 
ConnectAttach(), ConnectDetach() 
MsgDeliverEvent() 
MsgError() 
MsgRead(), MsgReadv() 
MsgReceive(), MsgReceivePulse(), MsgReceivev() 
MsgReply(), MsgReplyv() 
MsgSend(), MsgSendnc(), MsgSendsv(), MsgSendsvnc(), MsgSendv(), MsgSendvnc(), MsgSendvs(), MsgSendvsnc() 
MsgWrite(), MsgWritev() 

其中可以完成消息传递的最小使用集是 ChannelCreate(), ConnectAttach(), MsgReply(), MsgSend(), and MsgReceive().

接下来就从client和server两个方面了解一下这些函数该怎么使用,或者应该遵循什么样的原则。

1、client

client 只干两件事儿,

1、建立连接(establish a connection)

通过ConnectAttach()函数,该函数定义如下:

#include

int ConnectAttach (int nd,
                   pid_t pid,
                   int chid,
                   unsigned index,
                   int flags);

nd Node Descriptor 节点描述符
pid process ID 线程ID
chid channel ID 信道ID

剩下两个参数暂时不需要学习,暂且为0(默认值)

int coid;

coid = ConnectAttach (0, 77, 1, 0, 0);

表示连接到process ID 77,channel ID 1,原节点(0即为原节点)

可以通过以下方式中断连接:

ConnectDetach (coid);

2、传消息/数据 (transfer data)

需要用到的是 MsgSend() 函数,该函数定义如下:

#include 

int MsgSend (int coid,
             const void *smsg,
             int sbytes,
             void *rmsg,
             int rbytes);

具体参数解释如下:

the connection ID of the target server (coid), 
a pointer to the send message (smsg), 
the size of the send message (sbytes), 
a pointer to the reply message (rmsg), and 
the size of the reply message (rbytes). 

下面通过一段代码感受以下client的全部过程:

#include 

char *smsg = "This is the outgoing buffer";
char rmsg [200];
int  coid;

// establish a connection
coid = ConnectAttach (0, 77, 1, 0, 0);
if (coid == -1) {
    fprintf (stderr, "Couldn't ConnectAttach to 0/77/1!\n");
    perror (NULL);
    exit (EXIT_FAILURE);
}

// send the message
if (MsgSend (coid,
             smsg, 
             strlen (smsg) + 1, 
             rmsg, 
             sizeof (rmsg)) == -1) {
    fprintf (stderr, "Error during MsgSend\n");
    perror (NULL);
    exit (EXIT_FAILURE);
}

if (strlen (rmsg) > 0) {
    printf ("Process ID 77 returns \"%s\"\n", rmsg);
}

2、server

同样server也需要干两件事儿:

1、创建信道(creating the channel)

与信道有关的函数如下:

#include 

int ChannelCreate  (unsigned flags);

int ChannelDestroy (int chid);

暂且不学习其中的参数用法,且置为0.

int  chid;

chid = ChannelCreate (0);

channel是连接client和server的一个通道,它的由来是方便多线程的通信,因为之前版本的QNX基本是单线程的,连接通信仅靠 node ID 和 process ID ,在多线程中,这就很容易造成混淆,比如之前的thread pool例子,因此就需要一个channel ID 参数连接thread pool中的特定thread。

2、接收消息 (message handling)

这其中又分为接收和回复两个阶段。

#include 

int MsgReceive (int chid,
                void *rmsg,
                int rbytes,
                struct _msg_info *info);

int MsgReply (int rcvid,
              int status,
              const void *msg,
              int nbytes);

从下面这幅图中,你可以很清楚的看到数据的“流动”方向。
QNX学习笔记5 消息传递(1) Messgae Passing_第3张图片
下面通过另一段代码感受以下server的全过程:

#include void
server (void)
{
    int     rcvid;         // indicates who we should reply to
    int     chid;          // the channel ID
    char    message [512]; // big enough for our purposes

    // create a channel
    chid = ChannelCreate (0);

    // this is typical of a server:  it runs forever
    while (1) {

        // get the message, and print it
        rcvid = MsgReceive (chid, message, sizeof (message),
                            NULL);
        printf ("Got a message, rcvid is %X\n", rcvid);
        printf ("Message was \"%s\".\n", message);

        // now, prepare the reply.  We reuse "message"
        strcpy (message, "This is the reply");
        MsgReply (rcvid, EOK, message, sizeof (message));
    }
}
有关消息回复的一部分内容:
	1、reply()不是必须的,你也可以不回复
	2、你可以只回复确没有任何内容,使用MsgReply(rcvid,EOK,NULL,0);
	3、如果发生了错误的话,你可以使用MsgError(rcvid,EROFS);

3、发送等级/阶层 the send-hierarchy

在消息传递的环境中,不太明显的一点是 线程需要严格遵守发送等级

所有的发送都会从一个等级到一个更高的等级,不可能是同级或低级。而回复正好与它相反。

不过你终究会碰到要打破这种等级制度的情况,在后面的部分会介绍MsgDeliverEvent()函数。

4、有关消息传递的参数及一些注意事项

4.1 谁发送了消息?

server经常需要知道谁发送了消息,但是对于client来说,每发送一次消息都要提供发送者信息是十分繁琐的,甚至会有安全漏洞。

kernel会填充MsgReceive()中的struct _msg_info 参数,这个参数包含了许多信息,结构体定义如下:

struct _msg_info
{
    int     nd;
    int     srcnd;
    pid_t   pid;
    int32_t chid;
    int32_t scoid;
    int32_t coid;
    int32_t msglen;
    int32_t tid;
    int16_t priority;
    int16_t flags;
    int32_t srcmsglen;
    int32_t dstmsglen;
};

如果你将它填充为NULL也没有关系,因为这些信息会通过MsgInfo() 被检索出来。

4.2 receive ID

receive ID是MsgReceive() 的返回值,同时它也是server回复client的关键参数,如果你在使用MsgReceive()时没有接受其返回值,那么receive ID 就没有了。

如果你接收了receive ID ,并且你使用了MsgReply() 进行回复消息,那么receive ID就失去了意义。

4.3 如何找到server的准确地址?

1、使用name-location函数,name_attach()和name_detach(),然后在client端使用name_open()和name_close()函数操作。

2、使用resource manager,这个内容将会在Resource Managers这章中讲到。

4.4 优先级问题

消息传递终究还是thread的运行,所以它还是要遵循之前的高优先级先运行,同优先级看时间顺序的原则。

4.5 读写数据

如果client发送的消息比server预设的缓存小,那么server当然能够接收全部消息;但是,如果比预设的缓存大呢?那么一段data就会被分割成好多段接收,这样就会有不太好的缺点出现:

1、如果发送了一部分消息后,有更高优先级的client发送消息抢占了cpu怎么办?
2、会影响程序的运行速度。

有两个函数可以更好地帮助读写数据,MsgRead() 和 MsgWrite()。

#include 

int MsgRead (int rcvid,
             void *msg,
             int nbytes,
             int offset);

int MsgWrite (int rcvid,
              const void *msg,
              int nbytes,
              int offset);

在fs-qnx4中数据的传送会是以下方式:
QNX学习笔记5 消息传递(1) Messgae Passing_第4张图片
先通过一段伪代码了解一下server是怎么接收和读取消息的:

// part of the headers, fictionalized for example purposes
struct _io_write {
    uint16_t    type;
    uint16_t    combine_len;
    int32_t     nbytes;
    uint32_t    xtype;
};

typedef union {
    uint16_t           type;
    struct _io_read    io_read;
    struct _io_write   io_write;} header_t;

header_t    header;    // declare the header

rcvid = MsgReceive (chid, &header, sizeof (header), NULL);

switch (header.type) {case _IO_WRITE:
    number_of_bytes = header.io_write.nbytes;

其中nbytes这个变量告诉了server数据的大小,然后server就会以下面这种方式传送数据到缓冲区:

MsgRead (rcvid, cache_buffer [index].data,
         cache_buffer [index].size, sizeof (header.io_write));

当server要向client写数据时,基本原理与上述过程一致。

五、多条/多部分消息传递

有时候message会在不连续的缓冲区/存储区中,同时发送好几个文件一样,当数量足够多时你就需要一个批量发送的功能,不然就会太影响工作效率。

Neutrino中引入了IOV机制,即Input/Output Vector。

直接上代码感受:

#include 

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

    // set up the IOV to point to both parts:
    SETIOV (iov + 0, &whdr, sizeof (whdr));
    SETIOV (iov + 1, buf, nbytes);

    // fill in the io_write_t at the beginning
    whdr.type = _IO_WRITE;
    whdr.nbytes = nbytes;

    // send the message to the server
    return (MsgSendv (coid, iov, 2, iov, 1));
}

然后解释一下iov_t这个变量类型:

typedef struct iovec
{
    void    *iov_base;
    size_t   iov_len;
} iov_t;

很明显,它包含地址和数据的长度。

接着,你还需要了解MsgSendv()这个函数:

#include 

int MsgSendv (int coid,
              const iov_t *siov,
              int sparts,
              const iov_t *riov,
              int rparts);

这里这需要解释一下sparts和rparts这两个参数,原文解释为The number of send and receive parts specified by the iov_t parameters.

它的最根本的原理就是传递指针永远要比复制数据快得多。

你可能感兴趣的:(qnx)