41 To 45

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 30

NGÔN NGỮ LẬP TRÌNH CHO HỆ THỐNG THỜI GIAN THỰC

- Ngôn ngữ lập trình đã tạo ra những cuộc tranh luận sôi nổi giữa các lập trình viên trong
những năm đầu của hệ thống thời gian thực nhúng: liệu người ta nên tiếp tục sử dụng
ngôn ngữ hợp ngữ, tiến lên với một trong những biến thể của PL/I (PL/M của Intel, MPL
của Motorola, hay PL/Z của Zilog), hay thậm chí xem xét việc sử dụng ngôn ngữ C đang
nổi lên cho một dự án phần mềm? Ngày nay, rất ít cuộc tranh luận như vậy giữa các nhà
thực hành phát triển phần mềm nhúng. Nếu chúng ta xem xét cộng đồng toàn cầu của các
lập trình viên thời gian thực chuyên nghiệp, chúng ta thậm chí có thể rút gọn vấn đề lựa
chọn ngôn ngữ lập trình thành hai lựa chọn chính: C++ hoặc C - ngày càng theo thứ tự
này. Tất nhiên, có những ngoại lệ cho sự đơn giản hóa hơi ngây thơ của chúng tôi: Ada
có một vị trí trong các dự án mới và di sản cho Bộ Quốc phòng Hoa Kỳ (DoD), và Java
được sử dụng rộng rãi trong các ứng dụng chạy trên nhiều nền tảng. Tuy nhiên, chúng tôi
chọn đơn giản hóa quyết định xung quanh C++ và C: nếu bạn có một dự án phần mềm
lớn nơi hiệu suất của lập trình viên và khả năng bảo dưỡng mã sản xuất lâu dài là quan
trọng nhất, thì C++ là một lựa chọn tốt, trong khi trong các dự án nhỏ hơn với các thông
số thời gian phản hồi chặt chẽ và/hoặc áp lực chi phí vật liệu đáng kể để làm cho nền tảng
phần cứng bị giảm quá mức, C là ngôn ngữ phù hợp. Và cụ thể, chúng tôi đang nói về các
thế hệ sản phẩm mới chỉ, tức là phần mềm sẽ được phát triển chủ yếu từ đầu. Tình hình
rõ ràng khác biệt nếu chúng tôi muốn tái sử dụng một số phần mềm từ các dự án trước đó
hoặc chỉ mở rộng một sản phẩm hiện có.

- Việc viết phần mềm được coi là công việc hàng hóa, ngày càng có thể được giao phó
cho một công ty tư vấn phần mềm nếu các quy trình kỹ thuật yêu cầu nghiêm ngặt đã
được tuân theo. Tình hình này đặc biệt đúng với các dự án quy mô lớn, như hệ thống tự
động hóa tòa nhà hoặc trạm chuyển mạch điện thoại di động. Mặt khác, trong các ứng
dụng rất quan trọng về thời gian và trong các phần cốt lõi của các sản phẩm đổi mới, nỗ
lực phát triển phần mềm có thể diễn ra rất gần với việc phát triển thuật toán tương ứng để
đảm bảo rằng các tốc độ lấy mẫu yêu cầu được đạt được trong các hệ thống nhúng trong
vùng sử dụng CPU “nguy hiểm” (xem Bảng 1.3), hoặc tài sản trí tuệ quan trọng cần được
bảo vệ trong tổ chức.

- Trong chương này, chúng tôi cung cấp một cuộc thảo luận đánh giá về ngôn ngữ lập
trình cho hệ thống thời gian thực. Nhận ra rằng mỗi tổ chức và ứng dụng là duy nhất, và
nhu cầu xem xét nhiều lựa chọn ngôn ngữ thường là cần thiết, cuộc thảo luận đi xa hơn
quan điểm C++/C đơn giản đã được biểu thị ở đầu chương. Phần 4.1 giới thiệu chủ đề
chung về việc viết phần mềm thời gian thực với một tổng quan ngắn gọn về các tiêu
chuẩn mã hóa. Việc sử dụng tiếp tục nhưng hạn chế của ngôn ngữ hợp ngữ được xem xét
trong Phần 4.2, trong khi Phần 4.3 và 4.4 cung cấp các cuộc thảo luận thực tế về các ưu
và nhược điểm của ngôn ngữ thủ tục và ngôn ngữ hướng đối tượng, tương ứng. Phần 4.5
chứa một tổng quan tập trung về ngôn ngữ lập trình chính: Ada, C, C++, C#, và Java.
Việc tạo mã tự động đã là một giấc mơ của các kỹ sư phần mềm từ lâu, nhưng không có
kỹ thuật tổng quát nào để tạo phần mềm thời gian thực “tự động”. Một giới thiệu về việc
tạo mã tự động và những thách thức của nó được đưa ra trong Phần 4.6. Phần 4.7 trình
bày một số chiến lược tối ưu hóa mã tiêu chuẩn được sử dụng trong trình biên dịch.
Những chiến lược tối ưu hóa này đặc biệt quý giá để lưu ý khi viết mã quan trọng về thời
gian với một ngôn ngữ thủ tục, hoặc khi gỡ lỗi một hệ thống nhúng ở cấp độ hướng dẫn
hợp ngữ. Một tóm tắt sâu sắc về các phần trước đó được cung cấp tiếp theo trong Phần
4.8. Cuối cùng, một bộ bài tập được soạn thảo cẩn thận có sẵn trong Phần 4.9.

4.1 LẬP TRÌNH PHẦN MỀM THỜI GIAN THỰC

- Lạm dụng ngôn ngữ lập trình cơ bản có thể là nguồn gây suy giảm hiệu suất và lỡ hạn
chót lớn nhất trong các hệ thống thời gian thực. Hơn nữa, khi sử dụng ngôn ngữ hướng
đối tượng trong các hệ thống thời gian thực, các vấn đề về hiệu suất có thể khó phân tích
và kiểm soát hơn. Tuy nhiên, ngôn ngữ hướng đối tượng đang dần thay thế ngôn ngữ thủ
tục như là ngôn ngữ được lựa chọn trong phát triển hệ thống nhúng thời gian thực. Hình
4.1 mô tả việc sử dụng chính ngôn ngữ lập trình trong các ứng dụng thời gian thực nhúng
từ thập kỷ 1970 đến thập kỷ hiện tại.

- Một số phần của phần này đã được thích nghi từ Laplante (2003).

4.1.1 Khả năng phù hợp của một ngôn ngữ lập trình cho các ứng dụng thời gian
thực

- Một ngôn ngữ lập trình đại diện cho trung tâm của thiết kế và cấu trúc. Do đó, vì việc
“xây dựng” thực tế của phần mềm phụ thuộc vào các công cụ để biên dịch, tạo mã nhị
phân, liên kết và tạo đối tượng nhị phân, “lập trình” nên mất ít thời gian hơn tỷ lệ so với
các nỗ lực kỹ thuật yêu cầu và thiết kế. Tuy nhiên, “lập trình” (đồng nghĩa với “lập trình”
và “viết phần mềm”) truyền thống hơn là dựa trên công việc thủ công hơn là sản xuất, và
như với bất kỳ nghề thủ công nào, những người thực hành tốt nhất được biết đến với chất
lượng của công cụ của họ và kỹ năng liên quan để sử dụng chúng một cách hiệu quả.

- Công cụ chính trong quá trình tạo mã là trình biên dịch ngôn ngữ. Hiện nay, các hệ
thống thời gian thực đang được xây dựng với nhiều ngôn ngữ lập trình (Burns và
Wellings, 2009), bao gồm các biến thể khác nhau của C, C++, C#, Java, Ada, ngôn ngữ
hợp ngữ, và thậm chí Fortran hoặc Visual Basic. Từ danh sách đa dạng này, C++, C#, và
Java đều là hướng đối tượng, trong khi những ngôn ngữ khác là thủ tục. Tuy nhiên, cần
chỉ ra rằng C++ có thể bị lạm dụng theo cách mà tất cả các lợi thế hướng đối tượng đều bị
mất (ví dụ, bằng cách nhúng một chương trình C cũ vào một lớp “God”). Hơn nữa, Ada
95 có các yếu tố của cả ngôn ngữ hướng đối tượng và ngôn ngữ thủ tục, và do đó có thể
được sử dụng theo cả hai cách, tùy thuộc vào kỹ năng và sở thích của lập trình viên cũng
như chính sách dự án địa phương.

- Một câu hỏi liên quan thường được hỏi là: “Khả năng phù hợp của một ngôn ngữ lập
trình cho các ứng dụng thời gian thực là gì và những chỉ số nào có thể được sử dụng để
đo lường hoặc ít nhất là ước lượng khả năng phù hợp đó?” Để giải quyết câu hỏi đa chiều
này, hãy xem xét, ví dụ, năm tiêu chí của Cardelli (Cardelli, 1996):

C1. Hiệu quả thực thi. Chương trình chạy nhanh như thế nào?
C2. Hiệu quả biên dịch. Mất bao lâu để chuyển từ nhiều tệp nguồn sang một tệp thực thi?
C3. Hiệu quả phát triển quy mô nhỏ. Một lập trình viên cá nhân phải làm việc khó khăn
như thế nào?
C4. Hiệu quả phát triển quy mô lớn. Một nhóm lập trình viên phải làm việc khó khăn như
thế nào?
C5. Hiệu quả của các tính năng ngôn ngữ. Việc học hoặc sử dụng một ngôn ngữ lập trình
khó khăn như thế nào?

- Mỗi ngôn ngữ lập trình không nghi ngờ cung cấp những ưu điểm và nhược điểm riêng
của nó đối với hệ thống thời gian thực, và những tiêu chí định tính này, C1 - C5, có thể
được sử dụng để hiệu chỉnh các tính năng của một ngôn ngữ cụ thể cho việc so sánh cam
- với - cam trong một ứng dụng nhất định. Các tiêu chí Cardelli có thể được minh họa với
một biểu đồ ngũ giác (Sick và Ovaska, 2007) được hiển thị trong Hình 4.2. Những biểu
đồ như vậy cung cấp phương tiện đơn giản để so sánh trực quan các ngôn ngữ lập trình
ứng cử.

- Trong chương này, chúng tôi không có ý định thực hiện một cuộc khảo sát ngôn ngữ lập
trình toàn diện; thay vào đó, chúng tôi tập trung vào những tính năng ngôn ngữ có thể
được sử dụng để giảm thiểu thời gian thực thi mã cuối cùng và có thể dự đoán hiệu suất.
Dự đoán hiệu suất thực thi tại thời điểm biên dịch hỗ trợ trực tiếp cho phân tích khả năng
lập lịch. Trong việc thiết kế các ngôn ngữ lập trình thời gian thực đặc biệt, nhấn mạnh
vào việc loại bỏ những cấu trúc làm cho ngôn ngữ không thể phân tích, ví dụ, đệ quy
không giới hạn và vòng lặp while không giới hạn. Hầu hết các ngôn ngữ “thời gian thực”
được gọi là cố gắng loại bỏ tất cả những điều này. Mặt khác, khi các ngôn ngữ chính
được sử dụng cho lập trình thời gian thực, một số cấu trúc mã gây rắc rối có thể đơn giản
chỉ bị cấm thông qua các tiêu chuẩn mã hóa.

4.1.2 Tiêu chuẩn mã hóa cho phần mềm thời gian thực

- Tiêu chuẩn mã hóa (Li và Prasad, 2005) khác với tiêu chuẩn ngôn ngữ. Một tiêu chuẩn
ngôn ngữ, ví dụ, C++ ANSI/ISO/IEC 14882:2003, thể hiện các quy tắc cú pháp của ngôn
ngữ lập trình C++. Một chương trình nguồn vi phạm bất kỳ quy tắc nào trong số đó sẽ bị
trình biên dịch từ chối. Một tiêu chuẩn mã hóa, mặt khác, là một tập hợp các quy ước
phong cách hoặc “thực hành tốt nhất”. Vi phạm những quy ước này sẽ không dẫn đến
việc từ chối của trình biên dịch. Theo một nghĩa khác, việc tuân thủ tiêu chuẩn ngôn ngữ
là bắt buộc, trong khi việc tuân thủ tiêu chuẩn mã hóa, ít nhất là theo nguyên tắc, là tự
nguyện.

- Tuân thủ tiêu chuẩn ngôn ngữ thúc đẩy khả năng di động trên các trình biên dịch khác
nhau, và do đó, môi trường phần cứng. Tuân thủ tiêu chuẩn mã hóa, mặt khác, sẽ không
thúc đẩy khả năng di động, nhưng thay vào đó, trong nhiều trường hợp, khả năng đọc,
bảo dưỡng và tái sử dụng. Một số người thực hành thậm chí cho rằng việc sử dụng tiêu
chuẩn mã hóa nghiêm ngặt có thể tăng độ tin cậy của phần mềm. Tiêu chuẩn mã hóa
cũng có thể được sử dụng để thúc đẩy hiệu suất cải thiện bằng cách khuyến khích hoặc
yêu cầu sử dụng một số cấu trúc ngôn ngữ được biết đến là tạo ra mã hiệu quả hơn. Nhiều
phương pháp linh hoạt, ví dụ, eXtreme Programming (Hedin et al., 2003), chấp nhận các
tiêu chuẩn mã hóa đặc biệt.

