25/05/2018, 14:00

Kĩ thuật lập lịch và xử lý ngắt trong thời gian thực hệ thống nhúng

Các tác vụ (task) hoạt động dưới sự giám sát của kernel thời gian thực. Chúng bao gồm: Một tập hợp các dịch vụ thực hiện các công việc như đồng bộ hoá và giao tiếp truyền thông giữa các tác vụ. Một bộ lập lịch (scheduler) ...

Các tác vụ (task) hoạt động dưới sự giám sát của kernel thời gian thực. Chúng bao gồm:

  • Một tập hợp các dịch vụ thực hiện các công việc như đồng bộ hoá và giao tiếp truyền thông giữa các tác vụ.
  • Một bộ lập lịch (scheduler) với chức năng khẳng định rằng chỉ có duy nhất tác vụ với mức ưu tiên cao nhất đang được thực thi.

Bộ lập lịch xét các tác vụ như những cái máy trạng thái (state machine). Tất cả các kernel đều có mô hình trạng thái của nó, tuy nhiên, thông thường thì các mô hình trạng thái nảy rất phức tạp. Hình 9.1 chỉ ra cho các bạn thấy một mô hình trạng thái mang tính khái niệm của tác vụ. Trong hình, ta thấy có các trạng thái:

  • Đang thực thi (Running): chỉ có duy nhất một tác vụ là được nằm trong trạng thái này. Một tác vụ có thể tự động chuyển từ trạng thái Đang thực thi sang trạng thái Khoá (Blocked) bằng việc chờ đợi một sự kiện xảy ra. Trong một hệ thống có sự chiếm quyền thực thi (chúng ta sẽ đề cập đến nó sau), bộ lập lịch có thể bắt một tác vụ đang ở trạng thái Đang thực thi xuống trạng thái Sẵn sàng (Ready) nếu có một tác vụ với mức ưu tiên cao hơn chuyển đến trạng thái Sẵn sàng. Chúng ta gọi nó là sựchiếm quyền thực thi (preemption).
  • Sẵn sàng (Ready): nếu một tác vụ đã sẵn sàng để hoạt động nhưng lại có mức ưu tiên thấp hơn tác vụ đang thực thi, tác vụ đó sẽ được chuyển đến trạng thái này và chờ. Tác vụ này sẽ được chuyển đến trạng thái Đang thực thi nếu nó trở thành tác vụ có mức ưu tiên cao nhất.
  • Khoá (Blocked): một tác vụ bị khoá là tác vụ đang đợi một sự kiện nào đó xảy ra, ví dụ như một bản tin, tin nhắn được gửi đến hộp thư của tác vụ đó, hay thời gian chờ của tác vụ kết thúc….

Hình 8.1: Mô hình trạng thái của tác vụ

Có rất nhiều tác vụ mà công việc của nó chỉ là thức dậy theo chu kỳ, làm một vài công việc nào đó và quay trở lại ngủ tiếp. Có một vài phương pháp để thực hiện các tác vụ kiểu này như trên hình 8.2. Trong tất cả các hệ điều hành, chúng ta đều có thể tìm thấy một lệnh gọi là hàm trễ Delay(), hoặc là một vài hàm có chức năng tương tự. Hàm này làm cho tác vụ bị khoá trong một thời gian xác định cho trước, thông thường thời gian này được biểu diễn bằng xung đồng hồ (clock tick). Hình 8.2a cho ta thấy việc thực hiện một tác vụ khi ta sử dụng lệnh Delay() đối với tác vụ có tính chu kỳ đó. Trong trường hợp này, khoảng thời gian trễ là 3 clock tick. Hoạt động của hệ thống phụ thuộc vào thời gian thực thi của tác vụ. Nếu thời gian thực hiện nhỏ hơn 1 tick thì tác vụ sẽ thức dậy sau mỗi 3 tick như mong muốn. Tuy nhiên, nếu tác vụ hoạt động quá 1 tick, khi đó, sau khi tác vụ gọi lệnh Delay(), nó vẫn sẽ bị khoá trong 3 clock tick. Thế nhưng, trong ví dụ này, tác vụ thực tế là sẽ thức dậy sau mỗi 4 xung clock tick. Đó không phải là điều chúng ta mong muốn.

