Apa yang terjadi ketika program tertanam selesai?

29

Apa yang terjadi pada prosesor tertanam ketika eksekusi mencapai returnpernyataan akhir Apakah semuanya membeku seperti semula; konsumsi daya dll, dengan satu NOP abadi panjang di langit? atau apakah NOP terus-menerus dieksekusi, atau akankah prosesor mati sama sekali?

Bagian dari alasan saya bertanya adalah saya bertanya-tanya apakah prosesor perlu mematikan sebelum menyelesaikan eksekusi dan jika tidak, bagaimana bisa menyelesaikan eksekusi jika telah dimatikan sebelum tangan?

Toby
sumber
22
Itu tergantung pada keyakinan Anda. Ada yang bilang itu akan bereinkarnasi.
Telaclavo
9
Apakah itu untuk rudal?
Lee Kowalkowski
6
beberapa sistem mendukung instruksi HCF (Halt and Catch Fire) . :)
Stefan Paul Noack
1
itu akan bercabang ke rutin penghancuran diri

Jawaban:

41

Ini pertanyaan yang selalu ditanyakan ayahku. " Kenapa tidak menjalankan semua instruksi dan berhenti di akhir? "

Mari kita lihat contoh patologis. Kode berikut dikompilasi dalam kompiler C18 Microchip untuk PIC18:

void main(void)
{

}

Ini menghasilkan output assembler berikut:

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

Seperti yang Anda lihat, main () dipanggil, dan pada akhirnya berisi pernyataan pengembalian, meskipun kami sendiri tidak secara eksplisit menempatkannya di sana. Ketika main kembali, CPU menjalankan instruksi berikutnya yang hanya merupakan GOTO untuk kembali ke awal kode. main () hanya dipanggil berulang kali.

Sekarang, setelah mengatakan ini, ini bukan cara orang akan melakukan sesuatu biasanya. Saya belum pernah menulis kode tertanam yang memungkinkan main () keluar seperti itu. Sebagian besar, kode saya akan terlihat seperti ini:

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

Jadi saya biasanya tidak akan membiarkan main () keluar.

"OK, ok," katamu. Semua ini sangat menarik bahwa kompiler memastikan tidak pernah ada pernyataan pengembalian terakhir. Tetapi apa yang terjadi jika kita memaksakan masalah ini? Bagaimana jika saya memberi kode pada assembler saya, dan tidak melompat kembali ke awal?

Yah, jelas CPU hanya akan terus menjalankan instruksi berikutnya. Itu akan terlihat seperti ini:

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

Alamat memori berikutnya setelah instruksi terakhir di main () kosong. Pada mikrokontroler dengan memori FLASH, instruksi kosong berisi nilai 0xFFFF. Paling tidak pada PIC, kode op itu ditafsirkan sebagai 'nop', atau 'no operation'. Itu tidak melakukan apa-apa. CPU akan terus menjalankan nops-nops tersebut hingga ke memori.

Apa setelah itu

Pada instruksi terakhir, penunjuk instruksi CPU adalah 0x7FFe. Ketika CPU menambahkan 2 ke penunjuk instruksinya, ia mendapat 0x8000, yang dianggap sebagai melimpah pada PIC dengan hanya 32k FLASH, dan itu membungkus kembali ke 0x0000, dan CPU dengan senang hati melanjutkan menjalankan instruksi kembali pada awal kode , sama seperti jika itu telah diatur ulang.


Anda juga bertanya tentang perlunya mematikan. Pada dasarnya Anda dapat melakukan apa pun yang Anda inginkan, dan itu tergantung pada aplikasi Anda.

Jika Anda memang memiliki aplikasi yang hanya perlu melakukan satu hal setelah dinyalakan, dan kemudian tidak melakukan apa-apa lagi, Anda cukup meletakkannya (1); di akhir main () sehingga CPU berhenti melakukan sesuatu yang terlihat.

Jika aplikasi membutuhkan CPU untuk mematikan, maka, tergantung pada CPU, mungkin akan ada berbagai mode tidur yang tersedia. Namun, CPU memiliki kebiasaan untuk bangun lagi, jadi Anda harus memastikan tidak ada batasan waktu untuk tidur, dan tidak ada Watch Dog Timer yang aktif, dll.

