深入探究RTOS的IPC机制——消息队列

阅读引言: 因为将来工作需要, 最近在深入学习OS的内部机制,我把我觉得重要的、核心的东西分享出来, 希望对有需要的人有所帮助, 阅读此文需要读友有RTOS基础, 以及一些操作系统的基础知识, 学习过Linux的最佳, 特别是想RT-Thread适合Linux非常像的, 代码风格、IPC机制等等。

目录

一、同步互斥和通信引入

1, 同步互斥概念

二、引入IPC(进程间通信)好处

三、多任务如何编程

四、消息队列

1, 消息队列的概念和理解

2, 消息队列实现的原理

3, 消息队列源码分析

3.1 消息队列句柄

3.2 创建消息队列

3.3 消息队列发送函数

4, 消息队列的使用


一、同步互斥和通信引入


可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。
各类RTOS都会涉及这些概念:队列、事件、信号量、互斥量等。我们先站在更高角度来讲解这些概念, 以便大家理解。

1, 同步互斥概念

一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?


再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。
有时候看代码更容易理解,伪代码如下:

假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快地上厕所了。
在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。
同一时间只能有一个人使用的资源,被称为临界资源

二、引入IPC(进程间通信)好处

其实这一个节标题的内容很少, 但是我觉得需要大家知道引入IPC机制的好处, 在裸机编程中, 

请看上面框住的那个函数, 再裸机中, 当flg没有为真的时候do_thing_1函数就不会执行, 但是CPU还是会去判断, 一直轮询的问flg成立没。这不就是一种CPU资源的浪费吗, 要是可以通知就好了, 函数给CPU说, 我准备好了, 来执行我吧, 别慌, 操作系统就提供了这样一个功能, 专业的说法叫做当任务不能运行的时候, 不会占用CPU的资源, 而是挂起(阻塞)。

IPC机制的两个主要作用: 使得任务的执行可以互斥、同步、通信和节约CPU资源, 任务不需要运行的时候, 不会无效的占用CPU资源。

三、多任务如何编程

多任务编程中创建的就是每一个任务使用一个死循环, 周期性的执行当然上面的程序在优先级不同的情况下是不太对的, 因为低优先级的任务没有机会运行。

一最为常见的是采用事件驱动型编程, 伪代码如下

当任务由于信号量, 消息队列, 事件组等没有资源可用的时候, 会将自己从就绪链表移除, 挂载到挂起链表中(阻塞链表), 从而不占用CPU资源。

四、消息队列

1, 消息队列的概念和理解

首先, 要先明确一件事, 就是不同任务如何实现通信, 任务运行在OS上吗所以不同任务的相同点就是OS, 任务之间通信就是使用的OS提供的一些东西, 本质是在用户态调用了操作系统API, 在内存的某一个位置创建了内存, 有存储空间在加上不同机制的一些控制信息, 这样不同的任务就可以顺利、有序的IPC了。

消息队列可以用于"线程到线程"、"线程到中断"、"中断到线程"直接传输信息, 当然在中断中不要干阻塞了。

消息队列是一种常用的线程间通讯方式,用来传输数据。
可以应用在多种场合:线程间的消息交换、中断服务程序给线程发送数据。
消息队列的简化操如入下图所示,从此图可知:
 消息队列可以包含若干个消息
 每个消息可以容纳的数据大小是一样的
 创建消息队列时就要指定长度(消息个数)、消息的数据大小
 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
 也可以把紧急的数据写到消息队列的头部

消息队列对应的实际数据结构: 链式队列

2, 消息队列实现的原理

3, 消息队列源码分析

3.1 消息队列句柄

3.2 创建消息队列

解下来我们分析一下源码是如何实现的

字节对齐操作

把低三位清0

源码如下: 

#ifdef RT_USING_HEAP
/**
 * This function will create a message queue object from system resource
 *
 * @param name the name of message queue
 * @param msg_size the size of message
 * @param max_msgs the maximum number of message in queue
 * @param flag the flag of message queue
 *
 * @return the created message queue, RT_NULL on error happen
 */
rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag)
{
    struct rt_messagequeue *mq;				//定义一个消息队列控制块
    struct rt_mq_message *head;				//定义一个结构体, 里面是一个同类型指针
    register rt_base_t temp;				//定义一个long类型的中间变量

    RT_DEBUG_NOT_IN_INTERRUPT;  			//中断的一些操作

    /* 内核对象分配 */
    mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);  
    if (mq == RT_NULL)
        return mq;

    /* set parent */
    mq->parent.parent.flag = flag;

    /* 初始化内核对象, 其实就是将next指针和pre指针指向自己 */
    rt_ipc_object_init(&(mq->parent));

    /* initialize message queue */

    /* get correct message size */
    mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);    //四字节对齐
    mq->max_msgs = max_msgs;

    /* 分配指针域和数据域的空间, 这些空间都是系统管理的内存 */
    mq->msg_pool = RT_KERNEL_MALLOC((mq->msg_size + sizeof(struct rt_mq_message)) * mq->max_msgs);
    if (mq->msg_pool == RT_NULL)
    {
        rt_object_delete(&(mq->parent.parent));

        return RT_NULL;
    }

    /* initialize message list */
    mq->msg_queue_head = RT_NULL;    //初始化消息队列指针的指向, 指向(void *)0
    mq->msg_queue_tail = RT_NULL;

    /* initialize message empty list */
    mq->msg_queue_free = RT_NULL;


	/* 初始化head tail free 指针的指向 */
    for (temp = 0; temp < mq->max_msgs; temp ++)
    {
        head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
                                        temp * (mq->msg_size + sizeof(struct rt_mq_message)));
        head->next = (struct rt_mq_message *)mq->msg_queue_free;
        mq->msg_queue_free = head;
    }

    /* the initial entry is zero */
    mq->entry = 0;

    /* initialize an additional list of sender suspend thread */
    rt_list_init(&(mq->suspend_sender_thread));

    return mq;
}

