Hệ điều hành thời gian thực (RTOS)

ntaquan

Gà con
Phần 1: Các khái niệm cơ bản

1. Hệ điều hành (OS)

OS là một phần mềm dùng để điều hành và quản lý tài nguyên và điều phối các hoạt động của hệ thống máy tính, nó có các chức năng sau:
  • Allocation of memory
  • Power management
  • Which process uses the CPU
  • When I/O takes place
  • Safety and Security Features
Các ứng dụng (application) sẽ chạy trên nền của hệ điều hành.
1629987413136.png

2. Hệ thống thời gian thực
1629987483838.png

A: non-realtime, B: soft-realtime, C: hard-realtime​
Hard-realtime: thường là những hệ thống liên quan đến sự an toàn của người dùng, những hệ thống này yêu cầu phải đáp ứng trước deadline. Trễ deadline sẽ dẫn đến thất bại (failure).
Soft-realtime: trễ deadline vẫn có thể chấp nhận được, nhưng chất lượng sẽ giảm, thường được đánh giá bằng QoS.
1629987553916.png

3. Hệ điều hành thời gian thực (RTOS)
Sử dụng trong các hệ thống cần thời gian đáp ứng nhanh và chính xác.
Sử dụng trong các hệ thống lớn, cần quản lý tài nguyên.
Ưu điểm:
  • Maintainability/Extensibility
  • Code reuse/Easier testing
  • Team development
  • Improved efficiency
  • Power Management

4. Kernel: là một lớp trừu tượng giúp quản lý hoạt động giữa hệ điều hành và phần cứng, là thành phần cốt lõi trong hệ điều hành. Các dịch vụ của kernel bao gồm:
1629987622923.png

5. Task: chương trình sử dụng RTOS được chia thành những đơn vị nhỏ gọi là task, mỗi task là một hàm. Mỗi task sẽ bao gồm một vòng lặp vô tận và không có câu lệnh trả về.
Kernel sẽ chịu trách nhiệm quản lý các task bằng cách dừng và phục hồi hoạt động của task.
Một chương trình có thể bao gồm nhiều task, vi điều khiển thường chỉ có 1 core nên trong một thời điểm chỉ có đúng 1 task được chạy. Vậy task sẽ có 2 trạng thái Runing và Not running. Trong đó trạng thái Not running còn bao gồm các trạng thái nhỏ hơn:
1629987679560.png

  • RUNNING: executing on the CPU
  • READY: ready to be executed
  • WAITING / BLOCKED: waiting for an event
  • INACTIVE / SUSPENDED: not active
Task chuyển từ trạng thái Not running sang Running gọi là “swapped in”, “switched in”. Task chuyển từ trạng thái Running sang Not running gọi là “swapped out”, “switched out”.

6. Cấu trúc chương trình nhúng
1629987724964.png


Simple loop: Chương trình càng lớn, tính realtime càng bị ảnh hưởng.
Loop and ISRs: Chương trình càng lớn, càng khó quản lý (thứ tự ưu tiên các ngắt, chương trình ngắt lớn)
RTOS: Chia chương trình thành các task, chạy tách biệt với nhau. Các ISR thường đóng vai trò quản lý tài nguyên, notify tasks và trigger actions.

7. Multitasking Vs Concurrency
Bằng cách chuyển qua lại giữa các task, nó cho ta cảm giác như các task được chạy một cách đồng thời mặc dù tại một thời điểm chỉ 1 task chạy.
1629987781742.png
Ngày nay thì đa số là sử dụng vi xử lý đa nhân nên sẽ là real parallelism chứ không còn là concurrency so với vi xử lý đơn nhân nữa.

8. Scheduler: Là một phần của kernel, xác định task nào được phép chạy.
Một số luật cho scheduling:
Cooperative scheduling: Giống với lập trình thông thường, mỗi task chỉ có thể thực thi khi task trước đó thực hiện xong, task có thể dùng hết tất cả tài nguyên của CPU. Nhược điểm là một task có thể làm ‘đứng’ hệ thống (vd max time out, delay).
1629987855370.png
Round-robin scheduling: Mỗi task được thực hiện trong thời gian định trước (time slice) và không có ưu tiên.
1629987878287.png
Preemptive scheduling: Các task có mức ưu tiên thấp luôn nhường các task có mức ưu tiên cao hơn thực thi trước.
1629987938841.png

