Device Driver Fundamentals in C (Phần 4)

(Phần 1, Phần 2, Phần 3, Phần 4)

13. Error Handling

Một trong những vấn đề lớn nhất đối với ngôn ngữ lập trình C là hoàn toàn không có một cách tốt nhất nào để xử lý lỗi (error handling) hoặc bắt lỗi (error trapping). Các ngôn ngữ hướng đối tượng (object-oriented languages) có khả năng thử (try) một code block và nếu xảy ra một lỗi thì sẽ bắt (catch) lỗi. C không có khả năng như vậy. Điều tốt nhất mà C hỗ trợ là khả năng kiểm tra một giá trị trả về của hàm.

Vấn đề với việc kiểm tra giá trị trả về của một hàm là các developer thực sự rất rất kém trong việc kiểm tra giá trị trả về. Do không bắt buộc phải kiểm tra các giá trị trả về, nên nhiều developer sẽ bỏ qua chúng. Việc bỏ qua các giá trị trả về tất nhiên là một kỷ luật tồi. Trong nhiều trường hợp, việc xử lý lỗi trong C được thực hiện bằng cách trả về mã lỗi (error code) hoặc hàm đã chạy thành công.

Vậy, làm thế nào developer có thể xử lý lỗi trong các driver của họ? Cách tiếp cận tốt nhất mà developer có thể thực hiện là tạo danh sách tất cả các lỗi có thể xảy ra trong driver. Từ danh sách đó, hãy tạo một bảng liệt kê (enumeration) có chứa tất cả các mã lỗi. Xem lại danh sách và xác định các lỗi mà driver cần chủ động quản lý. Những lỗi này có thể bao gồm cờ truyền hoàn thành không bao giờ đặt (transmit flag complete never set), cờ nhận hoàn thành không bao giờ đặt (receive flag complete never set), truyền bị gián đoạn (transmission is interrupted), v.v. Làm mọi thứ cần thiết để cố gắng khôi phục từ trạng thái lỗi (error state) và nếu driver không thể làm được, đừng treo ở đó mãi mãi mà hãy trả về mã lỗi có thể giúp developer gỡ lỗi (debug).

14. Mẫu Thiết Kế Đòn Bẩy (Leverage Design Patterns)

Theo thời gian, khi các developer có nhiều kinh nghiệm hơn, họ bắt đầu nhận ra rằng có rất nhiều mẫu thiết kế (design pattern) xuất hiện thường xuyên trong phần mềm nhúng. Một mẫu thiết kế là một giải pháp có thể tái sử dụng chung cho một vấn đề thường xảy ra.10 Sử dụng một mẫu thiết kế đã tồn tại và giải quyết một vấn đề thiết kế thông thường có thể tăng tốc đáng kể việc phát triển phần mềm và đảm bảo một giải pháp mạnh mẽ hơn. Có rất nhiều ví dụ về mẫu thiết kế mà các embedded software developer có thể tận dụng. Một ví dụ tuyệt vời là mẫu thiết kế được sử dụng để nhận dữ liệu nối tiếp (serial data) trên một UART.

Mô hình thiết kế để nhận và xử lý dữ liệu nối tiếp có thể được nhìn thấy trong Figure 3-25. Một interrupt được sử dụng để nhận một ký tự đơn lẻ từ UART. Ký tự được đọc vào một bộ đệm (buffer) và sau đó một tín hiệu được sử dụng để thông báo một task rằng có một ký tự sẵn sàng được xử lý. Mẫu thiết kế rất đơn giản, nhưng nó thực hiện được một số điều đối với developer, chẳng hạn như:

  • Giảm thiểu chi phí phần mềm (software overhead) liên quan đến kiến trúc polling
  • Giảm thiểu quá trình xử lý trong ISR bằng cách chỉ đọc ký tự
  • Xử lý yêu cầu thời gian thực cứng (hard real-time requirement) – nhận một ký tự, và báo hiệu (signal) một task khác để xử lý yêu cầu thời gian thực mềm (soft real-time requirement) – xử lý dữ liệu
  • Cung cấp hành vi xác định cho hệ thống
Figure 3-25. UART Receive design pattern

Các mẫu thiết kế là những mảnh ghép có thể được sử dụng để nhanh chóng xây dựng một hệ thống nhúng. Ứng dụng càng tận dụng được các mẫu thiết kế thì phần mềm đó càng được phát triển nhanh hơn. Nhiều driver sẽ tuân thủ các mẫu thiết kế rất phổ biến mà chúng ta đã thảo luận trong chương này, chẳng hạn như kiến trúc chặn (blocking) và không chặn (non-blocking). Ở phần sau, khi chúng ta đi sâu vào các ví dụ cụ thể để develop các peripheral driver khác nhau, các mẫu thiết kế này sẽ trở nên rõ ràng hơn.

15. Expected Results and Recommendations

Trong chương này, chúng ta đã khám phá một số khái niệm nhằm giúp developer suy nghĩ về cách tổ chức và bắt đầu triển khai device driver của họ. Các kỹ thuật mà chúng ta đã thảo luận có rất nhiều lợi ích, bao gồm code base có tổ chức hơn, dễ bảo trì hơn. Có một số kết quả liên quan đến phần mềm mà developer cần lưu ý.

