[] TencentOS tiny depth source code analysis (6) - mutex

Mutex

Also called a mutex mutex mutex is a special kind of signals, semaphores, and it is different, it has 互斥锁所有权、递归访问以及优先级继承other characteristics, the critical resources for the operating system often 独占式treated. Mutex at any time of only two states, 开锁或闭锁when the mutex is held by the task, the mutex in the locked state, when the task releases the mutex, the mutex is unlocked.

  • A task holds a mutex says it owns the mutex, the task can only release the mutex, while other tasks will not hold the mutex, this is the mutex 所有权characteristics.
  • When the task of holding the mutex acquire the mutex again will not be suspended, but can be acquired recursively, this is the mutex 递归访问characteristics. The general characteristics of a signal amount are very different, in the signal amount, since the amount of available signal does not exist, recursion occurs when the task acquires the semaphore eventually pending tasks 死锁.
  • Mutex also has a 优先级继承mechanism that can be priority of the task 临时提升to get the mutex priority of the task 相同, as far as possible 降低the harm of priority inversion.

In practical applications, if you want to achieve synchronization, you can use semaphores, synchronization between tasks and tasks Although a mutex can also be used, but more mutex is exclusive access to critical resources.

When using mutex protection of critical resources, tasks must obtain to obtain ownership of the mutex to access the resource, when the task using the resource, you must release the mutex so other tasks can access the resource (using a semaphore to protect critical the priority inversion may occur when resources, and 危害is not controllable).

Priority inversion

Is simply a high-priority task is waiting for a low-priority task is finished, it has violated the operating system is designed (preemptive scheduling).

Why it happened priority inversion?

When the system is in a critical resource protection by a mutex, mutex when tasks need to access the resource, if the resource is being a 低优先级use task, this time mutex in the locked state; if this time a 高优先级task want to access the resource, then the high-priority task because not obtain exclusive lock into the blocking state, this time to form a high-priority task is waiting for low-priority task to run (wait for it to release the mutex).

Priority inversion is generated 危害, the design of the system at the beginning of time, it has been designated a priority task, the higher the more important task priority, but the priority inversion occurs, the high-priority task is waiting for low-priority task , which is likely to make a high-priority task of the lack of effective treatment, severe cases may cause the system to crash.

Hazard priority inversion is not controllable, because of the low priority task is likely to be other system 中间优先级tasks (low priority tasks between the priority and high-priority task) to seize, which may lead to high priority tasks We will wait for all medium priority task has finished running situation, a situation of high-priority task is unacceptable, and it is contrary to the principles of operating system design.

Priority Inheritance

