FREE RTOS
1.Tổng quan về FreeRTOS
2. Mô tả KernelFreeRTOS
2.1 Cấu hình
2.2 Quản lý Task
2.3 Quản lý List
2.4 Đặt lịch cho FreeRTOS
2.5 Xử lý Critical Section
2.6 Quản lý Queue
1. Tổng quan
FreeRTOS được nghiên cứu bởi Richard Barry với tên ban đầu là FRTOS07. Mục đích của FreeRTOS là portable, open source, mini kernel mà có thể đc thao tác trong chế độ ưu tiên cũng như phối hợp. Các chức năng chính
1.1 Thời gian thực
1.2 Thời gian thực hoặc hoạt động đồng bộ
Trong chế độ đồng bộ thì việc đặt lịch không được thực hiện bởi 1 điểm xác định của timer mà được xử lý qua việc điều khiển từ task này đến task khác bởi cơ chế yielding.
1.3 Dynamic Scheduling
Thời điểm xắp xếp lại lịch xảy ra sau 1 khoảng thời gian cụ thể (phụ thuộc vào tần số đồng hồ)
1.4 Giải thuật đặc lịch
Là giải thuật có độ ưu tiên cao nhất trước tiên. Nếu có nhiều hơn 1 tast có cùng độ ưu tiên cao nhất thì các task sẽ được thực hiện theo kiểu round-robin
1.5 Giao tiếp giữa các tiến trình 1.5.1 Queuing:
Inter-process comm được thực hiện bằng queue, thì hầu hết thông tin được truyền bằng giá trị chứ không phải tham chiếu nên vấn đề ràng buộc vê bộ nhớ là đáng xem xét.
Queue read or write từ bên trong ISR là non-blocking. Queue read or write bới timeout=0 non-blocking
1.5.2 Synchroniztion:
FreeRTOS cho phép tạo ra và sử dụng binary semaphore. Bản thân Semaphore là 1 trường hợp đặc biệt của queue với chiều dài =1 và kích thước dữ liệu là =0. Vì điều này nên việc lấy và trả semaphore là các thao tác nguyên thủy, do đó các ngắt sẽ bị vô hiệu hóa và bộ đặt lịch phải bị treo để nhận 1 lock trên queue này
1.6 Tránh Blocking và Deadlock
Trong FreeRTOS các task hoặc là non-blocking hoặc sẽ bị block với 1 khoảng thời gian cố định. Các task mà thức dậy khi hết timeout và vẫn ko thể truy nhập tới 1 tài nguyên vì đôi khi lời gọi API tới 1 tài nguyên có thể trả về fail 1.7 Xử lý critical Section
Critical Section được xử lý bởi việc vô hiệu hóa tất cả các ngắt. Critical section bên trong mỗi task có thể được xếp chồng lên nhau và mỗi task sẽ ghi dấu lại bởi việc tăng giá trị xếp chồng của nó.
1.8 Treo bộ đặt lịch
Khi dành riêng việc truy nhập tới MCU mà việc MCU bị yêu cầu là không gây nguy hiểm cho thao tác của ISR, bộ đặt lịch có thể bị treo. Việc treo này đảm bảo răng tiến trình hiện tại sẽ không được ưu tiên bởi 1 đặt lịch
1.9 Cấp phát bộ nhớ
FreeRTOS cung cấp 3 mô hình heap. Mô hình đơn giản nhất là việc cấp phát cố định, phức tạp hơn là mô hình cho phép cấp phát và giải phóng bằng việc sử
dụng giải thuật best fit và phức tạp nhất là heap_3 cho phép sử dụng malloc và calloc()
1.10 Đảo ngược độ ưu tiên
FreeRTOS không thực hiện bất kỳ kỹ thuật nâng cao nào với độ ưu tiên tiến trình như kế thừa độ ưu tiên hay xoay vòng độ ưu tiên để thực hiện đảo ngc prio
Cấu trúc của 1 con FreeRTOS là như trên. Trong đó phần nhân (HAL) là bao gồm trong 3 file
port.c, port.asm và portmacro.h
2. Mô tả nhân của FreeRTOS
2.1 FreeRTOS configuration 2.2 Task Management 2.2.1 TCB
FreeRTOS kernel quản lý các task qua TCB. 1 TCB tồn tại cho từng task trong FreeRTOS và bao gồm toàn bộ các thông tin về trạng thái của 1 task.
2.2.2Task State:
- FreeRTOS tạo ra 1 task bằng việc tạo ra và thiết lập giá trị cho TCB. Task mới đc đặt ngay vào trong ReadyList với state=ready
-ReadyLists đc sắp xếp để độ ưu tiên với các task là bằng nhau phục vụ cho việc round-robin.
-FreeRTOS tạo nhiều ReadyList- cho từng prirority. Khi lựa chọn task tiếp theo để thực thi, scheduler bắt đầu với list có độ ưu tiên cao nhất và làm với list có độ ưu tiên thấp dần
-FreeRTOS ko có 1 list hay state Running tường minh. Thay vào đó nó duy trì 1 biến là pxCurrentTCB để chỉ rõ tiến trình nào trong ReadyList đang chạy, pxCurrentTCB do đó đc định nghĩa như là 1 con trỏ trỏ đến cấu trúc TCB. -Task trong FreeRTOS có thể bị khóa khi truy nhập vào 1 tài nguyên mà ko có sẵn. Scheduler khóa các task chỉ khi chúng cố gắng đọc hay ghi vào queue mà là rỗng hay full.
-Việc cố gắng truy cập vào queues có thể bị blocking hay non-blocking. Sự phân biệt là qua biến vTicksToWait. Nếu =0, và queue là empty/full, task ko bị block. Ngc lại, task sẽ bị block 1 khoảng xTicksToWait ticks hoặc cho tới khi 1 sư kiện nào đó trên queue giải phóng tài nguyên
-Task cũng có thể tự nguyên block 1 khongar thời gian qua việc gọi hàm API. Scheduler duy trì 1 danh sách “delayed” cho mục đích này. Scheduler sẽ thăm
danh sách này tại mỗi decision point để quyết định liệu rằng task đã time-out. Sau đó chúng được đặt lên Ready list. Trong FreeRTOS cung cấp vTaskDelay và vTaskDelayUntil.
Bất kỳ task nào ngoài task đang chạy và các task phục vụ ISR có thể được đặt trong trạng thái Suspended vĩnh viễn
Task kết thúc chu kỳ sống của mình bằng việc bị xóa(tự xóa). Trạng thái deleted được yêu cầu cho tới khi không còn cần thiết và kết quả là việc giải phóng ngay tài nguyên bị giữ bởi task. Đẩy task vào trong trạng thái deleted. Scheduler trong FreeRTOs được hướng tới việc bỏ qua task này. IDLE task có trách nhiệm để thu dọn sau khi các task đã bị khóa và do đó IDLE task có độ ưu tiên thấp nhất.
2.3 Quản lý LIST
2.3.1 Ready and Blocked Lists
FreeRTOSConfig.h List Created
configMAX_PRIORITIES ReadyTaskLists[0]..[configMAX_PRIORITIES] INCLUDE_vTaskDelete==1 TaskWaitingTermination INCLUDE_vTaskSuspend==1 SuspendedTaskList N/A PendingReadyList N/A DelayedTaskList N/A OverflowDelayedTaskList Mỗi 1 list được tạo với kiểu xList mà cấu trúc như sau NumberOfItems Số lượng item trong danh sách
(xListItem) *pxIndex Con trở được sử dụng để đi trên danh sách. Nó trỏ tới item kế tiếp
(xMiniListItem)xListEnd 1 item của list mà đánh dấu kết thúc của list. Nó gồm 1 giá trị lớn nhất trong xItemValue và do đó nó luôn xuất hiện tại cuối của list
xListItem
xItemValue Giá trị được dùng để sắp thứ tự- thường là thời gian
*pxNext Con trỏ trỏ tới item tiếp *pxPrevious
*pvOwner Trỏ tới đối tượng mà bao gồm trong list item. Nó thường là TCB
*pvContainer Trỏ tới danh sách nới mà item được đặt xItemValue
*pxNext Trở tới item tiếp theo *pxPrevious
Khởi tạo List:
Vd khởi tạo DelayedTaskList. NumberOfItems=0
pxIndex,pxNext và pxPrevious tất cả đều đc set là địa chỉ của cấu trúc xListEnd. xItemValue phải giữ giá trị lớn nhất có thể =portMAX_DELAY
2.3.2 Inserting a Task Into a List
Vd DelayedTaskList. FreeRTOS sử dụng vListInsert.
Để chèn 1 task mới vào trong DelayedTaskList, vListInsert thực hiện như sau xItemValue trong GenericListItem mới đc so sánh với xItemValue từ TCB đầu của danh sách( số clock tick trên đó task nên được đánh thức). Nếu giá trị đang tồn tại là nhỏ hơn (24<38 ) con trỏ *pxNext được sử dụng để di chuyển tới TCB tiếp theo trong list. Khi so sánh là false, TCB phải được di chuyển tới đúng vị trí mà nó được chèn. Điều này được thực hiện bởi việc chỉnh sửa pxNext và pxPrev trong các items và cả pxNext và pxPrev của chính TCB đó. Cuối cùng pxContainer trong TCB mới đc đặt được sửa để trỏ tới DelayedTaskList. Con trỏ này giúp cho việc gỡ bỏ nhanh chóng sau này. Khi 1 TCB mới được vào , NumberOfItems trong {DelayedTaskList} được cập nhật
2.3.2 Timer Counter Size và {DelayedTaskList}
Các Task được đặt trên DelayedTaskList bởi Scheduler hoặc bởi gọi API khi gọi vTaskDelay hoặc vTaskDelayUntil.
FreeRTOS mà bộ đếm có resolution 8bit 255 ticks. Để thực hiện điều này FreeRTOS định nghĩa và sử dụng 2 danh sách delay- DelayedTaskList và OverflowDelayedTaskList
-xTimeToWake=xTickCount+xTicksToDelay; -Gỡ bỏ xGenericListItem từ ReadyTaskList
-Nếu xTimeToWake<xTickCount tràn thì chèn nó vào danh sách pxOverflowDelayedTaskList
Ngược lại chèn nó vào danh sách pxDelayedTaskList.
Để ý là số tick mà task có thể đc locked phải nhỏ hơn hoặc bằng portMAX_DELAY
Mỗi khi tick count được tăng lên (trong hàm vTaskIncrementTick),1 kiểm tra được thực hiện để quyết định xem liệu counter đã over. Nếu có thì các con trỏ trỏ tới DelayedTaskList và OverflowDelayTaskList được trao đổi
Thao tác đầu tiên được thực hiện bởi scheduler là reset counter/timer (1 chỉ lệnh cụ thể ua rphaanf cứng) để bắt đầu khoảng tick mới. FreeRTOS có thể được cấu hình cooperate hoặc preemtive.
Trong chế độ cooperative, thao tác duy nhất được thực hiện trươc skhi trả về từ ngắt timer là tăng tick count. Cũng còn 1 lượng đáng kể nằm đằng sau thao tác này Nếu Scheduler là preemptive, bước đầu là stack ngưc cảnh của task hiện tại trong event mà chuyển đổi ngữ cảnh yêu cầu. Scheduler tăng tick count và sau khi kiểm tra liệu rằng hành động này có gây nên 1 blocked task trở thành unblock hay không? Nếu task unblock và task này có độ ưu tiên hơn task hiện tại thì 1 việc chuyển ngữ cảnh được thực thi. Cuối cùng, ngữ cảnh được lueeu, các thanh ghi mềm được un-stacked và scheduler trả về từ ngắt
Khi ISR xảy ra, MCU sẽ ngay lập tức lueu ngữ cảnh MCU bằng việc sử dụng con trỏ đến stack hiện tại(SP). Ngữ cảnh của MCU gồm PC,Y,X reg, A,B reg và các thanh ghi điều kiện (CCR). Tất cả các thành ghi này được stack theo thứ tự đã chỉ ra trước khi MCU nhảy tới dịch vụ ngắt.
Trong thực thi FreeRTOS trên HC12 MCU, nó có 12bytes các “thanh ghi mềm” được stack lên đỉnh của trạng thái MCU được cung cấp bởi cơ chế ISR Các thanh ghi được stack 1 cách tường minh bởi việc thực thi macro portISR_HEAD bên trong HAL. Chúng được unstack bởi portISR_TAIL
Thông tin ngữ cảnh cuối cùng được cung cấp bởi việc thực thi macro
portSAVE_CONTEXT bên trong HAL. Macro này trước tiên stack 1 biến mà track- theo dõi độ sâu của nesting cho task. Nếu target đang sử dụng mô hình bộ nhớ xếp chồng cho thiết bị Freescale, sau đó thanh ghi PPAGE nên được lue. Sau đó macro này lưu giá trị hiện tại của thanh ghi SP vào trong TCB của Task1.
Để exit từ ISR, portRESTORE_CONTEXT, portISR_TAIL và 1 RTI phải được thực thi theo thứ tự để có thể xóa bỏ hoàn toàn stack của context frame
2.4.5 Chuyển ngữ cảnh bởi thao tác trên con trỏ Stack(SP)
Một trong số các task của Scheduler được quyết định nếu CS(context switch) được yêu cầu. Scheduler copy head entry của Task 2 vào trong thanh ghi SP. Nếu ngữ cảnh của Task 2 đã được lưu để xác định context frame, sau đó việc thực thi portRESTORE_CONTEXT, portISR_TAIL và 1 RTI sẽ phục hồi ngữ cảnh của Task2 tới MCU
2.4.6 Starting và Stopping Tasks
Mặc dù hoạt động này không trực tiếp trong hàm scheduler, nhưng ở đây chúng ta sẽ mô tả tóm tắt quá trình này. Do đó việc hiểu về tạo và xóa Task sẽ hỗ trợ trong việc mô tả phần còn lại của chức năng Scheduler
-Task được tạo bởi lời gọi xTaskCreate() từ trong main.c hoặc từ trong1 task Tham số
-Con trỏ trỏ tới hàm mà thực thi task -Tên của task
-Độ sâu stack của task này -Độ ưu tiên
xTaskCreate trước tiên phải cấp phát bộ nhớ cho TCB và Task của nó. Điều này được thực hiện bởi lời gọi AllocateTCBandStack gọi portMalloc để nhận được bộ nhớ cho TCB chính là kích thước của cấu trúc TCB và khối nhớ của stack với kthuoc= kiểu dữ liệu Stack * kích thước stack yêu cầu. Cuối cùng
Giả sử bước 1 và 2 trong hình 17 được thiết lập TCB với giá trị đã biết.
Bước 3 và 4 chuẩn bị cho việc chuyển ngữ cảnh. 1 con trỏ tới top-oF-Stack được thiết lập với giá trị cơ sở được tìm thấy trong TCB. Stack cho task sau đó được chiếm với dummy fraame mà trùng khớp tuyệt đối với những gì được yêu cầu khi 1 việc chuyển ngữ cảnh được thực hiện bởi sự kết hợp portRESTORE_CONTEXT và port_ISRTAIL như đã thảo luận trước đó. Thành phần quan trọng của dummy frame là địa chỉ trả về mà sẽ trỏ tới địa chỉ bắt đầu của task code
Sau quá trình cư trú dummy Stack cho task, con trỏ topOfStack được cập nhật và được viết lại tới TCB. SP này là giá trị đầu của TCB được lấy ra trực tiếp để thực hiện việc chuyển ngữ cánh
Khi 1 task được thực hiện thành công, xTAskCreate phải quyết định liệu rằng Scheduler đang chạy? Nếu có, task mới đơn giản là thêm vào Ready list và scheduler sẽ dựa trên ngắt đầu tiên tiếp theo để quyết định task có độ ưu tiên cao nhất. Nếu scheduler đang không chạy, thì xTaskCreate phải quyết định liệu rằng task vừa đc tạo có độ ưu tiên cáo nhất hay ko? Và sau đó đảm bảo rằng điều này đươc ghi lại (sử dụng pxCurrentTCB)
Bước cuối cùng trong việc tạo 1 task là thêm nó tới ReadyTaskList. Điều này được thực hiện bởi lời gọi tới hàm prvAddTaskToReadyQueue. Hàm này quyết định mức ưu tiên của task mà được thêm váo nó để tìm kiếm danh sách Ready phù hợp. Nếu danh sách này là ko tồn tại với độ ưu tiên đó( mà chỉ xảy ra nếu task được tạo động sau khi khởi động) thì danh sách phù hợp sẽ được tạo ra trước tiên.
pxCurrentTCB được thay đổi bới prvAddTaskToReadyQueue để ghi lại TCB được chuyển lần tiếp
Scheduler tương ứng với ngăt ISR. 1 ISR thứ hai được yêu cầu cho việc nhường 1 task trong khi 1 sự kiện đang bị khóa hoặc hoàn thành sớm. Điều này được thực hiện bởi ngắt mềm Swi. Bất kỳ lời gọi tới portYIELD gây ra chỉ lệnh ngắt “swi” để thực thi, triệu gọi đến mã ISR liên kết tới ngắt. Swi xây dựng context frame như đã mô tả ở trước đó- nó thực thi portISR_HEAD và portSAVE_CONTEXT, quyết định nếu bất kỳ việc chuyển ngữ cảnh là được yêu cầu (và load TCB head của task mới vào trong SP nếu cần thiết) và sau đó un-stack context frame phù hợp. Để ý rằng 1 Swi là non-maskable còn timer cho scheduler có thể bị mask
Starting the Scheduler
vTaskStartScheduler() nên là hàm được gọi cuối cùng trong main.c sau khi tất cả các task được yêu cầu khác đã được tạo ra sử dụng hàm xTaskCreate()
- Hàm vTaskStartScheduler trước tiên tạo IDLE task với độ ưu tiên thấp nhất và sau đó set global timer xTickCount=0. xSchedulerRunning=TRUE. Biến này được sử dụng ở nhiều nới để quyết định liệu rằng scheduler là sẵn dùng để quyết định scheduler hoặc quyết định khác cần thiết. Ví dụ, các task có thể được tạo trước hoặc sau khi scheduler được bắt đầu. Khi các tasks chỉ được tạo
với độ ưu tiên mới và chuyển pxTCBCurrent để phản ánh trạng thái(mà không thực hiện việc chuyển ngữ cảnh). Ngược lại scheduler đã được sử
dụng.vTaskStartScheduler chuyển quyền điều khiển tới xTaskStartScheduler trong HAL. HAL là cần thiết tại điểm này bởi vì thứ tự đầu tiên của công việc cho xTaskStartScheduler() là để thiết lập 1 ngắt timer để gọi scheduler. Do timer là phụ thuộc phần cứng, cấu hình nó phải xảy ra trong HAL.