Điều gì xảy ra khi vi điều khiển hết RAM?


12

Nó có thể chỉ là một sự trùng hợp ngẫu nhiên nhưng tôi đã nhận thấy các bộ vi điều khiển mà tôi đã sử dụng được khởi động lại khi chúng hết RAM (Atmega 328 nếu phần cứng cụ thể). Đó có phải là những gì vi điều khiển làm khi hết bộ nhớ? Nếu không thì chuyện gì xảy ra?

Sao lại như vậy? Con trỏ ngăn xếp chắc chắn được tăng lên một cách mù quáng đến một phạm vi bộ nhớ không được phân bổ (hoặc cuộn qua), nhưng điều xảy ra sau đó: có một loại bảo vệ nào đó làm cho nó khởi động lại hay không (trong số các hiệu ứng khác) là kết quả của việc ghi đè quan trọng dữ liệu (mà tôi giả sử khác với mã mà tôi nghĩ là chạy trực tiếp từ flash)?

Tôi không chắc chắn điều này sẽ ở đây hay trên Stack Overflow, vui lòng cho tôi biết nếu điều này nên được di chuyển mặc dù tôi khá chắc chắn phần cứng có vai trò trong đó.

Cập nhật

Tôi nên chỉ ra rằng tôi đặc biệt quan tâm đến cơ chế thực tế đằng sau tình trạng hỏng bộ nhớ (đó có phải là kết quả của việc SP bị lật -> điều đó có phụ thuộc vào ánh xạ bộ nhớ của uC không, v.v.)?


8
Một số micros sẽ thiết lập lại nếu bạn cố gắng truy cập các địa chỉ không hợp lệ. Đây là một tính năng có giá trị được triển khai trong phần cứng. Những lần khác, nó có thể sẽ nhảy đến một nơi nào đó tùy ý (giả sử bạn đã ghi lại địa chỉ trả lại cho ISR), có thể thực thi dữ liệu thay vì mã nếu kiến ​​trúc cho phép điều đó và có thể bị cuốn vào một vòng lặp mà cơ quan giám sát đưa ra của.
Spehro Pefhany

2
Một bộ xử lý không thể hết RAM, không có hướng dẫn nào sẽ khiến nó hết RAM. Hết RAM hoàn toàn là một khái niệm phần mềm.
dùng253751

Câu trả lời:


14

Nói chung, stack và heap va vào nhau. Tại thời điểm đó tất cả trở nên lộn xộn.

Tùy thuộc vào MCU, một trong nhiều điều có thể (hoặc sẽ) xảy ra.

  1. Các biến bị hỏng
  2. Ngăn xếp bị hỏng
  3. Chương trình bị hỏng

Khi 1 xảy ra, bạn bắt đầu có hành vi lạ - những điều không làm những gì họ nên làm. Khi 2 xảy ra tất cả các cách phá vỡ địa ngục. Nếu địa chỉ trả về trên ngăn xếp (nếu có) bị hỏng, thì cuộc gọi hiện tại sẽ trở về là điều ai cũng đoán được. Lúc đó về cơ bản MCU sẽ bắt đầu làm những việc ngẫu nhiên. Khi 3 xảy ra một lần nữa, ai biết khá rõ chuyện gì sẽ xảy ra. Điều này chỉ xảy ra khi bạn thực thi mã ra khỏi RAM.

Nói chung khi ngăn xếp bị hỏng tất cả. Những gì xảy ra là xuống MCU.