Một phương án khác, không phải hệ thống nào cũng có, được trình bày ở hình 8.2b. Trong trường hợp này, bộ lập lịch sẽ đánh thức tác vụ vào đúng thời điểm thích hợp mà không quan tâm đến thời gian thực hiện tác vụ. Do đó, thay vì dùng hàm Delay(), một tác vụ có tính chu kỳ sẽ gọi hàm WaitTilNext(). Hàm này sẽ khóa tác vụ cho tới phiên thực hiện kế tiếp.

Hình 8.2: Các tác vụ có tính chu kì

Một số tác vụ phải phản ứng lại các sự kiện xảy ra ngẫu nhiên tại các thời điểm khác nhau. Một sự kiện có thể là việc một gói dữ liệu từ trên mạng được gửi đến nơi, việc một cái công tắc đóng lại để chỉ ra là bể nước đã đầy hoặc cũng có thể là sự kết thúc việc convert một tín hiệu tương tự sang số của bộ ADC và đang cần được đọc. Thông thường, các sự kiện không đồng bộ này được giao tiếp với máy tính thông qua các ngắt. Chương trình con dịch vụ ngắt phải có cách nào đó để kết nối sự xuất hiện của ngắt với tác vụ chịu trách nhiệm xử lý sự kiện.

  • Lập lịch theo kiểu chiếm quyền thực thi và lập lịch không có chiếm quyền thực thi

Có 2 phương thức cơ bản cho việc lập lịch một tác vụ: chiếm quyền thực thikhông chiếm quyền thực thi. Xét 2 tác vụ: tác vụ 1 có mức ưu tiên thấp hơn đang thực hiện và tác vụ 2 có mức ưu tiên cao hơn đang bị khoá để chờ một sự kiện xảy ra, sự kiện này được thông báo bởi một tín hiệu ngắt. Hình 8.3a cho thấy những gì xảy ra trong hệ thống không có tính chiếm quyền ưu tiên. Chương trình con dịch vụ ngắt ISR làm cho tác vụ 2 với mức ưu tiên cao hơn chuyển từ trạng thái Khoá sang trạng thái Sẵn sàng. Tuy nhiên, đến khi ISR được thực hiện xong thì tác vụ 1 với mức ưu tiên thấp hơn vẫn sẽ được tiếp tục thực thi tại điểm nó bị ngắt. Sau đó, khi tác vụ 1 bị khoá để chờ sự kiện thì tác vụ 2 mới được chuyển sang trạng thái thực thi.

Hình 8.3 b ứng với trường hợp của hệ thống có tính chiếm quyền ưu tiên. Điểm khác biệt ở đây là bộ lập lịch được gọi đến ở cuối chương trình con dịch vụ ngắt. Bộ lập lịch xác định tác vụ có mức ưu tiên cao đang ở trạng thái Sẵn sàng và chuyển nó lên trạng thái thực thi. Do đó, tác vụ với mức ưu tiên thấp đã bị chiếm quyền thực thi. Một hệ thống không có tính chiếm quyền thực thi muốn rằng tất cả các tác vụ phải là “những công dân tốt” bằng cách tự nguyện trao trả bộ xử lý cho các tác vụ khác để chắc chắn một điều: các tác vụ đều có cơ hội để sử dụng bộ xử lý. Các thế hệ Windows trước kia đều là dạng này. Linux thì khác, nó là một hệ điều hành có tính chiếm quyền thực thi mặc dù các bản Linux chuẩn không quan tâm đến vấn đề thời gian thực bởi trong một thời gian dài, vấn đề chiếm quyền thực thi không được đề cập.

Các hệ thống có tính chiếm quyền thực thi cung cấp cho ta nhiều hơn thời gian phản ứng có thể dự đoán được bởi vì tác vụ có mức ưu tiên cao sẽ được xử lý ngay lập tức. Đây chính là điểm cốt lõi của thời gian thưc: khả năng đảm bảo một thời gian lớn nhất để phản ứng lại một sự kiện. Trong hệ thống không chiếm quyền thực thi, chẳng có gì để đảm bảo thời gian một tác vụ nhường lại bộ xử lý cho các tác vụ khác. Mặt khác, trong một hệ thống có chiếm quyền ưu tiên, vấn đề tranh chấp tài nguyên hệ thống cũng đáng được quan tâm cẩn thận.

