The secret of high salary, easy to get started with AliOS Things operating system: semaphore

Semaphore is a very common synchronization mechanism on operating systems. This article learns the semaphore mechanism by analyzing the core source code of AliOS Things.

The source path of the semaphore in AliOS Things is as follows

Semaphore source code location: core/rhino/k_sem.c

Semaphore header file location: core/rhino/include/k_sem.h

 

1. The semaphore structure ksem_t

The k_sem.h header file defines the semaphore structure ksem_t. The semaphore-related functions are based on this structure, so we first analyze the structure, and its specific definition is as follows:

 

typedef struct sem_s {

  blk_obj_t   blk_obj;

  sem_count_t count;

  sem_count_t peak_count;

#if (RHINO_CONFIG_KOBJ_LIST > 0)

  klist_t     sem_item;   /**< kobj list for statistics */

#endif

  uint8_t     mm_alloc_flag;  /**< buffer from internal malloc or caller input */

} ksem_t;

 

Member description:

(1) blk_obj This is a basic structure of the kernel, used to manage the basic information of the kernel structure. From an object-oriented perspective, it is equivalent to the parent class of ksem_t. Its main domains are: blk_list blocking queue, name object name, blk_policy blocking queue waiting strategy (mainly priority (PRI) and first in first out (FIO)), obj_type structure type;

(2) count records the number of signals;

(3) peak_count is a statistic value that records the highest number of signals of the semaphore during system operation.

(4) sem_item is a linked list node, used to insert the semaphore into the global linked list, mainly for debugging and statistics.

(5) mm_alloc_flag is a memory flag used to indicate whether the memory of the structure is allocated statically or dynamically.

 

2. Create the semaphore function sem_create

The core function to create a semaphore is sem_create, and its prototype is as follows:

static kstat_t sem_create(ksem_t *sem, const name_t *name, sem_count_t count,

                         uint8_t mm_alloc_flag);

Parameter meaning:

sem: semaphore structure pointer;

name: The name of the semaphore. Users can specify a name for their semaphore to facilitate debugging and distinction;

count: the number of initial signals;

mm_alloc_flag: memory type, that is, whether the memory pointed to by sem is allocated statically or dynamically. If it is dynamically allocated, the structure memory pointed to by sem needs to be released when the semaphore is deleted.

 

In this function:

(1) First use the statement CPSR_ALLOC() to define an internal variable, which is used by the critical section statement RHINO_CRITICAL_ENTER()/RHINO_CRITICAL_EXIT(). On a single core, the critical section uses off interrupt protection, so CPSR_ALLOC() actually defines a variable that saves the interrupt state. RHINO_CRITICAL_ENTER() will read the current interrupt status and save it, and then close the interrupt. RHINO_CRITICAL_EXIT() will restore the interrupted state. So enter/exit the critical section as follows:

CPSR_ALLOC();

RHINO_CRITICAL_ENTER(); //Enter the critical section

…… //Critical section

RHINO_CRITICAL_EXIT(); //Exit the critical section

 

In the sem_create function, it is used to protect access to the global linked list.

klist_insert(&(g_kobj_list.sem_head), &sem->sem_item); //Insert the semaphore structure into the global linked list

(2) The NULL_PARA_CHK() macro is used to check for non-null pointers. If the sem or name pointer passed in is found to be NULL, it will return directly.

(3) Next, the semaphore structure will be initialized. The blocking strategy is initialized to BLK_POLICY_PRI, which means that when multiple tasks are blocked on the semaphore, the high-priority task first obtains the semaphore. Another strategy is BLK_POLICY_FIFO, that is, the first blocked task gets the semaphore first. The initialized type of ksem_t is RHINO_SEM_OBJ_TYPE.

 

The functions krhino_sem_create() and krhino_sem_dyn_create() are external interfaces for creating semaphores. The difference between the two is that the former is static creation (K_OBJ_STATIC_ALLOC), that is, the memory of the ksem_t structure is imported from the outside. The latter is dynamically created (K_OBJ_DYN_ALLOC). This function will call krhino_mm_alloc to dynamically allocate the memory of the ksem_t structure, and pass the created structure object to the caller through the input parameter sem, so the type of the input parameter sem is ksem_t **.

 

Analyzing the source code of the semaphore creation function, we can get the first characteristic of the semaphore: the number of initial semaphores can be specified when creating the semaphore.

 

3. Request semaphore function krhino_sem_take

The function krhino_sem_take is used to request a semaphore. The function prototype is:

