RTOS cơ bản phần 1

RTOS hẳn mọi người đã có nghe qua, tuy nhiên làm sao để có thể hiểu được RTOS một cách đơn giản và cơ bản nhất thì hơi khó khăn với người mới bắt đầu. May thay có lần tham gia workshop của STM32 có bài hướng cơ bản dễ hiểu mình xin trình bày lại cho các bạn dễ nắm bắt.

RTOS ?

Trước tiên chúng ta sẽ tìm hiểu xem khi nào là non-real time, khi nào là soft real time và hard real time ?

Với mô phỏng máy tính sẽ là non-real time ở cấp độ cao nhất, tiếp đến là giao tiếp người dùng, truyền video thông qua internet, tới mức soft real time là hệ thống cruise control trên ô tô (hệ thống tự kiểm soát tốc độ), các ứng dụng về viễn thông, điều khiển trên máy bay,động cơ điện thì yêu cầu mức hard real time, bạn thử nghĩ xem nếu như đi máy bay mà máy bay hạ xuống đường băng rồi mà 1,2s sau mới có thông báo thì sẽ như thế nào ?

Một hệ điều hành có thể

  • Cho phép nhiều chương trình chạy cùng 1 lúc (multi-tasking)
  • Có quản lý tài nguyên về phần cứng và cung cấp các dịch vụ cho các chương trình khác

Hình trên là cấu tạo của một hệ điều hành thời gian thực (RTOS) các bạn có thể xem qua để biết được bên trong nó sẽ có những gì.

Khi nào bạn cần sử dụng RTOS ?

Các ứng dụng không cần dùng RTOS

  • Ứng dụng đơn (ứng dụng chỉ có 1 chức năng)
  • Ứng dụng có vòng lặp đơn giản
  • Ứng dụng <32kB

Nếu ứng dụng của bạn mà kích thước chương trình lớn dần và độ phức tạp tăng lên thì RTOS sẽ rất hữu dụng trong trường hợp này, lúc đó RTOS sẽ chia các ứng dụng phức tạp thành các phần nhỏ hơn và dễ quản lý hơn.

Tại sao lại phải dùng RTOS ?

  • Chia sẻ tài nguyên một cách đơn giản: cung cấp cơ chế để phân chia các yêu cầu về bộ nhớ và ngoại vi của MCU
  • Dễ debug và phát triển: Mọi người trong nhóm có thể làm việc một cách độc lập, các lập trình viên thì có thể tránh được các tương tác với ngắt, timer, với phần cứng (cái này mình không khuyến khích lắm vì hiểu được phần cứng vẫn sẽ tốt hơn nhiều)
  • Tăng tính linh động và dễ dàng bảo trì: thông qua API của RTOS,…

 

Các khái niệm cơ bản

Kernel

Kernel sẽ có nhiệm vụ quản lý nhiều task cùng chạy 1 lúc, mỗi task thường chạy mất vài ms. Tại lúc kết thúc task thường:

  • Lưu trạng thái task
  • Thanh ghi CPU sẽ load trạng thái của task tiếp theo
  • Task tiếp theo cần khoảng vài ms để thực hiện

Vì CPU thực hiện tác vụ rất nhanh nên dưới góc nhìn người dùng thì hầu như các task là được thực hiện 1 cách liên tục.

Task state

Một task trong RTOS thường có các trạng thái như sau

RUNNING: đang thực thi

READY: sẵn sàng để thực hiện

WAITING: chờ sự kiện

INACTIVE: không được kích hoạt

Scheduler

Đây là 1 thành phần của kernel quyết định task nào được thực thi. Có một số luật cho scheduling như:

  • Cooperative: giống với lập trình thông thường, mỗi task chỉ có thể thực thi khi task đang chạy dừng lại, nhược điểm của nó là task này có thể dùng hết tất cả tài nguyên của CPU
  • Round-robin: mỗi task được thực hiện trong thời gian định trước (time slice) và không có ưu tiên.

  • Priority base: Task được phân quyền cao nhất sẽ được thực hiện trước, nếu các task có cùng quyền như nhau thì sẽ giống với round-robin, các task có mức ưu tiên thấp hơn sẽ được thực hiện cho đến cuối time slice

  1. Task A chờ event
  2. Task B chờ event
  3. Task B event sẵn sàng
  4. Task A event sẵn sàng
  • Priority-based pre-emptive: Các task có mức ưu tiên cao nhất luôn nhường các task có mức ưu tiên thấp hơn thực thi trước.

  1. Task A chờ event
  2. Task B chờ event
  3. Task B event sẵn sàng
  4. Task A event sẵn sàng

Task