- Tiêu chuẩn mã hóa thường liên quan đến việc chuẩn hóa một số hoặc tất cả các yếu tố
sau của việc sử dụng ngôn ngữ lập trình:

• Định dạng tiêu đề


• Tần suất, độ dài và phong cách của các bình luận
• Đặt tên cho các lớp, dữ liệu, tệp, phương thức, thủ tục, biến, v.v.
• Định dạng mã nguồn chương trình, bao gồm việc sử dụng không gian trắng và thụt
lề
• Giới hạn kích thước về đơn vị mã, bao gồm số dòng mã tối đa và tối thiểu, và số
phương thức được sử dụng Quy tắc về lựa chọn cấu trúc ngôn ngữ để sử dụng; ví
dụ, khi nào sử dụng câu lệnh case thay vì câu lệnh if-then-else lồng nhau
- Mặc dù không rõ ràng việc tuân thủ những quy tắc này có thúc đẩy sự cải thiện đáng kể
về độ tin cậy hay không, rõ ràng việc tuân thủ chặt chẽ có thể làm cho chương trình dễ
đọc và hiểu hơn, và do đó có khả năng tái sử dụng và bảo dưỡng hơn (Hatton, 1995).

- Có nhiều chuẩn mã hóa khác nhau, có thể độc lập với ngôn ngữ hoặc phụ thuộc vào
ngôn ngữ. Chuẩn mã hóa có thể được áp dụng trên toàn công ty, toàn nhóm, nhóm người
dùng cụ thể (ví dụ, nhóm phần mềm GNU có chuẩn cho C và C++), hoặc khách hàng có
thể yêu cầu tuân thủ một chuẩn cụ thể của họ. Hơn nữa, còn có những chuẩn khác đã
được công bố. Một ví dụ là chuẩn ký hiệu Hungary (Petzold, 1999), được đặt theo tên
Charles Simonyi, người được công nhận là người đầu tiên tuyên truyền việc sử dụng nó.
Ký hiệu Hungary là một chuẩn công cộng dự định được sử dụng với các ngôn ngữ hướng
đối tượng, đặc biệt là C++. Chuẩn này sử dụng một lược đồ đặt tên có mục đích để nhúng
thông tin về kiểu của các đối tượng, phương thức, thuộc tính và biến vào tên. Bởi vì
chuẩn này cung cấp một tập hợp các quy tắc về việc đặt tên các định danh khác nhau, nó
có thể và đã được sử dụng với các ngôn ngữ khác, như Ada, Java, và thậm chí C. Một ví
dụ khác là trong Java, theo quy ước, sử dụng tất cả chữ in hoa cho các hằng số như PI và
E. Hơn nữa, một số lớp sử dụng dấu gạch dưới cuối để phân biệt một thuộc tính như x_
với một phương thức như x().

- Một vấn đề chung với các chuẩn phong cách, như ký hiệu Hungary, là chúng có thể dẫn
đến việc tên biến bị biến dạng, và chúng hướng sự chú ý của lập trình viên vào cách đặt
tên theo “Hungary” thay vì chọn một tên biến có ý nghĩa cho việc sử dụng trong mã. Nói
cách khác, mong muốn tuân thủ chuẩn có thể không luôn dẫn đến một tên biến đặc biệt
có ý nghĩa. Một vấn đề khác là sức mạnh chính của một chuẩn mã hóa cũng có thể làm
hỏng nó. Ví dụ, trong ký hiệu Hungary, nếu thông tin về kiểu được nhúng vào tên đối
tượng thực sự sai? Không có cách nào cho bất kỳ trình biên dịch nào nhận ra lỗi này. Có
những công cụ thương mại kiểm tra quy tắc, nhắc đến công cụ kiểm tra ngôn ngữ C, lint,
có thể được điều chỉnh để thực thi các chuẩn mã hóa, nhưng chúng phải được lập trình để
làm việc cùng với trình biên dịch. Hơn nữa, chúng có thể bỏ sót một số không nhất quán,
dẫn đến việc các nhà phát triển có cảm giác tự tin sai lầm.

- Cuối cùng, việc áp dụng chuẩn mã hóa không được khuyến nghị giữa dự án. Việc bắt
đầu tuân thủ từ đầu dễ dàng và động viên hơn so với việc được yêu cầu thay đổi phong
cách hiện tại để tuân thủ. Quyết định sử dụng một chuẩn mã hóa cụ thể là một quyết định
tổ chức đòi hỏi sự suy nghĩ kỹ lưỡng và thảo luận mở.

4.2 NGÔN NGỮ ASSEMBLY

- Từ giữa đến cuối những năm 1970, khi các ngôn ngữ cấp cao đầu tiên trở nên sẵn có
cho vi xử lý, một số giáo viên đại học đã nói với sinh viên của họ rằng “trong năm năm
tới, không ai sẽ viết các ứng dụng thực sự bằng ngôn ngữ assembly”. Tuy nhiên, sau hơn
30 năm từ những ngày đó, ngôn ngữ assembly vẫn đóng một vai trò liên tục, nhưng hạn
chế trong lập trình thời gian thực.
- Lý do đằng sau điều này là gì? Dù thiếu tính thân thiện với người dùng và các tính năng
năng suất của ngôn ngữ cấp cao, ngôn ngữ assembly có một lợi thế đặc biệt khi sử dụng
trong lập trình thời gian thực; nó cung cấp sự kiểm soát trực tiếp nhất của phần cứng máy
tính so với ngôn ngữ cấp cao. Lợi thế này đã mở rộng việc sử dụng ngôn ngữ assembly
trong hệ thống thời gian thực, mặc dù ngôn ngữ assembly không có cấu trúc và có các
thuộc tính trừu tượng rất hạn chế. Hơn nữa, cú pháp ngôn ngữ assembly thay đổi rất
nhiều từ bộ xử lý này sang bộ xử lý khác. Việc lập trình bằng ngôn ngữ assembly, nói
chung, mất thời gian để học, chán chường và dễ mắc lỗi. Cuối cùng, mã kết quả không dễ
dàng chuyển qua các bộ xử lý khác nhau, và do đó việc sử dụng ngôn ngữ assembly trong
hệ thống thời gian thực nhúng - hoặc trong bất kỳ hệ thống chuyên nghiệp nào - nên được
ngăn chặn.

- Một thế hệ trước, những lập trình viên giỏi nhất có thể tạo ra mã assembly thường hiệu
quả hơn mã được tạo ra bởi trình biên dịch ngôn ngữ thủ tục. Nhưng với những cải tiến
đáng kể trong trình biên dịch tối ưu hóa trong những thập kỷ qua, điều này hiếm khi xảy
ra ngày nay - nếu bạn có thể viết chương trình của mình bằng một ngôn ngữ thủ tục như
C, trình biên dịch nên có thể tạo ra mã máy rất hiệu quả về tốc độ thực thi và sử dụng bộ
nhớ. Do đó, nhu cầu viết mã assembly chỉ tồn tại trong các trường hợp đặc biệt khi trình
biên dịch không hỗ trợ một số hướng dẫn ngôn ngữ máy, hoặc khi các ràng buộc về thời
gian quá chặt chẽ đến nỗi chỉnh sửa thủ công là cách duy nhất để tạo ra mã đáp ứng yêu
cầu thời gian phản hồi cực đoan. Hơn nữa, bạn sẽ tìm thấy mã ngôn ngữ assembly trong
nhiều ứng dụng thời gian thực cũ, và ngay cả ngày nay bạn vẫn có thể gặp phải tình
huống khi một phần nhỏ của hệ thống thời gian thực cần được viết bằng ngôn ngữ
assembly. Chúng tôi sẽ thảo luận về một số tình huống này ngay sau đây.

- Về tiêu chí của Cardelli về các nền kinh tế khác nhau, ngôn ngữ assembly có nền kinh
tế thực thi xuất sắc, và rỗng rãi, của biên dịch cũng vậy vì chúng không được biên dịch.
Tuy nhiên, ngôn ngữ assembly có nền kinh tế phát triển quy mô nhỏ và lớn kém cùng với
các tính năng ngôn ngữ (xem Hình 4.2). Do đó, lập trình ngôn ngữ assembly nên được
giới hạn sử dụng trong các tình huống thời gian chặt chẽ hoặc trong việc kiểm soát các
tính năng phần cứng không được hỗ trợ bởi trình biên dịch. Vai trò tiếp tục của ngôn ngữ
assembly trong thập kỷ này được tóm tắt dưới đây:

• Đối với một số loại mã, chẳng hạn như trình xử lý ngắt và cho trình điều khiển
thiết bị cho phần cứng độc đáo nơi “khoảng cách trí tuệ” giữa phần cứng và phần
mềm cần được giảm thiểu.
• Đối với các tình huống mà hiệu suất dự đoán cho mã là rất khó hoặc không thể đạt
được do tương tác không mong muốn giữa ngôn ngữ lập trình - trình biên dịch.
• Để sử dụng hiệu quả tất cả các tính năng kiến trúc của CPU, ví dụ, các bộ cộng và
nhân song song.
• Để viết mã với thời gian thực thi tối thiểu có thể đạt được cho các ứng dụng quan
trọng về thời gian, chẳng hạn như các thuật toán xử lý tín hiệu phức tạp với tốc độ
lấy mẫu cao.
Để viết toàn bộ phần mềm cho CPU được thiết kế tùy chỉnh với một bộ lệnh nhỏ
(xem Mục 2.5.3) - nếu không có hỗ trợ ngôn ngữ cấp cao.
• Để gỡ lỗi các vấn đề khó khăn dưới cấp độ mã ngôn ngữ cấp cao và truy vết dòng
lệnh được tìm nạp bởi một máy phân tích logic.
• Để dạy và học về kiến trúc máy tính và hoạt động nội bộ của bộ xử lý.

- Để đối phó với những tình huống đặc biệt này, người phát triển phần mềm thường sẽ
viết một lớp vỏ của chương trình bằng một ngôn ngữ cấp cao và biên dịch mã thành một
biểu diễn assembly trung gian, sau đó được tinh chỉnh thủ công để đạt được hiệu quả
mong muốn. Một số ngôn ngữ, như Ada, cung cấp một cách để mã assembly được đặt
inline với mã ngôn ngữ cấp cao. Trong bất kỳ trường hợp nào, việc sử dụng ngôn ngữ
assembly trong một hệ thống thời gian thực phải được thực hiện một cách miễn cưỡng và
với sự cảnh giác cực độ.

4.3 NGÔN NGỮ THỦ TỤC

- Ngôn ngữ thủ tục như Ada, C, Fortran và Visual Basic, là những ngôn ngữ mà hành
động của chương trình được xác định bởi một tập hợp các hoạt động được thực hiện theo
trình tự. Những ngôn ngữ này được đặc trưng bởi các tiện ích cho phép hướng dẫn được
nhóm lại thành các thủ tục hoặc mô-đun. Cấu trúc thích hợp của các thủ tục cho phép đạt
được các thuộc tính mong muốn của phần mềm, ví dụ, tính mô-đun, độ tin cậy và khả
năng tái sử dụng.

- Có một số tính năng ngôn ngữ lập trình nổi bật trong ngôn ngữ thủ tục quan tâm đến hệ
thống thời gian thực, đặc biệt là:

• Tính mô-đun
• Kiểu mạnh
• Kiểu dữ liệu trừu tượng
• Cơ chế truyền tham số linh hoạt
• Cơ sở cấp phát bộ nhớ động
• Xử lý ngoại lệ

- Những tính năng ngôn ngữ này, sẽ được thảo luận ngắn gọn, giúp thúc đẩy các thuộc
tính mong muốn của thiết kế phần mềm và các thực hành triển khai thời gian thực tốt
nhất.

4.3.1 Vấn đề Mô-đun và Kiểu


- Ngôn ngữ thủ tục phù hợp với nguyên tắc ẩn thông tin có xu hướng thúc đẩy việc xây
dựng hệ thống thời gian thực có độ toàn vẹn cao. Trong khi C và Fortran cả hai đều có
các cơ chế có thể hỗ trợ ẩn thông tin (thủ tục và phụ chương), ngôn ngữ khác, như Ada,
có xu hướng thúc đẩy thiết kế mô-đun hơn do yêu cầu phải có đầu vào và đầu ra rõ ràng
trong danh sách tham số mô-đun.

- Trong Ada, khái niệm về một gói thể hiện rõ ràng khái niệm ẩn thông tin của Parnas
(Parnas, 1972). Gói Ada bao gồm một đặc tả và các khai báo bao gồm giao diện công
khai hoặc hiển thị của nó và các yếu tố riêng tư hoặc không hiển thị của nó. Ngoài ra,
phần thân của gói, có nhiều thành phần không hiển thị bên ngoài hơn, chứa mã làm việc
của gói. Các gói riêng lẻ là các thực thể có thể biên dịch riêng, điều này càng làm tăng
ứng dụng của chúng như hộp đen. Hơn nữa, ngôn ngữ C cung cấp cho các mô-đun được
biên dịch riêng và các tính năng khác thúc đẩy một cách tiếp cận thiết kế từ trên xuống
nghiêm ngặt, điều này nên dẫn đến một thiết kế mô-đun vững chắc.

