Điều gì xảy ra khi một chương trình nhúng kết thúc?


29

Điều gì xảy ra trong một bộ xử lý nhúng khi thực thi đạt đến returncâu lệnh cuối cùng Liệu mọi thứ chỉ đóng băng như hiện tại; tiêu thụ năng lượng, vv, với một NOP vĩnh cửu trên bầu trời? hoặc NOP liên tục được thực thi, hoặc bộ xử lý sẽ tắt hoàn toàn?

Một phần lý do tôi hỏi là tôi tự hỏi liệu bộ xử lý có cần tắt nguồn trước khi nó kết thúc thực thi không và liệu nó có làm thế nào để nó hoàn thành việc thực thi nếu nó bị tắt nguồn trước khi xử lý không?


22
Nó phụ thuộc vào niềm tin của bạn. Một số người nói rằng nó sẽ tái sinh.
Telaclavo

9
Có phải cho một tên lửa?
Lee Kowalkowski

6
một số hệ thống hỗ trợ hướng dẫn HCF (Halt and Catch Fire) . :)
Stefan Paul Noack

1
nó sẽ phân nhánh thành thói quen tự hủy

Câu trả lời:


41

Đây là một câu hỏi mà cha tôi luôn luôn hỏi tôi. " Tại sao nó không chạy qua tất cả các hướng dẫn và dừng lại ở cuối? "

Hãy xem xét một ví dụ bệnh lý. Đoạn mã sau được biên dịch trong trình biên dịch C18 của Microchip cho PIC18:

void main(void)
{

}

Nó tạo ra đầu ra trình biên dịch mã sau:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

Như bạn có thể thấy, hàm main () được gọi và ở cuối có chứa câu lệnh return, mặc dù chúng tôi không đặt nó ở đó một cách rõ ràng. Khi trả về chính, CPU thực hiện lệnh tiếp theo, đơn giản là GOTO để quay lại phần đầu của mã. main () được gọi đơn giản là lặp đi lặp lại.

Bây giờ, đã nói điều này, đây không phải là cách mọi người sẽ làm mọi thứ thường. Tôi chưa bao giờ viết bất kỳ mã nhúng nào cho phép main () thoát như thế. Hầu hết, mã của tôi sẽ trông giống như thế này:

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

Vì vậy, tôi thường không bao giờ để main () thoát.

"OK ok" bạn nói. Tất cả điều này rất thú vị khi trình biên dịch đảm bảo không bao giờ có câu lệnh trả lại cuối cùng. Nhưng điều gì xảy ra nếu chúng ta buộc vấn đề này? Điều gì sẽ xảy ra nếu tôi tự mã hóa trình biên dịch chương trình của mình và không đặt lại bước khởi đầu?

Chà, rõ ràng CPU sẽ tiếp tục thực hiện các hướng dẫn tiếp theo. Những thứ đó sẽ trông giống như thế này:

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

Địa chỉ bộ nhớ tiếp theo sau lệnh cuối cùng trong hàm main () trống. Trên một vi điều khiển có bộ nhớ FLASH, một lệnh trống chứa giá trị 0xFFFF. Trên PIC ít nhất, mã op đó được hiểu là 'nop' hoặc 'không hoạt động'. Nó chỉ đơn giản là không làm gì cả. CPU sẽ tiếp tục thực thi các bước đó cho đến hết bộ nhớ.

Sau đó là gì?

Ở lệnh cuối cùng, con trỏ lệnh của CPU là 0x7FFe. Khi CPU thêm 2 vào con trỏ lệnh của nó, nó nhận được 0x8000, được coi là tràn trên PIC chỉ với 32k FLASH và do đó, nó quấn quanh trở lại 0x0000 và CPU vui vẻ tiếp tục thực hiện lại các hướng dẫn khi bắt đầu mã , giống như nó đã được thiết lập lại.


Bạn cũng hỏi về sự cần thiết phải tắt điện. Về cơ bản bạn có thể làm bất cứ điều gì bạn muốn, và nó phụ thuộc vào ứng dụng của bạn.

Nếu bạn đã có một ứng dụng chỉ cần thực hiện một việc sau khi bật nguồn, và sau đó không làm gì khác, bạn chỉ có thể đặt một lúc (1); ở cuối main () để CPU ngừng làm bất cứ điều gì đáng chú ý.

Nếu ứng dụng yêu cầu CPU tắt nguồn, thì tùy thuộc vào CPU, có thể sẽ có nhiều chế độ ngủ khác nhau. Tuy nhiên, CPU có thói quen thức dậy một lần nữa, vì vậy bạn phải đảm bảo rằng không có giới hạn thời gian cho giấc ngủ và không có Watch Dog Timer hoạt động, v.v.