Anda bahkan dapat mengatur beberapa sirkuit eksternal yang memungkinkan CPU untuk benar-benar memotong dayanya sendiri setelah selesai. Lihat pertanyaan ini: Menggunakan tombol tekan sesaat sebagai sakelar sakelar on-off yang terkunci .

Roket
sumber
20

Untuk kode yang dikompilasi, itu tergantung pada kompiler. Rowley CrossWorks gcc ARM compiler yang saya gunakan melompat ke kode dalam file crt0.s yang memiliki loop tak terbatas. Kompiler Microchip C30 untuk perangkat 16-bit dsPIC dan PIC24 (juga berdasarkan gcc) mengatur ulang prosesor.

Tentu saja, sebagian besar perangkat lunak tertanam tidak pernah berakhir seperti itu, dan mengeksekusi kode secara terus menerus dalam satu lingkaran.

Leon Heller
sumber
13

Ada dua hal yang harus dibuat di sini:

  • Program tertanam, secara tegas, tidak dapat "menyelesaikan".
  • Sangat jarang ada kebutuhan untuk menjalankan program tertanam untuk beberapa waktu dan kemudian "selesai".

Konsep shutdown program biasanya tidak ada di lingkungan yang tertanam. Pada level rendah, CPU akan menjalankan instruksi selagi bisa; tidak ada yang namanya "pernyataan pengembalian akhir". CPU dapat menghentikan eksekusi jika menemui kesalahan yang tidak dapat dipulihkan atau jika dihentikan secara eksplisit (dimasukkan ke mode tidur, mode daya rendah, dll.), Tetapi perhatikan bahwa mode tidur atau kesalahan yang tidak dapat dipulihkan biasanya tidak menjamin bahwa tidak ada lagi kode yang akan dieksekusi. Anda dapat bangun dari mode tidur (itulah yang biasanya digunakan), dan bahkan CPU yang terkunci masih dapat menjalankan NMI handler (ini adalah kasus untuk Cortex-M). Watchdog akan tetap berjalan juga, dan Anda mungkin tidak dapat menonaktifkannya pada beberapa mikrokontroler setelah diaktifkan. Detailnya sangat bervariasi di antara arsitektur.

Dalam hal firmware yang ditulis dalam bahasa seperti C atau C ++, apa yang terjadi jika main () keluar ditentukan oleh kode startup. Sebagai contoh, berikut ini adalah bagian yang relevan dari kode startup dari Perpustakaan Peripheral Standar STM32 (untuk toolchain GNU, komentar adalah milik saya):

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

Kode ini akan memasuki loop tak terbatas ketika main () kembali, meskipun dengan cara yang tidak jelas ( bl maindimuat lrdengan alamat instruksi berikutnya yang secara efektif merupakan lompatan ke dirinya sendiri). Tidak ada upaya yang dilakukan untuk menghentikan CPU atau membuatnya memasuki mode daya rendah, dll. Jika Anda memiliki kebutuhan yang sah untuk semua itu dalam aplikasi Anda, Anda harus melakukannya sendiri.

Perhatikan bahwa sebagaimana ditentukan dalam ARMv7-M ARM A2.3.1, register tautan diatur ke 0xFFFFFFFF saat disetel ulang, dan cabang ke alamat itu akan memicu kesalahan. Jadi para desainer Cortex-M memutuskan untuk memperlakukan pengembalian dari handler reset sebagai tidak normal, dan sulit untuk berdebat dengan mereka.

Berbicara tentang kebutuhan yang sah untuk menghentikan CPU setelah firmware selesai, sulit untuk membayangkan apa pun yang tidak akan lebih baik dilayani dengan mematikan perangkat Anda. (Jika Anda menonaktifkan CPU "untuk selamanya", satu-satunya hal yang dapat dilakukan untuk perangkat Anda adalah siklus daya atau reset perangkat keras eksternal.) Anda dapat membatalkan sinyal ENABLE untuk konverter DC / DC Anda atau mematikan catu daya Anda di beberapa cara lain, seperti PC ATX.