Tài liệu tham khảo
  • Bài giảng "Thiết kế hệ thống nhúng", bộ môn Điện Tử
  • Bài giảng "Lập trình hệ thống nhúng", bộ môn Điện Tử
  • Bài giảng "Mạng cảm biến không dây và ứng dụng", bộ môn Viễn Thông
  • "Mastering the FreeRTOS Real Time Kernel - A Hands-On Tutorial Guide"
  • "Mastering STM32", Carmine Noviello
 
Last edited:

ntaquan

Gà con
Phần 2: FreeRTOS trên STM32

1. Vài phút giới thiệu

Board sử dụng: STM32F407VG Discovery
Với STM32 thì chúng ta sẽ có thêm một phần implement mới là CMSIS-RTOS, phần này được thêm vào trong file cmsis-os.c (ở thư mục FreeRTOS/Source/CMSIS_RTOS), nếu dùng cái này thì nó sẽ có một số thay đổi so với bản FreeRTOS gốc, có đổi tên một số API, cần phải check bảng mapping bên dưới để biết API tương ứng giữa CMSIS-RTOS API và FreeRTOS API.
1630179425846.png
Dù gì thì CMSIS cũng được build trên FreeRTOS API, ví dụ như hình dưới, trong hàm osKernelStart() có gọi vTaskStartScheduler() =)), nên để cho tiện thì các code demo bài này sử dụng sẽ là của CMSIS-RTOS API (do CubeMX gen code ra) tuy nhiên các phần giải thích sẽ theo các hàm của FreeRTOS API.
1630333848767.png

2. Cấu trúc thư viện FreeRTOS
1630179523693.png
Muốn sử dụng được thư viện của FreeRTOS (compiler hiểu và build được) cần phải thêm những thứ sau vào include path
  1. Đường dẫn tới thư mục chưa các file header nằm ở FreeRTOS/Source/include.
  2. Đường dẫn tới thư mục chưa các file source nằm ở FreeRTOS/Source/portable/[compiler]/[architecture]. Với architecture là kiến trúc của bộ xử lý (ở đây sẽ là ARM_CM4F) và compiler là bộ biên dịch (GCC hoặc CCS hoặc Clang).
  3. Đường dẫn tới FreeRTOSConfig.h header file.
  4. Nếu dùng CMSIS_RTOS cũng phải có đường dẫn tới thư mục chưa các file của cmsis-os.
1630179618778.png
CubeIDE đã hỗ trợ add những thứ này cho người dùng sẵn rồi. Có thể kiểm tra lại bằng cách vào Project -> Properties -> C/C++ Build -> Settings -> Tool Settings -> MCU GCC Compiler -> Include Paths.
Tiếp theo chúng ta cần match những hàm ngắt của vi điều khiển với hàm ngắt của RTOS trong file FreeRTOSConfig.h (CubeIDE cũng làm cho luôn rồi :5cool_still_dreaming:)
1630204012080.png
Đối với cái dòng Cortex-M4F or Cortex-M7 có hỗ trợ Floating Point Unit (FPU), bởi vì các thanh ghi cũng cần được lưu trong quá trình switch context nên nếu không enable FPU sẽ xuất hiện lỗi như sau:
1632672376484.png
Để enable FPU, vào Project Settings->C/C++ Build->Settings->Target Processor chọn FP instructions (hard) trong mục Float ABI và fpv4-sp-d16 đối với dòng Cortex-M4F hoặc pv5sp-d16 đối với dòng Cortex- M7 trong mục FPU Type.

3. Convention của FreeRTOS
3.1. Kiểu dữ liệu:
1630179732873.png
3.2. Tên biến:
Tên biến có tiền tố là kiểu dữ liệu: ‘c’ cho char, ‘s’ cho int16_t (short), ‘l’ cho int32_t (long), và ‘x’ cho BaseType_t và các kiểu dữ liệu không thuộc kiễu dữ liệu chuẩn (người dùng tự đặt) như: structures, task handles, queue handles,...
Nếu biến là số nguyên không dấu, sẽ có thêm tiền tố ‘u’.
Nếu biến là con trỏ, sẽ có thêm tiền tố ‘p’.
3.3. Tên hàm:
Cũng có cách đặt tiền tố giống tên biến ứng với kiểu dữ liệu trả về cùng với file mà chúng được định nghĩa. Ví dụ:
vTaskPrioritySet() trả về kiểu void và được định nghĩa trong task.c.
xQueueReceive() trả về kiểu BaseType_t và được định nghĩa trong queue.c.
pvTimerGetTimerID() trả về con trỏ kiểu void và được định nghĩa trong timers.c.
Để dùng các hàm của FreeRTOS API cần phải include ‘FreeRTOS.h’, kèm theo header file chứa nguyên mẫu của API function: ‘task.h’, ‘queue.h’, ‘semphr.h’, ‘timers.h’ or ‘event_groups.h’.

