【STM32F4】【银杏科技ARM+FPGA】iCore3移植RT-Thread--内核之临界区保护

一、临界区的引入

  在嵌入式系统中,通过传感器采集数据进行保存,其他线程间可能同时访问它,但是如果数据还未完成写入,访问到的数据明显会出错,通过对临界区保存,则可以避免此类问题的发生。

1. 基本概念

临界资源:一次仅允许一个(或者指定数量)线程访问的共享资源,可以是一个具体的硬件设备(如打印机、传真机等),也可以是一个变量,一个缓冲区。

临界区:在RT-Thread中,访问公共资源的一段代码成为临界区,每个线程中访问(操作)连接资源的那段代码成为临界区(Critical Section),每次只允许一个线程进入临界区,为了保护线程内的资源不会被其他线程抢占。

二、临界区的问题展示

   在RT-Thread系统中,临界区往往是对全局变量的操作,以下我们展示一个对全局变量操作的多线程临界区问题(此工程名中critical_sample.c中):

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

#define  THREAD1_STACK_SIZE          512                
#define  THREAD1_THREAD_PRIORITY     25
#define  THREAD1_TIMESLICE           2

#define  THREAD2_STACK_SIZE          512
#define  THREAD2_THREAD_PRIORITY     24
#define  THREAD2_TIMESLICE           1

uint32_t value = 0;

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

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

/* thread2线程入口函数 -------------------------------------------------------------------------*/
static void thread2_entry(void *parameter)
{
    rt_thread_delay(1);
    value++;
}

int critical_sample(void)
{
    /* 定义线程句柄 */
    rt_thread_t tid;

    /* 创建动态test1线程 :优先级 25 ,时间片2个系统滴答,线程栈512字节 */
    tid = rt_thread_create("thread1",
                  thread1_entry,
                  RT_NULL,
                  THREAD1_STACK_SIZE,
                  THREAD1_THREAD_PRIORITY,
                  THREAD1_TIMESLICE);

    /* 创建成功则启动动态线程 */
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }

    /* 创建动态test1线程 :优先级 24 ,时间片1个系统滴答,线程栈512字节 */
    tid = rt_thread_create("thread2",
                  thread2_entry,
                  RT_NULL,
                  THREAD2_STACK_SIZE,
                  THREAD2_THREAD_PRIORITY,
                  THREAD2_TIMESLICE);

    /* 创建成功则启动动态线程 */
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }

    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(critical_sample, critical sample);

实验结果:

创建的两个线程都是对全局变量value进行+1操作,运行结果为:

  我们期望的value的结果是100,而实际的value是101,原因是线程thread2的优先级高于thread1, thread2线程优先执行,thread2线程首先挂起1个时间片,thread2挂起期间内核调度执行thread1线程,在1个时间片之后,thread2线程被唤醒,此时thread2线程的优先级最高的,thread2线程打断thread1线程,所以最终临界区变量value的结果为101。

三、临界区保护

     为解决此问题发生,RT-Thread通过在访问临界区的时候只允许个线程运行。通过禁止调度或者关闭中断的方法关闭系统调度,从而保护临界区。
3.1、关闭中断

     因为线程的调度是建立在中断的基础上的,所有关闭中断以后,系统将不能再进行调度,线程自身也不会再被其他线程抢占。

void thread_entry(void *parameter)
{
    rt_base_t level;
    while(1)
    {
        /*关闭中断*/
        level = rt_hw_interrupt_disable();
        /*以下是临界区*/
        ……
        /*关闭中断*/
        rt_hw_interrupt_enable(level);
    }
}

3.2、禁止调度

     禁止调度就是把调度器锁住,不让其进行线程切换,从而保证当前运行的任务不被换出,直到调度器解锁。

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

  同关闭中断不同的是,对调度器上锁,系统依然可以响应外部中断,中断服务例程依然有可能被运行。所以在使用调度器上锁的方式来做任务同步时,需要考虑任务访问的临界区资源是否会被中断服务例程所修改。

四、临界区保护实例

     我们使用中断方式进行临界区保护。

#define THREAD_PRIORITY      20
#define THREAD_STACK_SIZE    512
#define THREAD_TIMESLICE     10
/* 同时访问的全局变量 */
static rt_uint32_t cnt;
void thread_entry(void *parameter)
{
    rt_uint32_t no;
    rt_uint32_t level;
    no = (rt_uint32_t) parameter;
    while (1)
    {
        /* 关闭中断 */
        level = rt_hw_interrupt_disable();
        cnt += no;
        rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt);
                /* 恢复中断 */
                rt_hw_interrupt_enable(level);
        rt_thread_mdelay(3);
    }
}
int interrupt_sample(void)
{
    rt_thread_t thread;
    /* 创建thread1线程 */
    thread = rt_thread_create("thread1", thread_entry, (void *)10,
                              THREAD_STACK_SIZE,
                              THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
        rt_thread_startup(thread);
    /* 创建thread2线程 */
    thread = rt_thread_create("thread2", thread_entry, (void *)20,
                              THREAD_STACK_SIZE,
                              THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
        rt_thread_startup(thread);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(interrupt_sample, interrupt sample);

代码分析:设定两个线程使用了同一个入口函数,并传递了不同的参数,第一个是10,第二个是20,线程都是对全局变量cnt操作(cnt+=no)。如果没有临界区保护,当第一个线程运行到加数操作之前时间片到了,则线程二开始运行,数据将会有丢失。我们看本次实验结果:

 

posted @ 2020-06-02 10:38  XiaomaGee  阅读(392)  评论(0编辑  收藏  举报