Hình 8.3: Lập lịch: Có và không có chiếm quyền thực thi

Hai phương án khác được tận dụng để xử lý các tác vụ có cùng mức ưu tiên. Trong phương thức lập lịch kiểu vòng lặp robin, một tác vụ sẽ được thực thi đến khi nào nó bị khoá (block) để chờ một sự kiện xuất hiện hoặc cũng có khi nó tình nguyện nhường (yield) bộ xử lý lại. Sự khác biệt giữa khoá và nhường là ở chỗ: trong trường hợp thứ 2, tác vụ sẽ quay trở lại trạng thái Sẵn sàng (ready). Xét trường hợp trong danh sách Sẵn sàng có 3 tác vụ thứ tự lần lượt là A, B, C. Các tác vụ này có cùng mức ưu tiên như nhau. Tác vụ A đứng đầu danh sách và được chuyển đến trạng thái Thực thi. Khi tác vụ A nhường (yield) bộ xử lý, tác vụ B trở thành trạng thái thực thi và danh sách Sẵn sàng sẽ như sau:

B C A

Khi B nhường, C sẽ chuyển trạng thái và danh sách sẽ chuyển thành:

B C A

Như vậy, tất cả các tác vụ sẽ hoạt động thành một vòng tròn, chúng hoạt động theo kiểu nhường nhau. Các tác vụ có mức ưu tiên thấp hơn trong trạng thái Sẵn sàng sẽ không bao giờ được thực hiện cho đến khi tất cả các tác vụ trên bị khoá.

Nhát cắt thời gian là một biến thể của vòng lặp robin. Trong đó, nó quy định mỗi tác vụ sẽ nhận được một lượng thời gian nhất định hay còn gọi là nhát cắt thời gian. Việc làm này bảo vệ các tác vụ khỏi trường hợp chiếm dụng bộ xử lý quá lâu. Do đó, một tác vụ sẽ chạy cho đến khi nó bị khoá, nó tình nguyện nhường hay quá hạn về thời gian cho phép. Tùy thuộc vào hoàn cảnh và yêu cầu, các tác vụ sẽ có lượng thời gian cho phép bằng nhau hoặc khác nhau. Xét trên khía cạnh nào đó, vòng lặp robin chỉ là một dạng khác của vòng lặp polling.

  • FCFS

Trong cơ chế lập lịch đến trước được phụ vụ trước thì các quá tình được xử lý theo thứ tự mà nó xuất hiện yêu cầu và cho đến khi hoàn thành. Cơ chế lập lịch này thuộc loại không ngắt được và có ưu điểm là dễ dàng thực thi. Tuy nhiên, nó không phù hợp cho các hệ thống mà hỗ trợ nhiều người sử dụng vì có một sự biến đổi lớn về thời gian trung bình mà một quá trình hay tác vụ phải chờ đợi để được xử lý. Hơn nữa do việc xử lý không ngắt được nên có hiện tượng chiếm hữu độc quyền bộ xử lý trong thời gian dài và có thể gây ra sự trễ bất thường trong quá trình thực hiện của các tác vụ phải chờ đợi khác.

  • Shortest Job First - SJF

Trong cơ chế lập lịch này tác vụ có thời gian thực thi ngắn nhất sẽ có quyền ưu tiên cao nhất và sẽ được phục vụ trước. Vấn đề chính gặp phải trong cơ chế lập lịch này là không biết trước được thời gian thực thi của các tác vụ tham gia trong chương trình và thông thường phải áp dụng cơ chế tiên đoán và đánh giá dựa vào kinh nghiệm về các tác vụ thực thi trong hệ thống. Điều này chắc chắn rất khó để luôn đảm bảo được độ chính xác. Cơ chế lập lịch này có thể áp dụng cho cả loại ngắt được và không ngắt được.

  • Rate monotonic (RM):