Bạn thậm chí có thể tổ chức một số mạch bên ngoài cho phép CPU tự cắt hoàn toàn nguồn điện của mình khi hoàn thành. Xem câu hỏi này: Sử dụng nút ấn tạm thời làm công tắc bật tắt chốt .


20

Đối với mã được biên dịch, nó phụ thuộc vào trình biên dịch. Trình biên dịch ARM gcc Rowley CrossWorks mà tôi sử dụng để nhảy mã trong tệp crt0.s có vòng lặp vô hạn. Trình biên dịch Microchip C30 cho các thiết bị 16-bit DSPIC và PIC24 (cũng dựa trên gcc) đặt lại bộ xử lý.

Tất nhiên, hầu hết các phần mềm nhúng không bao giờ chấm dứt như vậy và thực thi mã liên tục trong một vòng lặp.


13

Có hai điểm được thực hiện ở đây:

  • Một chương trình nhúng, nói đúng ra, không thể "kết thúc".
  • Rất hiếm khi cần chạy một chương trình nhúng trong một thời gian và sau đó "kết thúc".

Khái niệm tắt chương trình thường không tồn tại trong môi trường nhúng. Ở mức độ thấp, CPU sẽ thực hiện các hướng dẫn trong khi có thể; không có thứ gọi là "tuyên bố hoàn trả cuối cùng". CPU có thể dừng thực thi nếu gặp lỗi không thể phục hồi hoặc nếu bị dừng rõ ràng (đặt ở chế độ ngủ, chế độ năng lượng thấp, v.v.), nhưng lưu ý rằng ngay cả chế độ ngủ hoặc lỗi không thể phục hồi thường không đảm bảo rằng sẽ không có thêm mã nào xảy ra được thực thi. Bạn có thể thức dậy từ chế độ ngủ (đó là cách chúng thường được sử dụng) và ngay cả CPU bị khóa vẫn có thể thực thi trình xử lý NMI (đây là trường hợp của Cortex-M). Một cơ quan giám sát vẫn sẽ chạy, và bạn có thể không thể vô hiệu hóa nó trên một số bộ vi điều khiển một khi nó được kích hoạt. Các chi tiết rất khác nhau giữa các kiến ​​trúc.

Trong trường hợp phần sụn được viết bằng ngôn ngữ như C hoặc C ++, điều gì xảy ra nếu lối ra main () được xác định bởi mã khởi động. Ví dụ: đây là phần có liên quan của mã khởi động từ Thư viện ngoại vi tiêu chuẩn STM32 (đối với chuỗi công cụ GNU, các nhận xét là của tôi):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

Mã này sẽ nhập một vòng lặp vô hạn khi main () trả về, mặc dù theo cách không rõ ràng ( bl maintải lrvới địa chỉ của lệnh tiếp theo thực sự là một bước nhảy đến chính nó). Không có nỗ lực nào để ngăn chặn CPU hoặc làm cho nó chuyển sang chế độ năng lượng thấp, v.v. Nếu bạn có nhu cầu chính đáng cho bất kỳ điều nào trong ứng dụng của mình, bạn sẽ phải tự làm điều đó.

Lưu ý rằng như được chỉ định trong ARMv7-M ARM A2.3.1, thanh ghi liên kết được đặt thành 0xFFFFFFFF khi đặt lại và một nhánh đến địa chỉ đó sẽ gây ra lỗi. Vì vậy, các nhà thiết kế của Cortex-M đã quyết định coi sự trở lại từ trình xử lý thiết lập lại là bất thường và thật khó để tranh luận với họ.

Nói về một nhu cầu chính đáng để dừng CPU sau khi chương trình cơ sở kết thúc, thật khó để tưởng tượng bất kỳ điều gì sẽ không được phục vụ tốt hơn bằng cách tắt nguồn thiết bị của bạn. (Nếu bạn vô hiệu hóa CPU "tốt", điều duy nhất có thể thực hiện đối với thiết bị của bạn là chu kỳ nguồn hoặc thiết lập lại phần cứng bên ngoài.) Bạn có thể xác nhận lại tín hiệu ENABLE cho bộ chuyển đổi DC / DC của mình hoặc tắt nguồn điện trong một số cách khác, giống như một máy tính ATX.


1
"Bạn có thể thức dậy từ chế độ ngủ (đó là cách chúng thường được sử dụng) và ngay cả CPU bị khóa vẫn có thể thực thi trình xử lý NMI (đây là trường hợp của Cortex-M)." <- nghe có vẻ như là một phần tuyệt vời của một cuốn sách hoặc cốt truyện phim. :)
Mark Allen