4. Convention của CMSIS RTOS
Mọi thứ đều có tiền tố ‘os’ :6cool_boss: chấm hết.

5. Hello world
Config trong cubeMX:
1630227197711.png
1630227209576.png
Dùng time source là TIM14 cho HAL thay vì Systick. Vì FreeRTOS sử dụng time source Systick, ta muốn tránh nó xung đột với thư viện HAL.

1630202716080.png
1630202772302.png
Code generate ra sẽ có dạng giống như vậy:
C:
osThreadId defaultTaskHandle;
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();

  /* Create the thread(s) */
  /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */
  /* Infinite loop */
  while (1)
  {

  }
}
C:
void StartDefaultTask(void const * argument)
{
    /* Infinite loop */
    for(;;)
    {
        HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
        osDelay(500);
    }
}
Để tạo 1 task (thread) mới, dùng hàm:
osThreadId osThreadCreate(const osThreadDef_t *thread_def, void *argument);
Thông số của thread_def được đưa vào thông qua macro osThreadDef(name, thread, priority, instances, stacksz)
Trong đó:
  • name: là tên của task
  • thread: là con trỏ chỉ tới hàm gọi khi task chạy
  • priority: mức ưu tiên của task
  • instances: số lần có thể gọi osThreadCreate() trên cùng 1 osThreadDef
  • stacksz: dung lượng stack để lưu ngữ cảnh của task
  • argument: con trỏ chỉ tới tham số truyền vào hàm
osThreadCreate() sẽ tạo một task mới vào báo cho kernel biết để scheduling, hàm trả về Thread ID, người dùng có thể từ đó giám sát được trạng thái của task osThreadCreate() đưa tham số vào hàm.
Khai báo xong các task thì hàm osKernelStart() được gọi để bắt đầu scheduler.

Chương trình trên chỉ là một chương trình nháy led bình thường :gach. Chỉ khác ở chỗ thay vì dùng HAL_Delay() thì đổi thành osDelay() để tạo một delay 500ms. Hàm này có tác dụng đưa task vào trạng thái block để không ngốn tài nguyên của CPU. Sau 500ms thì task sẽ chạy trở lại và nháy LED. Tạm hiểu vậy đi, hàm delay sau này sẽ gặp lại :whaaat:.

Hàm xTaskCreate(), vTaskStartScheduler()vTaskDelay() interface hơi khác một xíu nhưng ý tưởng thì tương tự. Người viết bài này khuyến khích xem qua để tới phần sau không bị bỡ ngỡ :D.

Bài tập: Tạo 3 task cùng độ ưu tiên: Task 1 chớp tắt led chu kỳ 1s, Task 2 gửi 'Task 2\r\n' qua UART mỗi 1s, Task 3 gửi 'Task 3\r\n' qua UART mỗi 2s.
 
Last edited:

ntaquan

Gà con
Phần 3: Task
1. Create task

Tên của task thường được sử dụng cho mục đích debug. Macro configMAX_TASK_NAME_LEN quy định số ký tự tối đa có thể đặt cho task bao gồm cả ký tự NULL, nếu số ký tự vượt quá giá trị này thì chuỗi tên sẽ bị cắt bớt.
Mỗi task sẽ chiếm một phần bộ nhớ dùng để lưu thông tin về trạng thái làm việc và ngữ cảnh (context).
1632665493395.png
Khi các task được tạo ra, kernel sẽ cấp cho nó một vùng nhớ trong Heap gồm TCB và Task Stack. Heap có độ lớn định nghĩa bởi macro configTOTAL_HEAP_SIZE. Đối số usStackDepth của hàm xTaskCreate() đại diện cho độ lớn của Stack được cấp phát tính theo longword. usStackDepth = 100 thì (100 * 4) byte sẽ được cấp phát cho stack.
1632665507413.png
Tương tự khi create thêm Task, Semaphore, Queue kernel sẽ cấp các vùng nhớ tương ứng (TCB, SCB, QCB,...) trong heap. Cách thức cấp phát bộ nhớ tùy vào các file 'heap_x.c' trong thư viện RTOS.
1632665521867.png