Phương pháp lập lịch RM có lẽ hiện này là thuật toán được biết tới nhiều nhất áp dụng cho các tác vụ hay quá trình độc lập. Phương pháp này dựa trên một số giả thiết sau:

(1) Tất cả các tác vụ tham gia hệ thống phải có deadline kiểu chu kỳ

(2) Tất cả các tác vụ độc lập với nhau

(3) Thời gian thực hiện của các tác vụ biết trước và không đổi

(4) Thời gian chuyển đổi ngữ cảnh thực hiện là rất nhỏ và có thể bỏ qua

Thuật toán RM được thực thi theo nguyên lý gán mức ưu tiên cho các tác vụ dựa trên chu kỳ của chúng. Tác vụ nào có chu kỳ nhỏ thì sẽ có được gán mức ưu tiên cao. Theo nguyên lý này với các tác vụ chu kỳ không thay đổi thì RM sẽ là phương pháp lập lịch cho phép ngắt và mức ưu tiên cố định. Tuy nhiên RM hỗ trợ yêu cầu hệ thống không tốt.

  • Earliest deadline first (EDF)

Như đúng tên gọi của phương pháp, thuật toán lập lich theo phương pháp này sử dụng deadline của tác vụ hay như điều kiện ưu tiên để xử lý điều phối hoạt động. Tác vụ có deadline gần nhất sẽ có mức ưu tiên cao nhất và các tác vụ có deadline xa nhất sẽ nhận mức ưu tiên thấp nhất. Ưu điểm nổi bật của phương pháp này là giới hạn có thể lập lịch đáp ứng được 100% cho tất cả các tập tác vụ. Hơn nữa mức ưu tiên gán cho mỗi tác vụ trong quá trình hoạt động là động vì vậy chu kỳ của tác vụ có thể thay đổi bất kỳ lúc nào theo thời gian. EDF có thể được áp dụng cho các tập tác vụ chu kỳ và cũng có thể mở rộng để đáp ứng cho các trường hợp các deadline thay đổi khác nhau theo chu kỳ.

Vấn đề chính của thuật toán lập lich EDF là không thể đảm bảo được tác vụ nào trong hệ thống có thể không được thực thi trong tình huống quá độ hệ thống bị quá tải. Trong nhiều trường hợp mặc dù mức độ sử dụng trung bình nhỏ hơn 100% những vẫn có thể trong một tình huống nào đó vẫn vượt qua khả năng đáp ứng của hệ thống tức là sẽ có tác vụ không được đảm bảo thực thi đúng. Trong những trường hợp như vậy cần phải điều khiển để biết tác vụ nào bị lỗi không thực hiện thành công hoặc tác vụ nào được thực hiện thành công trong quá trình quá độ.

  • Minimum Laxity first (MLF)

Cơ chế lập lịch này sẽ ưu tiên tác vụ nào còn ít thời gian còn lại để thực hiện nhất trước khi nó phải kết thúc để đảm bảo yêu cầu thực thi đúng. Đây được xem là cơ chế lập lịch gán quyền ưu tiên động và dễ đạt được sự tối ưu về hiệu suất thực hiện và sự công bằng trong hệ thống.

  • Round Robin

Đây là một cơ chế lập lịch phân bổ đều đặn, ngắt được và đơn giản. Mỗi một tác vụ được xử lý/phục vụ trong một khoảng thời gian nhất định và lặp lại theo một chu trình xuyên suốt toàn bộ các tác vụ tham gia trong hệ thống. Khoảng thời gian phục vụ cho mỗi tác vụ trong quá trình là một sự thoả hiệp giữa thời gian thực hiện của các tác vụ và thời gian thực hiện một chu trình. Có thể chọn khoảng thời gian đó rất nhỏ và đôi lúc chúng ta không nhận được ra rằng đang có sự phân bổ thực hiện trong hệ thống. Tuy nhiên nếu thời gian đó quá nhỏ có thể làm mất tính hiệu quả thực hiện toàn hệ thống vì cần nhiều thời gian trong việc chuyển đổi ngữ cảnh cho mỗi tác vụ sau mỗi chu trình thực hiện.

