QNX相关历史文章:
- QNX简介
- QNX Neutrino微内核
- QNX IPC机制
- QNX进程管理器
- QNX资源管理器
- QNX字符I/O
- QNX之编写资源管理器(一)
- QNX之编写资源管理器(二)
- QNX之编写资源管理器(三)
- QNX之编写资源管理器(四)
- QNX之编写资源管理器(五)
- QNX之编写资源管理器(六)
Handling Other Messages
这篇文章主要描述如何处理其他消息。
1. Custom messages
大部分时候资源管理器都是处理来自客户端的消息,但有时候资源管理器也需要控制自身的行为,比如资源管理器在管理串口时,就需要控制波特率。
有多种方式来给资源管理器发送控制信息:
停止资源管理器,并使用新的命令行重新启动它。这是一种笨拙的方法,因为在重启的时候不能使用,并且如果管理多个设备的时候,需要为不同的设备配置不同选项,这不是一个可行的方案。
将控制信息包含在
write()
消息中,这样的话write()
消息可能会被分解成更小的消息,资源管理器需要保存好这些消息片段,重新组装,然后再对它们进行操作。因此io_write
处理程序在处理完客户端的write()
消息之外,还需要解析这些控制信息。直接通过
MsgSend()
来向资源管理器发送IPC消息,这个消息需要包含一个数据结构,在这个数据结构中包含控制信息。这种方式灵活简单,并且速度快,不好的地方在于MsgSend()
不是POSIX接口,不具备可移植性。通过
devctl()
来发送控制信息,这些消息不会与其他消息产生冲突,不好的地方也是不具备可移植性。在调用
resmgr_attach()
时,为"其他"I/O消息设置一个处理程序,此时需要将resmgr_attr_t
结构中的flags
置上RESMGR_FLAG_ATTACH_OTHERFUNC
,并将other_func
成员指向处理函数。不推荐使用这种方法。使用
message_attach()
或pulse_attach()
来将特定的消息范围或pulse code
绑定到dispatch handle
上。消息范围必须在I/O范围之外,并且消息和pulse
不与OCB关联。当这个范围的消息收到后,便会调用指定的handler去处理。使用一个
out-of-band
消息:_IO_MSG
。这个消息会关联到OCB上,并通过资源管理器框架进行处理。
2. Handling devctl() messages
devctl()
是用于与资源管理器通信的通用机制,客户端可以发送、接收数据,函数原型如下:
int devctl( int fd,
int dcmd,
void * data,
size_t nbytes,
int * return_info);
以下的结构对应到_IO_DEVCTL
消息:
struct _io_devctl {
uint16_t type;
uint16_t combine_len;
int32_t dcmd;
int32_t nbytes;
int32_t zero;
/* char data[nbytes]; */
};
struct _io_devctl_reply {
uint32_t zero;
int32_t ret_val;
int32_t nbytes;
int32_t zero2;
/* char data[nbytes]; */
} ;
typedef union {
struct _io_devctl i;
struct _io_devctl_reply o;
} io_devctl_t;
大部分资源管理器中的消息都会定义成一个union,包含input结构和reply/output结构。数据结构中的type
成员设置成_IO_DEVCTL
,nbytes
成员代表读写的大小,combine_len
成员代表消息组合。
需要注意的是dcmd
成员,这个是由宏产生的命令,类似于Linux中的ioctl()
中的命令,产生的方式如下:
#define _POSIX_DEVDIR_NONE 0
#define _POSIX_DEVDIR_TO 0x80000000
#define _POSIX_DEVDIR_FROM 0x40000000
#define __DIOF(class, cmd, data) ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_FROM)
#define __DIOT(class, cmd, data) ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_TO)
#define __DIOTF(class, cmd, data) ((sizeof(data)<<16) + ((class)<<8) + (cmd) + _POSIX_DEVDIR_TOFROM)
#define __DION(class, cmd) (((class)<<8) + (cmd) + _POSIX_DEVDIR_NONE)
其中8-bit的class
和8-bit的manager-specific
组合成命令的低16位,高16位放置的是传递数据结构的大小(< 16KB),以及传递方向(To、From)。
比如,定义一个只给服务器发送数据的命令:
struct _my_devctl_msg {
...
}
#define MYDCMD __DIOT(_DCMD_MISC, 0x54, struct _my_devctl_msg)
下边给出一个示例代码:
首先需要在头文件中定义命令:
typedef union _my_devctl_msg {
int tx; /* Filled by client on send */
int rx; /* Filled by server on reply */
} data_t;
#define MY_CMD_CODE 1
#define MY_DEVCTL_GETVAL __DIOF(_DCMD_MISC, MY_CMD_CODE + 0, int)
#define MY_DEVCTL_SETVAL __DIOT(_DCMD_MISC, MY_CMD_CODE + 1, int)
#define MY_DEVCTL_SETGET __DIOTF(_DCMD_MISC, MY_CMD_CODE + 2, union _my_devctl_msg)
示例中定义了三个命令,分别用于设置服务器端的值,获取服务器端的值,同时设置并获取服务器端的值。
io_devctl
的实现代码如下:
int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg,
RESMGR_OCB_T *ocb) {
int nbytes, status, previous;
union { /* See note 1 */
data_t data;
int data32;
/* ... other devctl types you can receive */
} *rx_data;
/*
Let common code handle DCMD_ALL_* cases.
You can do this before or after you intercept devctls, depending
on your intentions. Here we aren't using any predefined values,
so let the system ones be handled first. See note 2.
*/
if ((status = iofunc_devctl_default(ctp, msg, ocb)) !=
_RESMGR_DEFAULT) {
return(status);
}
status = nbytes = 0;
/*
Note this assumes that you can fit the entire data portion of
the devctl into one message. In reality you should probably
perform a MsgReadv() once you know the type of message you
have received to get all of the data, rather than assume
it all fits in the message. We have set in our main routine
that we'll accept a total message size of up to 2 KB, so we
don't worry about it in this example where we deal with ints.
*/
/* Get the data from the message. See Note 3. */
rx_data = _DEVCTL_DATA(msg->i);
/*
Three examples of devctl operations:
SET: Set a value (int) in the server
GET: Get a value (int) from the server
SETGET: Set a new value and return the previous value
*/
switch (msg->i.dcmd) {
case MY_DEVCTL_SETVAL:
global_integer = rx_data->data32;
nbytes = 0;
break;
case MY_DEVCTL_GETVAL:
rx_data->data32 = global_integer; /* See note 4 */
nbytes = sizeof(rx_data->data32);
break;
case MY_DEVCTL_SETGET:
previous = global_integer;
global_integer = rx_data->data.tx;
/* See note 4. The rx data overwrites the tx data
for this command. */
rx_data->data.rx = previous;
nbytes = sizeof(rx_data->data.rx);
break;
default:
return(ENOSYS);
}
/* Clear the return message. Note that we saved our data past
this location in the message. */
memset(&msg->o, 0, sizeof(msg->o));
/*
If you wanted to pass something different to the return
field of the devctl() you could do it through this member.
See note 5.
*/
msg->o.ret_val = status;
/* Indicate the number of bytes and return the message */
msg->o.nbytes = nbytes;
return(_RESMGR_PTR(ctp, &msg->o, sizeof(msg->o) + nbytes));
}
note 1
为可能收到的数据类型定义联合体。MY_DEVCTL_SETVAL
和MY_DEVCTL_GETVAL
使用data32
成员,而MY_DEVCTL_SETGET
使用data_t
成员。note 2
在处理自己的消息之前调用了默认的devctl
处理函数。这允许处理正常的命令消息,如果消息不是由默认的处理函数处理,它将返回_RESMGR_DEFAULT
,表明消息可能是定制消息。这也意味着需要根据资源管理器理解的命令去检查传入命令。note 3
传递的数据直接跟在io_devctl_t
结构之后,可以通过_DEVCTL_DATA(msg->i)
宏来获取指向该位置的指针,这个宏的参数必须是输入消息结构。note 4
返回给客户端的数据放置在回复消息的尾部,这跟输入数据的机制相同,因此也可使用_DEVCTL_DATA()
来获取指向该位置的指针。note 5
devctl()
函数的最后一个参数是一个整形指针,如果提供了这个指针,它将存放msg->o.ret_val
值,这种方式可以方便的将一些简单的状态信息返回。
下边是main函数:
int main(int argc, char **argv) {
int fd, ret, val;
data_t data;
if ((fd = open("/dev/sample", O_RDONLY)) == -1) {
return(1);
}
/* Find out what the value is set to initially */
val = -1;
ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
printf("GET returned %d w/ server value %d \n", ret, val);
/* Set the value to something else */
val = 25;
ret = devctl(fd, MY_DEVCTL_SETVAL, &val, sizeof(val), NULL);
printf("SET returned %d \n", ret);
/* Verify we actually did set the value */
val = -1;
ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
printf("GET returned %d w/ server value %d == 25? \n", ret, val);
/* Now do a set/get combination */
memset(&data, 0, sizeof(data));
data.tx = 50;
ret = devctl(fd, MY_DEVCTL_SETGET, &data, sizeof(data), NULL);
printf("SETGET returned with %d w/ server value %d == 25?\n",
ret, data.rx);
/* Check set/get worked */
val = -1;
ret = devctl(fd, MY_DEVCTL_GETVAL, &val, sizeof(val), NULL);
printf("GET returned %d w/ server value %d == 50? \n", ret, val);
return(0);
}
3. Handling ionotify() and select()
客户端可以使用ionotify()
和select()
接口向资源管理器查询某些条件状态,比如是否有可用数据。资源管理器可以:
- 立刻查询条件的状态,如果满足的话就返回;
- 在满足条件之后传递一个事件;
ionotify()
和select()
的区别在于,select()
中的大部分工作是在库里边实现的,比如客户端代码不知道将涉及的事件、也不知道等待事件的阻塞函数,这些都隐藏在select()
库代码中了。但在资源管理器来看,这两个函数没有区别。
ionotify()
和select()
需要资源管理器来做同样的工作,它们都发送_IO_NOTIFY
消息,io_notify
处理函数负责处理这个消息。消息定义如下:
struct _io_notify {
uint16_t type;
uint16_t combine_len;
int32_t action;
int32_t flags;
struct sigevent event;
};
struct _io_notify_reply {
uint32_t flags;
};
typedef union {
struct _io_notify i;
struct _io_notify_reply o;
} io_notify_t;
看看上述结构中的flags
成员,可以是以下位的组合:
-
_NOTIFY_COND_INPUT
,当有一个或多个输入数据单元可用时(客户端有数据可读),条件满足; -
_NOTIFY_COND_OUTPUT
,当缓冲区有空间容纳一个或多个数据单元时,条件满足; -
_NOTIFY_COND_OBAND
,当一个或多个带外数据单元可用时,条件满足; -
_NOTIFY_COND_EXTEN
,定义了一些扩展标识;
结构体中的event
成员表示当条件满足时传递的事件。
资源管理器需要保存一个客户端列表以及用于通知的事件,当条件满足时,资源管理器会去遍历客户端列表,找到对应的客户端,然后把事件发送过去。如果客户端已经关闭了文件描述符,则需要从列表中把相应的客户端移除掉。
为了让这些事情更简单,提供了一个结构体和几个帮助函数:
-
iofunc_notify_t structure
包含了三个通知列表,每一个列表对应一种可能的条件; -
iofunc_notify()
添加或移除通知条目,同时也会轮询条件。在io_notify
处理函数中调用此函数; -
iofunc_notify_trigger()
向入列的客户端发送通知,当满足一个或多个条件时调用此函数; -
iofunc_notify_remove()
从列表中移除通知条目,当关闭文件描述符时调用此函数;
示例代码如下:
首先添加如下声明:
struct device_attr_s;
#define IOFUNC_ATTR_T struct device_attr_s
#include
#include
/*
* Define a structure and variables for storing the data that
* is received. When clients write data to us, we store it here.
* When clients do reads, we get the data from here. Result: a
* simple message queue.
*/
typedef struct item_s {
struct item_s *next;
char *data;
} item_t;
/* the extended attributes structure */
typedef struct device_attr_s {
iofunc_attr_t attr;
iofunc_notify_t notify[3]; /* notification list used by
iofunc_notify*() */
item_t *firstitem; /* the queue of items */
int nitems; /* number of items in the queue */
} device_attr_t;
/* We only have one device; device_attr is its attribute structure */
static device_attr_t device_attr;
int io_read( resmgr_context_t *ctp, io_read_t *msg,
RESMGR_OCB_T *ocb);
int io_write( resmgr_context_t *ctp, io_write_t *msg,
RESMGR_OCB_T *ocb);
int io_notify( resmgr_context_t *ctp, io_notify_t *msg,
RESMGR_OCB_T *ocb);
int io_close_ocb( resmgr_context_t *ctp, void *reserved,
RESMGR_OCB_T *ocb);
static resmgr_connect_funcs_t connect_funcs;
static resmgr_io_funcs_t io_funcs;
需要一个地方来保存特定于设备的数据,好的方式是放在属性结构中,可以将它与我们注册的名字关联起来,在上述代码中,就定义了一个device_attr_t
的数据结构。
有两类特定于设备的数据:
- 存放三个通知列表的数组,每一个对应一种条件;
- 一个队列用于存放写入的数据,以及回复的数据;
把处理函数的地址告诉给资源管理器库:
/* initialize functions for handling messages */
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
/* For handling _IO_NOTIFY, sent as a result of client
calls to ionotify() and select() */
io_funcs.notify = io_notify;
io_funcs.write = io_write;
io_funcs.read = io_read;
io_funcs.close_ocb = io_close_ocb;
/* initialize attribute structure used by the device */
iofunc_attr_init(&device_attr.attr, S_IFNAM | 0666, 0, 0);
IOFUNC_NOTIFY_INIT(device_attr.notify);
device_attr.firstitem = NULL;
device_attr.nitems = 0;
/* attach our device name */
id = resmgr_attach(dpp, /* dispatch handle */
&resmgr_attr, /* resource manager attrs */
"/dev/sample", /* device name */
_FTYPE_ANY, /* open type */
0, /* flags */
&connect_funcs, /* connect routines */
&io_funcs, /* I/O routines */
&device_attr); /* handle */
添加_IO_NOTIFY
消息处理函数:
int
io_notify( resmgr_context_t *ctp, io_notify_t *msg,
RESMGR_OCB_T *ocb)
{
device_attr_t *dattr = (device_attr_t *) ocb->attr;
int trig;
/*
* 'trig' will tell iofunc_notify() which conditions are
* currently satisfied. 'dattr->nitems' is the number of
* messages in our list of stored messages.
*/
trig = _NOTIFY_COND_OUTPUT; /* clients can always give us data */
if (dattr->nitems > 0)
trig |= _NOTIFY_COND_INPUT; /* we have some data available */
/*
* iofunc_notify() will do any necessary handling, including
* adding the client to the notification list if need be.
*/
return (iofunc_notify( ctp, msg, dattr->notify, trig,
NULL, NULL));
}
当客户端调用ionotify()
或select()
函数时,就会调用到io_notify()
函数。在这个函数中,首先设置了trig
的值,代表了需要满足的条件,比如设置的_NOTIFY_COND_OUTPUT
表示总是能接受写操作,读操作则需要判断nitems
的值。最终调用iofunc_notify()
来处理,需要传入接收到的消息(msg),通知列表(notify),以及需要满足的条件(trig)。
前边讲到的三个条件_NOTIFY_COND_INPUT
、_NOTIFY_COND_OUTPUT
、_NOTIFY_COND_OBAND
提到当一个或多个数据单元满足,默认都是1个,如果需要更改的话,在iofunc_nofify()
接口的传入参数中进行设置。
当有数据到达时,需要进行_IO_WRITE
消息的处理:
int
io_write(resmgr_context_t *ctp, io_write_t *msg,
RESMGR_OCB_T *ocb)
{
device_attr_t *dattr = (device_attr_t *) ocb->attr;
int i;
char *p;
int status;
char *buf;
item_t *newitem;
if ((status = iofunc_write_verify(ctp, msg, ocb, NULL))
!= EOK)
return (status);
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
return (ENOSYS);
if (msg->i.nbytes > 0) {
/* Get and store the data */
if ((newitem = malloc(sizeof(item_t))) == NULL)
return (errno);
if ((newitem->data = malloc(msg->i.nbytes+1)) ==
NULL) {
free(newitem);
return (errno);
}
/* reread the data from the sender's message buffer */
resmgr_msgread(ctp, newitem->data, msg->i.nbytes,
sizeof(msg->i));
newitem->data[msg->i.nbytes] = NULL;
if (dattr->firstitem)
newitem->next = dattr->firstitem;
else
newitem->next = NULL;
dattr->firstitem = newitem;
dattr->nitems++;
/*
* notify clients who may have asked to be notified
* when there is data
*/
if (IOFUNC_NOTIFY_INPUT_CHECK(dattr->notify,
dattr->nitems, 0))
iofunc_notify_trigger(dattr->notify, dattr->nitems,
IOFUNC_NOTIFY_INPUT);
}
/* set up the number of bytes (returned by client's
write()) */
_IO_SET_WRITE_NBYTES(ctp, msg->i.nbytes);
if (msg->i.nbytes > 0)
ocb->attr->attr.flags |= IOFUNC_ATTR_MTIME |
IOFUNC_ATTR_CTIME;
return (_RESMGR_NPARTS(0));
}
在这个写操作处理函数中,分配了内存空间,并调用resmgr_msgread()
将客户端缓冲区的数据读取到分配的空间里,并添加到数据队列中。此时,还调用了iofunc_notify_trigger()
接口来通知那些读取等待的客户端。
收到通知的客户端会通过读操作来获取数据,io_read
处理函数如下:
int
io_read(resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
device_attr_t *dattr = (device_attr_t *) ocb->attr;
int status;
if ((status = iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK)
return (status);
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
return (ENOSYS);
if (dattr->firstitem) {
int nbytes;
item_t *item, *prev;
/* get last item */
item = dattr->firstitem;
prev = NULL;
while (item->next != NULL) {
prev = item;
item = item->next;
}
/*
* figure out number of bytes to give, write the data to the
* client's reply buffer, even if we have more bytes than they
* are asking for, we remove the item from our list
*/
nbytes = min (strlen (item->data), msg->i.nbytes);
/* set up the number of bytes (returned by client's read()) */
_IO_SET_READ_NBYTES (ctp, nbytes);
/*
* write the bytes to the client's reply buffer now since we
* are about to free the data
*/
resmgr_msgwrite (ctp, item->data, nbytes, 0);
/* remove the data from the queue */
if (prev)
prev->next = item->next;
else
dattr->firstitem = NULL;
free(item->data);
free(item);
dattr->nitems--;
} else {
/* the read() will return with 0 bytes */
_IO_SET_READ_NBYTES (ctp, 0);
}
/* mark the access time as invalid (we just accessed it) */
if (msg->i.nbytes > 0)
ocb->attr->attr.flags |= IOFUNC_ATTR_ATIME;
return (EOK);
}
在读操作中,在队列中找到最早的节点,并通过resmgr_msgwrite()
接口将节点中的数据写入到客户端的缓冲区中。然后再释放这个节点所包含的内存,并移除这个节点。
最后,当一个客户端关闭文件描述符时,必须将这个客户端从列表中移除,这个通过io_close_ocb
处理函数来完成:
int io_close_ocb( resmgr_context_t *ctp, void *reserved,
RESMGR_OCB_T *ocb)
{
device_attr_t *dattr = (device_attr_t *) ocb->attr;
/*
* A client has closed its file descriptor or has terminated.
* Remove the client from the notification list.
*/
iofunc_notify_remove(ctp, dattr->notify);
return (iofunc_close_ocb_default(ctp, reserved, ocb));
}
在这个函数中会调用iofunc_notify_remove()
接口来移除,其中传入参数ctp
中包含了需要移除的客户端的信息。
4. Handling out-of-band(_IO_MSG) messages
_IO_MSG
消息允许客户端通过文件描述符想资源管理器发送“带外”消息或控制消息。这个接口比ioctl()/devctl()
更通用,但是可移植性较差。
消息格式是特定于资源管理器的,客户端程序设置消息,并通过MsgSend()
将其发送到资源管理器,资源管理器必须设置一个io_msg
处理函数来接收消息,没有默认的处理程序。
消息的头部定义如下:
struct _io_msg {
_Uint16t type;
_Uint16t combine_len;
_Uint16t mgrid;
_Uint16t subtype;
};
-
type
,为_IO_MSG
; -
combine_len
,将其设置为sizeof(struct _io_msg)
; -
mgrid
,资源管理器的唯一ID,在头文件中定义了一些保留ID; -
subtype
,用这个成员来区分不同类型的_IO_MSG
消息;
其他数据应该紧跟在这个头部的后边,比如:
typedef struct {
struct _io_msg hdr;
/* Add any required data fields here. */
} my_msg_t;
客户端的代码可这么写:
#define MY_MGR_ID (_IOMGR_PRIVATE_BASE + 22)
my_msg_t msg, my_reply;
int fd, status;
fd = open ("/dev/sample", O_RDWR);
msg.hdr.type = _IO_MSG;
msg.hdr.combine_len = sizeof( msg.hdr );
msg.hdr.mgrid = MY_MGR_ID;
msg.hdr.subtype = 0;
/* Fill in the additional fields as required. */
status = MsgSend( fd, &msg, sizeof( msg ), &my_reply,
sizeof (my_reply));
资源管理器需要注册一个函数用于处理这个消息:
/* Initialize the functions for handling messages */
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
io_funcs.msg = my_io_msg;
/* This handler processes the message as appropriate */
int my_io_msg (resmgr_context_t *ctp, io_msg_t *msg,
RESMGR_OCB_T *ocb)
{
my_msg_t my_msg;
MsgRead (ctp->rcvid, &my_msg, sizeof (my_msg), 0);
if (my_msg.hdr.mgrid != MY_MGR_ID)
{
return (ENOSYS);
}
/* Process the data as required. */
/* Reply if necessary and tell the library that we've
already replied. */
MsgReply( ctp->rcvid, 0, &my_reply, sizeof(my_reply));
return (_RESMGR_NOREPLY);
}
在my_io_msg
处理函数中,返回值为_RESMGR_NOREPLY
,告诉库不需要它来进行回复。
5. Handling private messages and pulses
资源管理器可能需要接收和处理pulse
脉冲,因为在中断处理程序中或者其他线程或进程中可能会发送一个pulse
。
脉冲的主要问题是它们必须作为消息接收,这意味着线程必须显式的调用MsgReceive()
才能收到脉冲。除非将脉冲发送到与资源管理器接收主消息通道不同的通道,否则将会被库接收。因此,需要了解资源管理器如何将脉冲编码与处理程序关联起来,并将该消息传递给库。
可以使用pulse_attach()
接口将脉冲编码与处理程序关联起来,当收到脉冲时会去找到对应的处理函数来处理。
有时可能还需要定义自己的私有消息范围(0x0到0x1ff保留给系统),以便于资源管理器通信,可以使用message_attach()
接口来完成。
看一个示例代码:
#include
#include
#include
#define THREAD_POOL_PARAM_T dispatch_context_t
#include
#include
static resmgr_connect_funcs_t connect_func;
static resmgr_io_funcs_t io_func;
static iofunc_attr_t attr;
int
timer_tick( message_context_t *ctp, int code, unsigned flags,
void *handle)
{
union sigval value = ctp->msg->pulse.value;
/*
* Do some useful work on every timer firing
* ....
*/
printf("received timer event, value %d\n", value.sival_int);
return 0;
}
int
message_handler( message_context_t *ctp, int code, unsigned flags,
void *handle)
{
printf("received private message, type %d\n", code);
return 0;
}
int
main(int argc, char **argv) {
thread_pool_attr_t pool_attr;
resmgr_attr_t resmgr_attr;
struct sigevent event;
struct _itimer itime;
dispatch_t *dpp;
thread_pool_t *tpp;
resmgr_context_t *ctp;
int timer_id;
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;
/* We are doing resmgr and pulse-type attaches.
*
* If you're going to use custom messages or pulses with
* the message_attach() or pulse_attach() functions,
* then you MUST use the dispatch functions
* (i.e. dispatch_block(), dispatch_handler(), ...),
* NOT the resmgr functions (resmgr_block(), resmgr_handler()).
*/
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_func, _RESMGR_IO_NFUNCS,
&io_func);
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_func, &io_func, &attr)) == -1) {
fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
return EXIT_FAILURE;
}
/* We want to handle our own private messages, of type 0x5000 to 0x5fff */
if(message_attach(dpp, NULL, 0x5000, 0x5fff, &message_handler, NULL) == -1) {
fprintf(stderr, "Unable to attach to private message range.\n");
return EXIT_FAILURE;
}
/* Initialize an event structure, and attach a pulse to it */
if((event.sigev_code = pulse_attach(dpp, MSG_FLAG_ALLOC_PULSE, 0, &timer_tick,
NULL)) == -1) {
fprintf(stderr, "Unable to attach timer pulse.\n");
return EXIT_FAILURE;
}
/* Connect to our channel */
if((event.sigev_coid = message_connect(dpp, MSG_FLAG_SIDE_CHANNEL)) == -1) {
fprintf(stderr, "Unable to attach to channel.\n");
return EXIT_FAILURE;
}
event.sigev_notify = SIGEV_PULSE;
event.sigev_priority = -1;
/* We could create several timers and use different sigev values for each */
event.sigev_value.sival_int = 0;
if((timer_id = TimerCreate(CLOCK_REALTIME, &event)) == -1) {;
fprintf(stderr, "Unable to attach channel and connection.\n");
return EXIT_FAILURE;
}
/* And now set up our timer to fire every second */
itime.nsec = 1000000000;
itime.interval_nsec = 1000000000;
TimerSettime(timer_id, 0, &itime, NULL);
/* Never returns */
thread_pool_start(tpp);
}
可以自己定义脉冲的编码,比如#define OurPulseCode 57
,也可以使用pulse_attach()
接口来动态的分配。
6. Handling open(), dup(), and close() messages
资源管理器提供了另外一个方便的服务:它知道如何处理dup()
消息。假设客户端执行以下代码:
fd = open ("/dev/sample", O_RDONLY);
…
fd2 = dup (fd);
…
fd3 = dup (fd);
…
close (fd3);
…
close (fd2);
…
close (fd);
上述代码中,资源管理器会收到一个_IO_CONNECT
消息,两个_IO_DUP
消息,三个_IO_CLOSE
消息。由于dup()
函数会生成文件描述符副本,我们不希望每次dup()
都生成一个OCB
。由于没有在每次dup()
都生成OCB
,因此当有_IO_CLOSE
消息到达的时候,不要去做内存释放操作,否则第一次close()
的时候就会把OCB
结构给擦除掉。
资源管理器知道如何帮我们管理这种情况,它会记录客户端发送的_IO_DUP
和_IO_CLOSE
消息的数量,只有在最后一个_IO_CLOSE
消息时,库才会调用_IO_CLOSE_OCB
处理程序。
7. Handling mount()
挂载请求可以为需要使能或禁止资源管理器组件的程序提供了灵活方便的接口。
在使用和构建资源管理器的挂载功能时,主要考虑以下几个方面:
-
mount
工具 -
mount
函数调用 - 资源管理器中的
mount()
处理函数
7.1
mount() function call
mount()
函数原型如下:
int mount( const char *special_device,
const char *mount_directory,
int flags,
const char *mount_type,
const void *mount_data,
int mount_datalen);
mount支持挂载不存在的特殊设备(比如NFS设备),也支持挂载字符串(比如共享库的名字等),这个在mount
命令中,有-T/-t
参数来区分。
通常情况下,如果有实际的设备,mount
命令如下:
-
mount -t qnx4 /dev/hd0t77 /mnt/fs
挂载类型为qnx4
,设备为/dev/hd0t77
,挂载路径为/mntfs
。在这种情况下,挂载请求会被定向到负责管理/dev/hd0t77
的进程,也就是/dev/hd0t77
对应的资源管理器。资源管理器为/dev/hd0t77
提供了OCB
结构,而不是字符串"/dev/hd0t77"。
另外一种情况如下:
-
mount -T io-pkt /lib/dll/devn-i82544.so
这个命令中没有挂载点,NULL
(或者/
)充当了隐式的挂载点,在收到挂载请求时采取适当的操作。这里的特殊设备为/lib/dll/dev-i82544.so
,类型为io-pkt
。
在这种情况下,应该与普通设备的挂载区分出来。当有进程处理/lib/dll/dev-i82544.so
时,我们实际感兴趣的是挂载一个由io-pkt
进程管理的网络接口,理想情况下,mount处理函数应该只接收到特殊的设备字符串"/lib/dll/devn-i82544.so",而不是设备的OCB结构。
7.2 Mount in the resource manager
资源管理器在收到mount
请求时,会调用对应的处理函数,这个函数包含在resmgr_connect_funcs_t
结构体中,定义如下:
int mount( resmgr_context_t *ctp,
io_mount_t *msg,
RESMGR_HANDLE_T *handle,
io_mount_extra_t *extra);
与其他连接函数不一样的地方在于有一个io_mount_extra_t
结构:
typedef struct _io_mount_extra {
uint32_t flags; /* _MOUNT_? or ST_? flags above */
uint32_t nbytes; /* Size of entire structure */
uint32_t datalen; /* Length of the data structure following */
uint32_t zero[1];
union { /* If EXTRA_MOUNT_PATHNAME these set*/
struct { /* Sent from client to resmgr framework */
struct _msg_info info; /* Special info on first mount,
path info on remount */
} cl;
struct { /* Server receives this structure filled in */
void * ocb; /* OCB to the special device */
void * data; /* Server specific data of len datalen */
char * type; /* Character string with type information */
char * special; /* Optional special device info */
void * zero[4]; /* Padding */
} srv;
} extra;
} io_mount_extra_t;
为了能接收挂载请求,资源管理器需要注册一个NULL
路径,并且使用_FTYPE_MOUNT
和_RESMGR_FLAG_FTYPEONLY
,如下:
mntid = resmgr_attach(
dpp, /* Dispatch pointer */
&resmgr_attr, /* Dispatch attributes */
NULL, /* Attach at "/" */
/* We are a directory and want only matching ftypes */
_RESMGR_FLAG_DIR | _RESMGR_FLAG_FTYPEONLY,
_FTYPE_MOUNT,
mount_connect, /* Only mount filled in */
NULL, /* No io handlers */
& handle); /* Handle to pass to mount callout */
一个挂载请求的处理函数框架大体如下:
int io_mount( ... ) {
Do any sanity checks that you need to do.
Check type against our type with strcmp(), since
there may be no name for REMOUNT/UNMOUNT flags.
Error with ENOENT out if no match.
If no name, check the validity of the REMOUNT/UNMOUNT request.
Parse arguments or set up your data structure.
Check to see if we are remounting (_MOUNT_REMOUNT)
Change flags, etc., if you can remount.
Return EOK.
Check to see if we are unmounting _MOUNT_UNMOUNT
Change flags, etc., if you can unmount.
Return EOK.
Create a new node and attach it at the msg->connect.path
point (unless some other path is implied based on the
input variables and the resource manager) with resmgr_attach().
Return EOK.
}
需要注意的是,每个注册挂载处理程序的资源管理器都可以对请求进行检查,并判断是否能处理,这意味着需要严格检查类型和错误。当取消挂载的时候,需要做一些清理工作和完整性检查,并调用resmgr_detach()
接口。
7.3 mount utility
mount工具与Linux下使用类似,格式如下:
mount [-wreuv] -t type [-o options] [special] mntpoint
mount [-wreuv] -T type [-o options] special [mntpoint]
如果是自己实现一个mount的处理函数,需要对参数进行解析,此时可以调用mount_parse_generic_args()
接口来完成。
8. Handling stat()
当客户端调用stat()/lstat()/fstat()
时,资源管理器接收到_IO_STAT
消息,通常不需要提供这个消息的处理函数。处理函数原型如下:
int io_stat ( resmgr_context_t *ctp,
io_stat_t *msg,
RESMGR_OCB_T *ocb)
默认的处理函数iofunc_stat_default()
接口会调用iofunc_time_update()
来更新时间,并调用iofunc_stat()
帮助函数来填充stat
结构体信息。结构体如下:
struct _io_stat {
uint16_t type;
uint16_t combine_len;
uint32_t zero;
};
typedef union {
struct _io_stat i;
struct stat o;
} io_stat_t;
9. Handling lseek()
客户端调用lseek()/fseek()/rewinddir()
时,资源管理器会收到_IO_LSEEK
消息,io_lseek
处理函数的原型如下:
int io_lseek ( resmgr_context_t *ctp,
io_lseek_t *msg,
RESMGR_OCB_T *ocb)
默认的处理函数iofunc_lseek_default()
会调用iofunc_lseek()
帮助函数。io_lseek_t
的结构体如下:
struct _io_lseek {
uint16_t type;
uint16_t combine_len;
short whence;
uint16_t zero;
uint64_t offset;
};
typedef union {
struct _io_lseek i;
uint64_t o;
} io_lseek_t;