- Trong khi phần mềm mô-đun là mong muốn vì nhiều lý do, có một giá phải trả trong chi
phí liên quan đến cuộc gọi thủ tục và việc truyền tham số thiết yếu. Hiệu ứng tiêu cực này
nên được xem xét cẩn thận khi xác định kích thước mô-đun.

- Ngôn ngữ kiểu yêu cầu mỗi biến và hằng số phải thuộc một kiểu cụ thể (ví dụ, Boolean,
integer, hoặc real), và mỗi kiểu phải được khai báo như vậy trước khi sử dụng. Ngôn ngữ
kiểu mạnh cấm việc kết hợp các kiểu khác nhau trong các hoạt động và gán, và do đó
buộc lập trình viên phải chính xác về cách xử lý dữ liệu. Kiểu chính xác có thể ngăn chặn
việc hủy dữ liệu thông qua việc chuyển đổi kiểu không mong muốn hoặc không cần thiết.
Hơn nữa, kiểm tra kiểu của trình biên dịch là một bước quan trọng để tìm lỗi tại thời gian
biên dịch, thay vì tại thời gian chạy, khi chúng tốn kém hơn để sửa chữa. Do đó, ngôn
ngữ kiểu mạnh thực sự mong muốn cho hệ thống thời gian thực.

- Nói chung, các ngôn ngữ cấp cao cung cấp các kiểu số nguyên và số thực, cùng với các
kiểu Boolean, ký tự và chuỗi. Trong một số trường hợp, cũng hỗ trợ các kiểu dữ liệu trừu
tượng. Những điều này cho phép lập trình viên định nghĩa các kiểu của riêng họ cùng với
các hoạt động liên quan. Tuy nhiên, việc sử dụng các kiểu dữ liệu trừu tượng có thể gây
ra hậu quả về thời gian thực thi, vì thường cần các biểu diễn nội bộ phức tạp để hỗ trợ sự
trừu tượng.

- Một số ngôn ngữ được kiểu, nhưng không cấm việc kết hợp các kiểu trong các hoạt
động số học. Vì những ngôn ngữ này thường thực hiện các phép tính kết hợp bằng cách
sử dụng kiểu có độ phức tạp lưu trữ cao nhất, chúng phải thăng cấp tất cả các biến lên
kiểu cao nhất đó. Ví dụ, trong C, đoạn mã sau đây minh họa việc thăng cấp và hạ cấp tự
động của các kiểu biến:
- Ở đây, biến x sẽ được thăng cấp lên kiểu float (số thực) và sau đó phép nhân và phép
cộng sẽ diễn ra trong số thực. Sau đó, kết quả sẽ được cắt bớt và lưu trữ trong y dưới
dạng số nguyên. Tác động tiêu cực về hiệu suất là việc thăng cấp ẩn và hướng dẫn số học
tốn thời gian hơn được tạo ra, mà không đạt được độ chính xác thêm. Độ chính xác có thể
bị mất do việc cắt bớt, hoặc tồi tệ hơn, có thể xảy ra tràn số nguyên nếu giá trị thực lớn
hơn giá trị nguyên cho phép. Các chương trình được viết bằng ngôn ngữ kiểu yếu cần
được kiểm tra kỹ lưỡng vì những hiệu ứng như vậy. May mắn thay, hầu hết các trình biên
dịch C có thể được điều chỉnh để bắt lỗi không khớp kiểu trong các tham số hàm, ngăn
chặn việc chuyển đổi kiểu không mong muốn.

4.3.2 Truyền Tham số và Cấp phát Bộ nhớ Động

- Có một số phương pháp truyền tham số, bao gồm việc sử dụng danh sách tham số và
biến toàn cục. Mặc dù mỗi kỹ thuật này có các ứng dụng ưa thích, mỗi kỹ thuật đều có
một tác động hiệu suất khác nhau. Lưu ý rằng các cơ chế truyền tham số này cũng được
tìm thấy trong ngôn ngữ lập trình hướng đối tượng.

- Hai phương pháp truyền tham số phổ biến nhất là gọi - theo - giá trị và gọi - theo - tham
chiếu. Trong việc truyền tham số theo giá trị, giá trị của tham số thực tế trong cuộc gọi
thủ tục được sao chép vào tham số chính thức của thủ tục. Vì thủ tục chỉ thao tác trên
tham số chính thức, tham số thực tế không bị thay đổi. Kỹ thuật này hữu ích khi một bài
kiểm tra đang được thực hiện hoặc đầu ra là một hàm của các tham số đầu vào. Ví dụ, khi
truyền các đọc giá trị từ gia tốc kế từ chu kỳ 10 ms sang chu kỳ 40 ms, dữ liệu thô không
cần phải được trả về cho routine gọi trong dạng đã thay đổi. Khi các tham số được truyền
bằng cách gọi - theo - giá trị, chúng được sao chép lên ngăn xếp thời gian chạy, tạo ra chi
phí thời gian thực thi thêm.

- Trong việc gọi - theo - tham chiếu (hoặc gọi - theo - địa chỉ), địa chỉ của tham số được
truyền bởi routine gọi đến thủ tục được gọi để nội dung bộ nhớ tương ứng có thể được
thay đổi ở đó. Thực thi một thủ tục sử dụng gọi - theo - tham chiếu có thể mất thời gian
lâu hơn một thủ tục sử dụng gọi - theo - giá trị, vì trong gọi - theo - tham chiếu, cần các
hướng dẫn chế độ địa chỉ gián tiếp cho bất kỳ hoạt động nào liên quan đến các biến được
truyền. Tuy nhiên, trong trường hợp truyền các cấu trúc dữ liệu lớn, chẳng hạn như bộ
đệm giữa các thủ tục, việc sử dụng gọi - theo - tham chiếu là mong muốn hơn, vì việc
truyền một con trỏ hiệu quả hơn so với việc truyền dữ liệu theo byte.

- Danh sách tham số có khả năng thúc đẩy thiết kế mô-đun vì các giao diện giữa các mô-
đun được xác định rõ ràng. Các giao diện được xác định rõ ràng có thể giảm thiểu khả
năng hỏng dữ liệu không theo dõi được do các thủ tục sử dụng truy cập toàn cục. Tuy
nhiên, cả hai kỹ thuật truyền tham số gọi - theo - giá trị và gọi - theo - tham chiếu có thể
ảnh hưởng đến hiệu suất thời gian thực khi danh sách dài, vì ngắt thường bị vô hiệu hóa
trong quá trình truyền tham số để bảo vệ tính toàn vẹn của dữ liệu được truyền. Hơn nữa,
gọi - theo - tham chiếu có thể giới thiệu các hiệu ứng phụ tế nhị của hàm, tùy thuộc vào
trình biên dịch.

- Trước khi quyết định về một tập hợp cụ thể các quy tắc liên quan đến việc truyền tham
số để đạt hiệu suất tối ưu, nên xây dựng một tập hợp các trường hợp kiểm tra thực hiện
các phương án khác nhau. Những trường hợp kiểm tra này cần được chạy lại mỗi khi
trình biên dịch, phần cứng hoặc ứng dụng thay đổi để cập nhật các quy tắc.

- Biến toàn cục là các biến nằm trong phạm vi của tất cả mã. “Trong phạm vi” thường có
nghĩa là có thể tham chiếu đến các biến này với số lượng lấy dữ liệu tối thiểu để giải
quyết địa chỉ mục tiêu, và do đó nhanh hơn so với tham chiếu đến các biến được truyền
qua danh sách tham số, yêu cầu thêm các tham chiếu bộ nhớ. Ví dụ, trong nhiều ứng
dụng xử lý hình ảnh, các mảng toàn cục được định nghĩa để đại diện cho toàn bộ hình
ảnh, do đó cho phép tránh việc truyền tham số tốn kém.

- Tuy nhiên, biến toàn cục rất nguy hiểm vì có thể tham chiếu đến chúng bởi mã không
được ủy quyền, có thể giới thiệu các lỗi khó phân lập. Việc sử dụng biến toàn cục cũng vi
phạm nguyên tắc ẩn thông tin, làm cho mã khó hiểu và bảo dưỡng. Do đó, việc sử dụng
không cần thiết và phóng túng biến toàn cục nên được tránh. Truyền tham số toàn cục chỉ
được khuyến nghị khi ràng buộc thời gian yêu cầu như vậy, hoặc nếu việc sử dụng danh
sách tham số dẫn đến mã bị che mờ. Trong mọi trường hợp, việc sử dụng biến toàn cục
phải được điều phối nghiêm ngặt và được ghi chép rõ ràng.

- Quyết định sử dụng một phương pháp truyền tham số hay phương pháp khác có thể đại
diện cho sự đánh đổi giữa thực hành kỹ thuật phần mềm tốt và nhu cầu về hiệu suất. Ví
dụ, thường xuyên ràng buộc thời gian buộc phải sử dụng truyền tham số toàn cục trong
các trường hợp khi danh sách tham số sẽ được ưa chuộng vì sự rõ ràng và khả năng bảo
dưỡng.

- Hầu hết các ngôn ngữ lập trình cung cấp đệ quy trong đó một thủ tục có thể tự gọi hoặc
sử dụng chính nó trong việc xây dựng. Mặc dù đệ quy có thể thanh lịch và đôi khi cần
thiết, nhưng tác động tiêu cực của nó đối với hiệu suất thời gian thực phải được xem xét.
Cuộc gọi thủ tục yêu cầu cấp phát bộ nhớ trên ngăn xếp để truyền tham số và để lưu trữ
các biến cục bộ. Thời gian thực thi cần thiết để cấp phát và giải phóng, cũng như để lưu
trữ và lấy lại các tham số và biến cục bộ đó, có thể tốn kém. Ngoài ra, đệ quy yêu cầu sử
dụng một số lượng lớn các hướng dẫn gián tiếp bộ nhớ và đăng ký đắt tiền. Hơn nữa, cần
phải thận trọng để đảm bảo rằng routine đệ quy sẽ kết thúc, nếu không ngăn xếp thời gian
chạy sẽ cuối cùng tràn ra. Việc sử dụng đệ quy thường làm cho việc xác định chính xác
kích thước yêu cầu bộ nhớ thời gian chạy trở nên không thể. Do đó, các kỹ thuật lặp lại,
như vòng lặp while và for, phải được sử dụng nơi hiệu suất và quyết định là quan trọng
hoặc tự nhiên trong những ngôn ngữ không hỗ trợ đệ quy.
- Khả năng cấp phát bộ nhớ một cách linh hoạt rất quan trọng trong việc xây dựng và duy
trì nhiều cấu trúc dữ liệu cần thiết trong các hệ thống thời gian thực. Mặc dù việc cấp
phát bộ nhớ động có thể tốn thời gian, nhưng nó là cần thiết, đặc biệt trong việc xây dựng
trình xử lý ngắt, quản lý bộ nhớ, và những thứ tương tự. Danh sách liên kết, cây, đống, và
các cấu trúc dữ liệu động khác có thể được hưởng lợi từ sự rõ ràng và tiết kiệm được giới
thiệu bởi việc cấp phát bộ nhớ động. Hơn nữa, trong trường hợp chỉ sử dụng con trỏ để
truyền cấu trúc dữ liệu, chi phí phát sinh cho việc cấp phát động có thể hợp lý. Tuy nhiên,
khi lập trình các hệ thống thời gian thực, cần phải chú ý để đảm bảo rằng trình biên dịch
sẽ luôn truyền con trỏ đến các cấu trúc dữ liệu lớn chứ không phải chính cấu trúc dữ liệu
đó.

- Các ngôn ngữ không cho phép cấp phát bộ nhớ động, ví dụ, một số ngôn ngữ cấp cao
nguyên thủy hoặc ngôn ngữ hợp dẫn yêu cầu cấu trúc dữ liệu có kích thước cố định. Mặc
dù điều này có thể nhanh hơn, nhưng sự linh hoạt bị hy sinh và yêu cầu bộ nhớ phải được
xác định trước. Các ngôn ngữ thủ tục hiện đại, như Ada, C và Fortran 2003, có các cơ sở
cấp phát động.

4.3.3 Xử lý ngoại lệ

- Một số ngôn ngữ lập trình cung cấp các cơ sở để xử lý lỗi hoặc các điều kiện bất thường
khác có thể xảy ra trong quá trình thực hiện chương trình. Những điều kiện này bao gồm
những điều rõ ràng, chẳng hạn như tràn số dạng chấm động, căn bậc hai của một đối số
âm, chia cho không, cũng như những điều có thể được người dùng định nghĩa. Khả năng
định nghĩa và xử lý các điều kiện ngoại lệ trong ngôn ngữ cấp cao giúp trong việc xây
dựng trình xử lý ngắt và mã quan trọng khác được sử dụng cho xử lý sự kiện thời gian
thực. Hơn nữa, việc xử lý ngoại lệ kém có thể làm giảm hiệu suất. Ví dụ, lỗi tràn số dạng
chấm động có thể lan truyền dữ liệu xấu qua thuật toán và khởi động các routine phục hồi
lỗi tốn thời gian.