Một hệ thống thời gian thực được gọi là “điều khiển sự kiện” có nghĩa là hệ thống đó phải có chức năng chính là phản ứng lại các sự kiện xảy ra trong môi trường của hệ thống. Vậy thì hệ thống phản ứng lại các sự kiện như thế nào?. Hiện nay có hai phương pháp tiếp cận vấn đề này. Phương pháp đầu tiên là Polling hay Vòng lặp Polling và phương pháp thứ 2 là xử lý ngắt (Interrup).

Polling

Hình 8.4: Vòng lặp Polling

Hãy xem đoạn code trong hình 8.4. Chương trình được bắt đầu bằng một vài cài đặt ban đầu cho hệ thống rồi truy cập vào trong một vòng lặp vô hạn, trong đó, các sự kiện mà hệ thống có thể phản ứng lại được kiểm tra. Khi có một sự kiện xảy ra, dịch vụ phản ứng lại sự kiện đó được kích hoạt.

Tiến trình thực hiện của vòng lặp trên tỏ ra khá đơn giản và thích hợp với những hệ thống nhỏ không đòi hỏi quá gắt gao về mặt thời gian. Tuy nhiên, cũng có một số vấn đề cần bàn đến:

  • Thời gian phản ứng lại sự kiện phụ thuộc rất lớn vào vị trí mà chương trình đang thực hiện trong vòng lặp. Lấy ví dụ: Nếu sự kiện event_1 xảy ra ngay trước câu lệnh if(event_1) thì thời gian phản ứng là rất ngắn. Nhưng nếu sự kiện event_1 xảy ra ngay sau khi câu lệnh kiểm tra đó, chương trình lúc này phải quét toàn bộ vòng lặp và quay trở về điểm đầu và thực hiện dịch vụ của sự kiện event_1.
  • Và cũng là một hệ quả tất yếu, thời gian phản ứng cũng là một hàm của số lượng sự kiện được kích hoạt tại một thời điểm và sau đó là thời gian thực hiện các dịch vụ trong một lần quét vòng lặp của chương trình.
  • Tất cả các sự kiện được chương trình đối xử một cách bình đẳng và không có sự ưu tiên nào cả.
  • Khi có một đặc tính mới, do đó là dịch vụ mới, được thêm vào chương trình, thời gian phản ứng lại dài thêm ra.

Interrupt (ngắt)

Phương pháp tiếp cận thứ 2 là ngắt (Interrupt). Rất hữu dụng và cũng gây không ít khó khăn cho người lập trình. Ý tưởng của ngắt: sự xuất hiện của một sự kiện có thể “ngắt” tiến trình thực hiện của chương trình, “nhồi” thêm và thực hiện một tiến trình khác vào như hình 8.5. Khi tiến trình nhồi thêm được thực hiện xong, chương trình chính lại được thực hiện tiếp từ thời điểm bị ngắt. Tiến trình của sự kiện ngắt được thực hiện ngay lập tức mà không phải quan tâm đến chương trình chính. Những tiến trình như thế người ta gọi là “chương trình con dịch vụ ngắt” (Interrupt Service Routine) viết tắt ISR.

Các thế hệ vi xử lý hiện nay thường thực hiện 3 loại ngắt khác nhau:

  • Câu lệnh INT, hay nhiều khi còn được nhắc đến với cái tên là TRAP (bẫy). Nó như một câu lệnh để gọi một chương trình con đặc biệt. Chúng ta sẽ đề cập đến nó sau.
  • Các trường hợp đặc biệt của bộ xử lý. Các điều kiện lỗi như lỗi chia 0, lỗi truy cập bất hợp pháp vào bộ nhớ có thể được điều khiển thông qua cơ chế ngắt.

Hình 8.5: Ngắt

Hai loại ngắt kể trên đồng bộ với việc thực hiện lệnh. Trong đó: INT chính là một câu lệnh và các lỗi đặc biệt của bộ xử lý chính là kết quả trực tiếp của việc thực hiện lệnh.