"Bl main" sẽ tải "lr" với địa chỉ của lệnh sau ("bx lr"), phải không? Có bất kỳ lý do nào để mong đợi "lr" chứa bất cứ điều gì khác khi "bx lr" được thực thi không?
supercat

@supercat: bạn đúng rồi. Tôi đã chỉnh sửa câu trả lời của mình để loại bỏ lỗi và mở rộng nó ra một chút. Suy nghĩ về điều này, cách họ thực hiện vòng lặp này khá kỳ lạ; họ có thể dễ dàng thực hiện loop: b loop. Tôi tự hỏi nếu họ thực sự có nghĩa là để trở lại nhưng quên tiết kiệm lr.
Thorn

Thật tò mò. Tôi hy vọng rằng rất nhiều mã ARM sẽ thoát ra với LR giữ cùng giá trị mà nó giữ khi nhập cảnh, nhưng không biết rằng nó được đảm bảo. Việc bảo đảm như vậy thường không hữu ích, nhưng duy trì nó sẽ yêu cầu thêm một hướng dẫn vào các thói quen sao chép r14 vào một số đăng ký khác và sau đó gọi một số thói quen khác. Nếu lr được coi là "không xác định" khi trả lại, người ta có thể "bx" thanh ghi giữ bản sao đã lưu. Điều đó sẽ gây ra hành vi rất kỳ quặc với mã được chỉ định, mặc dù.
supercat

Trên thực tế tôi khá chắc chắn rằng các hàm không có lá được dự kiến ​​sẽ tiết kiệm lr. Chúng thường đẩy lr lên ngăn xếp trong prolog và quay lại bằng cách đưa giá trị đã lưu vào pc. Đây là những gì ví dụ như một main () C hoặc C ++ sẽ làm, nhưng các nhà phát triển của thư viện được đề cập rõ ràng đã không làm bất cứ điều gì như thế này trong Reset_Handler.
Thorn

9

Khi hỏi về return, bạn đang nghĩ mức độ quá cao. Mã C được dịch thành mã máy. Vì vậy, nếu bạn nghĩ về bộ xử lý một cách mù quáng rút các lệnh ra khỏi bộ nhớ và thực thi chúng, thì không biết cái nào là "cuối cùng" return. Vì vậy, các bộ xử lý không có kết thúc cố hữu, mà thay vào đó, tùy thuộc vào lập trình viên để xử lý trường hợp kết thúc. Như Leon đã chỉ ra trong câu trả lời của mình, các trình biên dịch đã lập trình một hành vi mặc định, nhưng thường thì lập trình viên có thể muốn trình tự tắt máy của họ (Tôi đã thực hiện nhiều việc khác nhau như vào chế độ năng lượng thấp và tạm dừng hoặc chờ cáp USB được cắm trong và sau đó khởi động lại).

Nhiều bộ vi xử lý có các hướng dẫn tạm dừng, làm dừng bộ xử lý mà không ảnh hưởng đến perhipemony. Các bộ xử lý khác có thể dựa vào "tạm dừng" bằng cách đơn giản chỉ cần nhảy đến cùng một địa chỉ nhiều lần. Có thể có các tùy chọn, nhưng tùy thuộc vào lập trình viên vì bộ xử lý sẽ chỉ đọc các hướng dẫn từ meory, ngay cả khi bộ nhớ đó không có ý định hướng dẫn.


7

Vấn đề không phải là nhúng (một hệ thống nhúng có thể chạy Linux hoặc thậm chí Windows) mà là độc lập hoặc kim loại trần: chương trình ứng dụng (đã biên dịch) là thứ duy nhất đang chạy trên máy tính (Không thành vấn đề nếu đó là vi điều khiển hoặc vi xử lý).

Đối với hầu hết các ngôn ngữ, ngôn ngữ không xác định điều gì xảy ra khi 'chính' chấm dứt và không có HĐH để quay lại. Đối với C, nó phụ thuộc vào những gì trong tệp khởi động (thường là crt0.s). Trong hầu hết các trường hợp, người dùng có thể (hoặc thậm chí phải) cung cấp mã khởi động, vì vậy câu trả lời cuối cùng là: bất cứ điều gì bạn viết là mã khởi động hoặc những gì xảy ra trong mã khởi động bạn chỉ định.

Trong thực tế có 3 cách tiếp cận:

  • không có biện pháp đặc biệt. Điều gì xảy ra khi lợi nhuận chính không được xác định.

  • nhảy về 0 hoặc sử dụng bất kỳ phương tiện nào khác để khởi động lại ứng dụng.

  • nhập một vòng lặp chặt chẽ (hoặc vô hiệu hóa các ngắt và thực hiện lệnh tạm dừng), khóa bộ xử lý mãi mãi.

