为了账号安全,请及时绑定邮箱和手机立即绑定

Nuttx消息队列机制

标签:
Java

介绍

广义来说,消息队列提供了一种从一个进程向另一个进程发送数据块的方法,也就是说它是一种进程间的通信方法。
Nuttx支持POSIX命名消息队列机制,用于内部的task之间的通信。任何task都可以发送和接收消息。中断处理函数中也可以通过消息队列来发送消息。

API接口

在使用API进行开发的时候,需要包含头文件#include <mqueue.h>

  1. 打开函数

mqd_t mq_open(const char *mqName, int oflags, ...)

该接口会在调用Task中打开/创建一个消息队列,消息队列与调用Task建立联系,调用Task可以使用返回值来引用消息队列。
其中oflags代表了不同的含义,可以将这些位进行组合:

  • O_RDONLY:只读

  • O_WRONLY:只写

  • O_RDWR:可读可写

  • O_CREAT:如果消息队列不存在,则创建

  • O_EXCL:打开的时候名字必须不能存在

  • O_NONBLOCK:非阻塞等数据

  1. 关闭函数

int mq_close(mqd_t mqdes)

调用Task负责将打开的消息队列进行关闭。

  1. unlink函数

int mq_unlink(const char *mqName)

该接口会删除名字为mqName的消息队列。当有一个或多个Task打开一个消息队列,此时调用mq_unlink,需要等到所有引用该消息队列的Task都执行关闭操作后,才会删除消息队列。

  1. 消息发送函数

int mq_send(mqd_t mqdes, const void *msg, size_t msglen, int prio)

该接口将msg消息添加到mqdes消息队列中,msglen指定了消息的字节长度,这个长度不能超过mq_getattr()接口中获取的最大长度。如果消息队列未满,mq_send()会将msg放置到prio指定的消息队列中。高优先级的消息会插在低优先级消息之前。prio的值不能超过MQ_PRIO_MAX
如果消息队列已满,并且O_NONBLOCK没有设置,mq_send()会一直阻塞,直到消息队列有空间去存放消息。如果NON_BLOCK设置了,那么消息将不会入列,并且会返回错误码。

int mq_timedsend(mqd_t mqdes, const char *msg, size_t msglen, int prio,                     const struct timespec *abstime);

该接口实现的功能与mq_send一致,唯一不同之处在于,如果消息队列已满,并且O_NONBLOCK没有设置,mq_timedsend不会一直阻塞,而会在设置的时间到期后被唤醒并接着往下执行。参数abstime指的是绝对时间。

  1. 消息接收函数

ssize_t mq_receive(mqd_t mqdes, void *msg, size_t msglen, int *prio);

该接口从mqdes消息队列中接收最高优先级中停留时间最久的消息。如果msglen的长度与mq_msgsize的长度不一致,mq_receive将会返回错误值。接收到的消息将会从消息队列中移除,并把内容拷贝至msg中。
如果消息队列是空的,并且O_NONBLOCK没有设置,mq_receive会一直阻塞。如果有多个task等待在一个消息队列上,当消息产生时,只有优先级最高并且等待时间最长的task将会被唤醒。

ssize_t mq_timedreceive(mqd_t mqdes, void *msg, size_t msglen,                            int *prio, const struct timespec *abstime);

该接口实现的功能与mq_receive是一样的,唯一的区别在于,如果消息队列是空的,并且O_NONBLOCK没有设置,mq_timedreceive不会一直阻塞,而会在设置的时间到期后被唤醒并接着往下执行。参数abstime指的是绝对时间。

  1. 消息队列通知函数

int mq_notify(mqd_t mqdes, const struct sigevent *notification);

当输入参数notification为非NULL时,mq_notify会在task和消息队列中建立连接,当消息队列从空队列到非空队列转换时,会发送一个特定的信号给建立连接的task。一个notification能和一个消息队列建立连接。当参数notificationNULL时,建立的连接会被断开,这样就能建立另一个新的连接。
notification发送给注册连接的task之后,这个注册连接关系就会移除掉,消息队列也就可以接受新的注册连接了。

  1. 消息队列属性设置函数

int mq_setattr(mqd_t mqdes, const struct mq_attr *mqStat,
                   struct mq_attr *oldMqStat);

该接口用于设置消息队列mqdes的属性。当oldMqStat为非空时,它将保存设置之前的属性值。

  1. 消息队列属性获取函数

int mq_getattr(mqd_t mqdes, struct mq_attr *mqStat);

