QNX之编写资源管理器(四)

QNX相关历史文章:

  • QNX简介
  • QNX Neutrino微内核
  • QNX IPC机制
  • QNX进程管理器
  • QNX资源管理器
  • QNX字符I/O
  • QNX之编写资源管理器(一)
  • QNX之编写资源管理器(二)
  • QNX之编写资源管理器(三)

Handling Read and Write Messages

这篇文章主要描述读写消息的处理。

1. Handling the _IO_READ message

io_read处理函数负责处理_IO_READ消息,将收到的数据返回给客户端,比如当客户端调用read()/readdir()/fread()/fgetc()等接口时。
消息的定义如下:

struct _io_read {
    uint16_t            type;
    uint16_t            combine_len;
    int32_t             nbytes;
    uint32_t            xtype;
};

typedef union {
    struct _io_read     i;
    /* unsigned char    data[nbytes];    */
    /* nbytes is returned with MsgReply  */
} io_read_t;
  • combine_len,用于消息组合;
  • nbytes,用于客户端期望读取的字节数;
  • xtype,这个字段下文会介绍到,主要用于扩展;

下边是一个完整的消息读取示例代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);

static char                     *buffer = "Hello world\n";

static resmgr_connect_funcs_t   connect_funcs;
static resmgr_io_funcs_t        io_funcs;
static iofunc_attr_t            attr;

main(int argc, char **argv)
{
    /* declare variables we'll be using */
    resmgr_attr_t        resmgr_attr;
    dispatch_t           *dpp;
    dispatch_context_t   *ctp;
    int                  id;

    /* initialize dispatch interface */
    if((dpp = dispatch_create()) == NULL) {
        fprintf(stderr, "%s: Unable to allocate dispatch handle.\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    /* initialize resource manager attributes */
    memset(&resmgr_attr, 0, sizeof resmgr_attr);
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    /* initialize functions for handling messages */
    iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs,
                     _RESMGR_IO_NFUNCS, &io_funcs);
    io_funcs.read = io_read;

    /* initialize attribute structure used by the device */
    iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);
    attr.nbytes = strlen(buffer)+1;
    
    /* attach our device name */
    if((id = resmgr_attach(dpp, &resmgr_attr, "/dev/sample", _FTYPE_ANY, 0,
                 &connect_funcs, &io_funcs, &attr)) == -1) {
        fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
        return EXIT_FAILURE;
    }

    /* allocate a context structure */
    ctp = dispatch_context_alloc(dpp);

    /* start the resource manager message loop */
    while(1) {
        if((ctp = dispatch_block(ctp)) == NULL) {
            fprintf(stderr, "block error\n");
            return EXIT_FAILURE;
        }
        dispatch_handler(ctp);
    }
}

int
io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
    int         nleft;
    int         nbytes;
    int         nparts;
    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);

    /*
     *  On all reads (first and subsequent), calculate
     *  how many bytes we can return to the client,
     *  based upon the number of bytes available (nleft)
     *  and the client's buffer size
     */

    nleft = ocb->attr->nbytes - ocb->offset;
    nbytes = min (msg->i.nbytes, nleft);

    if (nbytes > 0) {
        /* set up the return data IOV */
        SETIOV (ctp->iov, buffer + ocb->offset, nbytes);

        /* set up the number of bytes (returned by client's read()) */
        _IO_SET_READ_NBYTES (ctp, nbytes);

        /*
         * advance the offset by the number of bytes
         * returned to the client.
         */

        ocb->offset += nbytes;
        
        nparts = 1;
    } else {
        /*
         * they've asked for zero bytes or they've already previously
         * read everything
         */
        
        _IO_SET_READ_NBYTES (ctp, 0);
        
        nparts = 0;
    }

    /* mark the access time as invalid (we just accessed it) */

    if (msg->i.nbytes > 0)
        ocb->attr->flags |= IOFUNC_ATTR_ATIME;

    return (_RESMGR_NPARTS (nparts));
}

上述代码中,ocb结构存放了缓存的偏移位置,此外指向的属性结构体attr中,存放了缓存的实际大小。而attr中缓冲的大小是通过iofunc_attr_init()接口设置进去的。
此外,在代码中需要注意用到了ctp->iov,使用SETIOV()宏,将ctp->iov指向了需要回复的数据。
当没有读取到数据时,返回(_RESMGR_NPARTS(0)),当读取到数据后,怎么来告知客户端读取了多少呢?代码中_IO_SET_READ_NBYTES()宏将读取的信息保存到了ctp中,当返回到库的时候,会将读取的字节数当成MsgReplyv()函数的参数,并通知内核MsgSend()该发送什么,最终上层便能获取到字节数。

