Access control of critical area of rt'thread

Keywords: Programming

I. WHY

What is critical area and why do we need access control of critical area? First, let's look at wiki's definition of critical area:

Synchronizing Program design Critical section refers to a program segment that accesses shared resources (such as shared devices or shared memory), which cannot be shared by more than one at the same time thread Properties accessed. When a thread enters a critical section, other threads or processes must wait to ensure that these shared resources are XOR Use. Like printers.

2, Access control mechanism of critical area of rt'thread

2.1 global interrupt: global interrupt and preemption are prohibited

The global interrupt switch, also known as interrupt lock, is the simplest way to prevent multiple threads from accessing the critical area. That is, by turning off the interrupt, to ensure that the current thread will not be interrupted by other events (because the whole system no longer responds to those external events that can trigger thread rescheduling), that is, the current thread will not be preempted unless the thread gives up actively The function interface of processor control is as follows:

rt_base_t rt_hw_interrupt_disable(void);
void rt_hw_interrupt_enable(rt_base_t level);

Advantages: the method of using interrupt lock to operate critical area can be applied in any situation, and other several kinds of synchronization methods depend on interrupt lock. It can be said that interrupt lock is the most powerful and efficient synchronization method.

Disadvantage: during interrupt shutdown, the system will no longer respond to any interrupt, nor can it respond to external events. So interrupt lock has a huge impact on the real-time performance of the system. When it is not used properly, it will lead to no real-time performance of the system. It belongs to the most granular access control mechanism

Implementation example:

/*FILE: libcpu/arm/contex-m4/context_gcc.S*/
/*
 * rt_base_t rt_hw_interrupt_disable();
 */
.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR

/*
 * void rt_hw_interrupt_enable(rt_base_t level);
 */
.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
    MSR     PRIMASK, r0
    BX      LR

2.2 enable global interruption and prohibit preemption

In this case, turn on the global interrupt and turn off the scheduling function. Because the scheduler of the thread is disabled, even if some threads are restored to the ready state in ISR, other threads still cannot seize the running of the current process. Therefore, this mechanism can be used to prohibit the critical area synchronization among multiple threads.

Advantages: compared with 2.1, global interrupt is not prohibited, and ISR can still be processed.

Disadvantages: because preemption is prohibited, the high priority thread that is ready due to the release of ISR resources (locks, semaphores, etc.) cannot preempt the current thread, so it will still affect the real-time performance.

Code implementation:

void rt_enter_critical(void)
{
    register rt_base_t 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 ++;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}


void rt_exit_critical(void)
{
    register 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.3 enable global interrupt, enable preemption, and use exclusive lock synchronization

This mechanism will check whether the lock is valid before the critical area access. If it is valid, it will lock and start to access the critical area. After the access, it will release the lock. If it is invalid, it can block and wait for the lock to be valid. Through this mechanism, only one thread can access the critical area.

Advantage: minimal impact on ISR processing and other thread s of the system.

Disadvantages: because of the acquisition and release of locks, programming errors and deadlock are easy to occur.

Implementation code:

/**
 * This function will take a mutex, if the mutex is unavailable, the
 * thread shall wait for a specified time.
 *
 * @param mutex the mutex object
 * @param time the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* this function must not be used in interrupt even if time = 0 */
    RT_DEBUG_IN_THREAD_CONTEXT; [1]

    /* get current thread */
    thread = rt_thread_self();

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();

    /* reset thread error */
    thread->error = RT_EOK;

    if (mutex->owner == thread)
    {
        /* it's the same thread */
        mutex->hold ++;   [2]
    }
    else
    {
__again:
        /* The value of mutex is 1 in initial status. Therefore, if the
         * value is great than 0, it indicates the mutex is avaible.
         */
        if (mutex->value > 0)
        {
            /* mutex is available */
            mutex->value --;

            /* set mutex owner and original priority */
            mutex->owner             = thread;
            mutex->original_priority = thread->current_priority; [3]
            mutex->hold ++;
        }
        else
        {
            /* no waiting, return with timeout */
            if (time == 0)
            {
                /* set error as timeout */
                thread->error = -RT_ETIMEOUT;

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);

                return -RT_ETIMEOUT;
            }
            else
            {
                /* mutex is unavailable, push to suspend list */

                /* change the owner thread priority of mutex */
                if (thread->current_priority < mutex->owner->current_priority)
                {
                    /* change the owner thread priority */
                    rt_thread_control(mutex->owner,
                                      RT_THREAD_CTRL_CHANGE_PRIORITY,
                                      &thread->current_priority); [4]
                }

                /* suspend current thread */
                rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
                                    thread,
                                    mutex->parent.parent.flag);

                /* has waiting time, start thread timer */
                if (time > 0)
                {
                    /* reset the timeout of thread timer and start it */
                    rt_timer_control(&(thread->thread_timer),
                                     RT_TIMER_CTRL_SET_TIME,
                                     &time);
                    rt_timer_start(&(thread->thread_timer)); [5]
                }

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);

                /* do schedule */
                rt_schedule();     [6]

                if (thread->error != RT_EOK)
                {
                    /* interrupt by signal, try it again */
                    if (thread->error == -RT_EINTR) goto __again; [7]

                    /* return error */
                    return thread->error;    [8]
                }
                else
                {
                    /* the mutex is taken successfully. */
                    /* disable interrupt */
                    temp = rt_hw_interrupt_disable();
                }
            }
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    return RT_EOK;
}

[1] : you need to make sure that this function is called in a non interrupt context state?

[2] : use the hold count to record the number of times that the same thread takes up the lock, which facilitates the programming of different subfunctions of the same thread;

[3] [4]: in order to eliminate priority inversion, the priority of thread A waiting for mutual exclusive lock is higher than that of thread B occupying lock

 

Published 11 original articles, won praise 3, visited 836
Private letter follow

Posted by steve490 on Tue, 18 Feb 2020 00:36:47 -0800