该接口可以用于获取mqdes消息队列的状态信息。包括消息队列的最大容量、消息的最大长度、Flags以及当前队列中消息的数量等。

数据结构

消息队列的数据结构分为两类:一类用于描述消息定义;一类用于描述消息队列以及对应的属性。

  • 消息相关数据结构

enum mqalloc_e
{
  MQ_ALLOC_FIXED = 0,  /* pre-allocated; never freed */
  MQ_ALLOC_DYN,        /* dynamically allocated; free when unused */
  MQ_ALLOC_IRQ         /* Preallocated, reserved for interrupt handling */};/* This structure describes one buffered POSIX message. */struct mqueue_msg_s{
  FAR struct mqueue_msg_s *next;  /* Forward link to next message */
  uint8_t type;                   /* (Used to manage allocations) */
  uint8_t priority;               /* priority of message */#if MQ_MAX_BYTES < 256
  uint8_t msglen;                 /* Message data length */#else
  uint16_t msglen;                /* Message data length */#endif
  char mail[MQ_MAX_BYTES];        /* Message data */};

该结构主要描述消息的分配类型、优先级、消息的长度,以及消息的内容。

  • 消息队列相关数据结构

/* This structure defines a message queue */struct mq_des; /* forward reference */struct mqueue_inode_s{
  FAR struct inode *inode;    /* Containing inode */
  sq_queue_t msglist;         /* Prioritized message list */
  int16_t maxmsgs;            /* Maximum number of messages in the queue */
  int16_t nmsgs;              /* Number of message in the queue */
  int16_t nwaitnotfull;       /* Number tasks waiting for not full */
  int16_t nwaitnotempty;      /* Number tasks waiting for not empty */#if CONFIG_MQ_MAXMSGSIZE < 256
  uint8_t maxmsgsize;         /* Max size of message in message queue */#else
  uint16_t maxmsgsize;        /* Max size of message in message queue */#endif#ifndef CONFIG_DISABLE_SIGNALS
  FAR struct mq_des *ntmqdes; /* Notification: Owning mqdes (NULL if none) */
  pid_t ntpid;                /* Notification: Receiving Task's PID */
  struct sigevent ntevent;    /* Notification description */#endif};/* This describes the message queue descriptor that is held in the
 * task's TCB
 */struct mq_des{
  FAR struct mq_des *flink;        /* Forward link to next message descriptor */
  FAR struct mqueue_inode_s *msgq; /* Pointer to associated message queue */
  int oflags;                      /* Flags set when message queue was opened */};

其中struct mq_des结构用于描述在一个Task中的消息队列,保存在struct task_group_s结构中,该成员结构为sq_queue_t队列,是因为线程组可以拥有多个消息队列,可以用一个队列来存储这些消息队列描述符。如下:

struct task_group_s{...#ifndef CONFIG_DISABLE_MQUEUE
  /* POSIX Named Message Queue Fields *******************************************/

  sq_queue_t tg_msgdesq;            /* List of opened message queues           */#endif...
}

此外,还有三个全局的队列,其中g_msgfreeg_msgfreeirq队列用于存放message,区别是是否在中断处理函数中去使用。message会从这两个队列中进行申请,加入到消息队列中,当最终完成了消息的传递后,会将message再添加到这两个队列中。g_desfree队列用于存放消息队列描述符,每一个消息队列都对应一个描述符,当消息队列销毁的时候,需要将消息队列描述符添加到g_desfree队列中。

/* The g_msgfree is a list of messages that are available for general use.
 * The number of messages in this list is a system configuration item.
 */EXTERN sq_queue_t  g_msgfree;/* The g_msgfreeInt is a list of messages that are reserved for use by
 * interrupt handlers.
 */EXTERN sq_queue_t  g_msgfreeirq;/* The g_desfree data structure is a list of message descriptors available
 * to the operating system for general use. The number of messages in the
 * pool is a constant.
 */EXTERN sq_queue_t  g_desfree;

在上述三个队列,会在mq_initialize()接口中进行初始化,主要完成message的预分配,以及消息队列描述符的预分配。而mq_initialize()会在os_start()函数中进行调用。

实现原理

先来一个大体的介绍图吧


webp

消息队列

我们知道,每一个struct tcb_s结构体中,都保存了group->tg_msgdesq成员,用于存放打开的不同消息队列。当一个任务调用mq_open接口来打开一个消息队列时(假设此时消息队列不存在,需要新建),首先会从g_desfree这个全局队列中,获取一个消息队列描述符,并且会创建一个struct mqueue_inode_s消息队列,将创建的消息队列和消息队列描述符绑定在一起,并且添加到任务的结构体中,这样,每个任务就知道本身所创建的消息队列了。由于消息队列需要创建设备节点,因此在mq_open这个函数中,还需要添加创建inode相关的接口,用于文件系统相关操作,比如,如果该消息队列存在,也就是inode存在,那就不需要再额外创建了。
消息的发送与接收过程也比较简单,图中的struct mqueue_msg_s就相当于一个集装箱,实际的数据传递都会用这个集装箱进行搬运。因此,在发送的时候,如果不在中断上下文中,则从g_msgfree中申请一个节点,把需要发送的数据拷贝至该节点,并将该节点添加到消息队列中。接收的过程,则是从消息队列中拿出节点,使用完消息数据后,将该节点再返回给g_msgfree队列中。在中断上下文中,也同样的道理。,当然在这个过程中,涉及到阻塞睡眠的问题,以及队列信号通知的情况,下边会继续深入。
mq_close执行,会将所有的资源进行释放,返回给全局队列中,同时创建的inode如果引用值变成了0,则需要进行释放。

下边将分别从几个函数来分析:

mq_open()

mq_open函数会完成以下任务:

  1. 调用inode_find()接口去查询是否已经存在一个消息队列对应的inode了,由于消息队列都会对应到一个文件节点,比如"/var/mqueue/my_mq",需要为消息队列创建inode。如果mq_open()打开的是已有的消息队列,那么inode_find()就能找到对应的节点,否则就需要调用inode_reserve()去创建。

  2. 如果消息队列不存在,除了创建inode之外,还需要调用mq_descreate()接口创建消息队列描述符,mq_descreate()接口中调用mq_desalloc(),从全局队列中挪取描述符节点,并加入到struct tcb_s结构中,这个在上图中也能看出来。

  3. 调用mq_msgqalloc()接口,创建一个消息队列,并将消息队列与mq_descreate()获取的消息队列描述符进行绑定。最终更新更新inode的信息。
    关键代码如下:

/****************************************************************************
 * Name: mq_open
 *
 * Description:
 *   This function establish a connection between a named message queue and
 *   the calling task.  After a successful call of mq_open(), the task can
 *   reference the message queue using the address returned by the call. The
 *   message queue remains usable until it is closed by a successful call to
 *   mq_close().
 *
 * Parameters:
 *   mq_name - Name of the queue to open
 *   oflags - open flags
 *   Optional parameters.  When the O_CREAT flag is specified, two optional
 *   parameters are expected:
 *
 *     1. mode_t mode (ignored), and
 *     2. struct mq_attr *attr.  The mq_maxmsg attribute
 *        is used at the time that the message queue is
 *        created to determine the maximum number of
 *        messages that may be placed in the message queue.
 *
 * Return Value:
 *   A message queue descriptor or (mqd_t)-1 (ERROR)
 *
 * Assumptions:
 *
 ****************************************************************************/mqd_t mq_open(FAR const char *mq_name, int oflags, ...)
{
...
  sched_lock();  /* Get the inode for this mqueue.  This should succeed if the message
   * queue has already been created.  In this case, inode_find() will
   * have incremented the reference count on the inode.
   */

  SETUP_SEARCH(&desc, fullpath, false);

  ret = inode_find(&desc);  if (ret >= 0)
    {      /* Something exists at this path.  Get the search results */

      inode = desc.node;
      DEBUGASSERT(inode != NULL);      /* Verify that the inode is a message queue */

      if (!INODE_IS_MQUEUE(inode))
        {
          errcode = ENXIO;          goto errout_with_inode;
        }      /* It exists and is a message queue.  Check if the caller wanted to
       * create a new mqueue with this name.
       */

      if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
        {
          errcode = EEXIST;          goto errout_with_inode;
        }      /* Create a message queue descriptor for the current thread */

      msgq  = inode->u.i_mqueue;
      mqdes = mq_descreate(NULL, msgq, oflags);      if (!mqdes)
        {
          errcode = ENOMEM;          goto errout_with_inode;
        }
    }  else
    {      /* The mqueue does not exists.  Were we asked to create it? */

      if ((oflags & O_CREAT) == 0)
        {          /* The mqueue does not exist and O_CREAT is not set */

          errcode = ENOENT;          goto errout_with_lock;
        }      /* Create the mqueue.  First we have to extract the additional
       * parameters from the variable argument list.
       */

      va_start(ap, oflags);
      mode = va_arg(ap, mode_t);
      attr = va_arg(ap, FAR struct mq_attr *);
      va_end(ap);      /* Create an inode in the pseudo-filesystem at this path */

      inode_semtake();
      ret = inode_reserve(fullpath, &inode);
      inode_semgive();      if (ret < 0)
        {
          errcode = -ret;          goto errout_with_lock;
        }      /* Allocate memory for the new message queue.  The new inode will
       * be created with a reference count of zero.
       */

      msgq = (FAR struct mqueue_inode_s *)mq_msgqalloc(mode, attr);      if (!msgq)
        {
          errcode = ENOSPC;          goto errout_with_inode;
        }      /* Create a message queue descriptor for the TCB */

       mqdes = mq_descreate(NULL, msgq, oflags);       if (!mqdes)
         {
           errcode = ENOMEM;           goto errout_with_msgq;
         }      /* Bind the message queue and the inode structure */

      INODE_SET_MQUEUE(inode);
      inode->u.i_mqueue = msgq;
      msgq->inode       = inode;      /* Set the initial reference count on this inode to one */

      inode->i_crefs    = 1;
    }

  RELEASE_SEARCH(&desc);
  sched_unlock();

...
}

罗列一下struct inode的定义吧:

/* Named OS resources are also maintained by the VFS.  This includes:
 *
 *   - Named semaphores:     sem_open(), sem_close(), and sem_unlink()
 *   - POSIX Message Queues: mq_open() and mq_close()
 *   - Shared memory:        shm_open() and shm_unlink();
 *
 * These are a special case in that they do not follow quite the same
 * pattern as the other file system types in that they have operations.
 *//* These are the various kinds of operations that can be associated with
 * an inode.
 */union inode_ops_u
{
  FAR const struct file_operations     *i_ops;    /* Driver operations for inode */#ifndef CONFIG_DISABLE_MOUNTPOINT
  FAR const struct block_operations    *i_bops;   /* Block driver operations */
  FAR const struct mountpt_operations  *i_mops;   /* Operations on a mountpoint */#endif#ifdef CONFIG_FS_NAMED_SEMAPHORES
  FAR struct nsem_inode_s              *i_nsem;   /* Named semaphore */#endif#ifndef CONFIG_DISABLE_MQUEUE
  FAR struct mqueue_inode_s            *i_mqueue; /* POSIX message queue */#endif#ifdef CONFIG_PSEUDOFS_SOFTLINKS
  FAR char                             *i_link;   /* Full path to link target */#endif};/* This structure represents one inode in the Nuttx pseudo-file system */struct inode{
  FAR struct inode *i_peer;     /* Link to same level inode */
  FAR struct inode *i_child;    /* Link to lower level inode */
  int16_t           i_crefs;    /* References to inode */
  uint16_t          i_flags;    /* Flags for inode */
  union inode_ops_u u;          /* Inode operations */#ifdef CONFIG_FILE_MODE
  mode_t            i_mode;     /* Access mode flags */#endif
  FAR void         *i_private;  /* Per inode driver private data */
  char              i_name[1];  /* Name of inode (variable) */};

mq_send()

mq_send()函数主要完成以下任务:

  1. 调用mq_verifysend()对传入参数进行合法性验证,比如消息的长度、优先级等的设置,出错则设置errono并返回。

  2. 如果存在以下三种情况中的任意一种:1)mq_send()是在中断环境中调用;2)消息队列非满;3)调用mq_waitsend()等到了消息队列非满的信号;则调用mq_msgalloc()分配消息,并通过mq_dosend()完成实际的发送。否则就是发送失败。

  3. mq_waitsend()函数会被mq_send()/mq_timesend()调用,如果消息队列已经满了的话,这个函数会进行阻塞等待,在本函数中会去判断O_NONBLOCK标志是否被置上了。函数阻塞,也就是将自身让出CPU,并且调度其他Task运行,mq_wairtsend()是通过调用up_block_task(struct tcb_s *tcb, tstate_t task_state)接口实现,该接口会将tcb从任务队列中移除,并添加到task_state对应的队列中。

  4. mq_dosend()完成真正的消息发送,在该函数中,会将用户的消息内容拷贝至struct mqueue_msg_s描述的集装箱中,然后再把消息按优先级的顺序插入到消息队列中。此外,还会调用sig_mqnotempty()/sig_notification()接口发送队列非空的信号。最后,查询g_waitingformqnotempty队列,是否有任务在等待这个队列变成非空,如果有的话,就将该任务unblock掉。
    关键代码如下:

