阅读引言: 因为将来工作需要, 最近在深入学习OS的内部机制,我把我觉得重要的、核心的东西分享出来, 希望对有需要的人有所帮助, 阅读此文需要读友有RTOS基础, 以及一些操作系统的基础知识, 学习过Linux的最佳, 特别是想RT-Thread适合Linux非常像的, 代码风格、IPC机制等等。
目录
一、同步互斥和通信引入
可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。
各类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)