- Trong ANSI - C, các cơ sở raise và signal được cung cấp để tạo trình xử lý ngoại lệ.
Một tín hiệu là một loại trình xử lý ngắt phần mềm được sử dụng để phản ứng với một
ngoại lệ được chỉ ra bởi hoạt động raise. Cả hai đều được cung cấp dưới dạng lời gọi
hàm, thường được thực hiện dưới dạng macro.

- Nguyên mẫu sau có thể được sử dụng như mặt trước cho một trình xử lý ngoại lệ để
phản ứng với tín hiệu S.

- Khi tín hiệu S được đặt, hàm func được gọi. Hàm này đại diện cho trình xử lý ngắt thực
sự. Ngoài ra, chúng ta cần một nguyên mẫu bổ sung:
Ở đây, raise được sử dụng để gọi nhiệm vụ phản ứng với tín hiệu S.

- ANSI - C bao gồm một số tín hiệu được định nghĩa trước cần thiết để xử lý các điều
kiện bất thường, chẳng hạn như tràn, vi phạm truy cập bộ nhớ, và hướng dẫn bất hợp
pháp, nhưng những tín hiệu này có thể được thay thế bằng những tín hiệu do người dùng
định nghĩa. Mã C sau đây mô tả một trình xử lý ngoại lệ chung phản ứng với một điều
kiện lỗi nhất định:

- Trong ngôn ngữ C, lời gọi hàm thư viện signal được sử dụng để xây dựng trình xử lý
ngắt để phản ứng với một tín hiệu từ phần cứng bên ngoài và để xử lý một số bẫy, chẳng
hạn như tràn số dạng chấm động, bằng cách thay thế các trình xử lý thư viện C chuẩn.

- Trong số các ngôn ngữ thủ tục được thảo luận trong chương này, Ada có cơ sở xử lý
ngoại lệ rõ ràng nhất. Xem xét một trình xử lý ngoại lệ Ada để xác định xem một ma trận
vuông có đơn vị hay không (tức là, định thức của nó bằng không). Giả sử rằng một loại
ma trận đã được định nghĩa, và nó có thể xác định rằng ma trận là đơn vị. Một đoạn mã
liên quan có thể là:

- Ở đây, từ khóa exception được sử dụng để chỉ ra rằng đây là một trình xử lý ngoại lệ và
từ khóa raise đóng một vai trò tương tự như raise trong trình xử lý ngoại lệ C vừa được
trình bày. Định nghĩa của SINGULAR, đại diện cho một ma trận có định thức bằng
không, được định nghĩa ở nơi khác, chẳng hạn như trong một tệp tiêu đề.

4.3.4 Số liệu của Cardelli và Ngôn ngữ Thủ tục

- Lấy tập hợp chung của các ngôn ngữ thủ tục như một thể thống nhất, Cardelli đã xem
xét chúng để sử dụng trong các hệ thống thời gian thực liên quan đến tiêu chí của ông.
Nhận xét của ông được tóm tắt trong cuộc thảo luận trước đó. Đầu tiên, ông lưu ý rằng
kiểu biến đã được giới thiệu để cải thiện việc tạo mã. Do đó, hiệu quả thực thi cao cho
các ngôn ngữ thủ tục miễn là trình biên dịch hiệu quả. Hơn nữa, vì các mô-đun có thể
được biên dịch độc lập, biên dịch các hệ thống lớn là hiệu quả, ít nhất là khi các giao diện
ổn định. Những khía cạnh thách thức hơn của việc tích hợp hệ thống do đó đã được loại
bỏ.

- - Phát triển quy mô nhỏ là kinh tế vì kiểm tra kiểu có thể bắt được nhiều lỗi mã, giảm
bớt nỗ lực kiểm tra và gỡ lỗi. Những lỗi xảy ra dễ dàng hơn để gỡ lỗi, đơn giản vì các lớp
lỗi lớn khác đã được loại trừ. Cuối cùng, các lập trình viên có kinh nghiệm thường áp
dụng một phong cách mã hóa khiến một số lỗi logic xuất hiện như lỗi kiểm tra kiểu; do
đó, họ có thể sử dụng trình kiểm tra kiểu như một công cụ phát triển. Ví dụ, việc thay đổi
tên của một kiểu khi các bất biến của nó thay đổi mặc dù cấu trúc kiểu vẫn giữ nguyên sẽ
tạo ra báo cáo lỗi trên tất cả các sử dụng trước đó của nó.
- Hơn nữa, trừu tượng dữ liệu và modular hóa có những lợi thế phương pháp học cho việc
phát triển mã quy mô lớn. Các đội lập trình viên lớn có thể thương lượng các giao diện
cần được thực hiện, và sau đó tiến hành riêng để thực hiện các đoạn mã tương ứng. Sự
phụ thuộc giữa các đoạn mã như vậy được giảm thiểu, và mã có thể được sắp xếp lại cục
bộ mà không sợ ảnh hưởng toàn cầu.

- Cuối cùng, các ngôn ngữ thủ tục là kinh tế vì một số cấu trúc được thiết kế tốt có thể
được tự nhiên soạn thảo theo các cách trực giao. Ví dụ, trong C, một mảng của các mảng
mô hình hóa các mảng hai chiều. Tính trực giao của các tính năng ngôn ngữ giảm độ
phức tạp của một ngôn ngữ lập trình. Đường học tập cho lập trình viên do đó được giảm,
và nỗ lực học lại liên tục cần thiết khi sử dụng các ngôn ngữ phức tạp được tối thiểu hóa
(Cardelli, 1996).

4.4 NGÔN NGỮ HƯỚNG ĐỐI TƯỢNG

- Lợi ích của các ngôn ngữ hướng đối tượng, như nâng cao năng suất lập trình viên, tăng
độ tin cậy của phần mềm, và tiềm năng tái sử dụng mã cao hơn, đã được biết đến và đánh
giá cao. Các ngôn ngữ hướng đối tượng bao gồm Ada, C++, C#, và Java. Một cách chính
thức, các ngôn ngữ lập trình hướng đối tượng là những ngôn ngữ hỗ trợ trừu tượng hóa
dữ liệu, kế thừa, đa hình, và thông điệp.

- Đối tượng là một cách hiệu quả để quản lý sự phức tạp tăng lên của các hệ thống thời
gian thực, vì chúng cung cấp một môi trường tự nhiên cho việc ẩn thông tin, hoặc biến
thể được bảo vệ và đóng gói. Trong đóng gói, một lớp đối tượng và các phương thức liên
quan đến chúng được bao gồm hoặc đóng gói trong các định nghĩa lớp. Một đối tượng chỉ
có thể sử dụng dữ liệu được đóng gói của đối tượng khác bằng cách gửi một thông điệp
đến đối tượng đó với tên của phương thức cần áp dụng. Ví dụ, xem xét vấn đề sắp xếp
đối tượng. Một phương thức có thể tồn tại để sắp xếp một lớp đối tượng của số nguyên
theo thứ tự tăng dần. Một lớp người có thể được sắp xếp theo chiều cao của họ. Một lớp
đối tượng hình ảnh có một thuộc tính màu sắc có thể được sắp xếp theo thuộc tính đó. Tất
cả những đối tượng này đều có một phương thức thông điệp so sánh với các cách triển
khai khác nhau. Do đó, nếu một khách hàng gửi một thông điệp để so sánh một trong
những đối tượng này với một đối tượng khác, mã thời gian chạy phải giải quyết phương
thức nào để áp dụng một cách động — với rõ ràng là hình phạt thời gian thực thi. Vấn đề
này sẽ được thảo luận ngắn gọn sau đây.

- Các ngôn ngữ hướng đối tượng cung cấp một môi trường màu mỡ cho việc ẩn thông tin;
ví dụ, trong các hệ thống xử lý hình ảnh, có thể hữu ích khi định nghĩa một lớp kiểu
pixel, với các thuộc tính mô tả vị trí, màu sắc, và độ sáng của nó, và các hoạt động có thể
được áp dụng cho một pixel, như thêm, kích hoạt, và hủy kích hoạt. Cũng có thể mong
muốn định nghĩa các đối tượng kiểu hình ảnh như một tập hợp các pixel với các thuộc
tính khác như chiều rộng, chiều cao, và vân vân. Trong một số trường hợp, biểu thức của
chức năng hệ thống dễ dàng hơn khi thực hiện theo cách hướng đối tượng.
4.4.1 Đồng bộ hóa đối tượng và thu gom rác

- Thay vì mở rộng các lớp thông qua kế thừa, trên thực tế, thường thích hợp hơn để sử
dụng tổng hợp. Tuy nhiên, khi làm như vậy, cần phải hỗ trợ các chính sách đồng bộ hóa
khác nhau cho các đối tượng, do các ngữ cảnh sử dụng khác nhau. Cụ thể, xem xét các
chính sách đồng bộ hóa phổ biến sau đây cho các đối tượng:

• Đối tượng Đồng bộ hóa. Một đối tượng đồng bộ hóa, như mutex, được liên kết
với một đối tượng có thể được truy cập đồng thời bởi nhiều luồng. Nếu sử dụng
khóa nội bộ, thì khi vào phương thức, mỗi phương thức công cộng sẽ mở khóa
trên đối tượng đồng bộ hóa liên quan và giải phóng khóa khi thoát khỏi phương
thức. Nếu sử dụng khóa bên ngoài, thì khách hàng có trách nhiệm mở khóa trên
đối tượng đồng bộ hóa liên quan trước khi truy cập đối tượng và sau đó giải phóng
khóa khi hoàn thành.
• Đối tượng Đóng gói. Khi một đối tượng được đóng gói trong một đối tượng khác
(tức là, đối tượng được đóng gói không thể truy cập bên ngoài đối tượng bao
quanh), việc mở khóa trên đối tượng được đóng gói là thừa, vì khóa của đối tượng
bao quanh cũng bảo vệ đối tượng được đóng gói. Do đó, các hoạt động trên các
đối tượng được đóng gói không yêu cầu đồng bộ hóa.
• Đối tượng Cục bộ Luồng. Các đối tượng chỉ được truy cập bởi một luồng duy
nhất không yêu cầu đồng bộ hóa.
• Đối tượng Di chuyển giữa các Luồng. Trong chính sách này, quyền sở hữu của
một đối tượng di chuyển được chuyển giữa các luồng. Khi một luồng chuyển
quyền sở hữu của một đối tượng di chuyển, nó không thể truy cập nó nữa. Khi một
luồng nhận quyền sở hữu của một đối tượng di chuyển, nó được đảm bảo có quyền
truy cập độc quyền vào nó (tức là, đối tượng di chuyển là cục bộ đối với luồng).
Do đó, các đối tượng di chuyển không yêu cầu đồng bộ hóa. Tuy nhiên, việc
chuyển giao quyền sở hữu đòi hỏi đồng bộ hóa.
• Đối tượng Bất biến. Trạng thái của một đối tượng bất biến không bao giờ được
sửa đổi sau khi nó được khởi tạo. Do đó, các đối tượng bất biến không yêu cầu
đồng bộ hóa khi được truy cập bởi nhiều luồng vì tất cả các truy cập đều chỉ đọc.
• Đối tượng Không đồng bộ. Các đối tượng trong một chương trình đơn luồng
không yêu cầu đồng bộ hóa.

- Để minh họa sự cần thiết của việc hỗ trợ tham số hóa các chính sách đồng bộ hóa, xem
xét một thư viện lớp. Một nhà phát triển của một thư viện lớp muốn đảm bảo khán giả
rộng nhất có thể cho thư viện này, vì vậy anh ấy làm cho tất cả các lớp được đồng bộ hóa
để chúng có thể được sử dụng an toàn trong cả các ứng dụng đơn luồng và đa luồng. Tuy
nhiên, khách hàng của thư viện mà ứng dụng của họ là đơn luồng bị phạt không công
bằng với chi phí thực thi không cần thiết của đồng bộ hóa mà họ không cần. Ngay cả các
ứng dụng đa luồng cũng có thể bị phạt không công bằng nếu các đối tượng không yêu cầu
đồng bộ hóa (ví dụ, các đối tượng là cục bộ luồng). Do đó, để thúc đẩy khả năng tái sử
dụng của một thư viện lớp mà không hy sinh hiệu suất, các lớp trong một thư viện lý
tưởng sẽ cho phép khách hàng chọn trên cơ sở từng đối tượng chính sách đồng bộ hóa
nào để sử dụng.

- Rác là bộ nhớ đã được cấp phát nhưng không còn được sử dụng nữa nhưng cũng không
có sẵn ở nơi khác. Sự tích tụ rác quá mức có thể gây hại, và do đó rác phải được thu gom
định kỳ. Các thuật toán thu gom rác nói chung có hiệu suất không thể dự đoán, mặc dù
hiệu suất trung bình có thể được biết. Sự mất định hình kết quả từ số lượng rác không
biết, thời gian gắn thẻ của các cấu trúc dữ liệu không xác định, và thực tế là nhiều thu
gom rác tăng dần yêu cầu rằng mỗi phân bổ bộ nhớ hoặc giải phóng từ heap phải sẵn lòng
phục vụ trình xử lý bẫy trang.

