临界区访问
为什么从临界区开始分析呢?因为在RT-Thread的很多代码,比如线程调度、互斥锁、信号量等等,都会使用到临界区,所以要先掌握临界区的实现的原理,再回过头看线程调度、互斥锁这些模块才能更加得心应手。这里提到的临界区资源主要是多线程间共同访问的资源(不可以在中断中访问)。临界区主要提供下面两个函数,本文也是主要分析这两个函数的实现原理:
rt_base_t rt_enter_critical(void);
void rt_exit_critical(void);
1 单核场景下的临界区实现原理
1.1 rt_enter_critical分析
对于多线程访问临界区就是希望把并行访问,变成串行访问。当上个线程访问完毕后,下个线程才能访问。RT-Thread的方案也是比较简单,直接在访问临界区时,直接把线程调度关闭。关闭线程调度就需要操作线程锁(线程锁是个全局变量,访问它也是访问临界区)。
rt_enter_critical和rt_exit_critical函数支持嵌套的,并且必须要成对出现才行。在单核场景下rt_enter_critical的实现相对比较简单:
rt_base_t rt_enter_critical(void)
{
rt_base_t level;
rt_base_t critical_level;
/* disable interrupt */
level = rt_hw_interrupt_disable();
/*
* the maximal number of nest is RT_UINT16_MAX, which is big
* enough and does not check here
*/
rt_scheduler_lock_nest ++;
critical_level = rt_scheduler_lock_nest;
/* enable interrupt */
rt_hw_interrupt_enable(level);
return critical_level;
}
-
level = rt_hw_interrupt_disable();关闭中断,不响应中断 -
rt_scheduler_lock_nest ++;,关闭线程调度。临界区资源一般是多线程之间的资源 -
rt_hw_interrupt_enable(level);打开中断
这里关闭中断的原因其实就是为了执行rt_scheduler_lock_nest ++;,因为线程的调度是基于sys tick中断触发的。关闭中断后,执行rt_scheduler_lock_nest ++;,这是内核将不在调度线程。线程的调度是当rt_scheduler_lock_nest 等于0时,才会执行调度的。 因此在单核硬件系统中,中断中不可以访问临界资源。因为只有一个核,所以无法在访问临界资源时,关闭中断,会影响系统的实时性。
void rt_schedule(void)
{
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* using local variable to avoid unecessary function call */
struct rt_thread *curr_thread = rt_thread_self();
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* check the scheduler is enabled or not */
if (rt_scheduler_lock_nest == 0)
{
.... // 选取优先级最高的task进行调度。该部分代码在此省略,在分析调度时再做详细分析
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
__exit:
return;
}
1.2 rt_exit_critical分析
rt_exit_critical的代码逻辑实际上和rt_enter_critical的逻辑是相反的,rt_enter_critical是为了能够安全访问临界区,而禁止线程调度。所以rt_exit_critical则是打开线程调度锁,判断是否能够进行任务调度。打开线程调度锁则是执行rt_scheduler_lock_nest--操作,当rt_scheduler_lock_nest等于0时,此时可以进行任务调度。
void rt_exit_critical(void)
{
rt_base_t level;
/* disable interrupt */
level = rt_hw_interrupt_disable();
rt_scheduler_lock_nest --;
if (rt_scheduler_lock_nest <= 0)
{
rt_scheduler_lock_nest = 0;
/* enable interrupt */
rt_hw_interrupt_enable(level);
if (rt_current_thread)
{
/* if scheduler is started, do a schedule */
rt_schedule();
}
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
2 多核场景下的临界区实现原理
其实调度锁是个全局变量,某种程度上就是临界区资源。对于临界区的访问(在不使用锁的前提下)就是关闭线程调度,保证临界区访问的串行化。所以无论多核还是单核场景,关闭线程调度,都是需要关闭中断后才能操作调度锁。
多核场景下临界区的实现比单核场景下的稍微复杂些。单核场景下临界区不能在中断上下文中访问,而多核场景下临界区可能在进程(线程)上下文或者中断上下文中访问。那么可能存在下面几个问题:
- 在物理核0的线程A中正在访问临界区资源,然后线程A的时间片使用完,此时选取优先级最高的线程B执行,线程B也有访问临界区的资源,此时就可能导致有两个都进入了临界区。
- 在物理核0的线程A和物理核1的线程B都需要访问临界区,此时也可能出现两个线程同时进入临界区。
- 在物理核0的线程A正在访问临界区资源,此时系统接收到一个物理中断,系统保存线程A的上下文,并开始相应中断,中断处理函数也需要访问临界区,此时也出现了临界区的并发访问。
当同一个物理核的多个线程或者中断例程想访问临界区,可以按照串行访问的方式,如何串行访问呢?其实和单核场景类似:(1)关闭本核的中断,(2)关闭本核的任务调度。而多个物理核访问临界区则无法通过当前手段解决。 同样的,中断中不可以访问临界区。 后续会介绍自旋锁的实现机制,在多核场景下也是通过关中断、禁止抢占来实现临界区的访问。
rt_base_t rt_enter_critical(void)
{
rt_base_t critical_level;
struct rt_thread *current_thread;
rt_base_t level;
struct rt_cpu *pcpu;
/* disable interrupt */
level = rt_hw_local_irq_disable();
pcpu = rt_cpu_self();
current_thread = pcpu->current_thread;
if (!current_thread)
{
FREE_THREAD_SELF(level);
/* scheduler unavailable */
return -RT_EINVAL;
}
/* critical for local cpu */
RT_SCHED_CTX(current_thread).critical_lock_nest++;
critical_level = RT_SCHED_CTX(current_thread).critical_lock_nest;
FREE_THREAD_SELF(level);
return critical_level;
}
-
rt_hw_local_irq_disable();禁用本核的中断 -
critical_lock_nest++;禁止本核的任务调度 -
FREE_THREAD_SELF(level);打开本核中断
而rt_exit_critical的逻辑也是和rt_enter_critical恰恰相反:
void rt_exit_critical(void)
{
struct rt_thread *current_thread;
rt_bool_t need_resched;
rt_base_t level;
struct rt_cpu *pcpu;
/* disable interrupt */
level = rt_hw_local_irq_disable();
pcpu = rt_cpu_self();
current_thread = pcpu->current_thread;
if (!current_thread)
{
FREE_THREAD_SELF(level);
return;
}
/* the necessary memory barrier is done on irq_(dis|en)able */
RT_SCHED_CTX(current_thread).critical_lock_nest--;
/* may need a rescheduling */
if (RT_SCHED_CTX(current_thread).critical_lock_nest == 0)
{
/* is there any scheduling request unfinished? */
need_resched = IS_CRITICAL_SWITCH_PEND(pcpu, current_thread);
CLR_CRITICAL_SWITCH_FLAG(pcpu, current_thread);
FREE_THREAD_SELF(level);
if (need_resched)
rt_schedule();
}
else
{
/* each exit_critical is strictly corresponding to an enter_critical */
RT_ASSERT(RT_SCHED_CTX(current_thread).critical_lock_nest > 0);
FREE_THREAD_SELF(level);
}
}
-
RT_SCHED_CTX(current_thread).critical_lock_nest--;当critical_lock_nest等于0才启用任务调度 -
FREE_THREAD_SELF(level); 打开本核中断

浙公网安备 33010602011771号