RT-Thread semaphore (study notes)

This article refers to [Wildfire EmbedFire] "RT-Thread Kernel Implementation and Application Development - Based on STM32", which is only used as a personal study note. For more detailed content and steps, please check the original text (you can download it from the Wildfire Data Download Center)

The basic concept of semaphore

A semaphore is a lightweight kernel object used to solve the synchronization problem between threads. A thread can acquire or release it to achieve synchronization or mutual exclusion. A semaphore is like a key that locks a critical section and only allows access by the thread with the key: the thread gets the key before it is allowed to enter the critical section; after leaving, the key is passed to the waiting thread queued behind , so that subsequent threads enter the critical section in turn.

Schematic diagram of semaphore work As shown in the schematic diagram of semaphore work, each semaphore object has a semaphore value and a thread waiting queue. The value of the semaphore corresponds to the number of instances and resources of the semaphore object. If the semaphore value If it is 5, it means that a total of 5 semaphore instances (resources) can be used. When the number of semaphore instances is zero, the thread that applies for the semaphore will be suspended on the waiting queue of the semaphore, waiting for availability. s semaphore instance (resource).

——RT-Thread Official Chinese Manual

Image source: RT-Thread official Chinese manual

insert image description here

The semaphore count value corresponds to the number of valid resources, and its meaning includes the following two situations:

  • 0: Indicates that the semaphore can no longer be released, and a thread may be blocked on this semaphore.
  • Positive value: Indicates that one or more semaphores can be released.

Application scenarios of binary semaphore

Binary semaphore is an important means of synchronization between threads and between threads and interrupts. The reason why it is called a binary semaphore is that the maximum value of the semaphore is 1, that is, the semaphore value has only two cases: 0 and 1.

Inter-thread synchronization: If thread 1 needs to wait for thread 2 to perform an operation before starting to work, generally in this case, we will poll a global flag in thread 1, which consumes too much CPU resources. If the binary semaphore is used instead, if thread 2 acquires the semaphore but does not release it, thread 1 will enter the blocking state. Until thread 2 releases the semaphore, thread 1 will continue to execute, which can realize the synchronization of the two threads. It also doesn't take up CPU resources like polling.

Thread and interrupt synchronization: Similar to inter-thread synchronization, except that the "flag" change at this time occurs in the interrupt function.

How the binary semaphore works

Create a binary semaphore, allocate memory for the created semaphore object, and initialize the available semaphore to a user-defined number. The maximum number of available semaphores for a binary semaphore is 1.

Semaphore acquisition, acquires a semaphore from the created semaphore resource, and returns correct if the acquisition is successful. Otherwise, the thread will wait for other threads to release the semaphore, and the timeout period is set by the user. When the thread fails to acquire the semaphore, the thread will enter the blocking state, and the system will hang the thread in the blocking list of the semaphore.

--original

How counting semaphores work

The initial value of the semaphore set when the semaphore is created is greater than 1, and the semaphore is a counting semaphore. The counting semaphore supports multiple threads to acquire the semaphore. When the number of threads acquiring the semaphore reaches the initial value of the semaphore, the thread that acquires the semaphore later will be blocked until the thread holding the semaphore releases the semaphore.

The following figure is a schematic diagram of the operation of a counting semaphore with an initial value of 3.

Image source: "RT-Thread Kernel Implementation and Application Development - Based on STM32"

insert image description here

Semaphore control block

The structure members of the semaphore control block are very simple, and the main member is only the semaphore value.

struct rt_semaphore
{
    
    
    struct rt_ipc_object parent;                        /**< 继承自 ipc_object 类 */

    rt_uint16_t          value;                         /**< 信号量的值,最大为 65535 */
    rt_uint16_t          reserved;                      /**< 保留 */
};

The rt_semaphore object is derived from rt_ipc_object and is managed by the IPC container. The maximum value of the semaphore is 65535.

Common semaphore interface

Only a few commonly used interfaces are introduced