- Hơn nữa, rác có thể được tạo ra trong cả ngôn ngữ thủ tục và hướng đối tượng. Ví dụ,
trong C, rác được tạo ra bằng cách cấp phát bộ nhớ, nhưng không giải phóng nó đúng
cách. Tuy nhiên, rác nói chung được liên kết với các ngôn ngữ hướng đối tượng như C++
và Java. Java đáng chú ý vì môi trường chuẩn bao gồm thu gom rác, trong khi C++ thì
không.

4.4.2 Số liệu của Cardelli và Ngôn ngữ Hướng đối tượng

- Xem xét ngôn ngữ hướng đối tượng trong bối cảnh của các số liệu của Cardelli như
được tóm tắt từ phân tích của ông. Về mặt hiệu quả thực thi, phong cách hướng đối tượng
về bản chất kém hiệu quả hơn phong cách thủ tục. Trong phong cách hướng đối tượng
thuần túy, mọi thủ tục đều được cho là một phương thức. Điều này giới thiệu các sự
chuyển hướng bổ sung thông qua bảng phương thức và ngăn chặn các tối ưu hóa mã đơn
giản, chẳng hạn như inlining. Giải pháp truyền thống cho vấn đề này (phân tích và biên
dịch toàn bộ chương trình) vi phạm tính mô-đun và không áp dụng được cho thư viện.

- Với tư cách là hiệu quả biên dịch, thường không có sự phân biệt giữa mã và giao diện
của một lớp. Một số ngôn ngữ hướng đối tượng không đủ mô-đun và yêu cầu biên dịch
lại các lớp cha khi biên dịch các lớp con. Do đó, thời gian dành cho biên dịch có thể tăng
không tỷ lệ với kích thước của hệ thống.

- Mặt khác, ngôn ngữ hướng đối tượng vượt trội với tư cách là hiệu quả phát triển quy mô
nhỏ. Ví dụ, các lập trình viên cá nhân có thể tận dụng các thư viện lớp và khung công
việc, giảm đáng kể khối lượng công việc của họ. Tuy nhiên, khi phạm vi dự án tăng lên,
lập trình viên phải có khả năng hiểu chi tiết của những thư viện lớp đó, và nhiệm vụ này
hóa ra khó khăn hơn so với việc hiểu các thư viện mô-đun thông thường. Hệ thống kiểu
của hầu hết các ngôn ngữ hướng đối tượng không đủ biểu cảm; lập trình viên thường phải
tìm đến kiểm tra động hoặc các tính năng không an toàn, làm hại độ robust của chương
trình của họ.

- Về mặt hiệu quả phát triển quy mô lớn, nhiều nhà phát triển thường xuyên tham gia
phát triển các thư viện lớp mới và tùy chỉnh các thư viện hiện có. Mặc dù tái sử dụng là
một lợi ích của ngôn ngữ hướng đối tượng, nhưng cũng là trường hợp mà những ngôn
ngữ này có các thuộc tính mô-đun cực kỳ kém liên quan đến mở rộng lớp và sửa đổi
thông qua kế thừa. Ví dụ, rất dễ để ghi đè một phương thức không nên được ghi đè, hoặc
để triển khai lại một lớp theo cách gây ra vấn đề trong các lớp con. Các vấn đề phát triển
quy mô lớn khác bao gồm sự nhầm lẫn giữa các lớp và các kiểu đối tượng, điều này hạn
chế việc xây dựng trừu tượng, và thực tế là đa hình kiểu con không đủ tốt để biểu thị các
lớp chứa.

- Ngôn ngữ hướng đối tượng có hiệu quả thấp về tính năng ngôn ngữ. Ví dụ, C++ dựa
trên một mô hình khá đơn giản, nhưng gây choáng ngợp trong sự phức tạp của nhiều tính
năng của nó. Thật không may, điều bắt đầu như ngôn ngữ kinh tế và đồng nhất (“mọi thứ
đều là một đối tượng”) đã kết thúc như một bộ sưu tập lớn các loại lớp. Java, mặt khác,
đại diện cho một bước tiến hướng đến việc giảm độ phức tạp, nhưng thực tế lại phức tạp
hơn nhiều người nhận ra (Cardelli, 1996).

4.4.3 Ngôn ngữ Hướng đối tượng so với Ngôn ngữ Thủ tục

- Không có sự đồng thuận chung về việc ngôn ngữ nào tốt hơn cho các hệ thống thời gian
thực - ngôn ngữ hướng đối tượng hay ngôn ngữ thủ tục. Điều này một phần do việc có
một loạt các ứng dụng thời gian thực - từ các hệ thống đặt chỗ và đặt vé hàng không
không nhúng đến các cảm biến không dây nhúng trong giày chạy, ví dụ.

- Lợi ích của cách tiếp cận giải quyết vấn đề theo hướng đối tượng và việc sử dụng ngôn
ngữ hướng đối tượng rõ ràng, và đã được mô tả. Hơn nữa, có thể tưởng tượng một số
khía cạnh của hệ điều hành thời gian thực sẽ được hưởng lợi từ việc đưa vào đối tượng,
như quá trình, luồng, tệp hoặc thiết bị. Hơn nữa, một số lĩnh vực ứng dụng rõ ràng có thể
được hưởng lợi từ cách tiếp cận hướng đối tượng. Tuy nhiên, các lập luận chính chống lại
ngôn ngữ lập trình hướng đối tượng cho các hệ thống thời gian thực là chúng có thể dẫn
đến các hệ thống không thể đoán trước và không hiệu quả, và rằng chúng khó để tối ưu
hóa. Dù sao, chúng ta có thể tự tin đề xuất ngôn ngữ hướng đối tượng cho các hệ thống
thời gian thực mềm và cứng.

- Tuy nhiên, lập luận về sự không thể đoán trước khó để bảo vệ, ít nhất là đối với ngôn
ngữ hướng đối tượng, như C++, không sử dụng thu gom rác. Rất có thể một hệ thống có
thể dự đoán - cũng là một hệ thống thời gian thực cứng - có thể được xây dựng dễ dàng
như C++ cũng như C. Tương tự, có lẽ cũng dễ dàng để xây dựng một hệ thống không thể
đoán trước trong C như trong C++. Trường hợp cho các hệ thống không thể đoán trước
hơn khi sử dụng ngôn ngữ hướng đối tượng dễ dàng hơn khi tranh luận về ngôn ngữ thu
gom rác như Java.

- Trong mọi trường hợp, lập luận về sự không hiệu quả chống lại ngôn ngữ hướng đối
tượng là một lập luận mạnh mẽ. Nói chung, có một hình phạt thời gian thực thi trong
ngôn ngữ hướng đối tượng so với ngôn ngữ thủ tục. Hình phạt này một phần do việc liên
kết muộn (giải quyết vị trí bộ nhớ tại thời gian chạy thay vì tại thời gian biên dịch) do đa
hình chức năng, kế thừa, và tổng hợp. Những hiệu ứng này tạo ra các yếu tố trễ đáng kể
và thường không chắc chắn. Một vấn đề khác là do chi phí phát sinh từ các routine thu
gom rác. Một cách để giảm những hình phạt này là không định nghĩa quá nhiều lớp và
chỉ định nghĩa các lớp chứa chi tiết thô và chức năng cấp cao.

Vignette: Ngôn ngữ Hướng đối tượng Thiếu một số Linh hoạt

Câu chuyện sau (được báo cáo bởi một trong những khách hàng của Laplante
muốn giữ ẩn danh) minh họa rằng việc sử dụng ngôn ngữ hướng đối tượng cho các
hệ thống thời gian thực có thể gặp phải những khó khăn tinh tế. Một nhóm thiết kế
cho một hệ thống thời gian thực cụ thể khăng khăng rằng C++ phải được sử dụng
để thực hiện một đặc tả yêu cầu khá đơn giản và rõ ràng. Sau khi mã hóa hoàn tất,
việc kiểm tra bắt đầu. Mặc dù hệ thống được phát triển không bao giờ thất bại, một
số người dùng muốn thêm một số yêu cầu; tuy nhiên, việc thêm những tính năng
này khiến hệ thống thời gian thực bỏ lỡ các thời hạn quan trọng. Khách hàng sau
đó thuê một nhà cung cấp bên ngoài để thực hiện thiết kế đã sửa đổi bằng một
ngôn ngữ thủ tục. Nhà cung cấp đáp ứng các yêu cầu mới bằng cách viết mã trong
C và sau đó tối ưu hóa thủ công một số phần mã ngôn ngữ hợp dẫn từ đầu ra trình
biên dịch. Họ có thể sử dụng cách tiếp cận tối ưu hóa này vì sự tương ứng chặt chẽ
giữa mã C thủ tục và hướng dẫn được trình biên dịch tạo ra. Tùy chọn đơn giản
này không có sẵn cho các nhà phát triển sử dụng C++.

- Đoạn văn không phải là sự ủng hộ cho chiến lược giải pháp này. Nó chỉ đơn giản là
minh họa cho một trường hợp đặc biệt. Đôi khi những trường hợp như vậy được sử dụng
để tranh cãi về khả năng thực hiện của ngôn ngữ hướng đối tượng cho các ứng dụng thời
gian thực, điều này không công bằng - nhiều hệ thống thời gian thực đúng giờ và mạnh
mẽ được xây dựng trong ngôn ngữ hướng đối tượng. Hơn nữa, trong khi vấn đề của
khách hàng đã được giải quyết theo cách đơn giản được mô tả trong đoạn văn, thì rõ ràng
rằng khả năng hiểu, bảo dưỡng, và di chuyển của hệ thống sẽ gặp vấn đề. Do đó, giải
pháp cho trường hợp của khách hàng nên đã liên quan đến việc tái kỹ thuật toàn bộ hệ
thống để bao gồm việc đánh giá lại các thời hạn và kiến trúc hệ thống tổng thể.

- Một vấn đề tổng quát hơn là bất thường kế thừa trong ngôn ngữ hướng đối tượng. Bất
thường kế thừa xảy ra khi có nỗ lực sử dụng kế thừa như một cơ chế tái sử dụng mã, điều
này không bảo tồn tính thay thế (tức là, lớp con không phải là kiểu con). Nếu tính thay
thế được bảo tồn, thì bất thường sẽ không xảy ra. Vì việc sử dụng kế thừa để tái sử dụng
đã không được ưa chuộng trong các cách tiếp cận hướng đối tượng (thay vì tổng hợp),
dường như hầu hết các từ chối bất thường kế thừa của ngôn ngữ hướng đối tượng cho các
hệ thống thời gian thực phản ánh quan điểm lỗi thời về hướng đối tượng.

- Xem xét ví dụ sau từ một cuốn sách xuất sắc về hệ điều hành thời gian thực (Shaw,
2001):
- Giả sử rằng các điều kiện tiên quyết được kiểm tra và có “ngữ nghĩa chờ” (tức là, chờ
cho đến khi điều kiện tiên quyết trở thành đúng), thì rõ ràng MyBoundedBuffer đã làm
mạnh điều kiện tiên quyết của BoundedBuffer, và do đó vi phạm tính thay thế - và như
vậy là một cách sử dụng kế thừa đáng nghi.

- Hầu hết các đối thủ của ngôn ngữ hướng đối tượng cho lập trình thời gian thực khẳng
định rằng đồng thời và đồng bộ hóa được hỗ trợ kém. Tuy nhiên, khi hỗ trợ ngôn ngữ tích
hợp cho đồng thời không tồn tại, nó là một thực hành tiêu chuẩn để tạo “lớp bao - mặt
nạ” để đóng gói giao diện chương trình ứng dụng (API) đồng thời hệ thống để sử dụng
trong hướng đối tượng (ví dụ, lớp bao trong C++ cho luồng POSIX). Hơn nữa, có một số
mẫu đồng thời có sẵn cho các hệ thống thời gian thực hướng đối tượng (Douglass, 2003;
Schmidt et al., 2000). Trong khi đồng thời có thể được hỗ trợ kém ở cấp độ ngôn ngữ, đó
không phải là vấn đề vì các nhà phát triển sử dụng thư viện thay thế.

- Tóm lại, những người phê phán ngôn ngữ hướng đối tượng hiện tại cho các hệ thống
thời gian thực dường như chỉ tập trung vào Java, bỏ qua C++. C++ phù hợp hơn cho lập
trình thời gian thực vì, giữa những điều khác, nó không có thu gom rác tích hợp và các
phương thức lớp, và theo mặc định không sử dụng “liên kết động”. Trong mọi trường
hợp, không có hướng dẫn nghiêm ngặt khi nên ưu tiên các cách tiếp cận và ngôn ngữ
hướng đối tượng. Mỗi tình huống cụ thể cần được xem xét riêng lẻ.

4.5 TỔNG QUAN VỀ NGÔN NGỮ LẬP TRÌNH

- Để mục đích minh họa các thuộc tính ngôn ngữ đã nêu, việc xem xét một số ngôn ngữ
hiện đang được sử dụng trong lập trình hệ thống thời gian thực là hữu ích. Các ngôn ngữ
thủ tục và hướng đối tượng được chọn được thảo luận theo thứ tự chữ cái, và không theo
bất kỳ thứ hạng nào về sự chấp thuận hay thuộc tính nổi bật.

4.5.1 Ada