2. Handling the _IO_WRITE message

io_write处理函数负责处理_IO_WRITE消息,将数据写入到设备。当客户端调用write()/fflush()等操作时执行。
消息定义如下:

struct _io_write {
    uint16_t            type;
    uint16_t            combine_len;
    int32_t             nbytes;
    uint32_t            xtype;
    /* unsigned char    data[nbytes]; */
};

typedef union {
    struct _io_write    i;
    /*  nbytes is returned with MsgReply  */
} io_write_t;

这个结构中的字段与io_read_t中一致,下边看一个写操作的代码:

int
io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb)
{
    int     status;
    char    *buf;

    if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK)
        return (status);

    if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
        return(ENOSYS);

    /* set up the number of bytes (returned by client's write()) */

    _IO_SET_WRITE_NBYTES (ctp, msg->i.nbytes);

    buf = (char *) malloc(msg->i.nbytes + 1);
    if (buf == NULL)
        return(ENOMEM);

    /*
     *  Reread the data from the sender's message buffer.
     *  We're not assuming that all of the data fit into the
     *  resource manager library's receive buffer.
     */

    resmgr_msgread(ctp, buf, msg->i.nbytes, sizeof(msg->i));
    buf [msg->i.nbytes] = '\0'; /* just in case the text is not NULL terminated */
    printf ("Received %d bytes = '%s'\n", msg -> i.nbytes, buf);
    free(buf);

    if (msg->i.nbytes > 0)
        ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME;

    return (_RESMGR_NPARTS (0));
}

当在写入的时候,如果提供的buffer大小不够,而需要写入的数据又很大,此时就需要使用一个循环机制来多次写入了。

3. Methods of return and replying

可以使用各种方式从处理程序中返回到资源管理器库:

  • 返回错误
    比如申请内存不够时:return (ENOMEM)

  • 返回指向数据的IOV数组
    可以使用IOV数组来指向多片数据,比如如下代码:

my_header_t     header;
a_buffer_t      buffers[N];

...

SETIOV(&ctp->iov[0], &header, sizeof(header));
SETIOV(&ctp->iov[1], &buffers[i], sizeof(buffers[i]));
return (_RESMGR_NPARTS(2));
  • 返回一个包含数据的缓存
    比如在响应read()请求时,所有的数据都在一个缓存中,那么可以以下两种方式:
return (_RESMGR_PTR(ctp, buffer, nbytes));

和:

SETIOV (ctp->iov, buffer, nbytes);
return (_RESMGR_NPARTS(1));
  • 成功返回,但是不携带数据
    比如:return (EOK),不过更常见的方式是:return (_RESMGR_NPARTS(0))

  • 让资源管理器库去返回

  • 在服务器端执行回复
    上边讲到的返回情况,都是通过资源管理器库调用MsgReply*()/MsgError()来解除客户端的阻塞状态,在有些case中,你可能不需要这些库来回复,可以使用return (_RESMGR_NOREPLY)

  • 推迟回复,让客户端保持阻塞
    一个例子是管道资源管理器,当一个客户端读取管道时,而此时管道中没有数据,可以有两种选择:1)返回错误,2)保持客户端阻塞,等有数据时再回复。
    另外一个例子是往一个设备写数据时,希望数据全部写入后再回复。

  • 返回并告诉库执行默认处理
    可以执行return (_RESMGR_DEFAULT)

4. Handling other read/write details

4.1 处理xtype成员

从上文中可知,在io_read_t/io_write_t等消息结构中,有一个xtype成员,这个成员包含了可用于调整标准I/O函数行为的一些扩展信息,如下:

  • _IO_XTYPE_NONE,没有提供扩展类型信息;
  • _IO_XTYPE_OFFSET,客户端调用pread()/pread64()/pwrite()/pwrite64()时,不希望用到OCB中的偏移,而是提供了一个一次性的偏移量,该偏移量存放在消息缓冲的开头,紧跟在struct _io_readstruct _io_write后,比如:
struct myread_offset {
    struct _io_read        read;
    struct _xtype_offset   offset;
}   
  • _IO_XTYPE_READCOND,如果客户端调用readcond(),通常会对时间和缓冲区的大小增加一些限制。这个限制放置在消息缓冲区的开头,紧跟在struct _io_readstruct _io_write后,比如:
