Ở bài viết này mình sẽ hướng dẫn các bạn về Semaphore trong RTOS. Semaphore trong RTOS là gì, có bao nhiêu loại Semaphore, nó được sử dụng và khởi tạo như thế nào? Các API của Semaphore là những API nào và cách sử dụng nó ra sao ?

Semaphore

Trước tiên chúng ta cần xem lại khái quát nội dung về Queue trong RTOS ở bài viết RTOS cơ bản. Mình xin nhắc lại bên dưới

Semaphore được dùng để động bộ giữa các

  • Task
  • Ngắt và task

Có 2 dạng là

  • Binary semaphore
  • Counting semaphore

Binary semaphore có duy nhất 1 token và sử dụng để đồng bộ một action

Counting semaphore sẽ có nhiều token và đồng bộ nhiều action khác nhau

Mặc định thì có binary semaphore còn counting semaphore được disable trong CubeMX, do đó muốn dùng thì chúng ta phải bật nó lên bằng cờ USE_COUNTING_SEMAPHORES

Người ta thường dùng semaphore để couting event hoặc resouce management

Counting event

  • Một event handler sẽ ‘give’ semaphore khi có event xảy ra (tăng giá trị đếm semaphore)
  • Một task handler sẽ ‘take’ semaphore khi nó thực thi sự kiện (giảm giá trị đếm semaphore)
  • Count value là khác nhau giữa số sự kiện xảy ra và số sự kiện được thực thi
  • Trong trường hợp counting event thì semaphore được khởi tạo giá trị đếm bằng 0

Resource management

  • Count value sẽ chỉ ra số resource sẵn có
  • Để điều khiển và kiểm soát được resource của task dựa trên count value của semaphore(giá trị giảm), nếu count value giảm xuống bằng 0 nghĩa là không có resource nào free.
  • Khi một task finish với resource thì nó sẽ give semaphore trở lại để tăng count value của semaphore.
  • Trong trường hợp resouce management thì count value sẽ bằng với giá trị max của count value khi semaphore được tạo.

Tạo semaphore

Dưới đây là các bước tạo semaphore cơ bản nhất

Bước 1: Định nghĩa semaphore bằng hàm

osSemaphoreDef (myCountingSem01);

Trong đó:

  • myCountingSem01: là tên của semaphore

Bước 2: Tạo semaphore dùng hàm osSemaphoreCreate(), hàm này sẽ return lại ID/handle được dùng bởi các API khác liên quan tới tạo semaphore

osSemaphoreId (myCountingSem01Handle);
myCountingSem01Handle = osSemaphoreCreate(osSemaphore(myCountingSem01), 2);

Trong đó:

  • myCountingSem01: Tên của semaphore
  • 2: Số lượng token được dùng để tạo semaphore

Acquire và release semaphore

Task có thể acquire(lấy) semaphore bằng hàm

osSemaphoreWait(myCountingSem01Handle, osWaitForever);

Trong đó

  • myCountingSem01Handle: Semaphore Id/handle
  • osWaitForever: giá trị timeout, nếu không dùng timeout thì giá trị là 0

Task release semaphore bằng hàm

osSemaphoreRelease(myCountingSem01Handle);

Với myCountingSem01Handle: semaphore ID/handle

Trước khi vào ví dụ cụ thể ta có thể xem ví dụ minh họa dưới đây

Ở ví dụ trên, task A, task B, task C có cùng một priority, semaphore có 2 token

  1. Task C gọi hàm osSemaphoreWait() nhưng semaphore token đã được take bởi task A và task B
  2. Trạng thái của task C sẽ được set sang waiting và Task A đã được bắt đầu. Task B thì đang trong ready
  3. Task B gọi hàm osSemaphoreRelease(). Trạng thái của task C sẽ được set sang ready. Vì tất cả các task có cùng priority nên task B sẽ tiếp tục thực thi
  4. Cuối cùng là Task C sẽ được thực thi

Semaphore API

Bên dưới là bảng tổng hợp các API có trong Semaphore

Feature CMSIS RTOS API FreeRTOS API
Create semaphore osSemaphoreCreate xSemaphoreCreateCounting vSemaphoreCreateBinary
Delete semaphore osSemaphoreDelete vSemaphoreDelete
Acquire semaphore osSemaphoreWait xSemaphoreTake
Release semaphore osSemaphoreRelease xSemaphoreGive