Duri
sumber
1
"Anda dapat bangun dari mode tidur (itulah yang biasanya digunakan), dan bahkan CPU yang terkunci masih dapat menjalankan pengendali NMI (ini adalah kasus untuk Cortex-M)." <- terdengar seperti bagian yang luar biasa dari buku atau plot film. :)
Mark Allen
"Bl main" akan memuat "lr" dengan alamat instruksi berikut ("bx lr"), bukan? Apakah ada alasan untuk mengharapkan "lr" mengandung hal lain ketika "bx lr" dijalankan?
supercat
@ supercat: tentu saja Anda benar. Saya mengedit jawaban saya untuk menghapus kesalahan dan sedikit memperluasnya. Berpikir tentang ini, cara mereka mengimplementasikan loop ini cukup aneh; mereka bisa dengan mudah dilakukan loop: b loop. Saya bertanya-tanya apakah mereka benar-benar bermaksud melakukan pengembalian tetapi lupa untuk menyelamatkan lr.
Thorn
Ini penasaran. Saya berharap bahwa banyak kode ARM akan keluar dengan LR memegang nilai yang sama seperti pada entri, tetapi tidak tahu bahwa itu dijamin. Jaminan semacam itu tidak akan sering berguna, tetapi menjunjung tinggi itu akan memerlukan menambahkan instruksi ke rutin yang menyalin r14 ke beberapa register lain dan kemudian memanggil beberapa rutin lainnya. Jika dianggap "tidak dikenal" saat kembali, seseorang dapat "bx" register memegang salinan yang disimpan. Itu akan menyebabkan perilaku yang sangat aneh dengan kode yang ditunjukkan.
supercat
Sebenarnya saya cukup yakin bahwa fungsi non-daun diharapkan untuk menghemat lr. Ini biasanya mendorong lr ke tumpukan di prolog dan kembali dengan memasukkan nilai yang disimpan ke dalam pc. Inilah yang dilakukan misalnya oleh C atau C ++ main (), tetapi pengembang perpustakaan yang bersangkutan jelas tidak melakukan hal seperti ini di Reset_Handler.
Thorn
9

Ketika bertanya tentang return, Anda berpikir level terlalu tinggi. Kode C diterjemahkan ke dalam kode mesin. Jadi, jika Anda berpikir tentang prosesor yang secara membabi buta menarik instruksi dari memori dan menjalankannya, ia tidak tahu yang mana yang merupakan "final" return. Jadi, prosesor tidak memiliki akhir yang melekat, tetapi sebaliknya tergantung pada programmer untuk menangani kasus akhir. Seperti yang ditunjukkan Leon dalam jawabannya, kompiler telah memprogram perilaku default, tetapi sering kali programmer mungkin menginginkan urutan shutdown mereka sendiri (saya telah melakukan berbagai hal seperti memasuki mode daya rendah dan terputus-putus, atau menunggu kabel USB terpasang) di dan kemudian reboot).

Banyak mikroprosesor yang menghentikan instruksi, yang menghentikan prosesor tanpa memengaruhi mineral perhip. Prosesor lain mungkin mengandalkan "penghentian" hanya dengan melompat ke alamat yang sama berulang kali. Ada beberapa opsi, tetapi terserah programmer karena prosesor hanya akan terus membaca instruksi dari meory, bahkan jika memori itu tidak dimaksudkan untuk menjadi instruksi.

Klox
sumber
7

Masalahnya bukan tertanam (sistem tertanam dapat menjalankan Linux atau bahkan Windows) tetapi berdiri sendiri atau telanjang-logam: program aplikasi (dikompilasi) adalah satu-satunya hal yang berjalan di komputer (Tidak masalah apakah itu adalah mikrokontroler atau mikroprosesor).

Untuk sebagian besar bahasa, bahasa tidak mendefinisikan apa yang terjadi ketika 'utama' berakhir dan tidak ada OS untuk kembali. Untuk C itu tergantung pada apa yang ada di file startup (seringkali crt0.s). Dalam kebanyakan kasus, pengguna dapat (atau bahkan harus) menyediakan kode startup, jadi jawaban akhirnya adalah: apa pun yang Anda tulis adalah kode startup, atau apa yang terjadi pada kode startup yang Anda tentukan.

Dalam praktiknya ada 3 pendekatan:

  • tidak mengambil tindakan khusus. apa yang terjadi ketika pengembalian utama tidak ditentukan.

  • lompat ke 0, atau gunakan cara lain untuk me-restart aplikasi.

  • masukkan loop ketat (atau nonaktifkan interupsi dan eksekusi instruksi berhenti), mengunci prosesor selamanya.