1632665528956.png
Tìm hiểu thêm về cách thức hoạt động của cấp phát bộ nhớ động: Basics of Dynamic Memory Allocation - YouTube

2. Task priority
Khi có 2 task có cùng mức ưu tiên, scheduler sẽ cho chúng chạy luân phiên nhau như hình:
1632665590321.png
C:
void StartTask01(void const * argument)
{
    osPriority priority = osThreadGetPriority(myTask01Handle);
    char str[25];
    /* Infinite loop */
    for(;;)
    {
        sprintf(str, "Task 1,priority:%d\r\n", priority);
        HAL_UART_Transmit(&huart2, str, strlen(str), 100);
        for (int i = 0; i < 100000; i++);
    }
}

void StartTask02(void const * argument)
{
    osPriority priority = osThreadGetPriority(myTask02Handle);
    char str[25];
    /* Infinite loop */
    for(;;)
    {
        sprintf(str, "Task 2,priority:%d\r\n", priority);
        HAL_UART_Transmit(&huart2, str, strlen(str), 100);
        for (int i = 0; i < 100000; i++);
    }
}
Đối số uxPriority của hàm xTaskCreate() đặt cho task một mức ưu tiên nhất định. Mức ưu tiên này có thể được thay đổi sau khi bắt đầu scheduler bằng hàm vTaskPrioritySet().
Số mức ưu tiên được define bằng macro configMAX_PRIORITIES. Vậy ta có mức ưu tiên thấp nhất là 0 và mức ưu tiên cao nhất là (configMAX_PRIORITIES-1).
Scheduler sẽ cho các task có mức ưu tiên cao (và có thể chạy) vào Running State. Khi có nhiều task có cùng mức ưu tiên thì scheduler sẽ cho chúng chạy luân phiên nhau. Mỗi task sẽ chạy trong một khoảng ‘time slice’.
Vào cuối mỗi time slice, sẽ có một tick interrupt nhằm để cho scheduler chọn task nào được chạy trong time slice tiếp theo. Tần số tick interrupt quyết định thời gian của time slice, có thể config bằng macro configTICK_RATE_HZ. configTICK_RATE_HZ=100 tương ứng với time slice là 10ms.
vTaskPriorityGet()vTaskPrioritySet() dùng để lấy và thay đổi mức ưu tiên của task. Chỉ có thể dùng 2 hàm này khi macro INCLUDE_vTaskPrioritySet=1.
Khi 2 task có mức ưu tiên khác nhau. Task có mức ưu tiên cao hơn sẽ luôn chạy, task có mức ưu tiên thấp hơn không được chạy cho đến khi các task có mức ưu tiên cao hơn bị block hoặc suspend.
1632673422127.png
C:
void StartTask02(void const * argument)
{
    osPriority priority = osThreadGetPriority(myTask02Handle);
    char str[25];
    priority = priority + 1;
    /* Infinite loop */
    for(;;)
    {
        osThreadSetPriority(myTask02Handle, priority);
        sprintf(str, "Task 2,priority:%d\r\n", priority);
        HAL_UART_Transmit(&huart2, str, strlen(str), 100);
        for (int i = 0; i < 100000; i++);
    }
}
3. Delete Task
Để xóa một task, dùng hàm vTaskDelete(). Tuy nhiên vTaskDelete() sẽ không xóa đi vùng nhớ trong heap của task mà chỉ đánh dấu là task đó đã được xóa.

4. Task Delay


5. Task States

Nhắc lại: Task sẽ có 2 trạng thái chính là Running và Not Running. Trong đó trạng thái Not Running bao gồm:
  • Block state: ta đã biết task khi gọi hàm delay sẽ bị block, đây gọi là Time event. Ngoài ra task còn có thể bị block khi chờ một sự kiện từ queue, semaphore, mutex, đây gọi là Synchronization events.
  • Suspended State: task có thể vào ra trạng thái này thông qua 2 hàm vTaskSuspend()vTaskResume(). Khi ở trạng thái suspend, scheduler sẽ bỏ qua task. Ít dùng nên chả có gì để nói thêm :v
  • Ready State: task không bị blocked và suspend thì sẽ ở trạng thái này, chờ scheduler xếp lịch cho chạy.

6. Idle Task
Idle task được tạo ra bởi scheduler trong hàm vTaskStartScheduler() để đảm bảo trong 1 thời điểm luôn có 1 task đang chạy. Có nghĩa là khi các task khác vào trạng thái blocked hoặc suspended, idle task sẽ chạy.

