• 【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
    ", value);
        for (i = 0; i < 100; i++)
        {
            value++;
        }
        rt_kprintf("value = %d
    ", 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
    ", 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)。如果没有临界区保护,当第一个线程运行到加数操作之前时间片到了,则线程二开始运行,数据将会有丢失。我们看本次实验结果:

  • 相关阅读:
    什么人一亏再亏,什么人亿万富翁? —兼谈本周经济与股市
    数组排序
    倒水
    倒水
    lua string
    lua string
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/xiaomagee/p/13029782.html
Copyright © 2020-2023  润新知