struct myreadcond {
    struct _io_read        read;
    struct _xtype_readcond cond;
}   
  • _IO_XFLAG_DIR_EXTRA_HINT,只有在读取目录时才有效。

当你不想使用扩展功能时,可以参考以下例子:

int
io_read (resmgr_context_t *ctp, io_read_t *msg,
         RESMGR_OCB_T *ocb)
{
    int    status;

    if ((status = iofunc_read_verify(ctp, msg, ocb, NULL))
         != EOK) {
        return (status);
    }

    /* No special xtypes */
    if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
        return (ENOSYS);

    ...
}

4.2 处理pread*()pwrite*()

客户端调用pread*()函数时,处理_IO_READ消息的示例代码如下:

/* we are defining io_pread_t here to make the code below
   simple */
typedef struct {
    struct _io_read         read;
    struct _xtype_offset    offset;
} io_pread_t;

int
io_read (resmgr_context_t *ctp, io_read_t *msg,
         RESMGR_OCB_T *ocb)
{
    off64_t offset; /* where to read from */
    int     status;

    if ((status = iofunc_read_verify(ctp, msg, ocb, NULL))
         != EOK) {
        return(status);
    }
    
    switch(msg->i.xtype & _IO_XTYPE_MASK) {
    case _IO_XTYPE_NONE:
        offset = ocb->offset;
        break;
    case _IO_XTYPE_OFFSET:
        /*
         *  io_pread_t is defined above.
         *  Client is doing a one-shot read to this offset by
         *  calling one of the pread*() functions
         */
        offset = ((io_pread_t *) msg)->offset.offset;
        break;
    default:
        return(ENOSYS);
    }

    ...
}

客户端调用pwrite*()时,处理_IO_WRITE消息的示例代码如下:

/* we are defining io_pwrite_t here to make the code below
   simple */
typedef struct {
    struct _io_write        write;
    struct _xtype_offset    offset;
} io_pwrite_t;

int
io_write (resmgr_context_t *ctp, io_write_t *msg,
          RESMGR_OCB_T *ocb)
{
    off64_t offset; /* where to write */
    int     status;
    size_t  skip;   /* offset into msg to where the data
                       resides */

    if ((status = iofunc_write_verify(ctp, msg, ocb, NULL))
         != EOK) {
        return(status);
    }
    
    switch(msg->i.xtype & _IO_XTYPE_MASK) {
    case _IO_XTYPE_NONE:
        offset = ocb->offset;
        skip = sizeof(io_write_t);
        break;
    case _IO_XTYPE_OFFSET:
        /* 
         *  io_pwrite_t is defined above
         *  client is doing a one-shot write to this offset by
         *  calling one of the pwrite*() functions
         */
        offset = ((io_pwrite_t *) msg)->offset.offset;
        skip = sizeof(io_pwrite_t);
        break;
    default:
        return(ENOSYS);
    }

    ...
    
    /* 
     *  get the data from the sender's message buffer, 
     *  skipping all possible header information
     */
    resmgr_msgreadv(ctp, iovs, niovs, skip);
    
    ...
}

需要注意的是,通常情况下在发送消息缓冲中,数据会紧跟着struct _io_write,而在上边这种情况下,数据紧跟在struct _xtype_offset后边,这里边会存在一个偏移。

4.3 处理readcond()

与处理pread()/_IO_XTYPE_OFFSET类似,如下:

typedef struct {
    struct _io_read        read;
    struct _xtype_readcond cond;
} io_readcond_t

struct _xtype_readcond *cond
...
    CASE _IO_XTYPE_READCOND:
        cond = &((io_readcond_t *)msg)->cond
        break;
}

5. Updating the time for reads and writes

在读操作的那个示例代码中,有如下代码:

if (msg->i.nbytes > 0)
        ocb->attr->flags |= IOFUNC_ATTR_ATIME;

根据POSIX标准,当读取大于0字节的数据并且成功后,需要去更新访问时间,但是POSIX标准中并没有说需要立刻去更新,因此当有多次读的时候,可能并不想每次都从内核中获取时间,可以在需要更新的时候再统一更新,这样做的缺点是时间记录的不是每一次读操作的时间。如果想记录每次读的时间,那么可以调用iofunc_time_update()接口,代码如下:

if (msg->i.nbytes > 0) {
        ocb->attr->flags |= IOFUNC_ATTR_ATIME;
        iofunc_time_update(ocb->attr);
}

写操作也是一样的原理。

你可能感兴趣的:(QNX之编写资源管理器(四))