- Ada ban đầu được lên kế hoạch để trở thành ngôn ngữ bắt buộc cho tất cả các dự án của
Bộ Quốc phòng Hoa Kỳ mà bao gồm tỷ lệ lớn các hệ thống nhúng. Phiên bản đầu tiên, đã
được chuẩn hóa vào năm 1983, gặp phải những vấn đề nghiêm trọng. Ada được dự định
sử dụng cụ thể cho lập trình hệ thống thời gian thực, nhưng, vào thời điểm đó, các nhà
xây dựng hệ thống thấy mã thực thi kết quả rất cồng kềnh và không hiệu quả. Hơn nữa,
các vấn đề lớn đã được phát hiện khi cố gắng triển khai đa nhiệm bằng cách sử dụng các
công cụ hạn chế do ngôn ngữ cung cấp, chẳng hạn như cơ chế hẹn hò bị chỉ trích gay gắt.
Cộng đồng ngôn ngữ lập trình đã nhận thức được những vấn đề này, và hầu như kể từ khi
giao trình biên dịch Ada 83 đầu tiên, đã tìm cách giải quyết chúng. Những nỗ lực cải cách
này cuối cùng đã dẫn đến một phiên bản mới của ngôn ngữ. Ngôn ngữ đã được sửa đổi
kỹ lưỡng, được gọi là “Ada 95”, được coi là ngôn ngữ lập trình hướng đối tượng được
chuẩn hóa quốc tế đầu tiên, và thực tế, một số người gọi Ada 95 là “ngôn ngữ thời gian
thực đầu tiên”.

- Ba cấu trúc đặc biệt hữu ích đã được giới thiệu trong Ada 95 để giải quyết nhược điểm
của Ada 83 trong lập lịch, tranh chấp tài nguyên, và đồng bộ hóa:

1. Một pragma kiểm soát cách các tác vụ được gửi.


2. Một pragma kiểm soát sự tương tác giữa lập lịch tác vụ.
3. Một pragma kiểm soát chính sách hàng đợi của tác vụ - và hàng đợi nhập tài
nguyên.

Hơn nữa, các bổ sung khác cho ngôn ngữ đã cố gắng làm cho Ada 95 hoàn toàn hướng
đối tượng. Những điều này bao gồm:

• Các kiểu được gắn thẻ

• Gói

• Đơn vị được bảo vệ

- Sử dụng đúng cách của những cấu trúc này cho phép xây dựng các đối tượng thể hiện
bốn đặc điểm của ngôn ngữ hướng đối tượng: kiểu dữ liệu trừu tượng, kế thừa, đa hình,
và thông điệp.

- Vào tháng 10 năm 2001, một Sửa đổi Kỹ thuật cho Tiêu chuẩn Ada 95 đã được
ISO/IEC công bố, và một Sửa đổi lớn cho tiêu chuẩn quốc tế đã được xuất bản vào tháng
3 năm 2007. Phiên bản mới nhất của Ada được gọi là “Ada 2005”. Sự khác biệt giữa Ada
95 và Ada 2005 không rộng lớn - trong bất kỳ trường hợp nào, không gần như quan trọng
như những thay đổi giữa Ada 83 và Ada 95. Do đó, khi chúng tôi đề cập đến “Ada” cho
phần còn lại của cuốn sách này, chúng tôi nghĩa là Ada 95, vì Ada 2005 không phải là
một tiêu chuẩn mới, mà chỉ là một sửa đổi.

- Sửa đổi, ISO/IEC 8652:1995/Amd 1:2007, bao gồm một số thay đổi đặc biệt quan tâm
đến cộng đồng hệ thống thời gian thực, chẳng hạn như:

• Phụ lục hệ thống thời gian thực chứa các chính sách điều phối bổ sung, hỗ trợ
cho các sự kiện thời gian, và hỗ trợ cho việc kiểm soát sử dụng thời gian CPU.
• Mô hình hướng đối tượng đã được cải thiện để cung cấp kế thừa nhiều.
• Độ tin cậy tổng thể của ngôn ngữ đã được cải thiện bởi nhiều cải tiến.

- Ada chưa bao giờ đạt được lời hứa về tính phổ quát của nó. Tuy nhiên, ngôn ngữ đã
được sửa đổi đang trở lại một cách tương đối, đặc biệt là vì các hệ thống DoD mới được
chọn và nhiều hệ thống cũ sử dụng Ada, và vì sự có sẵn của các phiên bản mã nguồn mở
của Ada cho môi trường Linux phổ biến.
4.5.2 C

- Ngôn ngữ lập trình C, được phát minh vào khoảng năm 1972 tại Bell Laboratories, là
một ngôn ngữ tốt cho lập trình “cấp thấp”. Lý do cho điều này là nó bắt nguồn từ ngôn
ngữ rõ ràng, BCPL (người kế nhiệm, cha mẹ của C, là “B”), chỉ hỗ trợ một kiểu, từ máy.
Do đó, C hỗ trợ các mục liên quan đến máy như địa chỉ, bit, byte, và ký tự, được xử lý
trực tiếp trong ngôn ngữ cấp cao này. Những thực thể cơ bản này có thể được sử dụng
hiệu quả để kiểm soát các thanh ghi làm việc của CPU, đơn vị giao diện ngoại vi, và phần
cứng được ánh xạ bộ nhớ khác cần thiết trong hệ thống thời gian thực.

- Ngôn ngữ C cung cấp các kiểu biến đặc biệt, chẳng hạn như đăng ký, biến đổi, tĩnh, và
hằng số, cho phép kiểm soát hiệu quả việc tạo mã ở cấp ngôn ngữ thủ tục. Ví dụ, khai báo
một biến dưới dạng kiểu đăng ký cho biết nó sẽ được sử dụng thường xuyên. Điều này
hướng dẫn trình biên dịch đặt biến được khai báo như vậy trong một thanh ghi làm việc,
thường dẫn đến các chương trình nhanh hơn và nhỏ hơn. Hơn nữa, C hỗ trợ chỉ gọi theo
giá trị, nhưng gọi theo tham chiếu có thể được thực hiện dễ dàng bằng cách truyền một
con trỏ đến bất cứ thứ gì như một giá trị. Các biến được khai báo dưới dạng kiểu biến đổi
không được tối ưu hóa bởi trình biên dịch. Tính năng này cần thiết trong việc xử lý I/O
được ánh xạ bộ nhớ và các trường hợp đặc biệt khác nơi mã không nên được tối ưu hóa.

- Ép buộc tự động đề cập đến việc ép kiểu dữ liệu ngầm định mà đôi khi xảy ra trong C.
Ví dụ, một giá trị float có thể được gán cho một biến int, có thể dẫn đến mất thông tin do
cắt bớt. Hơn nữa, C cung cấp các hàm, chẳng hạn như printf, mà nhận một số lượng biến
đổi của đối số. Mặc dù đây là một tính năng thuận tiện, nhưng không thể cho trình biên
dịch kiểm tra kiểu kỹ lưỡng các đối số, có nghĩa là các vấn đề có thể bí ẩn xảy ra tại thời
gian chạy.

- Ngôn ngữ C cung cấp cho việc xử lý ngoại lệ thông qua việc sử dụng tín hiệu, và hai cơ
chế khác, setjmp và longjmp, được cung cấp để cho phép một thủ tục trở về nhanh chóng
từ một mức độ lồng sâu - một tính năng đặc biệt hữu ích trong các thủ tục yêu cầu hủy
bỏ. Lệnh gọi thủ tục setjmp, thực ra là một macro (nhưng thường được thực hiện như một
hàm), lưu thông tin môi trường có thể được sử dụng bởi một lệnh gọi hàm thư viện
longjmp sau. Lệnh gọi longjmp khôi phục chương trình về trạng thái vào thời điểm cuối
cùng của lệnh gọi setjmp. Ví dụ, giả sử một thủ tục được gọi để thực hiện một số xử lý và
kiểm tra lỗi. Nếu phát hiện lỗi, longjmp có thể được sử dụng để chuyển đến lệnh đầu tiên
sau setjmp.

- Nói chung, ngôn ngữ C rất tốt cho lập trình nhúng, vì nó cung cấp cấu trúc và linh hoạt
mà không có các hạn chế ngôn ngữ phức tạp. Phiên bản mới nhất của tiêu chuẩn quốc tế
của ngôn ngữ C là từ năm 1999 (ANSI/ISO/IEC 9899:1999).

4.5.3 C++
- C++ là một ngôn ngữ lập trình hướng đối tượng kết hợp được triển khai ban đầu như
một phần mở rộng macro của C vào những năm 1980. Ngày nay, C++ đứng như một
ngôn ngữ biên dịch riêng lẻ, mặc dù trình biên dịch C++ nên chấp nhận mã C chuẩn. C++
thể hiện tất cả các đặc điểm của một ngôn ngữ hướng đối tượng và thúc đẩy thực hành kỹ
thuật phần mềm tốt hơn thông qua việc đóng gói và các cơ chế trừu tượng hóa tiên tiến
hơn C.

- Trình biên dịch C++ thực hiện một giai đoạn tiền xử lý cơ bản thực hiện việc tìm kiếm
và thay thế thông minh trên các định danh đã được khai báo bằng các chỉ thị #define hoặc
#typedef. Mặc dù hầu hết những người ủng hộ C++ không khuyến khích việc sử dụng
tiền xử lý, được kế thừa từ C, nhưng nó lại được sử dụng rộng rãi. Hầu hết các định nghĩa
tiền xử lý trong C++ được lưu trữ trong các tệp tiêu đề, bổ sung cho các tệp mã nguồn
thực sự. Vấn đề với cách tiếp cận tiền xử lý là nó cung cấp một cách cho lập trình viên vô
tình thêm độ phức tạp không cần thiết vào một chương trình. Một vấn đề khác với cách
tiếp cận tiền xử lý là nó có kiểm tra kiểu yếu và xác thực.

- Hầu hết các nhà phát triển phần mềm đều đồng ý rằng việc lạm dụng con trỏ gây ra
phần lớn lỗi trong lập trình C/C++. Trước đây, lập trình viên C++ đã sử dụng số học con
trỏ phức tạp để tạo và duy trì các cấu trúc dữ liệu động, đặc biệt là trong quá trình thao
tác chuỗi. Do đó, họ đã dành rất nhiều thời gian săn lùng các lỗi phức tạp cho việc quản
lý chuỗi đơn giản. Tuy nhiên, ngày nay, các thư viện chuẩn của các cấu trúc dữ liệu động
đã có sẵn. Ví dụ, ngôn ngữ mẫu chuẩn (STL), là một thư viện chuẩn của C++, và nó có cả
kiểu dữ liệu chuỗi và wstring cho chuỗi ký tự thông thường và chuỗi ký tự rộng, tương
ứng. Các kiểu dữ liệu này làm giảm bớt bất kỳ tranh cãi nào đối với các phiên bản C++
sớm, dựa trên các vấn đề thao tác chuỗi.

- Có ba kiểu dữ liệu phức tạp trong C++: lớp, cấu trúc và liên minh. Tuy nhiên, C++
không có hỗ trợ tích hợp cho chuỗi văn bản. Kỹ thuật chuẩn là sử dụng mảng ký tự kết
thúc bằng null để biểu diễn chuỗi.

- Mã C thông thường được tổ chức thành các hàm, đó là các tiểu chương trình toàn cục có
thể truy cập vào một chương trình. C++ thêm lớp và phương thức lớp, đó thực sự là các
hàm được kết nối với lớp. Tuy nhiên, vì C++ vẫn hỗ trợ C, không có gì, về nguyên tắc,
ngăn cản lập trình viên C++ sử dụng các hàm thông thường. Điều này sẽ dẫn đến việc kết
hợp sử dụng hàm và phương thức tạo ra các chương trình gây nhầm lẫn.

- Kế thừa nhiều là một tính năng hữu ích của C++ cho phép một lớp được dẫn xuất từ
nhiều lớp cha. Mặc dù kế thừa nhiều thực sự mạnh mẽ, nó có thể khó sử dụng đúng và
gây ra nhiều vấn đề khác. Nó cũng phức tạp để thực hiện từ góc độ trình biên dịch.

- Ngày nay, ngày càng nhiều hệ thống nhúng được xây dựng bằng C++, và nhiều người
thực hành hỏi, “Tôi nên triển khai hệ thống của mình bằng C hay C++?” Câu trả lời ngay
lập tức luôn là “nó phụ thuộc.” Việc chọn C thay vì C++ trong các ứng dụng nhúng là
một sự đánh đổi khó khăn: một chương trình C sẽ nhanh hơn và dự đoán hơn nhưng khó
duy trì hơn, và chương trình C++ sẽ chậm hơn và ít dự đoán hơn nhưng có thể dễ duy trì
hơn. Vì vậy, việc chọn ngôn ngữ tương đương với việc hỏi tôi nên ăn một “quả táo xanh”
hay một “quả táo đỏ?”

- C++ vẫn cho phép kiểm soát cấp thấp; ví dụ, nó có thể sử dụng các phương thức nội
tuyến thay vì một cuộc gọi thời gian chạy. Loại triển khai này không đặc biệt trừu tượng,
cũng không hoàn toàn cấp thấp, nhưng được chấp nhận trong các môi trường nhúng
thông thường. Dịch văn bản sang tiếng Việt.