kstat_t krhino_sem_take(ksem_t *sem, tick_t ticks);

Parameter Description:

(1) sem points to a pointer to the semaphore structure;

(2) ticks blocking time. If there is no semaphore, the task will block ticks system clocks at most. Two special values ​​are: (a) RHINO_NO_WAIT, if there is no semaphore, it will return directly; (2) RHINO_WAIT_FOREVER, which will block the task until the semaphore is obtained.

 

In this function:

(1) NULL_PARA_CHK(sem); Check whether the input parameter sem is NULL, if it is NULL, exit the function directly;

(2) RHINO_CRITICAL_ENTER(); used to enter the critical section, because multiple tasks may access the sem structure at the same time, so critical section protection is required;

(3) Call cpu_cur_get() to get the current core number, which is to support multi-core architecture. On single-core processors, this function returns 0;

(4) TASK_CANCEL_CHK(sem) is used to check whether the current task has been terminated. INTRPT_NESTED_LEVEL_CHK() is used to check whether it is in the interrupt context. The interrupt processing function is not allowed to be blocked, so krhino_sem_take() cannot be called;

(5) The conditional judgment statement if (sem->blk_obj.obj_type != RHINO_SEM_OBJ_TYPE) is used to check whether the sem type is RHINO_SEM_OBJ_TYPE. This can avoid misuse after the semaphore structure is deleted;

(6) Now formally enter the logic of applying for semaphore. If sem->count is greater than 0, it means that there is a semaphore, then sem->count is reduced by 1, and the application is successful. Of course, use the statement RHINO_CRITICAL_EXIT(); to exit the critical section before returning;

(7) If there is currently no semaphore and the waiting time is RHINO_NO_WAIT, then return directly and the application fails;

(8) The remaining situation is: there is currently no semaphore, and the caller wants to wait for a period of time until it times out or obtains the semaphore. The conditional judgment g_sched_lock[cur_cpu_num]> 0u is used to check whether scheduling is currently allowed. Because the subsequent code will suspend the current task and schedule to other tasks. Therefore, if g_sched_lock[cur_cpu_num]> 0u (scheduling is not allowed), then the following code cannot be executed, and the critical section is directly exited and returned;

(9) The sentence pend_to_blk_obj will set the current task to the non-ready state;

(10) The statement RHINO_CRITICAL_EXIT_SCHED(); will exit the critical section and trigger scheduling. Here will switch to other tasks, until the timeout or a task releases the semaphore and wakes up the current task;

(11) The sentence pend_state_end_proc is used to do post-wakeup processing, mainly to determine the reason for being awakened: the semaphore is obtained or the timeout expires or the semaphore is deleted. The place where krhino_sem_take is called can determine subsequent operations based on the return value;

 

4. Release the semaphore function sem_give

This interface is also called the semaphore. The function prototype is as follows:

static kstat_t sem_give(ksem_t *sem, uint8_t opt_wake_all)

Parameter Description:

(1) sem points to a pointer to the semaphore structure;

(2) opt_wake_all wake up a waiting task (WAKE_ONE_SEM) or wake up all tasks (WAKE_ALL_SEM).

 

In this function:

(1) RHINO_CRITICAL_ENTER(); Used to enter the critical section, because multiple tasks may access the sem structure at the same time, so critical section protection is required.

(2) The conditional judgment statement if (sem->blk_obj.obj_type != RHINO_SEM_OBJ_TYPE) is used to check whether the sem type is RHINO_SEM_OBJ_TYPE. This can avoid misuse after the semaphore structure is deleted.

(3) The function cpu_cur_get() is used to obtain the current core number, which is to support multi-core architecture. On single-core processors, this function returns 0.

(4) The conditional statement if (is_klist_empty(blk_list_head)) is used to determine whether the blocking queue is empty, that is, no task is currently blocked on the semaphore. In this case, the processing is relatively simple, just add 1 to the number of semaphores.

Here is an error check first. If sem->count == (sem_count_t)-1, there is a problem with the system. If there is no problem, increase the number of semaphores by 1:

sem->count++;

If sem->count is greater than sem->peak_count, sem->peak_count will be updated to record the highest number of semaphores in history.

(5) If there is a task waiting for the semaphore, there is no need to add 1 to sem->count, just wake up the task directly and consume the semaphore. There are two situations: if opt_wake_all is not 0 (WAKE_ALL_SEM), then call pend_task_wakeup to wake up all tasks. Otherwise, only one task is awakened.

 