Có thể là việc cố gắng phân bổ bộ nhớ ở nơi đầu tiên thất bại để tham nhũng không xảy ra. Trong trường hợp này, MCU có thể đưa ra một ngoại lệ. Nếu không có trình xử lý ngoại lệ nào được cài đặt, thì phần lớn MCU sẽ dừng lại (tương đương while (1);. Nếu có trình xử lý được cài đặt, thì nó có thể khởi động lại sạch sẽ.

Nếu việc phân bổ bộ nhớ đi trước hoặc nếu nó cố gắng, thất bại và chỉ tiếp tục mà không có bộ nhớ được phân bổ, thì bạn sẽ đi vào cõi "ai biết?". MCU có thể tự khởi động lại thông qua sự kết hợp đúng của các sự kiện (các sự cố gây ra mà cuối cùng là đặt lại chip, v.v.), nhưng không có gì đảm bảo điều đó xảy ra.

Tuy nhiên, điều thường có khả năng xảy ra là rất cao, tuy nhiên, nếu được bật, là bộ đếm thời gian theo dõi nội bộ (nếu có), hết thời gian và khởi động lại chip. Khi chương trình hoàn toàn AWOL thông qua loại sự cố này, các hướng dẫn để đặt lại bộ hẹn giờ thường sẽ không được chạy, vì vậy nó sẽ hết thời gian và đặt lại.


Cảm ơn câu trả lời của bạn, đó là một bản tóm tắt tuyệt vời về các hiệu ứng. Có lẽ tôi nên xác định mặc dù tôi muốn biết thêm chi tiết về cơ chế thực tế của những lỗi đó: toàn bộ RAM được phân bổ cho stack và heap, sao cho con trỏ ngăn xếp cuộn qua và ghi đè lên các biến / địa chỉ trước đó? Hoặc là ít phụ thuộc vào ánh xạ bộ nhớ của mỗi vi mô? Tùy chọn (có thể là một chủ đề trong chính nó), tôi muốn tìm hiểu cách các trình xử lý phần cứng đó được thực hiện.
Mystère

1
Nó chủ yếu phụ thuộc vào trình biên dịch và thư viện C tiêu chuẩn đang sử dụng. Đôi khi nó cũng phụ thuộc vào cách trình biên dịch được cấu hình (tập lệnh liên kết, v.v.).
Majenko

Bạn có thể mở rộng về điều đó, có lẽ với một vài ví dụ?
Mystère

Không thực sự, không. Một số hệ thống phân bổ không gian hữu hạn cho các phân khúc khác nhau, một số thì không. Một số sử dụng tập lệnh liên kết để xác định phân đoạn, một số thì không. Chọn một vi điều khiển mà bạn quan tâm và thực hiện một số nghiên cứu về cách phân bổ bộ nhớ của nó hoạt động.,
Majenko

12

Một quan điểm khác: Vi điều khiển không hết bộ nhớ.

Ít nhất, không phải khi được lập trình đúng. Lập trình một vi điều khiển không hoàn toàn giống như lập trình cho mục đích chung, để thực hiện đúng cách, bạn phải nhận thức được các ràng buộc và chương trình của nó. Có các công cụ để giúp đảm bảo điều này. Tìm kiếm chúng và tìm hiểu chúng - ít nhất là làm thế nào để đọc các đoạn script và cảnh báo của trình liên kết.

Tuy nhiên, như Majenko và những người khác nói, một bộ vi điều khiển được lập trình kém có thể hết bộ nhớ, và sau đó làm bất cứ điều gì kể cả vòng lặp vô hạn (ít nhất là cho bộ đếm thời gian theo dõi cơ hội để thiết lập lại. Bạn đã kích hoạt bộ đếm thời gian theo dõi, phải không? )

Các quy tắc lập trình phổ biến cho vi điều khiển tránh điều này: ví dụ: tất cả bộ nhớ được phân bổ trên ngăn xếp hoặc phân bổ tĩnh (toàn cầu); "Mới" hoặc "malloc" đều bị cấm. Đệ quy cũng vậy, sao cho độ sâu tối đa của lồng con có thể được phân tích và hiển thị để phù hợp với ngăn xếp có sẵn.

Do đó, dung lượng lưu trữ tối đa cần thiết có thể được tính khi chương trình được biên dịch hoặc liên kết và so sánh với kích thước bộ nhớ (thường được mã hóa trong tập lệnh liên kết) cho bộ xử lý cụ thể mà bạn đang nhắm mục tiêu.

Sau đó, vi điều khiển có thể không hết bộ nhớ, nhưng chương trình của bạn có thể. Và trong trường hợp đó, bạn có thể

  • viết lại nó, nhỏ hơn, hoặc
  • chọn một bộ xử lý lớn hơn (chúng thường có sẵn với các kích thước bộ nhớ khác nhau).

Một bộ quy tắc chung cho lập trình vi điều khiển là MISRA-C , được ngành công nghiệp ô tô áp dụng.

Thực tiễn tốt nhất theo quan điểm của tôi là sử dụng tập hợp con SPARK-2014 của Ada. Ada thực sự nhắm mục tiêu các bộ điều khiển nhỏ như AVR, MSP430 và ARM Cortex một cách hợp lý và vốn cung cấp một mô hình tốt hơn cho lập trình vi điều khiển so với C. Nhưng SPARK thêm chú thích vào chương trình, dưới dạng nhận xét, mô tả những gì chương trình đang làm.

Bây giờ các công cụ SPARK sẽ phân tích chương trình, bao gồm các chú thích đó và chứng minh các thuộc tính về nó (hoặc báo cáo các lỗi tiềm ẩn). Bạn không phải lãng phí thời gian hoặc không gian mã xử lý các truy cập bộ nhớ sai hoặc tràn số nguyên vì chúng đã được chứng minh là không bao giờ xảy ra.

Mặc dù có nhiều công việc trực tiếp hơn liên quan đến SPARK, nhưng kinh nghiệm cho thấy nó có thể đến với một sản phẩm nhanh hơn và rẻ hơn vì bạn không dành thời gian để theo đuổi các khởi động lại bí ẩn và hành vi kỳ lạ khác.

So sánh MISRA-C và SPARK


3
+1 cái này. Chuyển malloc()(và là bạn đồng hành C ++ new) với AVR là một trong những điều tồi tệ nhất mà người arduino có thể làm, và đã dẫn đến nhiều, rất nhiều lập trình viên rất bối rối với mã bị hỏng cả trên diễn đàn của họ và trao đổi ngăn xếp arduino. Có rất, rất ít tình huống có mallocATmega là có lợi.
Sói Connor

3
+1 cho triết học, -1 cho chủ nghĩa hiện thực. Nếu công cụ sẽ được lập trình đúng, sẽ không cần câu hỏi này. Câu hỏi là những gì xảy ra khi vi điều khiển hết bộ nhớ. Làm thế nào để ngăn chặn chúng hết bộ nhớ là một câu hỏi khác. Một lưu ý khác, đệ quy là một công cụ mạnh mẽ, để giải quyết vấn đề hết stack.
LOLP

2
@Brian, vì tôi không phải là một thằng ngốc, tôi rõ ràng đồng ý với bạn. Tôi chỉ muốn nghĩ về nó theo quan điểm ngược lại - tôi muốn hy vọng rằng khi bạn nhận ra những hậu quả khủng khiếp của việc hết bộ nhớ (stack), bạn sẽ tìm cách ngăn chặn nó xảy ra. Bằng cách đó, bạn có một động lực thực sự để tìm kiếm các thực tiễn lập trình tốt thay vì chỉ làm theo lời khuyên lập trình tốt ... và khi bạn vượt qua rào cản bộ nhớ, bạn có nhiều khả năng thực thi các thực tiễn tốt ngay cả khi phải trả giá bằng sự thuận tiện. Đó chỉ là một quan điểm ...
LOLP

2
@PkP: nghe ya to và rõ ràng. Tôi gắn nhãn này là một quan điểm thay thế - bởi vì nó không thực sự trả lời câu hỏi!
Brian Drumond

2
@ MisterMystère: Vi điều khiển thường không hết bộ nhớ. Một vi điều khiển có 4096 byte RAM khi được bật nguồn lần đầu tiên sẽ có 4096 byte mãi mãi. Có thể mã có thể cố gắng truy cập sai các địa chỉ không tồn tại hoặc hy vọng rằng hai phương thức địa chỉ máy tính khác nhau sẽ truy cập vào bộ nhớ khác nhau khi chúng không hoạt động, nhưng chính bộ điều khiển sẽ chỉ thực hiện các hướng dẫn mà nó đưa ra.
supercat

6

Tôi thực sự thích câu trả lời của Majenko và + 1'd nó. Nhưng tôi muốn làm rõ điều này đến một điểm sắc nét:

Bất cứ điều gì có thể xảy ra khi một vi điều khiển hết bộ nhớ.

Bạn thực sự không thể dựa vào bất cứ điều gì khi nó xảy ra. Khi máy hết bộ nhớ ngăn xếp, hầu hết các ngăn xếp có thể bị hỏng. Và khi điều đó xảy ra, bất cứ điều gì cũng có thể xảy ra. Giá trị biến, sự cố tràn, thanh ghi tạm thời, tất cả trở nên bị hỏng, làm gián đoạn dòng chương trình. Nếu / thì / elses có thể đánh giá không chính xác. Địa chỉ trả lại bị cắt xén, làm cho chương trình nhảy đến địa chỉ ngẫu nhiên. Bất kỳ mã nào bạn đã viết trong chương trình có thể thực thi. (Xem xét mã như: "if [condition] thì {fire_all_missiles ();}"). Ngoài ra, toàn bộ các lệnh bạn chưa viết có thể thực thi khi lõi nhảy đến vị trí bộ nhớ không được kết nối. Tất cả các cược đã tắt.


2
Cảm ơn về phần phụ lục, tôi đặc biệt thích dòng fire_all_missiles ().
Mystère

1

AVR đã thiết lập lại vector tại địa chỉ số không. Khi bạn ghi đè lên ngăn xếp với rác ngẫu nhiên, cuối cùng bạn sẽ lặp lại và ghi đè lên một số địa chỉ trả lại và nó sẽ trỏ đến "hư không"; sau đó khi bạn trở về từ một chương trình con đến nơi đó, việc thực thi sẽ lặp lại xung quanh đến địa chỉ 0 trong đó bước nhảy để thiết lập lại trình xử lý thường là.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.