资源管理器(resource manager)应该是QNX编程中最常用的,如果没写过几个资源管理器,大概都不好意思说自已做过QNX编程。唯其重要,QNX也有很多官方文档解释。这篇文章详细解释了QNX资源管理器的基本设计,管理器框架,并辅以代码。希望能够帮助读者掌握QNX上的资源管理器,从而更容易地搭建系统。
文章里提到的资源管理器示例,完整代码可以在这个链接里找到。
什么是资源管理器
资源管理器顾名思义就是管理“资源”的服务器,这里问题是,到底什么是“资源”呢?在QNX上,“资源”可以是一个硬件(硬件资源管理器其实就是我们常说的硬件驱动),“资源”也可以是一种服务,比如TCPIP网络服务,或者ntfs文件系统服务;“资源”甚至可以是一个文件(或者目录)。
如果你还记得,Unix的基本思想,就是“把驱动当成文件”,那资源管理器就非常有用了。所以/dev/ser1是一个管理串口的资源管理器,而/dev/random则是一个提供随机数的资源管理器。甚至传统Unix里那些mount point,像是根目录 /,或者用户目录 /home在QNX里也可以是一个个资源管理器。
好,show me the code,假设我们要写一个md5的资源管理器, 要怎么办呢?md5是这样一个服务,假设客户端传给它一串数据, 它计算md5值, 然后让客户端来取。
一个最基本的资源管理器
一个资源管理器基本上来说就是一个服务器。只要看过《从API开始理解QNX》那篇的同学应该很容易就可以想到最基本的资源管理器,应该就是下面这样的:
(这里只是示意代码,完整的可以在QNX上编译运行的代码可以在GitHub上找到)
#define MD5_SEND_DATA 0x00000001
#define MD5_RECV_DIGEST 0x00000002
typedef struct {
int msgtype;
int msglen;
} md5_msg_t;
name_attach(...);
for (;;){
revid = MsgReceive(chid, &msg, sizeof(msg), &info);
switch msg.msgtype {
case MD_SEND_DATA:
MsgRead(rcvid, ...);
....
MsgReply(rcvid, ...);
break;
case MD_RECV_DIGEST :
....
MsgReply(rcvid, …)
break;
}
}
对的, 就是一个循环不断收信息, 然后按定好的信息类型进行处理就好了。客户端要怎样获取这个服务呢?
int md5_send(int fd, unsigned char *data, int len)
{
md5_msg_t msg;
iov_t iov[2];
msg.msgtype = MD5_SEND_DATA;
msg.msglen = len;
SETIOV(&iov[0], &msg, sizeof(msg));
SETIOV(&iov[1], data, len);
return MsgSendv(fd, iov, 2, 0, 0);
}
int md5_recv(int fd, unsigned char *digest, int len)
{
md5_msg_t msg;
if (len < 16) {
errno = EINVAL;
return -1;
}
msg.msgtype = MD5_RECV_DIGEST;
msg.msglen = len;
return MsgSend(fd, &msg, sizeof(msg), digest, len);
}
当然做为一个资源管理器的开发者, 你会把md5_send(), md5_recv() 函数打包成一个库,让别人来使用你的服务。
int main(int argc, char **argv)
{
if ((fd = name_open("md5name", 0)) == -1) {
perror("name_open");
return -1;
}
if ((cfd = open(argv[1], O_RDONLY)) == -1) {
perror("open");
return -1;
}
total = 0;
for (;;) {
n = read(cfd, buf, 16 * 1024);
if (n <= 0)
break;
if (md5_send(fd, buf, n) <= 0) {
perror("md5_send");
return -1;
}
total += n;
}
md5_recv(fd, digest, 16);
printf("%10dttt", total);
for (n = 0; n < 16; n++) {
printf(" %02X", digest[n]);
}
printf("n");
return 0;
}
看上去一切都完成了,但是,没有问题吗?
首先这个服务器只能一次处理一个请求,如果有两个程序同时申请这服务时,逻辑上有错误。另外,这个解决方案,意味着每个想要使用这个md5服务的客户端都要链接一个专用的库, 难道没有办法做得通用一点吗?
答案当然是有的。既然md5服务是通过名字来提供服务的,而POSIX对于文件可以进行的操作是有标准定义的呀, 对于我们这个服务, 立刻可以想到的就是客户端可以直接write() 数据到比如 /dev/md5, 然后read() 结果的呀。
那么,资源管理器要怎么做呢,如果客户端write() 时, 我会收到什么信息? QNX已经为你准备好了,这就是,资源管理器框架。
资源管理器框架
因为一个资源管理器都是由一个路经名作为入口的,而POSIX又定义了对一个文件可进行的操作,所以QNX就替大家预定义了这些操作所需要传递的消息类型和数据格式
QNX提供的资源管理器框架大致可以分成4个部份:
- iofunc (iomsg) 层,这一层提供了所有POSIX对文件可以进行的io操作 (sys/iofuncs.h, sys/iomsg.h)
- resmgr层,这一层提供了登记路径名,接收数据并分发给iofunc执行具体操作。iofunc 和 resmgr,是写一个资源管理器的基础
- dispatch层,在一些复杂的资源管理器里,“外来的消息传递”并不是唯一需要处理的。也有可能需要处理“脉冲”,或者有时候一个“信号”。dispatch层会主动识别不同的输入信息,然后转给不同的处理函数进行处理
- thread pool层,这一层提供了一个线程池管理,可以配置实现多个线程进行资源管理。
下面我们深入地看一下各层
iofunc (iomsg) 层:
QNX总结了总共34个对文件操作,基本上POSIX对文件的处理,都可以通过这34个操作进行。而资源管理器的iofunc层,其实也就是准备回调函数,通过响应这些操作请求,来提供服务。
这34个回调函数,又根据性质不同,被分为8个 “connect" 回调函数,和26个 "io&#