Ví dụ

Binary Semaphore

Trong ví dụ này ta sẽ tạo ra 2 task, một task sẽ release semaphore và một task sẽ wait semaphore

Mở CubeMX chỉnh cấu hình cho FreeRTOS

  • Tạo ra 2 task với cùng priority
  • Tạo binary semaphore, set name

Sau khi gen code, xem chương trình trong file main.c ta thấy

Định nghĩa semaphore handle

/* Private variables ---------------------------------------------------------*/
osThreadId Task1Handle;
osThreadId Task2Handle;
osSemaphoreId myBinarySem01Handle;

Tạo semaphore

/* Create the semaphores(s) */
/* definition and creation of myBinarySem01 */
osSemaphoreDef(myBinarySem01);
myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);

Thay đổi một chút ở task 1

  • Dùng semaphore
  • Nếu task/interrupt mà hoàn thành thì semaphore sẽ được release
/* StartTask1 function */
void StartTask1(void const *argument)
{
 
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for (;;)
  {
    osDelay(2000);
    printf("Task 1 Release semaphore\n");
    osSemaphoreRelease(myBinarySem01Handle);
  }
  /* USER CODE END 5 */
}

Task 2 sẽ được thay đổi thành

  • Đợi semaphore
  • Task 2 sẽ đợi cho tới khi semaphore release, sau khi task release thì sẽ tiếp tục công việc như bình thường
/* StartTask2 function */
void StartTask2(void const *argument)
{
  /* USER CODE BEGIN StartTask2 */
  /* Infinite loop */
  for (;;)
  {
    osSemaphoreWait(myBinarySem01Handle, 4000);
    printf("Task 2 synchronized\n");
  }
  /* USER CODE END StartTask2 */
}

Task 1 sẽ được đồng bộ với Task 2 như hình dưới

Kết quả

Counting semaphore

Ở ví dụ này ta sẽ tạo ra 3 task, task 1 và 2 như nhau, task 3 sẽ chờ semaphore release từ 2 task trước

Mở cubeMX cấu hình:

  • Tạo 3 task với cùng priority
  • Chỉnh cấu hình cho phép add counting semaphore
  • Tạo counting semaphore và set số lượng token là 2

Sau khi gen code ra thì điều chỉnh trong file main.c

Tạo Counting semaphore

/* definition and creation of myCountingSem01 */
osSemaphoreDef(myCountingSem01);
myCountingSem01Handle = osSemaphoreCreate(osSemaphore(myCountingSem01), 2);

Task 1 và task 2 giống nhau đều release counting semaphore
Task 1

/* USER CODE END Header_StartTask1 */
void StartTask1(void const *argument)
{
 
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for (;;)
  {
    osDelay(2000);
    printf("Task 1 Release counting semaphore\n");
    osSemaphoreRelease(myBinarySem01Handle);
  }
  /* USER CODE END 5 */
}

Task 2

/* USER CODE END Header_StartTask2 */
void StartTask2(void const *argument)
{
  /* USER CODE BEGIN StartTask2 */
  /* Infinite loop */
  for (;;)
  {
    osDelay(2000);
    printf("Task 2 Release counting semaphore\n");
    osSemaphoreRelease(myBinarySem01Handle);
  }
  /* USER CODE END StartTask2 */
}

Task 3 sẽ đợi cho tới khi có semaphore được release từ task 1 và task 2

/* USER CODE END Header_StartTask3 */
void StartTask3(void const *argument)
{
  /* USER CODE BEGIN StartTask3 */
  /* Infinite loop */
  for (;;)
  {
    osSemaphoreWait(myCountingSem01Handle, 4000);
    osSemaphoreWait(myCountingSem01Handle, 4000);
    printf("Task 3 Synchronized\n");
  }
  /* USER CODE END StartTask3 */
}

Hoạt động chương trình sẽ như hình minh họa bên dưới

Kết quả

Tạm kết

Vậy là ở bài viết này mình đã làm rõ về Semaphore trong RTOS là gì, sơ lược về Binary Semaphore và Couting semaphore, cách tạo Semaphore ? Các API của semaphore và cách sử dụng nó trong các ví dụ với binary semaphore và counting semaphore. Còn một anh cuối cùng là mutex lại xin hẹn các bạn ở bài viết kế tiếp.