- Để tự làm hại mình, có thể có xu hướng lấy mã C hiện có và đưa nó vào đối tượng bằng
cách đơn giản là đóng gói mã thủ tục vào các đối tượng mà ít quan tâm đến các thực hành
tốt nhất của lập trình hướng đối tượng. Cách tiếp cận này cần được tránh một cách
nghiêm ngặt vì nó có khả năng kết hợp tất cả các nhược điểm của C++, và không có lợi
ích nào của nó. Hơn nữa, C++ không cung cấp bộ thu gom rác tự động, có nghĩa là bộ
nhớ động phải được quản lý theo cách thủ công hoặc bộ thu gom rác phải được tự xây
dựng. Do đó, khi chuyển đổi một chương trình C sang C++, một thiết kế hoàn toàn mới là
cần thiết để nắm bắt hoàn toàn tất cả các lợi ích của thiết kế hướng đối tượng trong khi
giảm thiểu các nhược điểm thời gian chạy.

4.5.4 C#

- C# (đọc là “C sharp”) là một ngôn ngữ giống C++ mà, cùng với môi trường hoạt động
của nó, có sự tương đồng với Java và máy ảo Java, tương ứng. Do đó, C# được biên dịch
trước thành một ngôn ngữ trung gian, sau đó được sử dụng để tạo ra một hình ảnh gốc tại
thời gian chạy. C# được liên kết với .NET framework của Microsoft cho các hệ điều hành
thu nhỏ như Windows CE. Windows CE có thể cấu hình cao, có khả năng mở rộng từ các
dấu chân hệ thống nhúng nhỏ (<1 M byte) và lên trên (ví dụ, cho các hệ thống thời gian
thực yêu cầu hỗ trợ giao diện người dùng). Cấu hình kernel tối thiểu cung cấp hỗ trợ
mạng cơ bản, quản lý luồng, hỗ trợ thư viện liên kết động, và quản lý bộ nhớ ảo. Mặc dù
một cuộc thảo luận chi tiết nằm ngoài phạm vi của văn bản này, rõ ràng là Windows CE
ban đầu được dự định là một hệ điều hành thời gian thực cho nền tảng .NET.

- Phần lớn cuộc thảo luận này được thích ứng từ Lutz và Laplante (2003).

- C# hỗ trợ “mã không an toàn,” cho phép con trỏ tham chiếu đến các vị trí bộ nhớ cụ thể.
Các đối tượng được tham chiếu bởi con trỏ phải được “ghim” một cách rõ ràng, không
cho phép bộ thu gom rác thay đổi vị trí của chúng trong bộ nhớ. Bộ thu gom rác thu thập
các đối tượng đã ghim; nó chỉ không di chuyển chúng. Khả năng này có thể tăng khả
năng lập lịch, và nó cũng cho phép truy cập bộ nhớ trực tiếp (DMA) để ghi vào các vị trí
bộ nhớ cụ thể; một khả năng cần thiết trong các hệ thống thời gian thực nhúng. .NET
cung cấp một cách tiếp cận theo thế hệ đối với việc thu gom rác nhằm giảm thiểu sự chặn
luồng trong quá trình đánh dấu và quét. Ví dụ, một phương tiện để tạo một luồng tại một
thời điểm cụ thể, và đảm bảo luồng hoàn thành vào một thời điểm cụ thể, không được hỗ
trợ. Hơn nữa, C# cung cấp nhiều cơ chế đồng bộ hóa luồng, nhưng không có cơ chế nào
với mức độ chính xác này. C# hỗ trợ một loạt các cấu trúc đồng bộ hóa luồng: khóa, giám
sát, mutex, và interlock. Một Khóa có ngữ nghĩa giống hệt với một phần quan trọng - một
đoạn mã đảm bảo chỉ cho phép một luồng vào bên trong nó tại một thời điểm. Khóa là
một ký hiệu viết tắt cho kiểu lớp giám sát. Một mutex có ngữ nghĩa tương đương với một
khóa, với khả năng bổ sung là hoạt động trên các không gian quá trình. Nhược điểm của
mutexes là hình phạt hiệu suất của chúng. Cuối cùng, interlock, một tập hợp các phương
thức tĩnh quá tải, được sử dụng để tăng và giảm số học theo cách an toàn luồng để thực
hiện giao thức kế thừa ưu tiên.

- Các bộ hẹn giờ có chức năng tương tự như bộ hẹn giờ Win32 được sử dụng rộng rãi tồn
tại trong C#. Khi được xây dựng, các bộ hẹn giờ được cấu hình bao lâu để chờ trong mili
giây trước lần gọi đầu tiên của chúng, và cũng được cung cấp một khoảng thời gian, lại
trong mili giây, chỉ định khoảng thời gian giữa các lần gọi tiếp theo. Độ chính xác của
các bộ hẹn giờ này phụ thuộc vào máy, và do đó không được đảm bảo, giảm tính hữu ích
của chúng trong các hệ thống thời gian thực để được sử dụng trong nhiều nền tảng phần
cứng.

- C# và nền tảng .NET không phù hợp cho đa số các hệ thống thời gian thực cứng vì một
số lý do, bao gồm việc thực thi không giới hạn của môi trường thu gom rác của nó và
thiếu các cấu trúc luồng để hỗ trợ đủ khả năng lập lịch và quyết định. Tuy nhiên, khả
năng của C# để tương tác hiệu quả với các API hệ điều hành, bảo vệ các nhà phát triển
khỏi logic quản lý bộ nhớ phức tạp, cùng với hiệu suất dấu phẩy động tốt của C#, làm
cho nó trở thành một ngôn ngữ lập trình có tiềm năng cao cho các ứng dụng thời gian
thực mềm và thậm chí là thời gian thực cứng. Tuy nhiên, yêu cầu phải có phong cách lập
trình kỷ luật (Lutz và Laplante, 2003).

4.5.5 Java

- Java, giống như C#, là một ngôn ngữ được thông dịch, tức là, mã được biên dịch thành
mã trung gian độc lập với máy chạy trong một môi trường thực thi được quản lý. Môi
trường này là một máy ảo (xem Hình 4.3), thực thi các chỉ dẫn “đối tượng” mã như một
chuỗi chỉ thị chương trình. Lợi ích rõ ràng của cách sắp xếp này là mã Java có thể chạy
trên bất kỳ thiết bị nào thực hiện máy ảo. Triết lý “viết một lần, chạy ở bất cứ đâu” này
có các ứng dụng quan trọng trong tính toán di động và di động, như trong điện thoại di
động và thẻ thông minh, cũng như trong tính toán dựa trên Web.

- Tuy nhiên, cũng có các trình biên dịch Java mã gốc, cho phép Java chạy trực tiếp “trên
kim loại trần,” tức là, các trình biên dịch chuyển đổi Java thành mã hợp dẫn hoặc mã đối
tượng. Ví dụ, bắt đầu từ Java 2, các máy ảo Java hỗ trợ các trình biên dịch đặc biệt biên
dịch thành mã máy cho một số kiến trúc chuẩn. Hơn nữa, còn có cả các vi xử lý Java đặc
biệt, thực hiện trực tiếp mã byte Java trong phần cứng (El-Kharashi và Elguibaly, 1997).
- Java là một ngôn ngữ hướng đối tượng và mã trông rất giống C++. Giống như C, Java
hỗ trợ gọi - theo - giá trị, nhưng gọi - theo - tham chiếu có thể được mô phỏng, sẽ được
thảo luận ngắn gọn. Nhưng Java là một ngôn ngữ hướng đối tượng thuần túy, tức là, tất
cả chức năng trong Java phải được thực hiện bằng cách tạo lớp đối tượng, khởi tạo các
đối tượng của các lớp đó (hoặc lớp cơ sở), và thao tác các thuộc tính của đối tượng thông
qua các phương thức. Do đó, nó hầu như không thể lấy mã kế thừa đã được viết bằng một
ngôn ngữ thủ tục, nói C, và “chuyển đổi” mã đó sang Java mà không thực sự thể hiện một
cách tiếp cận thiết kế hướng đối tượng. Tất nhiên, một thiết kế hướng đối tượng tốt không
được đảm bảo, nhưng thiết kế thu được trong quá trình chuyển đổi sẽ là một thiết kế
hướng đối tượng thực sự dựa trên các quy tắc của ngôn ngữ. Tình huống này khá khác
biệt so với loại chuyển đổi hướng đối tượng giả mạo có thể được nhận được từ C sang
C++ theo cách thô lỗ đã nêu trước đây.

- Java cung cấp một tiền xử lý. Các thành viên dữ liệu hằng số được sử dụng thay cho chỉ
thị #define, và các định nghĩa lớp được sử dụng thay cho chỉ thị #typedef. Kết quả là mã
nguồn Java thường nhất quán hơn và dễ đọc hơn mã nguồn C++. Trình biên dịch Java
xây dựng các định nghĩa lớp trực tiếp từ các tệp mã nguồn, chứa cả các định nghĩa lớp và
các cài đặt phương thức. Tuy nhiên, có các hình phạt hiệu suất tự nhiên cho khả năng di
động kết quả.

- Ngôn ngữ Java không hỗ trợ con trỏ, nhưng nó cung cấp chức năng tương tự thông qua
tham chiếu. Java truyền tất cả các mảng và đối tượng theo tham chiếu, ngăn chặn các lỗi
phổ biến do quản lý con trỏ sai lầm. Sự thiếu con trỏ có vẻ như sẽ ngăn chặn việc thực
hiện các cấu trúc dữ liệu, chẳng hạn như mảng động. Tuy nhiên, bất kỳ chức năng con trỏ
nào có thể được thực hiện một cách thuận tiện với tham chiếu, với sự an toàn được cung
cấp bởi hệ thống thời gian chạy Java, chẳng hạn như kiểm tra ranh giới trên các hoạt
động chỉ mục mảng - tất cả điều này với hình phạt hiệu suất.

- Java chỉ triển khai một kiểu dữ liệu phức tạp: lớp. Các lập trình viên Java sử dụng lớp
khi cần chức năng của cấu trúc và liên minh. Sự nhất quán này đi kèm với giá trị tăng
thời gian thực thi so với các cấu trúc dữ liệu đơn giản.

- Ngôn ngữ Java không hỗ trợ các hàm độc lập. Thay vào đó, Java yêu cầu lập trình viên
đóng gói tất cả các routine vào các phương thức lớp một lần nữa với chi phí đáng kể.

- Hơn nữa, Java không hỗ trợ trực tiếp kế thừa nhiều. Tuy nhiên, giao diện cho phép thực
hiện kế thừa nhiều. Giao diện Java cung cấp các mô tả phương thức đối tượng, nhưng
không chứa các cài đặt.

- Trong Java, chuỗi được triển khai như các đối tượng lớp một (String và StringBuffer),
có nghĩa là chúng nằm ở trung tâm của ngôn ngữ Java. Việc triển khai chuỗi của Java
dưới dạng đối tượng mang lại một số lợi ích. Đầu tiên, việc tạo và truy cập chuỗi là nhất
quán trên tất cả các hệ thống. Tiếp theo, vì các lớp chuỗi Java được định nghĩa là một
phần của ngôn ngữ Java nên chuỗi hoạt động dự đoán được mỗi lần. Cuối cùng, các lớp
chuỗi Java thực hiện kiểm tra thời gian chạy rộng rãi, giúp loại bỏ lỗi. Nhưng tất cả các
hoạt động này làm tăng thời gian thực thi.

- Toán tử nạp chồng không được hỗ trợ trong Java. Tuy nhiên, trong lớp chuỗi của Java,
“+” biểu thị việc nối chuỗi, cũng như cộng số.

- Ngôn ngữ Java không hỗ trợ ép kiểu tự động. Trong Java, nếu một ép kiểu sẽ dẫn đến
mất dữ liệu, thì cần phải ép kiểu dữ liệu một cách rõ ràng thành kiểu mới. Java có “ép
kiểu lên” ngầm định. Tuy nhiên, bất kỳ thực thể nào có thể được ép kiểu lên thành
Object, đó là lớp cha cho tất cả các đối tượng. Ép kiểu xuống là rõ ràng, và yêu cầu một
lệnh ép kiểu. Sự rõ ràng này quan trọng để ngăn chặn mất độ chính xác ẩn.

- Các đối số dòng lệnh được hệ thống truyền vào một chương trình Java khác với các đối
số dòng lệnh thông thường được truyền vào một chương trình C++. Trong C và C++, hệ
thống truyền hai đối số vào một chương trình: argc và argv. argc chỉ định số lượng đối số
được lưu trữ trong argv, và argv là một con trỏ đến một mảng ký tự chứa các đối số thực
sự. Trong Java, ngược lại, hệ thống truyền một giá trị duy nhất vào một chương trình:
args. args là một mảng chuỗi chứa các đối số dòng lệnh.

4.5.6 Java Thời gian thực

- Phần này được dành cho việc thích nghi thời gian thực của Java. Mặc dù Java thời gian
thực chỉ là một sửa đổi của ngôn ngữ Java chuẩn, nó xứng đáng được thảo luận riêng
biệt, vì nó ngày càng được sử dụng nhiều hơn trong việc triển khai các hệ thống thời gian
thực mềm, cứng, và thậm chí là thời gian thực cứng, trong khi Java chuẩn chủ yếu chỉ
được sử dụng cho các hệ thống thời gian thực mềm. Trong khi chúng tôi bao gồm cuộc
thảo luận về Java thời gian thực để hoàn thiện và vì nó minh họa một số điểm thú vị,
chúng tôi nhắc lại sự ưu tiên của chúng tôi cho C++ hơn các phiên bản của Java trong hầu
hết các trường hợp.