/****************************************************************************
 * Name: mq_send
 *
 * Description:
 *   This function adds the specified message (msg) to the message queue
 *   (mqdes).  The "msglen" parameter specifies the length of the message
 *   in bytes pointed to by "msg."  This length must not exceed the maximum
 *   message length from the mq_getattr().
 *
 *   If the message queue is not full, mq_send() place the message in the
 *   message queue at the position indicated by the "prio" argument.
 *   Messages with higher priority will be inserted before lower priority
 *   messages.  The value of "prio" must not exceed MQ_PRIO_MAX.
 *
 *   If the specified message queue is full and O_NONBLOCK is not set in the
 *   message queue, then mq_send() will block until space becomes available
 *   to the queue the message.
 *
 *   If the message queue is full and O_NONBLOCK is set, the message is not
 *   queued and ERROR is returned.
 *
 * Parameters:
 *   mqdes - Message queue descriptor
 *   msg - Message to send
 *   msglen - The length of the message in bytes
 *   prio - The priority of the message
 *
 * Return Value:
 *   On success, mq_send() returns 0 (OK); on error, -1 (ERROR)
 *   is returned, with errno set to indicate the error:
 *
 *   EAGAIN   The queue was full and the O_NONBLOCK flag was set for the
 *            message queue description referred to by mqdes.
 *   EINVAL   Either msg or mqdes is NULL or the value of prio is invalid.
 *   EPERM    Message queue opened not opened for writing.
 *   EMSGSIZE 'msglen' was greater than the maxmsgsize attribute of the
 *            message queue.
 *   EINTR    The call was interrupted by a signal handler.
 *
 * Assumptions/restrictions:
 *
 ****************************************************************************/int mq_send(mqd_t mqdes, FAR const char *msg, size_t msglen, int prio){
...  /* mq_send() is a cancellation point */

  (void)enter_cancellation_point();  /* Verify the input parameters -- setting errno appropriately
   * on any failures to verify.
   */

  if (mq_verifysend(mqdes, msg, msglen, prio) != OK)
    {
      leave_cancellation_point();      return ERROR;
    }  /* Get a pointer to the message queue */

  sched_lock();
  msgq = mqdes->msgq;  /* Allocate a message structure:
   * - Immediately if we are called from an interrupt handler.
   * - Immediately if the message queue is not full, or
   * - After successfully waiting for the message queue to become
   *   non-FULL.  This would fail with EAGAIN, EINTR, or ETIMEOUT.
   */

  flags = enter_critical_section();  if (up_interrupt_context()      || /* In an interrupt handler */
      msgq->nmsgs < msgq->maxmsgs || /* OR Message queue not full */
      mq_waitsend(mqdes) == OK)      /* OR Successfully waited for mq not full */
    {      /* Allocate the message */

      leave_critical_section(flags);
      mqmsg = mq_msgalloc();      /* Check if the message was sucessfully allocated */

      if (mqmsg == NULL)
        {          /* No... mq_msgalloc() does not set the errno value */

          set_errno(ENOMEM);
        }
    }  else
    {      /* We cannot send the message (and didn't even try to allocate it)
       * because:
       * - We are not in an interrupt handler AND
       * - The message queue is full AND
       * - When we tried waiting, the wait was unsuccessful.
       *
       * In this case mq_waitsend() has already set the errno value.
       */

      leave_critical_section(flags);
    }  
  /* Check if we were able to get a message structure -- this can fail
   * either because we cannot send the message (and didn't bother trying
   * to allocate it) or because the allocation failed.
   */

  if (mqmsg != NULL)
    {      /* The allocation was successful (implying that we can also send the
       * message). Perform the message send.
       *
       * NOTE: There is a race condition here: What if a message is added by
       * interrupt related logic so that queue again becomes non-empty.
       * That is handled because mq_dosend() will permit the maxmsgs limit
       * to be exceeded in that case.
       */

      ret = mq_dosend(mqdes, mqmsg, msg, msglen, prio);
    }
...
}
/****************************************************************************
 * Name: mq_waitsend
 *
 * Description:
 *   This is internal, common logic shared by both mq_send and mq_timesend.
 *   This function waits until the message queue is not full.
 *
 * Parameters:
 *   mqdes - Message queue descriptor
 *
 * Return Value:
 *   On success, mq_send() returns 0 (OK); on error, -1 (ERROR) is
 *   returned, with errno set to indicate the error:
 *
 *   EAGAIN   The queue was full and the O_NONBLOCK flag was set for the
 *            message queue description referred to by mqdes.
 *   EINTR    The call was interrupted by a signal handler.
 *   ETIMEOUT A timeout expired before the message queue became non-full
 *            (mq_timedsend only).
 *
 * Assumptions/restrictions:
 * - The caller has verified the input parameters using mq_verifysend().
 * - Executes within a critical section established by the caller.
 *
 ****************************************************************************/int mq_waitsend(mqd_t mqdes){
  FAR struct tcb_s *rtcb;
  FAR struct mqueue_inode_s *msgq;

  /* mq_waitsend() is not a cancellation point, but it is always called from
   * a cancellation point.
   */

  if (enter_cancellation_point())
    {#ifdef CONFIG_CANCELLATION_POINTS
      /* If there is a pending cancellation, then do not perform
       * the wait.  Exit now with ECANCELED.
       */

      set_errno(ECANCELED);
      leave_cancellation_point();      return ERROR;#endif
    }  /* Get a pointer to the message queue */

  msgq = mqdes->msgq;  /* Verify that the queue is indeed full as the caller thinks */

  if (msgq->nmsgs >= msgq->maxmsgs)
    {      /* Should we block until there is sufficient space in the
       * message queue?
       */

      if ((mqdes->oflags & O_NONBLOCK) != 0)
        {          /* No... We will return an error to the caller. */

          set_errno(EAGAIN);
          leave_cancellation_point();          return ERROR;
        }      /* Yes... We will not return control until the message queue is
       * available or we receive a signal or at timout occurs.
       */

      else
        {          /* Loop until there are fewer than max allowable messages in the
           * receiving message queue
           */

          while (msgq->nmsgs >= msgq->maxmsgs)
            {              /* Block until the message queue is no longer full.
               * When we are unblocked, we will try again
               */

              rtcb = this_task();
              rtcb->msgwaitq = msgq;
              msgq->nwaitnotfull++;

              set_errno(OK);
              up_block_task(rtcb, TSTATE_WAIT_MQNOTFULL);              /* When we resume at this point, either (1) the message queue
               * is no longer empty, or (2) the wait has been interrupted by
               * a signal.  We can detect the latter case be examining the
               * errno value (should be EINTR or ETIMEOUT).
               */

              if (get_errno() != OK)
                {
                  leave_cancellation_point();                  return ERROR;
                }
            }
        }
    }

  leave_cancellation_point();  return OK;
}
/****************************************************************************
 * Name: mq_dosend
 *
 * Description:
 *   This is internal, common logic shared by both mq_send and mq_timesend.
 *   This function adds the specified message (msg) to the message queue
 *   (mqdes).  Then it notifies any tasks that were waiting for message
 *   queue notifications setup by mq_notify.  And, finally, it awakens any
 *   tasks that were waiting for the message not empty event.
 *
 * Parameters:
 *   mqdes - Message queue descriptor
 *   msg - Message to send
 *   msglen - The length of the message in bytes
 *   prio - The priority of the message
 *
 * Return Value:
 *   This function always returns OK.
 *
 * Assumptions/restrictions:
 *
 ****************************************************************************/int mq_dosend(mqd_t mqdes, FAR struct mqueue_msg_s *mqmsg, FAR const char *msg,              size_t msglen, int prio){
  FAR struct tcb_s *btcb;
  FAR struct mqueue_inode_s *msgq;
  FAR struct mqueue_msg_s *next;
  FAR struct mqueue_msg_s *prev;
  irqstate_t flags;  /* Get a pointer to the message queue */

  sched_lock();
  msgq = mqdes->msgq;  /* Construct the message header info */

  mqmsg->priority = prio;
  mqmsg->msglen   = msglen;  /* Copy the message data into the message */

  memcpy((FAR void *)mqmsg->mail, (FAR const void *)msg, msglen);  /* Insert the new message in the message queue */

  flags = enter_critical_section();  /* Search the message list to find the location to insert the new
   * message. Each is list is maintained in ascending priority order.
   */

  for (prev = NULL, next = (FAR struct mqueue_msg_s *)msgq->msglist.head;
       next && prio <= next->priority;
       prev = next, next = next->next);  /* Add the message at the right place */

  if (prev)
    {
      sq_addafter((FAR sq_entry_t *)prev, (FAR sq_entry_t *)mqmsg,
                  &msgq->msglist);
    }  else
    {
      sq_addfirst((FAR sq_entry_t *)mqmsg, &msgq->msglist);
    }  /* Increment the count of messages in the queue */

  msgq->nmsgs++;
  leave_critical_section(flags);  /* Check if we need to notify any tasks that are attached to the
   * message queue
   */#ifndef CONFIG_DISABLE_SIGNALS
  if (msgq->ntmqdes)
    {      struct sigevent event;
      pid_t pid;      /* Remove the message notification data from the message queue. */

      memcpy(&event, &msgq->ntevent, sizeof(struct sigevent));
      pid = msgq->ntpid;      /* Detach the notification */

      memset(&msgq->ntevent, 0, sizeof(struct sigevent));
      msgq->ntpid   = INVALID_PROCESS_ID;
      msgq->ntmqdes = NULL;      /* Notification the client via signal? */

      if (event.sigev_notify == SIGEV_SIGNAL)
        {          /* Yes... Queue the signal -- What if this returns an error? */#ifdef CONFIG_CAN_PASS_STRUCTS
          DEBUGVERIFY(sig_mqnotempty(pid, event.sigev_signo,
                      event.sigev_value));#else
          DEBUGVERIFY(sig_mqnotempty(pid, event.sigev_signo,
                      event.sigev_value.sival_ptr));#endif
        }#ifdef CONFIG_SIG_EVTHREAD
      /* Notify the client via a function call */

      else if (event.sigev_notify == SIGEV_THREAD)
        {
          DEBUGVERIFY(sig_notification(pid, &event));
        }#endif

    }#endif

  /* Check if any tasks are waiting for the MQ not empty event. */

  flags = enter_critical_section();  if (msgq->nwaitnotempty > 0)
    {      /* Find the highest priority task that is waiting for
       * this queue to be non-empty in g_waitingformqnotempty
       * list. sched_lock() should give us sufficent protection since
       * interrupts should never cause a change in this list
       */

      for (btcb = (FAR struct tcb_s *)g_waitingformqnotempty.head;
           btcb && btcb->msgwaitq != msgq;
           btcb = btcb->flink);      /* If one was found, unblock it */

      ASSERT(btcb);

      btcb->msgwaitq = NULL;
      msgq->nwaitnotempty--;
      up_unblock_task(btcb);
    }

  leave_critical_section(flags);
  sched_unlock();  return OK;
}