Create semaphore rt_sem_create()

rt_sem_t rt_sem_create (const char* name, rt_uint32_t value, rt_uint8_t flag);

When this function is called, the system will first allocate a semaphore object, and initialize this object, and then initialize the IPC object and semaphore-related parts. The flag determines how the thread waits (FIFO or PRIO) when the semaphore is unavailable.

parameter describe
name the name of the semaphore
value Semaphore initial value
flag semaphore wait mode

Delete semaphore rt_sem_delete()

rt_err_t rt_sem_delete(rt_sem_t sem);

When this function is called, the system will delete the semaphore. If a thread is waiting for the semaphore when the semaphore is deleted, the deletion operation will first wake up the thread waiting on the semaphore (the return value of the waiting thread is -RT_ERROR), and then release the memory resources of the semaphore.

parameter describe
sem rt_sem_create creates a processed semaphore object

Initialize the semaphore rt_sem_init()

rt_err_t rt_sem_init (rt_sem_t sem, const char* name, rt_uint32_t value, rt_uint8_t flag);

When this function is called, the system will first allocate a semaphore object, and initialize this object, and then initialize the IPC object and semaphore-related parts. The flag determines how the thread waits (FIFO or PRIO) when the semaphore is unavailable.

parameter describe
sem handle to the semaphore object
name the name of the semaphore
value Semaphore initial value
flag semaphore wait mode

Semaphore release rt_sem_release()

rt_err_t rt_sem_release(rt_sem_t sem);

When the value of the semaphore is equal to zero, and there is a thread waiting for the semaphore, the first thread waiting in the thread queue of the semaphore will wake up and acquire the semaphore. Otherwise, the value of the semaphore will be incremented by one.

parameter describe
sem handle to the semaphore object

Semaphore acquisition rt_sem_take()

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

When calling this function, if the value of the semaphore is equal to zero, it means that the current semaphore resource instance is unavailable, and the thread applying for the semaphore will choose to return directly, suspend and wait for a period of time, or wait forever according to the time parameter. Until another thread or interrupt releases the semaphore. If the semaphore is still not available within the time specified by the parameter time, the thread will timeout and return, and the return value is -RT_ETIMEOUT.

parameter describe
sem handle to the semaphore object
time The specified wait time, in OS Tick

Semaphore experiment

Code reference "RT-Thread Kernel Implementation and Application Development - Based on STM32"

Binary semaphore synchronization experiment

In this experiment, two threads are created, namely the receiving thread and the sending thread. The experimental function is to ensure that the receiving thread does not interrupt the data update in the sending thread.

#include "board.h"
#include "rtthread.h"

// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;

// 定义信号量控制块
static rt_sem_t test_sem = RT_NULL;

// 应用程序要用到的全局变量
uint8_t ucValue[2] = {
    
    0x00, 0x00};