3.3 消息队列发送函数

函数原型

/**
 * This function will send a message to message queue object. If the message queue is full,
 * current thread will be suspended until timeout.
 *
 * @param mq the message queue object
 * @param buffer the message
 * @param size the size of buffer
 * @param timeout the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mq_send_wait(rt_mq_t      mq,                   //要往哪一个消息队列中发送消息,句柄
                         const void *buffer,				//需要发送的内容的地址
                         rt_size_t   size,					//消息的大小, 注意不要超过创建时候的消息块的大小
                         rt_int32_t  timeout)				//阻塞等待时间
{
    register rt_ubase_t temp;			//unsigned long类型的变量
    struct rt_mq_message *msg;			//链表指针
    rt_uint32_t tick_delta;				//unsigned int类型变量
    struct rt_thread *thread;			//任务控制块指针



    /* parameter check */
    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 0);

    /* greater than one message size, 参数检查 */
    if (size > mq->msg_size)
        return -RT_ERROR;

    /* initialize delta tick, 用于记录超时阻塞时间, 每一个任务控制块中都会有一个软件定时器的对象 */
    tick_delta = 0;
    /* get current thread,获取当前线程控制块, 这里的控制块类似Linux中的task_struct */
    thread = rt_thread_self();

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();   //原子操作

    /* get a free list, there must be an empty item */
    msg = (struct rt_mq_message *)mq->msg_queue_free;      //msg_queue_free指针指向队列中空闲位置的消息块
    /* for non-blocking call */
    if (msg == RT_NULL && timeout == 0)
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        return -RT_EFULL;     					//错误码
    }


    /* message queue is full */
    while ((msg = mq->msg_queue_free) == RT_NULL)
    {
        /* reset error number in thread */
        thread->error = RT_EOK;

        /* no waiting, return timeout */
        if (timeout == 0)
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(temp);

            return -RT_EFULL;
        }

        RT_DEBUG_IN_THREAD_CONTEXT;
        /* suspend current thread */
        rt_ipc_list_suspend(&(mq->suspend_sender_thread),     //suspend list item, 挂载到消息队列的挂起链表上
                            thread,
                            mq->parent.parent.flag);

        /* has waiting time, start thread timer 超时时间不为0的情况 */
        if (timeout > 0)
        {
            /* get the start tick of timer 获取systick此时的计数值 */
            tick_delta = rt_tick_get();

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mq_send_wait: start timer of thread:%s\n",
                                        thread->name));

            /* reset the timeout of thread timer and start it */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);
            rt_timer_start(&(thread->thread_timer));	//计时相关的操作都是使用线程控制块里面定时器对象, 插到软件定时器链表里面去
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        /* re-schedule */
        rt_schedule();

        /* resume from suspend state */
        if (thread->error != RT_EOK)
        {
            /* return error */
            return thread->error;
        }

        /* disable interrupt */
        temp = rt_hw_interrupt_disable();

        /* if it's not waiting forever and then re-calculate timeout tick */
        if (timeout > 0)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }

    /* move free list pointer */
    mq->msg_queue_free = msg->next;

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    /* the msg is the new tailer of list, the next shall be NULL */
    msg->next = RT_NULL;
    /* copy buffer */
    rt_memcpy(msg + 1, buffer, size);

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();
    /* link msg to message queue */
    if (mq->msg_queue_tail != RT_NULL)
    {
        /* if the tail exists, */
        ((struct rt_mq_message *)mq->msg_queue_tail)->next = msg;
    }

    /* set new tail */
    mq->msg_queue_tail = msg;
    /* if the head is empty, set head */
    if (mq->msg_queue_head == RT_NULL)
        mq->msg_queue_head = msg;

    if(mq->entry < RT_MQ_ENTRY_MAX)
    {
        /* increase message entry */
        mq->entry ++;
    }
    else
    {
        rt_hw_interrupt_enable(temp); /* enable interrupt */
        return -RT_EFULL; /* value overflowed */
    }

    /* resume suspended thread */
    if (!rt_list_isempty(&mq->parent.suspend_thread))
    {
        rt_ipc_list_resume(&(mq->parent.suspend_thread));

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        rt_schedule();

        return RT_EOK;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    return RT_EOK;
}
RTM_EXPORT(rt_mq_send_wait)

4, 消息队列的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@daiwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值