Những gì phù hợp phụ thuộc vào ứng dụng. Một thiệp chúc mừng lông thú và một hệ thống kiểm soát phanh (chỉ đề cập đến hai hệ thống nhúng) có lẽ nên khởi động lại. Nhược điểm của việc khởi động lại là vấn đề có thể không được chú ý.


5

Tôi đã xem xét một số mã ATtiny45 đã được phân tách (C ++ được biên dịch bởi avr-gcc) vào ngày khác và những gì nó làm ở cuối mã được chuyển đến 0x0000. Về cơ bản thực hiện thiết lập lại / khởi động lại.

Nếu bước nhảy cuối cùng đến 0x0000 bị trình biên dịch / trình biên dịch bỏ qua, tất cả các byte trong bộ nhớ chương trình được hiểu là mã máy 'hợp lệ' và nó chạy hết cỡ cho đến khi bộ đếm chương trình cuộn qua 0x0000.

Trên AVR, một byte 00 (giá trị mặc định khi một ô trống) là NOP = Không hoạt động. Vì vậy, nó chạy rất nhanh, không làm gì ngoài việc chỉ mất một chút thời gian.


1

mainMã được biên dịch chung sau đó được liên kết với mã khởi động (nó có thể được tích hợp vào toolchain, được cung cấp bởi nhà cung cấp chip, được viết bởi bạn, v.v.).

Trình liên kết sau đó đặt tất cả mã ứng dụng và mã khởi động vào các phân đoạn bộ nhớ, vì vậy việc trả lời cho bạn các câu hỏi phụ thuộc vào: 1. mã từ khởi động, vì ví dụ: có thể:

  • kết thúc bằng vòng lặp trống ( bl lrhoặc b .), tương tự như "kết thúc chương trình", nhưng các ngắt và các thiết bị ngoại vi được bật trước đó vẫn sẽ hoạt động,
  • kết thúc bằng việc nhảy đến đầu chương trình (hoàn toàn chạy lại khởi động hoặc jsut đến main).
  • chỉ cần bỏ qua "những gì sẽ tiếp theo" sau khi gọi để maintrở lại.

    1. Trong viên đạn thứ ba, khi bộ đếm chương trình chỉ tăng sau khi quay lại từ mainhành vi sẽ phụ thuộc vào trình liên kết của bạn (và / hoặc tập lệnh liên kết được sử dụng trong quá trình liên kết).
  • Nếu hàm / mã khác được đặt sau hàm của bạn, mainnó sẽ được thực thi với các giá trị đối số không hợp lệ / không xác định,

  • Nếu bộ nhớ sau bắt đầu với ngoại lệ hướng dẫn xấu, migh sẽ được tạo và MCU cuối cùng sẽ được đặt lại (nếu ngoại lệ tạo lại thiết lập).

Nếu watchdog được bật, cuối cùng nó sẽ thiết lập lại MCU mặc dù tất cả các vòng lặp vô tận mà bạn đang ở (tất nhiên nếu nó sẽ không được tải lại).


-1

Cách tốt nhất để ngăn chặn một thiết bị nhúng là chờ mãi mãi với các hướng dẫn NOP.

Cách thứ hai là đóng thiết bị bằng cách sử dụng chính thiết bị. Nếu bạn có thể kiểm soát một relay với hướng dẫn của bạn, bạn chỉ có thể mở công tắc được cung cấp năng lượng thiết bị nhúng của bạn và Huh thiết bị nhúng của bạn đã biến mất không có điện năng tiêu thụ.


Điều đó thực sự không trả lời câu hỏi.
Matt Young

-4

Nó đã được giải thích rõ ràng trong hướng dẫn. Thông thường, một ngoại lệ chung sẽ được CPU ném ra vì nó sẽ truy cập vào một vị trí bộ nhớ nằm ngoài phân đoạn ngăn xếp. [ngoại lệ bảo vệ bộ nhớ].

Ý của hệ thống nhúng là gì? Vi xử lý hoặc vi điều khiển? Dù bằng cách nào, nó được xác định trong hướng dẫn.

Trong CPU x86, chúng tôi tắt máy tính bằng cách gửi lệnh đến bộ điều khiển ACIP. Vào Chế độ quản lý hệ thống. Vì vậy, bộ điều khiển đó là chip I / O và bạn không cần phải tắt thủ công.

Đọc thông số kỹ thuật ACPI để biết thêm thông tin.


3
-1: TS không đề cập đến bất kỳ CPU cụ thể nào, vì vậy đừng giả sử quá nhiều. Các hệ thống khác nhau xử lý trường hợp này theo những cách rất khác nhau.
Wouter van Ooijen
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.