Apa yang sesuai tergantung pada aplikasi. Kartu ucapan bulu-elise dan sistem kontrol-rem (hanya menyebutkan dua sistem tertanam) mungkin harus restart. Kelemahan dari memulai kembali adalah bahwa masalahnya mungkin tidak diperhatikan.

Wouter van Ooijen
sumber
5

Saya melihat beberapa ATtiny45 yang dibongkar (C ++ dikompilasi oleh avr-gcc) beberapa hari yang lalu dan apa yang dilakukannya pada akhir kode tersebut melompat ke 0x0000. Pada dasarnya melakukan reset / restart.

Jika lompatan terakhir ke 0x0000 ditinggalkan oleh kompiler / assembler, semua byte dalam memori program ditafsirkan sebagai kode mesin 'valid' dan itu berjalan sepanjang jalan sampai penghitung program bergulir ke 0x0000.

Pada AVR, 00 byte (nilai defaultnya ketika sebuah sel kosong) adalah NOP = Tidak Ada Operasi. Jadi itu berjalan sangat cepat, tidak melakukan apa-apa selain hanya mengambil waktu.

jippie
sumber
1

mainKode yang dikompilasi secara umum kemudian dihubungkan dengan kode startup (mungkin diintegrasikan ke dalam toolchain, disediakan oleh vendor chip, ditulis oleh Anda dll.).

Linker kemudian menempatkan semua kode aplikasi dan startup di segmen memori, jadi jawaban untuk pertanyaan Anda tergantung pada: 1. kode dari startup, karena dapat misalnya:

  • diakhiri dengan loop kosong ( bl lratau b .), yang akan mirip dengan "akhir program", tetapi interupsi dan periferal yang diaktifkan sebelumnya akan tetap beroperasi,
  • akhiri dengan lompat ke awal program (baik menjalankan kembali startup atau hanya sepenuhnya menjalankan main).
  • abaikan saja "apa yang akan terjadi selanjutnya" setelah panggilan untuk mainkembali.

    1. Di bullet ketiga, ketika penghitung program hanya menambah setelah kembali dari mainperilaku akan tergantung pada Anda linker (dan / atau skrip linker digunakan selama menghubungkan).
  • Jika fungsi / kode lain ditempatkan setelah Anda mainakan dieksekusi dengan nilai argumen yang tidak valid / tidak terdefinisi,

  • Jika memori berikut dimulai dengan pengecualian instruksi yang buruk mungkin dihasilkan dan MCU pada akhirnya akan mengatur ulang (jika pengecualian menghasilkan reset).

Jika watchdog diaktifkan, akhirnya MCU akan disetel ulang meskipun semua loop tak berujung Anda berada (tentu saja jika itu tidak akan dimuat ulang).

kwesolowski
sumber
-1

Cara terbaik untuk menghentikan perangkat yang disematkan adalah menunggu selamanya dengan instruksi NOP.

Cara kedua adalah menutup perangkat dengan menggunakan perangkat itu sendiri. Jika Anda dapat mengontrol relai dengan instruksi Anda, Anda bisa membuka sakelar yang memberi daya pada perangkat yang disematkan dan ya perangkat yang disematkan hilang tanpa konsumsi daya.

Enes Unal
sumber
Itu benar-benar tidak menjawab pertanyaan.
Matt Young
-4

Itu jelas dijelaskan dalam manual. Biasanya pengecualian umum akan dilemparkan oleh CPU karena akan mengakses lokasi memori yang berada di luar segmen stack. [pengecualian perlindungan memori].

Apa yang Anda maksudkan dengan sistem embedded? Mikroprosesor atau mikrokontroler? Yang manapun, itu didefinisikan pada manual.

Di CPU x86 kami mematikan komputer dengan mengirimkan perintah ke pengontrol ACIP. Memasuki Mode Manajemen Sistem. Jadi kontroler itu adalah chip I / O dan Anda tidak perlu mematikannya secara manual.

Baca spesifikasi ACPI untuk informasi lebih lanjut.

Sandun standar
sumber
3
-1: TS tidak menyebutkan CPU spesifik, jadi jangan berasumsi terlalu banyak. Sistem yang berbeda menangani kasus ini dengan cara yang sangat berbeda.
Wouter van Ooijen