mq_receive

mq_receive()接口,与mq_send()类似,主要完成以下几个任务:

  1. 调用mq_verifyreceive()对参数进行验证

  2. 调用mq_waitreceive()进行等待消息操作,如果消息队列为空并且没有设置O_NONBLOCK,则睡眠等待,让出CPU,设置了O_NONBLOCK的话就直接报错返回。如果消息队列不为空,则直接从队列头部挪取一个消息节点。

  3. 调用mq_doreceive()来完成实际的接收处理。

  4. do_mqreceive()接口中,将``struct mqueue_msg_s中的内容拷贝至用户提供的ubuffer地址中。调用mq_msgree()释放struct mqueue_msg_s内容,也就是返回g_msgfree全局队列中。查询g_waitingformqnotfull队列中的任务,是否有任务在等待该队列变成非满,如果有的话,则调用up_unblock_task()把该任务unblock`。
    关键代码如下:

/****************************************************************************
 * Name: mq_receive
 *
 * Description:
 *   This function receives the oldest of the highest priority messages
 *   from the message queue specified by "mqdes."  If the size of the
 *   buffer in bytes (msglen) is less than the "mq_msgsize" attribute of
 *   the message queue, mq_receive will return an error.  Otherwise, the
 *   selected message is removed from the queue and copied to "msg."
 *
 *   If the message queue is empty and O_NONBLOCK was not set,
 *   mq_receive() will block until a message is added to the message
 *   queue.  If more than one task is waiting to receive a message, only
 *   the task with the highest priority that has waited the longest will
 *   be unblocked.
 *
 *   If the queue is empty and O_NONBLOCK is set, ERROR will be returned.
 *
 * Parameters:
 *   mqdes - Message Queue Descriptor
 *   msg - Buffer to receive the message
 *   msglen - Size of the buffer in bytes
 *   prio - If not NULL, the location to store message priority.
 *
 * Return Value:
 *   One success, the length of the selected message in bytes is returned.
 *   On failure, -1 (ERROR) is returned and the errno is set appropriately:
 *
 *   EAGAIN   The queue was empty, and the O_NONBLOCK flag was set
 *            for the message queue description referred to by 'mqdes'.
 *   EPERM    Message queue opened not opened for reading.
 *   EMSGSIZE 'msglen' was less than the maxmsgsize attribute of the
 *            message queue.
 *   EINTR    The call was interrupted by a signal handler.
 *   EINVAL   Invalid 'msg' or 'mqdes'
 *
 * Assumptions:
 *
 ****************************************************************************/ssize_t mq_receive(mqd_t mqdes, FAR char *msg, size_t msglen,
                   FAR int *prio)
{
  FAR struct mqueue_msg_s *mqmsg;
  irqstate_t flags;  ssize_t ret = ERROR;

  DEBUGASSERT(up_interrupt_context() == false);  /* mq_receive() is a cancellation point */

  (void)enter_cancellation_point();  /* Verify the input parameters and, in case of an error, set
   * errno appropriately.
   */

  if (mq_verifyreceive(mqdes, msg, msglen) != OK)
    {
      leave_cancellation_point();      return ERROR;
    }  /* Get the next message from the message queue.  We will disable
   * pre-emption until we have completed the message received.  This
   * is not too bad because if the receipt takes a long time, it will
   * be because we are blocked waiting for a message and pre-emption
   * will be re-enabled while we are blocked
   */

  sched_lock();  /* Furthermore, mq_waitreceive() expects to have interrupts disabled
   * because messages can be sent from interrupt level.
   */

  flags = enter_critical_section();  /* Get the message from the message queue */

  mqmsg = mq_waitreceive(mqdes);
  leave_critical_section(flags);  /* Check if we got a message from the message queue.  We might
   * not have a message if:
   *
   * - The message queue is empty and O_NONBLOCK is set in the mqdes
   * - The wait was interrupted by a signal
   */

  if (mqmsg)
    {
      ret = mq_doreceive(mqdes, mqmsg, msg, prio);
    }

  sched_unlock();
  leave_cancellation_point();  return ret;
}
/****************************************************************************
 * Name: mq_waitreceive
 *
 * Description:
 *   This is internal, common logic shared by both mq_receive and
 *   mq_timedreceive.  This function waits for a message to be received on
 *   the specified message queue, removes the message from the queue, and
 *   returns it.
 *
 * Parameters:
 *   mqdes - Message queue descriptor
 *
 * Return Value:
 *   On success, a reference to the received message.  If the wait was
 *   interrupted by a signal or a timeout, then the errno will be set
 *   appropriately and NULL will be returned.
 *
 * Assumptions:
 * - The caller has provided all validity checking of the input parameters
 *   using mq_verifyreceive.
 * - Interrupts should be disabled throughout this call.  This is necessary
 *   because messages can be sent from interrupt level processing.
 * - For mq_timedreceive, setting of the timer and this wait must be atomic.
 *
 ****************************************************************************/FAR struct mqueue_msg_s *mq_waitreceive(mqd_t mqdes){
  FAR struct tcb_s *rtcb;
  FAR struct mqueue_inode_s *msgq;
  FAR struct mqueue_msg_s *rcvmsg;

  /* mq_waitreceive() is not a cancellation point, but it is always called
   * from a cancellation point.
   */

  if (enter_cancellation_point())
    {#ifdef CONFIG_CANCELLATION_POINTS
      /* If there is a pending cancellation, then do not perform
       * the wait.  Exit now with ECANCELED.
       */

      set_errno(ECANCELED);
      leave_cancellation_point();      return NULL;#endif
    }  /* Get a pointer to the message queue */

  msgq = mqdes->msgq;  /* Get the message from the head of the queue */

  while ((rcvmsg = (FAR struct mqueue_msg_s *)sq_remfirst(&msgq->msglist)) == NULL)
    {      /* The queue is empty!  Should we block until there the above condition
       * has been satisfied?
       */

      if ((mqdes->oflags & O_NONBLOCK) == 0)
        {          /* Yes.. Block and try again */

          rtcb = this_task();
          rtcb->msgwaitq = msgq;
          msgq->nwaitnotempty++;

          set_errno(OK);
          up_block_task(rtcb, TSTATE_WAIT_MQNOTEMPTY);          /* When we resume at this point, either (1) the message queue
           * is no longer empty, or (2) the wait has been interrupted by
           * a signal.  We can detect the latter case be examining the
           * errno value (should be either EINTR or ETIMEDOUT).
           */

          if (get_errno() != OK)
            {              break;
            }
        }      else
        {          /* The queue was empty, and the O_NONBLOCK flag was set for the
           * message queue description referred to by 'mqdes'.
           */

          set_errno(EAGAIN);          break;
        }
    }  /* If we got message, then decrement the number of messages in
   * the queue while we are still in the critical section
   */

  if (rcvmsg)
    {
      msgq->nmsgs--;
    }

  leave_cancellation_point();  return rcvmsg;
}



作者:Loyen
链接:https://www.jianshu.com/p/8e43b4fef638


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消