RT-Thread | 临界区问题及IPC机制

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!

什么是临界区?

在多线程实时系统中,多个线程操作/访问同一块区域(代码),这块代码就称为临界区

例如一项工作中的两个线程:一个线程从传感器中接收数据并且将数据写到共享内存中,同时另一个线程周期性的从共享内存中读取数据并发送去显示,下图描述了两个线程间的数据传递:

如果对共享内存的访问不是排他性的,那么各个线程间可能同时访问它,这将引起数据一致性的问题。

例如,在显示线程试图显示数据之前,接收线程还未完成数据的写入,那么显示将包含不同时间采样的数据,造成显示数据的错乱。

临界区的问题

在 RT-Thread 里面,这个临界段最常出现的就是对全局变量的操作。下面我们以一个全局变量为例演示多线程系统中的临界区问题:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define  TEST1_STACK_SIZE	 512
#define  TEST1_THREAD_PRIORITY	 25
#define  TEST1_TIMESLICE 	 2

#define  TEST2_STACK_SIZE	 512
#define  TEST2_THREAD_PRIORITY	 24
#define  TEST2_TIMESLICE  	 1

uint32_t gulTmp = 0;

/* test1线程入口函数 -------------------------------------------------------------------------*/
static void test1_thread_entry(void *parameter)
{
  uint16_t i = 0;

  rt_kprintf("gulTmp = %d\n", gulTmp);
  for (i = 0; i < 10000; i++)
  {
    gulTmp++;
  }
  rt_kprintf("gulTmp = %d\n", gulTmp);
}

/* test2线程入口函数 -------------------------------------------------------------------------*/
static void test2_thread_entry(void *parameter)
{
  rt_thread_delay(1);
  gulTmp++;
}

/* 主函数 ----------------------------------------------------------------------------------*/
int main(void)
{
    /* 定义线程句柄 */
    rt_thread_t tid;

    /* 创建动态test1线程 :优先级 25 ,时间片2个系统滴答,线程栈512字节 */
    tid = rt_thread_create("test1_thread",
              test1_thread_entry,
              RT_NULL,
              TEST1_STACK_SIZE,
              TEST1_THREAD_PRIORITY,
              TEST1_TIMESLICE);

    /* 创建成功则启动动态线程 */
    if (tid != RT_NULL)
    {
    	rt_thread_startup(tid);
    }
	
    /* 创建动态test1线程 :优先级 24 ,时间片1个系统滴答,线程栈512字节 */
    tid = rt_thread_create("test2_thread",
              test2_thread_entry,
              RT_NULL,
              TEST2_STACK_SIZE,
              TEST2_THREAD_PRIORITY,
              TEST2_TIMESLICE);
	
    /* 创建成功则启动动态线程 */
    if (tid != RT_NULL)
    {
    	rt_thread_startup(tid);
    }
	
    return 0;
}

创建两个测试线程test1、test2,这两个线程都有对全局变量gulTmp进行+1操作,编译、下载、运行得到的结果为:

我们期望gulTmp的结果为10000,而实际得到的gulTmp的值却为10001,那是因为test2线程的优先级高于test1线程,因此test2线程优先执行。

test2线程首先挂起1个时间片,test2挂起期间内核调度执行test1线程,在1个时间片之后,test2线程被唤醒,此时test2线程的优先级最高的,test2线程打断test1线程,所以最终临界区变量gulTmp的结果为10001。

从以上结果中可以看到,  当公共资源在多个线程中公用时,如果缺乏必要的保护错误,最后的输出结果可能与预期的结果完全不同。

IPC机制

为了解决这样的问题,RT-Thread引入了IPC机制  (Inter-Process Communication):

其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入/退出临界区的方法有:关闭中断调度器上锁。 我们可通过这两种简单的途径来禁止系统调度,防止线程被打断,从而保证临界区不被破坏。

1、关闭中断

线程中关闭中断保护临界区的结构如下:

void test1_thread_entry(void* parameter)
{
    rt_base_t level;

    while(1)
    {
        /* 关闭中断*/
        level = rt_hw_interrupt_disable();
        /* 以下是临界区*/
        . . . .
        /* 关闭中断*/
        rt_hw_interrupt_enable(level);
    }
}

所有线程的调度都是建立在中断的基础上的,拿 CM3 核来举例:在 cm3 处理器上,所有的调度条件满足后(不管是在任务还是在中断中)系统会触发pendsv 中断, 在 pensv 中断中去执行调度工作。所以,当我们关闭中断后,系统将不能再进行调度,线程自身也自然不会被其他线程抢占了。

2、调度器上锁

锁住调度器以保护临界区的结构如下:

void test1_thread_entry(void* parameter)
{
    . . .
    while(1)
    {
        /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
        rt_enter_critical();
        /* 以下进入临界区 */
        . . . .
        /* 调度器解锁 */
        rt_exit_critical();
    }
}

把调度器锁住也能让当前运行的任务不被换出,直到调度器解锁。但和关闭中断有一点不相同的是,对调度器上锁,系统依然能响应外部中断,中断服务例程依然有可能被运行。

所以在使用调度器上锁的方式来做任务同步时,需要考虑好, 任务访问的临界资源是否会被中断服务例程所修改,如果可能会被修改,那么将不适合采用此种方式作为同步的方法。

以上就是本次的笔记分享,如有错误欢迎指出!谢谢。欢迎收藏、转发、在看~

参考资料:

《RT-Thread编程指南》
《RT-Thread 内核实现与应用开发实战指南》
《一起来学 RT-Thread 教程连载》

猜你喜欢:

STM32的ISP下载的原理是什么呢?

STM32串口IAP分享

C语言代码优化的一些技巧(四)

【RT-Thread笔记】内核对象模型

【RT-Thread笔记】线程的基本知识

【RT-Thread笔记】IO设备模型及PIN设备

【RT-Thread笔记】PIN设备中断实验

RT-Thread线下培训实例分享:基于i.MX RT1050的云接入

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

嵌入式大杂烩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值