Loại ngắt thứ 3 được tạo ra bởi sự kiện xảy ra bên ngoài bộ xử lý. Loại ngắt này được tạo ra bởi các I/O phần cứng và xảy ra không đồng bộ với việc thực hiện lệnh.

Các ngắt ngoài không đồng bộ:

  • Làm tối đa hoá hiệu suất và thông lượng của hệ thống máy tính
  • Gây ra phần lớn các lỗi và rắc rối cho người lập trình

Hầu hết các bộ xử lý đều sử dụng lược đồ ngắt giống nhau. Hình 8.6 chỉ ra kiến trúc ngắt của Intel x86. 1kilo byte (KB) đầu tiên của bộ nhớ được giành cho bảng véctơ ngắt (Interrupt Vector Table). Mỗi một véctơ có 4 byte thể hiện địa chỉ (segment và offset) của chương trình con dịch vụ ngắt. Các véctơ này mang những ý nghĩa, chức năng khác nhau và được định nghĩa bởi kiến trúc của bộ xử lý. Ví dụ: véctơ 0 là lỗi chia 0, véctơ 3 là breakpoint (lệnh ngắt INT 1byte).

Hình 8.6: Ngắt - Bảng véctơ ngắt

Một số véctơ được dành cho các ngắt ngoài. Trong PC, các véctơ 8 đến 15 và 0x70 đến 0x77 được dành cho phần cứng.

Tất cả các véctơ được truy cập thông qua lệnh ngắt INT 2byte, trong đó, byte thứ 2 chỉ ra số thứ tự của véctơ (ngắt). Phần mềm hệ thống thường thiết lập các quy ước liên quan đến nhiều véctơ này. Ví dụ: PC BIOS sử dụng một số ngắt cho các dịch vụ phần cứng và LINUX sử dụng ngắt INT 0x80 để gọi dịch vụ của kernel.

Sau đây là một ví dụ về việc sử dụng ngắt INT 0x80 của Linux:

  • Bộ xử lý lưu lại giá trị hiện thời của thanh ghi bộ đếm chương trình Program Counter (PC) và Code Segment (CS) vào ngăn xếp stack cùng với từ điều khiển trạng thái bộ xử lý Proccesor Status Word (PSW).
  • Byte thứ 2 trong câu lệnh INT là một chỉ số trong bảng véctơ ngắt để từ đó tìm được địa chỉ của chương trình con dịch vụ ngắt (ISR). Bộ xử lý nạp địa chỉ này vào thanh ghi PC và CS và việc thực hiện chương trình con được thực hiện từ điểm này.Hình 8.7: Ngắt và hoạt động của ngắt
  • Kết thúc của ISR là câu lệnh IRET (Interrupt Return). Nó giải phóng PC và CS để nạp lại giá trị của chương trình chính và thực hiện tiếp lệnh tiếp theo sau lệnh INT.

Lệnh INT cũng tương tự như lệnh gọi chương trình con CALL nhưng có đôi chút khác biệt: trong khi địa chỉ đích của lệnh CALL được nhúng vào trong câu lệnh đó thì với INT, ta không cần quan tâm đến địa chỉ của ISR. Địa chỉ của nó nầm trong bảng véctơ ngắt. Đây là một điểm thuận lợi cho việc truyền thông giữa chương trình được biên dịch và chương trình được tải, ví dụ như chương trình ứng dụng và hệ điều hành.

Các ngắt ngoài có cách thức thực hiện như thể hiện trong hình 8.8. Một thiết bị bên ngoài đưa ra một “yêu cầu ngắt” Interrupt Request (IRQ). Khi bộ xử lý phản ứng lại bằng một xác nhận “chấp nhận ngắtInterrupt Acknowledge (IAK), thiết bị đó sẽ gửi số thứ tự của véctơ ngắt lên bus dữ liệu. Bộ xử lý sau đó sẽ thiết lập một lệnh ngắt INT với chỉ số véctơ ngắt đã được cung cấp.

Hình 8.8: Ngắt cứng