Một task là một chương trình, chương trình này chạy liên tục trong vòng lặp vô tận và không bao giờ dừng lại. Với mỗi task thì có niềm tin duy nhất là chỉ mình nó đang chạy và có thể sử dụng hết nguồn tài nguyên sẵn có của bộ xử lý (mặc dù là thực tế thì nó vẫn phải chia sẻ nguồn tài nguyên này với các task khác).

Một chương trình thường sẽ có nhiều task khác nhau. Ví dụ như máy bán đồ uống tự động sẽ có các thành task sau

  • Task quản lý việc lựa chọn của người dùng
  • Task để kiểm tra đúng số tiền người dùng đã trả
  • Task để điều khiển động cơ/ cơ cấu cung cấp nước uống.

Kernel sẽ quản lý việc chuyển đổi giữa các task, nó sẽ lưu lại ngữ cảnh của task sắp bị hủy và khôi phục lại ngữ cảnh của task tiếp theo bằng cách:

  • Kiểm tra thời gian thực thi đã được định nghĩa trước (time slice được tạo ra bởi ngắt systick)
  • Khi có các sự kiện unblocking một task có quyền cao hơn xảy ra (signal, queue, semaphore,…)
  • Khi task gọi hàm Yield() để ép Kernel chuyển sang các task khác mà không phải chờ cho hết time slice

Khi khởi động thì kernel sẽ tạo ra một task mặc định gọi là Idle Task.

Để tạo một task thì cần phải khai báo hàm định nghĩa task, sau đó tạo task và cấp phát bộ nhớ, phần này mình sẽ nói sau.

Kết nối Inter-task & Chia sẻ tài nguyên

Các task cần phải kết nối và trao đổi dữ liệu với nhau để có thể chia sẻ tài nguyên, có một số khái niệm cần lưu ý

Với Inter-task Communication:

  • Signal Events – Đồng bộ các task
  • Message queue – Trao đổi tin nhắn giữa các task trong hoạt động giống như FIFO
  • Mail queue – Trao đổi dữ liệu giữa các task sử dụng hằng đợi của khối bộ nhớ

Với Resource Sharing

  • Semaphores – Truy xuất tài nguyên liên tục từ các task khác nhau
  • Mutex – Đồng bộ hóa truy cập tài nguyên sử dụng Mutual Exclusion

 

Signal event

Signal event được dùng để đồng bộ các task, ví dụ như bắt task phải thực thi tại một sự kiện nào đó được định sẵn

Ví dụ: Một cái máy giặt có 2 task là Task A điều khiển động cơ, Task B đọc mức nước từ cảm biến nước đầu vào

  • Task A cần phải chờ nước đầy trước khi khởi động động cơ. Việc này có thể thực hiện được bằng cách sử dụng signal event
  • Task A phải chờ signal event từ Task B trước khi khởi động động cơ
  • Khi phát hiện nước đã đạt tới mức yêu cầu thì Task B sẽ gửi tín hiệu tới Task A

Với trường hợp này thì task sẽ đợi tín hiệu trước khi thực thi, nó sẽ nằm trong trạng thái là WAITING cho đến khi signal được set. Ngoài ra ta có thể set 1 hoặc nhiều signal trong bất kỳ các task nào khác.

Mỗi task có thể được gán tối đa là 32 signal event

Ưu điểm của nó là thực hiện nhanh, sử dụng ít RAM hơn so với semaphore và message queue nhưng có nhược điểm lại chỉ được dùng khi một task nhận được signal.

Message queue

Message queue là cơ chế cho phép các task có thể kết nối với nhau, nó là một FIFO buffer được định nghĩa bởi độ dài (số phần tử mà buffer có thể lưu trữ) và kích thước dữ liệu (kích thước của các thành phần trong buffer). Một ứng dụng tiêu biểu là buffer cho Serial I/O, buffer cho lệnh được gửi tới task

Task có thể ghi vào hằng đợi (queue)

  • Task sẽ bị khóa (block) khi gửi dữ liệu tới một message queue đầy đủ
  • Task sẽ hết bị khóa (unblock) khi bộ nhớ trong message queue trống
  • Trường hợp nhiều task mà bị block thì task với mức ưu tiên cao nhất sẽ được unblock trước

Task có thể đọc từ hằng đợi (queue)

  • Task sẽ bị block nếu message queue trống
  • Task sẽ được unblock nếu có dữ liệu trong message queue.
  • Tương tự ghi thì task được unblock dựa trên mức độ ưu tiên

Ví dụ:

Mail queue

Giống như message queue nhưng dũ liệu sẽ được truyền dưới dạng khối(memory block) thay vì dạng đơn. Mỗi memory block thì cần phải cấp phát trước khi đưa dữ liệu vào và giải phóng sau khi đưa dữ liệu ra.