Đặc điểm:
  • Idle task sẽ có mức ưu tiên thấp nhất (0).
  • Idle task có nhiệm vụ giải phóng bộ nhớ khi có 1 task bị xóa.
1633400814370.png
Trong prvIdleTask() sẽ gọi prvCheckTasksWaitingTermination() -> prvDeleteTCB( pxTCB ) để xóa TCB.
1633401184071.png

Idle task hook là 1 hàm được idle task gọi trong mỗi vòng lặp của nó.
Idle task hook thường dùng để:
  • Thực hiện các xử lý có ưu tiên thấp
  • Đo thời gian rảnh của hệ thống
  • Đưa CPU vào trạng thái công suất thấp
1633401514259.png
Để sử dụng vApplicationIdleHook() thì phải để configUSE_IDLE_HOOK = 1.
Nếu hàm vTaskDelete() được gọi, idle task hook phải quay về idle task sau 1 khoảng thời gian.

VD sử dụng idle task hook để đưa MCU vào sleep mode:
C:
void vApplicationIdleHook( void )
{
    //Assume __HAL_RCC_PWR_CLK_ENABLE() is called elsewhere
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFE);
}
 
Last edited:

ntaquan

Gà con
Phần 4: Scheduler
1. Luật scheduling

Người dùng có thể thay đổi luật scheduling thông qua 2 macro configUSE_PREEMPTION và configUSE_TIME_SLICING
1633440858848.png
Câu hỏi: Giải thích 3 luật scheduling trên?

2. Kiến trúc của Scheduler
Code của scheduler bao gồm 2 phần: Generic (task.c) và Architecture (port.c)
Phần Generic bắt đầu từ hàm xTaskStartScheduler(), hàm này sau khi tạo idle task và timer service task sẽ gọi xPortStartScheduler().
Phần Architecture bắt đầu từ hàm xPortStartScheduler(), hàm này sẽ:
  • Thiết lập systick timer để tạo tần số theo macro configTICK_RATE_HZ
  • Thiếp lập mức ưu tiên của PendSV và ngắt systick
  • Chạy task đầu tiên, bằng lệnh SVC
1633440929457.png
Vậy sẽ có 3 hàm interrupt handler chịu trách nhiệm scheduling.
  • vPortSVCHandler() gọi bởi lệnh SVC dùng để chạy task đầu tiên
  • xPortPendSVHandler() thực hiện context switching nhằm chuyển qua lại giữa các task
  • xPortSystickHandler() quản lý systick và tạo ngắt systick

3. RTOS Tick


4. TCB và Stack

TCB (Task control block) chứa thông tin của 1 task (tên task, mức ưu tiên, số thứ tự, con trỏ chỉ tới stack, ...).
1633711617973.png
Mỗi task sẽ có 1 stack tương ứng, cần phân biệt với kernel stack (bắt đầu từ vị trí SRAM_END = 0x20008000). Vậy sẽ có 2 loại stack trong 1 ứng dụng RTOS là task stack (điều khiển bởi PSP) và kernel stack (điều khiển bởi MSP).
1633710795157.png

5. Context switching
Ngữ cảnh (context) tất cả dữ liệu cần để định nghĩa trạng thái hiện tại của task. Gồm các thanh ghi của processor ngoài ra còn có thể có thông tin của các ngoại vi.
1633441039940.png
Nếu luật scheduling là Preemptive, cứ sau mỗi một RTOS tick, scheduler sẽ so sánh mức ưu tiên của task đang chạy với từng task trong 1 list các task ready. Nếu task trong list đó có mức ưu tiên lớn hơn task hiện tại thì context switch sẽ diễn ra.
1633441383846.png
Nếu luật scheduling là Cooperative, có thể gọi context switching thông qua hàm taskYEILD()

1634829212149.png
Thủ tục switch out gồm có:
  1. Các thanh ghi R0-R3, R12, LR, PC, xPSR lưu vào stack trước khi ngắt Systick xảy ra.​
  2. Nếu cần context switching, sẽ gọi PendSV Handler.​
  3. Các thanh ghi R4-R11 lưu vào stack.​
  4. Lưu top of stack (PSP) vào TCB​
  5. Chọn task tiếp theo để chạy​