Đầu tiên, tổ chức code base thành các thành phần (component) tạo ra một dự án rất có tổ chức. Các thành phần dễ dàng di chuyển từ dự án này sang dự án tiếp theo và dễ dàng tìm thấy trong cấu trúc dự án. Một nhược điểm tiềm ẩn khi tổ chức dự án theo cách này là càng nhiều module được thêm vào dự án, thì càng có nhiều file trong dự án, dẫn đến nhiều cấu trúc folder hơn. Kết quả có thể là:

  • Thời gian biên dịch chậm hơn do mở và đóng quá nhiều file
  • Danh sách include phức tạp vì mỗi thành phần cần được thêm vào compiler và linker include path

Nói chung, đây là những vấn đề nhỏ và các developer không nên để chúng cản trở khi develop các driver có tổ chức. Điều quan trọng là không phải lúc nào công việc cũng toàn hoa hồng đỏ và cỏ xanh (it isn’t all red roses and green grass).

Thứ hai, việc xác nhận (assertion) rất có ích để kiểm tra những giả định về input, điều kiện trước (pre-conditions), điều kiện sau (post-conditions), v.v. là đúng, nhưng chúng không hoàn toàn miễn phí. Mọi biểu thức được đánh giá trong assertion mất một khoảng thời gian xử lý để được đánh giá. Mặc dù việc này chỉ là vài chục lệnh và thực thi rất nhanh, nhưng nó có thể ảnh hưởng đến hiệu suất hệ thống theo thời gian thực. Thậm chí hiệu suất tệ hơn, assertion có thể chiếm một ít bộ nhớ (code space) trên vi điều khiển. Theo thời gian, mật độ assertion của một dự án có thể dễ dàng chiếm từ 3 đến 5 phần trăm, điều này có thể làm cho code trông cồng kềnh đáng kể. Đây là những lý do tại sao mà các assertion thường bị vô hiệu hóa (disable) trước khi test và phát hành sản xuất.

Thứ ba, các developer cần đảm bảo rằng họ đã cẩn thận sử dụng các callback. Trong nhiều trường hợp, các callback đăng ký một hàm vào một interrupt service routine. Vì các callback thực thi trong interrupt, chúng cần phải ngắn, nhanh và đi vào trọng tâm. Developer cần đảm bảo rằng họ tuân theo các phương pháp tốt nhất để sử dụng callback, điều này đã được thảo luận trong phần callback.

Cuối cùng, các developer cần phải cẩn thận xem họ mang khái niệm “object-oriented C” đến đâu. Bạn nên đóng gói dữ liệu (encapsulate data), sử dụng một vài kiểu dữ liệu trừu tượng (abstract data type), v.v. nhưng cuối cùng sẽ tới lúc cần phải cân nhắc đến việc nâng cấp lên C++. Nếu bạn cần hành vi hướng đối tượng đầy đủ (full object-oriented behavior), chỉ cần sử dụng ngôn ngữ hướng đối tượng.

16. Going Further

Có một vài hoạt động mà độc giả có thể thực hiện để củng cố các khái niệm về driver mà chúng ta vừa thảo luận. Driver là một nền tảng quan trọng trong hệ thống nhúng, cho nên cần phải hiểu rõ các khái niệm cơ bản này. Một số hoạt động bổ sung được khuyến nghị bao gồm:

1. Tìm memory map cho bộ xử lý yêu thích của bạn. Những bộ nhớ sau sẽ chiếm vùng nhớ (memory region) nào?

  • Flash
  • RAM
  • GPIO
  • SPI

2. Có bất kỳ lỗ hổng (hole) nào trên memory map mà bạn có thể tìm thấy không? Có bất kỳ vùng nhớ nào để có thể mở rộng bộ nhớ không?

3. Hãy lập danh sách tất cả input, output, pre-condition và post-condition sẽ liên quan tới GPIO driver.

4. Trong IDE yêu thích của bạn, hãy xem lại cách bật (enable) các assertion. Tạo một ứng dụng example với printf assert. Tạo một hàm đơn giản và tìm hiểu những điều sau:

  • Cách sử dụng printf assert
  • Thời điểm sử dụng printf
  • Chi phí liên quan đến assert
  • Thực hành bật và tắt các assert. Bạn có thể đo lường tác động của hàm này đối với code của bạn không?

5. Định nghĩa các quy ước code (coding convention) của riêng bạn.

  • Bạn định tổ chức các thành phần phần mềm (software component) của mình như thế nào?
  • Bạn định sử dụng quy ước đặt tên (naming convention) nào?
  • Kiểm định bất kỳ quy ước nào khác mà bạn có thể sử dụng khi phát triển phần mềm về sau.

6. Kiểm tra kỹ năng của bạn bằng cách tạo ra một kiểu dữ liệu trừu tượng (abstract data type). Làm theo ví dụ ngăn xếp (Stack Example) và triển khai stack ADT. Nếu bạn quan tâm đến nguồn Stack Example thì có thể tải xuống tại đây.11

7. Tạo một ứng dụng hàm callback đơn giản để khởi tạo một mảng. Tạo một callback để khởi tạo một mảng với tất cả các số 0 (zero) và một cái khác để khởi tạo mảng thành các số ngẫu nhiên (random).

Trở về

https://en.wikipedia.org/wiki/Software_design_pattern

11http://www.beningo.com/wp-content/uploads/Downloads/ATP.zip

Icons made by Freepik from www.flaticon.com