Gửi dữ liệu với mail queue

  • Cấp phát bộ nhớ từ mail queue cho dữ liệu được đặt trong mail queue
  • Lưu dữ liệu cần gửi vào bộ nhớ đã được cấp phát
  • Đưa dữ liệu vào mail queue

Nhận dữ liệu trong mail queue bởi task khác

  • Lấy dữ liệu từ mail queue, sẽ có một hàm để trả lại cấu trúc/ đối tượng
  • Lấy con trỏ chứa dữ liệu
  • Giải phóng bộ nhớ sau khi sử dụng dữ liệu

Ví dụ:

Semaphore

Được sử dụng để đồng bộ task với các sự kiện khác trong hệ thống. Có 2 loại

Binary semaphore

  • Trường hợp đặc biệt của counting semaphore
  • Có duy nhất 1 token
  • Chỉ có 1 hoạt động đồng bộ

Counting semaphore

  • Có nhiều token
  • Có nhiều hoạt động đồng bộ

Couting semaphore được dùng để:

Đếm sự kiện ( counting event)

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

Quản lý tài nguyên

  • Giá trị đếm (count value) sẽ chỉ ra số tài nguyên sẵn có
  • Để kiểm soát được tài nguyên dựa vào biến đếm giảm xuống, khi bằng 0 nghĩa là hết tài nguyên sẵn có
  • Khi một task hoàn thành thì tài khuyên sẽ được giải phóng và tăng giá trị đếm semaphore
  • Trong trường hợp này thì giá trị đếm sẽ bằng giá trị đếm lớn nhất  khi semaphore được tạo.

 

Mutex

Sử dụng cho việc loại trừ (mutial exclution), hoạt động như là một token để bảo vệ tài nguyên được chia sẻ. Một task nếu muốn truy cập vào tài nguyên chia sẻ

  • Cần yêu cầu (đợi) mutex trước khi truy cập vào tài nguyên chia sẻ
  • Đưa ra token khi kết thúc với tài nguyên.

Tại mỗi một thời điểm thì chỉ có 1 task có được mutex. Những task khác muốn cùng mutex thì phải block cho đến khi task cũ thả mutex ra.

Về cơ bản thì Mutex giống như binary semaphore nhưng được sử dụng cho việc loại trừ chứ không phải đồng bộ. Ngoài ra thì nó bao gồm cơ chế thừa kế mức độ ưu tiên(Priority inheritance mechanism) để giảm thiểu vấn đề đảo ngược ưu tiên, cơ chế này có thể hiểu đơn giản qua ví dụ sau:

  • Task A (low priority) yêu cầu mutex
  • Task B (high priority) muốn yêu cầu cùng mutex trên.
  • Mức độ ưu tiên của Task A sẽ được đưa tạm về Task B để cho phép Task A được thực thi
  • Task A sẽ thả mutex ra, mức độ ưu tiên sẽ được khôi phục lại và cho phép Task B tiếp tục thực thi.

 

Tạm kết

Phù, thế cũng xong được một số khái niệm cơ bản thường hay dùng trong RTOS, bài này khá nhiều lý thuyết, bài sau mình sẽ tiếp tục với phần ứng dụng và lập trình một bài toán với cách lập trình thông thường và cách sử dụng RTOS với kit F4 chẳng hạn.

Update: Phần 2 các bạn có thể theo dõi tại đây thực hành RTOS.

7
Leave a Reply

avatar
4 Comment threads
3 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
scphanHUSTQuangThích Khách Recent comment authors

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.

  Subscribe  
newest oldest most voted
Notify of
Thích Khách
Guest
Thích Khách

Bài viết hay quá, khái niệm về những thành phần cơ bản của hệ điều hành thời gian thực được viết rất mạch lạc. Mình hiểu thêm rất nhiều về RTOs sau khi đọc bài viết này. Chỉ có phần thuộc nội dung ‘Kết nối Inter-task & Chia sẻ tài nguyên ‘ thì hơi khó hiểu một chút do thiếu ví dụ đi kèm. Hóng phần 2 ghê 🙂

Cảm ơn các bạn rất nhiều, bài viết thật sự rất hữu ích !

phanHUST
Guest
phanHUST

hình như em có đọc 1 số bài trong blog của anh a có thể share lại em link blog của anh ko. em quen mất thanks anh

Quang
Guest
Quang

Chào bạn. Cảm ơn bạn đã chia sẻ kiến thức. Bạn có biết cấu trúc lênh Signal event k cho mình tham khảo cấu trúc với. Cảm ơn rất nhiều.

trackback

[…] ở bài trước mình có nói về RTOS cơ bản và các khái niệm của nó rồi, hôm nay mình xin đi vào các ví dụ cụ thể để […]

trackback

[…] xin lấy lại nội dung đã ghi ở bài viết RTOS cơ bản: Một task là một chương trình, chương trình này chạy liên tục trong vòng lặp […]