1634830930834.png
Thủ tục switch in gồm có:
  1. Lấy top of stack từ TCB lưu vào PSP​
  2. Pop các thanh ghi R4-R11 khỏi stack​
  3. Thoát khỏi Interrupt, pop các thanh ghi R0-R3, R12, LR, PC, xPSR khỏi stack​
1634829285254.png

Context switching cho first task?
1634829106293.png
Có thể nhìn vào hình và giải thích tương tự, lười viết qué :5cool_sweat:

6. Task priority list
1633712505849.png
Làm thế nào để scheduler có thể chọn được task tiếp theo để chạy?? Câu trả lời là các task ở trạng thái ready sẽ được lập thành một array với index là mức ưu tiên, mỗi phần tử sẽ là một linked list, mỗi node trong linked list sẽ chỉ tới TCB của task. Scheduler sẽ đi từ pxReadyTasksLists[uxTopReadyPriority] xuống để tìm task có mức ưu tiên cao nhất (!empty list), nếu tìm thấy pxCurrentTCB sẽ chỉ tới task đó và gọi hàm vTaskSwitchContext().
Phần code trên được gọi trong PendSV_Handler().
 
Last edited:

ntaquan

Gà con
Phần 5: Memory management
1. Phân vùng nhớ

1635363200987.png
Câu hỏi: Stack overflow là gì? Nguyên nhân dẫn đến stack overflow? Cách khắc phục?
Nguyên nhân:
  • Hàm gọi hàm, đệ quy
  • Đối số của hàm quá nhiều, quá lớn
  • Khai báo quá nhiều biến cục bộ hàm
Cách khắc phục:
  • Khử đệ quy​
  • Truyền tham trị​
  • Sử dụng inline function​
  • Sử dụng chế độ optimization​

2. Cấp phát bộ nhớ trong RTOS
Khi tạo task, TCB và Stack của task sẽ được lưu ở đâu? Có 2 phương án:
  • Nếu sử dụng cấp phát động (calloc, malloc), lưu trong Heap​
  • Nếu sử dụng cấp phát tĩnh, lưu trong .data section​
Câu hỏi: Tại sao người ta thường dùng cấp phát động cho RTOS?
Bởi vì các ứng dụng RTOS thường rất lớn, mà dung lượng của vi điều khiển có hạn. Với cấp phát động, ta có thể giải phỏng bộ nhớ khi không sử dụng bằng cách gọi hàm free().
Câu hỏi: Tại sao những ứng dụng thường lại không có cấp phát động?
Bởi vì malloc và free tốn nhiều chu kỳ xử lý và nếu không được quản lý sẽ dễ gây ra phân mảnh bộ nhớ.

3. Các phương thức cấp phát bộ nhớ sử dụng trong FreeRTOS
Code của cấp phát bộ nhớ thuộc phần Port, ở trong file heap_1.c tới heap_5.c, thư mục portable/MemMang.

Heap 1: Không bao gồm hàm vPortFree() được sử dụng trong các ứng dụng không cần giải phóng bộ nhớ. Vậy nên không cần lo tới vấn đề phân mảnh.
Heap 2: Cho phép giải phóng bộ nhớ, kèm với một thuật toán best-fit để tái sử dụng lại vùng nhớ đã được giải phóng.
1637256735267.png
Heap 3: Sử dụng hàm malloc()free() của thư viện chuẩn. Trong lúc đó scheduler sẽ bị tạm ngưng.
1637256892090.png
Heap 4: Cũng có thuật toán best fit, nhưng nó tiếp tục chia nhỏ vùng nhớ đó ra nếu còn chỗ trống, nhằm tránh phân mảnh bộ nhớ.
1637257068070.png
Heap 5: khó hiểu quá, chưa viết được.
 
Last edited:
  • Like
Reactions: Ref

ntaquan

Gà con
Phần 6: Queue
1. Queue là gì

Là một cấu trúc dữ liệu, nó hoạt động dựa trên cơ chế FIFO (first in fisrt out), nghĩa là phần tử nào và trước thì phần tử đó sẽ ra trước. Khi đưa một phần tử vào queue thì gọi là enqueue, khi lấy một phần tử khỏi queue gọi là dequeue.
1637258071372.png
2. Truyền nhận dữ liệu giữa các task
Vấn đề: Giả sử task A gửi dữ liệu liên tục cho task B
  • A gửi khi Queue đầy
  • B nhận khi Queue rỗng
  • B đọc Queue trong lúc A đang ghi
 
  • Like
Reactions: Ref
Top