/******************************************************************************
* @ 函数名  : recv_thread_entry
* @ 功  能  : 接收线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
    
    
	while(1)
	{
    
    
		// 获取信号量,一直阻塞等待
		rt_sem_take(test_sem, RT_WAITING_FOREVER); 
		if(ucValue[0] == ucValue[1]) //数据更新是否完成
		{
    
    
			rt_kprintf("sucess\n");
		}
		else
		{
    
    
			rt_kprintf("fail\n");
		}
		rt_sem_release(test_sem);  // 释放信号量
		rt_thread_delay(1000);
	}
}

/******************************************************************************
* @ 函数名  : send_thread_entry
* @ 功  能  : 发送线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
    
    
	while(1)
	{
    
    
		// 获取信号量,一直阻塞等待
		rt_sem_take(test_sem, RT_WAITING_FOREVER); 
		//数据更新
		ucValue[0]++;
		rt_thread_delay(100);
		ucValue[1]++;
		rt_sem_release(test_sem); // 释放信号量
		rt_thread_yield();        // 放弃剩下的时间片,进行线程切换
	}
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
	
	// 创建一个信号量
	test_sem =                                    // 信号量控制块指针
	rt_sem_create("test_sem",                     // 信号量名字
	                1,                            // 信号量初始值
	                RT_IPC_FLAG_FIFO);            // FIFO队列模式(先进先出)
	
	if(test_sem != RT_NULL)
		rt_kprintf("信号量创建成功!\n");

	// 创建一个动态线程
	recv_thread =                                 // 线程控制块指针
	rt_thread_create("recv",                      // 线程名字
	                recv_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(recv_thread != RT_NULL)
		rt_thread_startup(recv_thread);
	else
		return -1;
							
	// 创建一个动态线程
	send_thread =                                 // 线程控制块指针
	rt_thread_create("send",                      // 线程名字
	                send_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	// 开启线程调度
	if(send_thread != RT_NULL)
		rt_thread_startup(send_thread);
	else
		return -1;
}


Experimental phenomena

The sending thread is responsible for updating the two global variables, but there is a 100ms suspended state in the middle. The receiving thread can obtain the semaphore after the sending thread has processed the update of the two global variables and continue to run, and print "success" at the same time.

insert image description here

Counting Semaphore Experiment

This experiment simulates a parking lot. The initial semaphore value of the semaphore is 5, that is, there are 5 parking spaces. Two threads are created to receive (acquire) and send (release) the semaphore respectively (acquiring is equivalent to entering the parking lot and parking, Release is equivalent to leaving the parking lot).

The following does not include hardware initialization code

#include "board.h"
#include "rtthread.h"

// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;

// 定义信号量控制块
static rt_sem_t test_sem = RT_NULL;

/******************************************************************************
* @ 函数名  : recv_thread_entry
* @ 功  能  : 接收线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	while(1)
	{
    
    
		// KEY0 被按下
		if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
		{
    
    
			// 获取一个信号量,不等待
			uwRet = rt_sem_take(test_sem, 0); 
			
			if(uwRet == RT_EOK)
			{
    
    
				rt_kprintf("成功获取车位!\n");
			}
			else
			{
    
    
				rt_kprintf("停车位已满!\n");
			}
		}
		rt_thread_delay(20);
	}
}

/******************************************************************************
* @ 函数名  : send_thread_entry
* @ 功  能  : 发送线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	while(1)
	{
    
    
		// WK_UP 被按下
		if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
		{
    
    
			// 释放一个信号量
			uwRet = rt_sem_release(test_sem); 
			
			if(uwRet == RT_EOK)
			{
    
    
				rt_kprintf("停车位+1!\n");
			}
			else
			{
    
    
				rt_kprintf("停车场已无车!\n");
			}
		}
		rt_thread_delay(20);
	}
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
	
	// 创建一个信号量
	test_sem =                                    // 信号量控制块指针
	rt_sem_create("test_sem",                     // 信号量名字
	                5,                            // 信号量初始值
	                RT_IPC_FLAG_FIFO);            // FIFO队列模式(先进先出)
	
	if(test_sem != RT_NULL)
		rt_kprintf("信号量创建成功!\n");

	// 创建一个动态线程
	recv_thread =                                 // 线程控制块指针
	rt_thread_create("recv",                      // 线程名字
	                recv_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(recv_thread != RT_NULL)
		rt_thread_startup(recv_thread);
	else
		return -1;
							
	// 创建一个动态线程
	send_thread =                                 // 线程控制块指针
	rt_thread_create("send",                      // 线程名字
	                send_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	// 开启线程调度
	if(send_thread != RT_NULL)
		rt_thread_startup(send_thread);
	else
		return -1;
}



Experimental phenomena

Press KEY0 to obtain parking spaces, and a maximum of 5 parking spaces will be obtained. If there are more than 5 parking spaces, it will print that the parking spaces are full; press the WK_UP button to release the semaphore, and the parking space will be +1. (But continuing to release the semaphore when the semaphore value is 5 will not return an error)

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43772810/article/details/123726887