The external interface functions krhino_sem_give/krhino_sem_give_all all call sem_give to release the semaphore. The difference between the two is that the former only wakes up one blocked task, and the latter wakes up all blocked tasks.

 

Analyzing the source code of the semaphore release function, we can get two other characteristics of the semaphore:

The second feature: The semaphore request and release do not need to appear in pairs, and the semaphore can be released without obtaining the semaphore. So the interrupt processing function can release the semaphore, which is often used in the synchronization between the interrupt and the task.

The third feature: It can wake up all blocked tasks on the semaphore.

 

5. Delete the semaphore function krhino_sem_del/krhino_sem_dyn_del

krhino_sem_dyn_del is used to delete the semaphore created by krhino_sem_dyn_create. krhino_sem_del is used to delete the semaphore created by krhino_sem_create. These two sets of functions must be used together, otherwise serious problems will occur.

Compared with krhino_sem_del, krhino_sem_dyn_del has one more step to release the semaphore structure. Here we analyze the krhino_sem_dyn_del function:

(1) The function entry is similar to the previous function processing, mainly to do some correctness checks;

(2) sem->blk_obj.obj_type = RHINO_OBJ_TYPE_NONE sets the type of structure to NONE. The advantage of this statement is that if the semaphore is misused after it is released, it can be detected.

(3) The loop statement calls the pend_task_rm function to wake up all tasks blocked on the semaphore. Without this step, once the semaphore is deleted, the tasks waiting for the semaphore will never be awakened.

(4) The statement klist_rm(&sem->sem_item); is used to delete the semaphore structure from the global g_kobj_list.sem_head linked list;

(5) Finally, exit the critical section and call krhino_mm_free to release the memory space of the semaphore structure.

 

Analyzing the source code of the semaphore delete function, we can see a note about using semaphores:

If the task calling krhino_sem_take() is blocked, the task will be awakened when the semaphore is deleted, so when krhino_sem_take() returns, it does not mean that the semaphore must be obtained. It should be judged whether the return value is RHINO_SUCCESS.

 

6. Example

In this example, task 2 sends a semaphore every 1 second. Task 1 requests the semaphore, and prints one line after receiving the semaphore.

/* 定义信号量结构体*/

ksem_t sem_test;  



/* 定义任务相关资源*/

ktask_t     test_task1_tcb;

cpu_stack_t test_task1_stack[TEST_TASK_STACKSIZE];

ktask_t     test_task2_tcb;

cpu_stack_t test_task2_stack[TEST_TASK_STACKSIZE];



/* 前向声明任务入口函数*/

static void test_task1(void *arg);

static void test_task2(void *arg);



/* 主入口 */

int application_start(int argc, char *argv[])

{

   /* 静态创建信号量,初始个数为0 */

   krhino_sem_create(&sem_test, "sem_test", 0);

   /* 创建两个测试任务 */

   krhino_task_create(&test_task1_tcb, TEST_TASK1_NAME, 0, TEST_TASK1_PRI, 50,

                      test_task1_stack, TEST_TASK_STACKSIZE, test_task1, 0);

   krhino_task_create(&test_task2_tcb, TEST_TASK2_NAME, 0, TEST_TASK2_PRI, 50,

                      test_task2_stack, TEST_TASK_STACKSIZE, test_task2, 0);



}



/* 任务1的入口 */

static void test_task1_entry(void *arg)

{

   kstat_t stat;

   while (1) {

       /* 请求信号量*/

       stat = krhino_sem_take(&sem_test, RHINO_WAIT_FOREVER);

       if (stat == RHINO_SUCCESS) {

           printf("revc sem\r\n");

       }

   }

}



/* 任务2的入口 */

static void test_task2(void *arg) {

   while(1) {

       /* 睡眠1s */

       aos_msleep(1000);

       /* 释放信号量*/

       krhino_sem_give(&sem_test);

   }

}

 

7. Summary

This article analyzes the source code of AliOS Things semaphore, and summarizes three characteristics and one use note. The two interfaces krhino_sem_count_get and krhino_sem_count_set are left unanalyzed. With the above analysis basis, readers can try to analyze it by themselves. I also hope that you can take this article as an opportunity to start reading the AliOS Things kernel source code, and welcome contributions.

 

8. Developer technical support

If you need more technical support, you can join the DingTalk developer group or follow the WeChat public account

For more technology and solution introduction, please visit the Aliyun AIoT homepage https://iot.aliyun.com/

Guess you like

Origin blog.csdn.net/HaaSTech/article/details/113919776