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_DEVCTLnbytes成员代表读写的大小,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_SETVALMY_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 <sys/iofunc.h>
#include <sys/dispatch.h>

/*
 * 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,在<sys/iomgr.h>头文件中定义了一些保留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 <stdio.h>
#include <stddef.h>
#include <stdlib.h>

#define THREAD_POOL_PARAM_T     dispatch_context_t
#include <sys/iofunc.h>
#include <sys/dispatch.h>

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;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容