In TencentOS tinyin order to 降低harm priority inversion resulting from the use of 优先级继承算法: temporarily raise the priority of the low priority task possession of some critical resources to make it with all the resources to wait for the task, the highest priority task priority equal, when this low-priority task is finished release the resource priority to restore the initial set value (here can be seen as a low priority task temporarily inherited the high-priority task's priority). Therefore, inheritance priority task to avoid the system resources are preempted any intermediate priority task. Priority inheritance mutex lock mechanism, which ensures high-priority tasks into the blocked state of time as short as possible, and to have emerged "priority inversion" harm 降低到最小, 但不能消除priority inversion harm.

It is worth mentioning that TencentOS tinyallows the caller to modify the priority of the task in holding mutex API interface Shihai.

Mutex data structure

Mutex control block

TencentOS tinyMutex operations is controlled by block mutex, which is a data type k_mutex_t, a plurality of control blocks mutex elements.

  • pend_objSomewhat similar to the object-oriented inheritance, some properties, there are described the type of kernel resources (e.g. mutex queue mutex, etc, and a waiting list list).
  • pend_nestingIs actually a uint8_ttype of variable, the number of recording mutex is acquired, and if pend_nesting0 indicates an unlocked state, not zero indicates a locked state, and it records the number of times the value of the mutex was obtained (have descend characteristic)
  • *ownerA task control block pointer, the current record is held mutex which task.
  • owner_orig_prioMutex variable recording the holding priority of the task, because there may be a priority inheritance mechanism of temporarily change the priority of the task. (Has priority inheritance mechanism).
  • owner_listIs a list of nodes, when a mutex is acquired task, the node is added to the task control block of task->mutex_own_listthe list indicates that the task themselves to get what mutex. When the mutex 完全release ( pend_nesting等于0), the node of the task control block from the task->mutex_own_listremoval list.
  • prio_pendingMore interesting variables: operating systems in general, are not allowed to modify the priority tasks while holding mutex lock, but TencentOS tinyis permissible, because this variable, when a task is in a mutex priority inversion I suppose because he priority inversion priority has improved, there is at this time the user attempts to change the priority of this task, but will make this task a priority after this change contrary to its priority must wait than all of his holdings when the mutex principle task is higher, in which case you need to change the priority of the requesting user to record prio_pendingwhere they hold until their release mutex after the change, which is equivalent to the task priority change postponed.

    For example: like a task priority is 10 and holds a lock, this time a priority task 6 attempts to acquire the lock, then the task priority will be promoted to 6, if the user tries at this time Changed his priority 7, you can not respond to this request immediately, we must wait for the lock to let go when they could make a real priority modification

typedef struct k_mutex_st {
    pend_obj_t      pend_obj;
    k_nesting_t     pend_nesting;
    k_task_t       *owner;
    k_prio_t        owner_orig_prio;
    k_list_t        owner_list;
} k_mutex_t;
typedef struct k_task_st {
#if TOS_CFG_MUTEX_EN > 0u
    k_list_t            mutex_own_list;     /**< 任务拥有的互斥量 */
    k_prio_t            prio_pending;       /*< 在持有互斥锁时修改任务优先级将被记录到这个变量中,在释放持有的互斥锁时再更改 */
#endif
} k_task_t;

Macro definitions associated with the mutex

In tos_config.hthe so macros can mutex isTOS_CFG_MUTEX_EN

#define TOS_CFG_MUTEX_EN            1u

Create a mutex

Each system has a corresponding mutex mutex control block mutex control block contains all the information mutex, such as its waiting list that resource type, and its value mutex then imagine, create nature mutex mutex is not that of the control block to initialize it? Obviously it is like that. Because of the mutex in a subsequent operation are controlled by the block mutex operation, if the control information block is not, then how can operate Well ~

Create function is a mutex tos_mutex_create(), passing a pointer to the control block mutex *mutexcan.

Mutex created actually call the pend_object_init()function mutex control block mutex->pend_objmember variable is initialized, it is identified as the resource type PEND_TYPE_MUTEX. Then the mutex->pend_nestingmember variable is set to 0indicate the mutex is unlocked; the mutex->ownermember variable to K_NULLindicate no matter the task mutex is held; the mutex->owner_orig_priomember variable set to default values K_TASK_PRIO_INVALID, after all, no matter the task holding mutex lock, and without mutex record holders priority tasks. The final call tos_list_init()function will hold a list of nodes task mutex mutex owner_list.

__API__ k_err_t tos_mutex_create(k_mutex_t *mutex)
{
    TOS_PTR_SANITY_CHECK(mutex);

    pend_object_init(&mutex->pend_obj, PEND_TYPE_MUTEX);
    mutex->pend_nesting     = (k_nesting_t)0u;
    mutex->owner            = K_NULL;
    mutex->owner_orig_prio  = K_TASK_PRIO_INVALID;
    tos_list_init(&mutex->owner_list);

    return K_ERR_NONE;
}

Destroying a Mutex

Destructor mutex is destroyed under the direct control block mutex, after the destruction of all of the information will be cleared mutex, and can not use the mutex again, when the mutex is destroyed, which is present on a waiting list task, the system is necessary to wake them wait for these tasks, and inform the task mutex has been destroyed PEND_STATE_DESTROY. And then generates a task scheduler to switch to the highest priority task execution.

TencentOS tiny Mutex destruction process is as follows:

  1. Call the pend_is_nopending()function to determine whether there are tasks waiting mutex
  2. If so, call pend_wakeup_all()the function will wake up these tasks, and wait for the task to inform the mutex lock has been destroyed (ie set the wait state task control block member variable pend_stateis PEND_STATE_DESTROY).
  3. Call the pend_object_deinit()function block content mutex control clearance, the most important is the control block resource type is set PEND_TYPE_NONE, so that the child can not use the mutex lock.
  4. The mutex->pend_nestingrecovery member variables to their default values 0.
  5. If you have time to delete the task holds the mutex, then call the mutex_old_owner_release()function to release the mutex.
  6. Task schedulingknl_sched()

Note: If the mutex control RAM block is made 编译器静态分配of, so even destroyed mutex, this is no way to release the memory. Of course, you can also use dynamic memory control block for the mutex allocate memory, but after the destruction freed want this memory to avoid memory leaks.

__API__ k_err_t tos_mutex_destroy(k_mutex_t *mutex)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(mutex);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (!pend_is_nopending(&mutex->pend_obj)) {
        pend_wakeup_all(&mutex->pend_obj, PEND_STATE_DESTROY);
    }

    pend_object_deinit(&mutex->pend_obj);
    mutex->pend_nesting = (k_nesting_t)0u;

    if (mutex->owner) {
        mutex_old_owner_release(mutex);
    }

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

Acquire the mutex

tos_mutex_pend_timed()Function is used to acquire the mutex mutex is like a token of critical resources, the task can only get access to critical resources when the mutex. If and only if the mutex lock in an unlocked state, in order to acquire the mutex task successfully, when a task holds an exclusive lock when other tasks you can not get the mutex, need to wait until the mutex is held after the release of the task, other tasks in order to succeed, a task acquisition function to obtain ownership of the mutex by mutex, task ownership of the mutex is exclusive, any time a mutex can only be held by a task If the mutex is unlocked, it acquires the mutex task will successfully obtained the mutex, and has the right to use a mutex; task if the mutex in the locked state, will acquire the mutex can not get mutex, the task may be blocked, or it may return immediately, blocking time timeoutis specified by the user, can not get to the mutex at a specified time, it will send a timeout, wait for the task will automatically revert to the ready state. Before the task is blocked, priority inheritance will be high if the current task priority than holding mutex priority task, it will temporarily elevate the priority task of holding a mutex.

TencentOS tinyAPI provides two sets of interfaces used to obtain a mutex, respectively, tos_mutex_pend_timed()and tos_mutex_pend()the main difference is the different parameters: Optional blocking and permanent blocking mutex lock is acquired, the process of actually acquired are the same. Acquire the mutex is as follows:

  1. First detected incoming parameters are correct, will not only check information mutex control block here, but also call TOS_IN_IRQ_CHECK()to check if the context is in outage because the mutex operations are not allowed in the interrupt.
  2. Analyzing mutex control block mutex->pend_nestingmember variable is 0, 0 indicates that the mutex is unlocked, the call mutex_fresh_owner_mark()to save the information acquired mutex function to the task control block mutex, as mutex->pend_nestingthe value of a member variable becomes 1represents the mutex in the locked state, other tasks can not be obtained, mutex->ownerthe member variable points to the current acquire the mutex task control block, mutex->owner_orig_priomember variables is to record the current priority of the task, the end-use tos_list_add()function blocks mutex control mutex->owner_listnode mount the task control block of task->mutex_own_listthe list, and return the task to succeed K_ERR_NONE.
  3. If the mutex control block mutex->pend_nestingmember variable is not 0, then the mutex in the locked state, due to the recursive mutex has access characteristics, it will determine what is not already own the mutex task to acquire the mutex again lock ( knl_is_self()), because this is allowed to determine what the mutex->pend_nestingvalue of a member variable of whether (k_nesting_t)-1, and if it means that recursive reached the maximum number of visits, mutex has overflowed, and returns an error code K_ERR_MUTEX_NESTING_OVERFLOW. Otherwise it will be mutex->pend_nestingincremented by one member variables, return K_ERR_MUTEX_NESTINGrepresents a recursive for success.
  4. If the mutex in the locked state, and the current task does not own the mutex, blocking a look at a user-specified time timeoutwhether or not to block TOS_TIME_NOWAIT, if not blocked directly returns K_ERR_PEND_NOWAITan error code.
  5. If the scheduler is locked knl_is_sched_locked(), you can not wait for the operation and return an error code K_ERR_PEND_SCHED_LOCKED, after all, need to switch tasks, the scheduler is locked you can not switch tasks.
  6. Most most important feature here, before blocking the current task, you need to determine what the current task and holds task priority size mutex case, if the current task priority than holding mutex lock on task priority, the need for priority inheritance, temporarily holding the mutex task priority boost to the current priority, by tos_task_prio_change()change priority function.
  7. Call the pend_task_block()function task blocked, this function is actually ready to be removed from the task list k_rdyq.task_list_head[task_prio], and inserted into the waiting list object->list, if the waiting time is not wait forever TOS_TIME_FOREVER, but also the task list insertion time k_tick_list, blocking time timeout, then a task scheduling knl_sched().
  8. When the program to do it pend_state2errno(), it represents 任务等获取到互斥锁, or 等待发生了超时, then call the pend_state2errno()function to get about the task of wait states, is to look at what has led to resume running task, if the task has been to acquire a mutex lock, then you need to call mutex_new_owner_mark()a function of your experiences saving acquired task information acquired mutex-related information to a task control block mutex.

Note: When acquiring mutex task from the obstruction to resume operation, not necessarily to acquire a mutex, it could be a timeout has occurred, it is necessary to determine what the state of mutex acquisition in written procedures, If the value is K_ERR_NONEand K_ERR_MUTEX_NESTINGit means to succeed!

__API__ k_err_t tos_mutex_pend_timed(k_mutex_t *mutex, k_tick_t timeout)
{
    TOS_CPU_CPSR_ALLOC();
    k_err_t err;

    TOS_PTR_SANITY_CHECK(mutex);
    TOS_IN_IRQ_CHECK();

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();
    if (mutex->pend_nesting == (k_nesting_t)0u) { // first come
        mutex_fresh_owner_mark(mutex, k_curr_task);
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    if (knl_is_self(mutex->owner)) { // come again
        if (mutex->pend_nesting == (k_nesting_t)-1) {
            TOS_CPU_INT_ENABLE();
            return K_ERR_MUTEX_NESTING_OVERFLOW;
        }
        ++mutex->pend_nesting;
        TOS_CPU_INT_ENABLE();
        return K_ERR_MUTEX_NESTING;
    }

    if (timeout == TOS_TIME_NOWAIT) { // no wait, return immediately
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_NOWAIT;
    }

    if (knl_is_sched_locked()) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_SCHED_LOCKED;
    }

    if (mutex->owner->prio > k_curr_task->prio) {
        // PRIORITY INVERSION:
        // we are declaring a mutex, which's owner has a lower(numerically bigger) priority.
        // make owner the same priority with us.
        tos_task_prio_change(mutex->owner, k_curr_task->prio);
    }

    pend_task_block(k_curr_task, &mutex->pend_obj, timeout);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    err = pend_state2errno(k_curr_task->pend_state);

    if (err == K_ERR_NONE) {
        // good, we are the owner now.
        TOS_CPU_INT_DISABLE();
        mutex_new_owner_mark(mutex, k_curr_task);
        TOS_CPU_INT_ENABLE();
    }

    return err;
}

__API__ k_err_t tos_mutex_pend(k_mutex_t *mutex)
{
    TOS_PTR_SANITY_CHECK(mutex);

    return tos_mutex_pend_timed(mutex, TOS_TIME_FOREVER);
}

mutex_fresh_owner_markAnd mutex_new_owner_mark()implementation of the function:

__STATIC_INLINE__ void mutex_fresh_owner_mark(k_mutex_t *mutex, k_task_t *task)
{
    mutex->pend_nesting     = (k_nesting_t)1u;
    mutex->owner            = task;
    mutex->owner_orig_prio  = task->prio;

    tos_list_add(&mutex->owner_list, &task->mutex_own_list);
}

__STATIC_INLINE__ void mutex_new_owner_mark(k_mutex_t *mutex, k_task_t *task)
{
    k_prio_t highest_pending_prio;

    mutex_fresh_owner_mark(mutex, task);

    // we own the mutex now, make sure our priority is higher than any one in the pend list.
    highest_pending_prio = pend_highest_prio_get(&mutex->pend_obj);
    if (task->prio > highest_pending_prio) {
        tos_task_prio_change(task, highest_pending_prio);
    }
}

Release mutex

Release the mutex is allowed to be released in the interrupt, the main reason is because there is no interruption in the context of the concept, so the interrupt context can not hold, it can not release the mutex; mutex have 所属relationships only hold each other denounced the task to lock the mutex release, and is the holder of the task.

When the task you want to access a resource, you need to acquire the mutex and resource access, when the task is finished using the resource, it is necessary to 及时release the mutex so other tasks can access critical resources. Tasks can call the tos_mutex_post()function to release the mutex when the mutex is unlocked it means I've run out of other people can acquire the mutex to access critical resources.

Use tos_mutex_post()the time function interface, only the tasks already holds ownership of mutex to release it when the task calls the tos_mutex_post()function when will release the mutex once, when a mutex is fully released is completed ( mutex->pend_nestingthe value of member variables 0) becomes unlocked state, waiting to acquire the mutex task will be awakened. If the priority inversion mechanism priority task is temporarily promoted mutex, then when the mutex lock is released, the priority tasks will revert to the original setting of priorities.

TencentOS tiny You can only make a task waiting to acquire the mutex (with the highest priority task of waiting).

In the tos_mutex_post()handler is very simple, the execution of the idea is as follows:

  1. Mutex is first incoming control block parameters related to detection, and then determine what the task is to hold a mutex to release the mutex, if a release operation is performed. If not, return an error code K_ERR_MUTEX_NOT_OWNER.
  2. The mutex->pend_nestingvalue of a member variable minus 1, then it is determined whether the value 0, if not zero then the current task holding the mutex is, there is no need for subsequent operation directly returns K_ERR_MUTEX_NESTING.
  3. If the mutex->pend_nestingmember variable is 0, it indicates that the mutex is unlocked, it is necessary to call mutex_old_owner_release()the function fully relieved mutex, member variables in this function will mutex control block (e.g., owner_list、owner、owner_orig_prioare all set to an initial value whether), in addition to the most important task is to determine what had happened priority inheritance, and if you need to restore the original task priority, otherwise invalid ignored.
  4. Call the pend_is_nopending()function to determine whether there are tasks waiting mutex, if there is no return K_ERR_NONEshows the release mutex success, because at this time there is no need to not wake task scheduling, can be returned directly.
  5. If there are tasks waiting for the mutex, direct call pend_wakeup_one()function wakes up a waiting task, the task is waiting for the task at the highest priority, because TencentOS tinythe waiting task is sorted by priority.
  6. To conduct a task scheduling knl_sched().
__API__ k_err_t tos_mutex_post(k_mutex_t *mutex)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(mutex);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&mutex->pend_obj, PEND_TYPE_MUTEX)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();
    if (!knl_is_self(mutex->owner)) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_MUTEX_NOT_OWNER;
    }

    if (mutex->pend_nesting == (k_nesting_t)0u) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_MUTEX_NESTING_OVERFLOW;
    }

    --mutex->pend_nesting;
    if (mutex->pend_nesting > (k_nesting_t)0u) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_MUTEX_NESTING;
    }

    mutex_old_owner_release(mutex);

    if (pend_is_nopending(&mutex->pend_obj)) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    pend_wakeup_one(&mutex->pend_obj, PEND_STATE_POST);
    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

Task holds a mutex release the mutex mutex_old_owner_release().

__STATIC_INLINE__ void mutex_old_owner_release(k_mutex_t *mutex)
{
    k_task_t *owner;

    owner = mutex->owner;

    tos_list_del(&mutex->owner_list);
    mutex->owner = K_NULL;

    // the right time comes! let's do it!
    if (owner->prio_pending != K_TASK_PRIO_INVALID) {
        tos_task_prio_change(owner, owner->prio_pending);
        owner->prio_pending = K_TASK_PRIO_INVALID;
    } else if (owner->prio != mutex->owner_orig_prio) {
        tos_task_prio_change(owner, mutex->owner_orig_prio);
        mutex->owner_orig_prio = K_TASK_PRIO_INVALID;
    }
}

I like to focus on it!

I welcome public attention No.

Related code can reply "19" Get back in the public number.
For more information please concern "Things IoT development," the public number!

Guess you like

Origin www.cnblogs.com/iot-dev/p/11688957.html