Trong thực tế, hầu hết các hệ thống đều sát nhập thêm một thiết bị ngoại vi đặc biệt được gọi là bộđiều khiển ngắt Interrupt Controller để quản lý các công việc chi tiết chẳng hạn như gửi chỉ số của véctơ ngắt lên bus dữ liệu vào đúng thời điểm cần thiết. Kiến trúc PC bao gồm 2 bộ điều khiển ngắt 8259, mỗi một bộ có thể quản lý được 8 ngắt. Mỗi một bộ 8259 có một cơ chế quản lý ngắt theo kiểu ưu tiên. Do đó, một thiết bị quan trọng sẽ được ưu tiên hơn thiết bị khác ít quan trọng hơn.

Ngắt có thể được kích hoạt hoặc bị vô hiệu hoá. Ở cấp độ của bộ xử lý, ngắt có thể được kích hoạt hoặc vô hiệu hoá thông qua câu lệnh STI và CLI. Các ngắt có thể được kích hoạt hoặc vô hiệu một cách có chọn lọc ở cả bộ điều khiển ngắt 8259 hay ở chính thiết bị đó. Trên thực tế, việc kích hoạt và vô hiệu ngắt chính làđiểm mấu chốtđể thiết kế và thực thi một phần mềm thời gian thực

Cũng không có gì đáng ngạc nhiên khi nói rằng ngắt không đồng bộ có những vấn đề đáng bàn của nó. Để ý một ứng dụng thu thập dữ liệu dựa trên bộ A/D đa kênh như trên hình 8.9. Cứ mỗi khi bộ chuyển đổi A/D thu thập một tập hợp dữ liệu trên các kênh, nó ngắt bộ xử lý. Chương trình con dịch vụ ngắt đọc dữ liệu và cất vào bộ nhớ đệm, nơi mà chương trình khác (còn gọi là chương trình nền) sẽ tiếp tục xử lý.

Hình 8.9: Ví dụ về ngắt

Hoạt động điều khiển ngắt cho phép chúng ta phản ứng lại A/D một cách nhanh chóng trong khi bộ nhớ đệm tách chương trình nền khỏi nguồn dữ liệu, ví dụ: chương trình nền không cần quan tâm đến dữ liệu được từ đâu mà có được. Bây giờ hãy xem đến đoạn mã lệnh được ghi trong hình 8.9. Giả thiết chỉ là thí nghiệm, chúng ta cung cấp một tín hiệu biến đổi liên tục vào cả kênh 5 và 6. Đồng thời, giả thiết rằng chương trình sẽ không bị “fail” khi đang thực hiện đo tín hiệu đồng nhất.

Trong thực tế, chương trình như đã viết chắc chắn sẽ bị “fail” bởi vì một ngắt có thể xảy ra trong khi cập nhật biến Cur_temp và cập nhật biến Set_temp với kết quả là giá trị của biến Cur_temp được cập nhật từ tập hợp dữ liệu cũ trước đó, còn giá trị của biến Set_temp được cập nhật từ tập hợp dữ liệu hiện thời. Như vậy, khi tín hiệu đầu vào thay đổi theo thời gian và các tập hợp dữ liệu được tách rời nhau ở các thời gian xác định, giá trị các biến sẽ khác nhau và do đó, chương trình sẽ “fail”.

Đây chính là bản chất của vấn đề lập trình thời gian thực. Cần phải quản lý các ngắt khôngđồng bộđể chúng không xảy ra vào những thờiđiểm không thích hợp.

Có một giải pháp, dù không hay cho lắm, để giải quyết vấn đề này. Ta có thể dùng một lệnh vô hiệu hoá ngắt (CLI) trước khi cập nhật biến Cur_temp và kích hoạt ngắt bằng lệnh STI sau khi cập nhật biến Set_temp. Việc làm này giúp các ngắt tránh khỏi phiền phức của việc cập nhật liên tục như đã đề cập. Có vẻ như chúng ta đã sáng suốt khi sử dụng các lệnh CLI và STI như một chìa khoá cho một giải pháp đúng đắn, nhưng nếu chỉ đơn giản là rải các lệnh CLI và STI trong code của chương trình thì cũng chẳng khác gì việc sử dụng các lệnh “go to” và các biến toàn cục.

0