- Ngoài hiệu suất không thể đoán trước của việc thu gom rác, thông số kỹ thuật Java chỉ
cung cấp hướng dẫn rộng rãi cho việc lập lịch. Ví dụ, khi có sự cạnh tranh về tài nguyên
xử lý, các luồng có độ ưu tiên cao hơn thường được thực thi ưu tiên hơn so với các luồng
có độ ưu tiên thấp hơn. Tuy nhiên, sự ưu tiên này không phải là một đảm bảo rằng luồng
có độ ưu tiên cao nhất trong số các luồng sẵn sàng sẽ luôn được chạy, và độ ưu tiên luồng
không thể được sử dụng để thực hiện đáng tin cậy việc loại trừ lẫn nhau. Sớm nhận ra
rằng điều này và những khuyết điểm khác khiến Java chuẩn không đủ cho hầu hết các hệ
thống thời gian thực.

- Để đáp ứng vấn đề này, một nhóm nhiệm vụ của Viện Quốc gia Tiêu chuẩn và Công
nghệ (NIST) được giao nhiệm vụ phát triển một phiên bản của Java đặc biệt phù hợp cho
các ứng dụng thời gian thực nhúng. Báo cáo cuối cùng của hội thảo, đã được công bố vào
tháng 9 năm 1999, định rõ chín yêu cầu cốt lõi cho thông số kỹ thuật thời gian thực của
Java (RTSJ 1.0):

R1. Thông số kỹ thuật phải bao gồm một khung cho việc tra cứu và phát hiện các
hồ sơ có sẵn.
R2. Bất kỳ việc thu gom rác nào được cung cấp phải có độ trễ chuyển nhượng giới
hạn.
R3. Thông số kỹ thuật phải định rõ mối quan hệ giữa các luồng Java thời gian
thực ở cùng một mức độ chi tiết như hiện có trong các tài liệu tiêu chuẩn hiện tại.
R4. Thông số kỹ thuật phải bao gồm các API để cho phép giao tiếp và đồng bộ
hóa giữa các tác vụ Java và không phải Java.
R5. Thông số kỹ thuật phải bao gồm việc xử lý cả sự kiện không đồng bộ nội bộ và
bên ngoài.
R6. Thông số kỹ thuật phải bao gồm một hình thức nào đó của việc kết thúc luồng
không đồng bộ.
R7. Lõi phải cung cấp các cơ chế để thực thi việc loại trừ lẫn nhau mà không
chặn.
R8. Thông số kỹ thuật phải cung cấp một cơ chế để cho phép mã truy vấn liệu nó
đang chạy dưới một luồng Java thời gian thực hay một luồng Java không thời gian
thực.
R9. Thông số kỹ thuật phải định rõ các mối quan hệ tồn tại giữa các luồng Java
thời gian thực và các luồng Java không thời gian thực.

- RTSJ 1.0 đáp ứng tất cả ngoại trừ yêu cầu đầu tiên, được coi là không liên quan vì việc
truy cập vào bộ nhớ vật lý không phải là một phần của các yêu cầu của NIST, nhưng đầu
vào từ ngành công nghiệp đã dẫn nhóm bao gồm nó (Bollella và Gosling, 2000). Năm
2006, một phiên bản nâng cao của thông số kỹ thuật thời gian thực, RTSJ 1.1, đã được
công bố (Dibble và Wellings, 2009).

- Phần lớn cuộc thảo luận sau đây đã được thích ứng từ Bollella và Gosling (2000).

- RTSJ định nghĩa lớp luồng thời gian thực để tạo luồng, mà trình lập lịch cư trú thực thi.
Các luồng thời gian thực có thể truy cập các đối tượng trên heap, và do đó có thể gây ra
sự chậm trễ do thu gom rác.

- Đối với việc thu gom rác, RTSJ mở rộng mô hình bộ nhớ để hỗ trợ quản lý bộ nhớ theo
cách không can thiệp vào khả năng cung cấp hành vi xác định của mã thời gian thực.
Những phần mở rộng này cho phép cả các đối tượng ngắn và dài tuổi được cấp phát
ngoài heap thu gom rác. Cũng có đủ linh hoạt để sử dụng các giải pháp quen thuộc, chẳng
hạn như các bể đối tượng được cấp phát trước.

- RTSJ sử dụng “ưu tiên” một cách lỏng lẻo hơn so với cách chấp nhận truyền thống.
“Luồng ưu tiên cao nhất” chỉ đơn giản chỉ ra luồng đủ điều kiện nhất - luồng mà bộ lập
lịch sẽ chọn từ trong tất cả các luồng sẵn sàng để chạy. Nó không nhất thiết giả định một
cơ chế phân phối dựa trên ưu tiên nghiêm ngặt. Hệ thống phải xếp hàng tất cả các luồng
đang chờ để có được một tài nguyên theo thứ tự ưu tiên. Những tài nguyên này bao gồm
bộ xử lý cũng như các khối đồng bộ hóa. Nếu chính sách lập lịch đang hoạt động cho
phép các luồng có cùng mức độ ưu tiên, các luồng sẽ được xếp hàng theo nguyên tắc
FIFO. Cụ thể, hệ thống (1) sắp xếp các luồng đang chờ để vào các khối đồng bộ hóa
trong một hàng đợi ưu tiên; (2) thêm một luồng bị chặn trở thành sẵn sàng để chạy vào
cuối hàng đợi sẵn sàng cho mức độ ưu tiên đó; (3) thêm một luồng mà mức độ ưu tiên
của nó được thiết lập một cách rõ ràng bởi chính nó hoặc một luồng khác vào cuối hàng
đợi sẵn sàng cho mức độ ưu tiên mới; và (4) đặt một luồng thực hiện một lệnh yield vào
cuối hàng đợi ưu tiên của nó. Giao thức kế thừa ưu tiên được thực hiện theo mặc định.
Thông số kỹ thuật thời gian thực cũng cung cấp một cơ chế mà theo đó một chính sách
mặc định toàn hệ thống có thể được thực hiện.

- Cơ sở sự kiện không đồng bộ bao gồm hai lớp: AsyncEvent và AsyncEventHandler.


Một đối tượng AsyncEvent đại diện cho một điều gì đó có thể xảy ra - như một ngắt phần
cứng - hoặc nó đại diện cho một sự kiện được tính toán - như một máy bay vào một khu
vực được giám sát. Khi một trong những sự kiện này xảy ra, được chỉ ra bởi phương thức
fire() được gọi, hệ thống lập lịch các AsyncEventHandler liên quan. Một AsyncEvent
quản lý hai điều: việc gửi các trình xử lý khi sự kiện được kích hoạt, và tập hợp các trình
xử lý liên kết với sự kiện. Ứng dụng có thể truy vấn tập hợp này và thêm hoặc loại bỏ các
trình xử lý. Một AsyncEventHandler là một đối tượng có thể lập lịch tương tự như một
luồng. Khi sự kiện kích hoạt, hệ thống gọi các phương thức run() của các trình xử lý liên
quan.

- Tuy nhiên, không giống như các đối tượng khả chạy khác, một AsyncEventHandler có
các tham số lập lịch, phát hành, và bộ nhớ liên kết kiểm soát việc thực thi thực sự của đọc
hoặc viết.

- Chuyển đổi kiểm soát không đồng bộ cho phép xác định các phương thức cụ thể bằng
cách khai báo chúng để ném một AsynchronouslyInterrupted Exception (AIE). Khi một
phương thức như vậy đang chạy ở đầu ngăn xếp thực thi của một luồng và hệ thống gọi
java.lang.Thread.interrupt() trên luồng, phương thức sẽ ngay lập tức hành động như thể
hệ thống đã ném một AIE. Nếu hệ thống gọi một ngắt trên một luồng không đang thực thi
một phương thức như vậy, hệ thống sẽ đặt AIE vào trạng thái chờ xử lý cho luồng và sẽ
ném nó vào lần tiếp theo kiểm soát chuyển qua một phương thức như vậy, bằng cách gọi
nó hoặc trả về nó. Hệ thống cũng đặt trạng thái của AIE thành “chờ xử lý” trong khi kiểm
soát đang ở trong, trả về, hoặc nhập vào các khối đồng bộ hóa.

- RTSJ định nghĩa hai lớp cho các lập trình viên muốn truy cập trực tiếp vào bộ nhớ vật
lý từ mã Java. Lớp đầu tiên, RawMemoryAccess, định nghĩa các phương thức cho phép
bạn xây dựng một đối tượng đại diện cho một phạm vi địa chỉ vật lý và sau đó truy cập
vào bộ nhớ vật lý với độ nhỏ nhất là byte, word, long, và nhiều byte. RTSJ không ngụ ý
ngữ nghĩa nào khác ngoài các phương thức set và get. Lớp thứ hai, PhysicalMemory, cho
phép xây dựng một đối tượng PhysicalMemoryArea đại diện cho một phạm vi địa chỉ bộ
nhớ vật lý nơi hệ thống có thể định vị các đối tượng Java. Ví dụ, một đối tượng Java mới
trong một đối tượng PhysicalMemory cụ thể có thể được xây dựng bằng cách sử dụng
phương thức newInstance() hoặc newArray(). Một thực thể của RawMemoryAccess mô
hình hóa một khu vực lưu trữ thô dưới dạng một chuỗi cố định của các byte. Các phương
thức nhà máy cho phép tạo các đối tượng RawMemoryAccess từ bộ nhớ tại một phạm vi
địa chỉ cụ thể hoặc sử dụng một loại bộ nhớ cụ thể. Việc triển khai phải cung cấp và thiết
lập một phương thức nhà máy mà giải thích các yêu cầu này một cách tương ứng. Một bộ
đầy đủ các phương thức get và set cho phép hệ thống truy cập vào nội dung khu vực bộ
nhớ vật lý thông qua các offset từ cơ sở - được giải thích như các giá trị dữ liệu byte,
short, int, hoặc long - và sao chép chúng đến hoặc từ các mảng byte, short, int, hoặc long.

4.5.7 Ngôn ngữ thời gian thực đặc biệt

- Một loạt các ngôn ngữ chuyên biệt cho lập trình thời gian thực đã xuất hiện và nhận
được nhiều hoặc ít thành công trong những thập kỷ qua. Chúng bao gồm, ví dụ:

• PEARL. Ngôn ngữ thời gian thực tự động hóa quá trình và thí nghiệm được phát
triển vào đầu những năm 1970 bởi một nhóm các nhà nghiên cứu Đức. PEARL sử
dụng chiến lược tăng cường và có ứng dụng khá rộng rãi ở Đức, đặc biệt là trong
các cài đặt điều khiển công nghiệp. Phiên bản hiện tại là PEARL - 90.
• Real - Time Euclid. Một ngôn ngữ thử nghiệm cũng từ những năm 1970 được
biết đến là một trong số ít ngôn ngữ hoàn toàn phù hợp cho phân tích khả năng lập
lịch. Điều này được đạt được thông qua các hạn chế ngôn ngữ. Nó bắt nguồn từ
ngôn ngữ lập trình Pascal, nhưng không bao giờ tìm thấy con đường của nó vào
các ứng dụng chính thống.
• Occam 2. Một ngôn ngữ dựa trên chủ nghĩa hình thức quá trình giao tiếp - tuần
tự được thiết kế để hỗ trợ đồng thời trên transputers (xem Phần 2.5.2). Nó xuất
hiện vào cuối những năm 80 và tìm thấy các ứng dụng thực tế chủ yếu ở Vương
quốc Anh, nhưng biến mất cùng với transputer.
• Real - Time C. Thực ra là một tên chung cho bất kỳ một trong số các gói mở
rộng macro C. Những mở rộng macro này thường cung cấp các cấu trúc thời gian
và kiểm soát mà không tìm thấy trong C chuẩn.
• Neuron® C. Một sự cải tiến so với C chuẩn với các phần mở rộng cho việc xử lý
sự kiện, giao tiếp mạng, và I/O phần cứng. Nó được dự định để hỗ trợ các ứng
dụng LonWorks (một tiêu chuẩn fieldbus cho mạng điều khiển) cho các bộ xử lý
Neuron® và các bộ thu phát thông minh tương ứng, và được sử dụng rộng rãi
trong cộng đồng tự động hóa tòa nhà.
• Real - Time C++. Một tên chung cho một trong số một số thư viện lớp đối tượng
được phát triển cụ thể cho C++. Những thư viện này tăng cường C++ chuẩn để
cung cấp một mức độ thời gian và kiểm soát tăng lên.
- Hơn nữa, có nhiều ngôn ngữ thời gian thực khác và các môi trường hoạt động tương
ứng, chẳng hạn như Anima, DROL, Erlang, Esterel, Hume, JOVIAL, LUSTRE, Maruti,
RLUCID, RSPL, và Timber. Một số trong số này được sử dụng cho các ứng dụng chuyên
biệt hoặc chỉ trong nghiên cứu.

You might also like