Kompleksitas pemrograman tertanam

2020-07-31 12:54:55 53

Ini adalah langkah pertama untuk dapat melihat masalah tertanam dari pemrograman mesin PC, untuk belajar menggunakan ide-ide pemrograman tertanam, itu adalah langkah kedua, untuk menggunakan ide-ide PC dan ide-ide tertanam untuk menggabungkan mereka dalam proyek-proyek aktual, kemudian Ini adalah langkah ketiga. Banyak teman telah beralih dari pemrograman PC ke pemrograman tertanam. Di Cina, beberapa teman pemrograman tertanam telah lulus dari jurusan komputer, mereka semua lulus dari jurusan terkait otomatis dan elektronik. Sepatu anak-anak ini memiliki pengalaman praktis yang kuat, tetapi tidak memiliki pengetahuan teoretis, sebagian besar sepatu anak-anak yang lulus dalam ilmu komputer beralih ke aplikasi tingkat yang lebih tinggi seperti game online dan halaman web yang tidak tergantung pada sistem operasi. Saya juga enggan untuk terlibat dalam industri tertanam, setelah semua, jalan ini tidak mudah. Mereka memiliki pengetahuan teoritis yang kuat, tetapi mereka tidak memiliki sirkuit dan pengetahuan terkait lainnya. Mereka perlu belajar beberapa pengetahuan khusus ketika belajar tertanam, yang lebih sulit.


Meskipun saya belum melakukan survei industri, dari apa yang saya lihat dan rekrut, para insinyur yang terlibat dalam industri tertanam tidak memiliki pengetahuan teoretis atau pengalaman praktis. Jarang melakukan keduanya. Alasannya masih menjadi masalah pendidikan universitas Cina. Masalah ini tidak akan dibahas di sini untuk menghindari perang kata-kata. Saya ingin mendaftar beberapa contoh dari latihan saya. Bangkitkan perhatian semua orang terhadap beberapa masalah saat melakukan proyek yang tertanam.


pertanyaan pertama:


Seorang kolega mengembangkan driver port serial di bawah UC / OS-II, dan driver dan antarmuka ditemukan memiliki masalah dalam pengujian. Program komunikasi dikembangkan dalam aplikasi, dan driver port serial menyediakan fungsi untuk menanyakan karakter dalam buffer driver: GetRxBuffCharNum (). Lapisan atas perlu menerima sejumlah karakter sebelum dapat mengurai paket. Kode yang ditulis oleh seorang rekan diungkapkan sebagai berikut dalam kode semu:


bExit = FALSE;


lakukan {


if (GetRxBuffCharNum ()> = 30)




       bExit = ReadRxBuff (buff, GetRxBuffCharNum ());


} while (! bExit);


Kode ini menilai bahwa ada lebih dari 30 karakter dalam buffer saat ini, dan membaca semua karakter dalam buffer ke buffer hingga pembacaan berhasil. Logikanya jelas, dan alur pemikirannya jelas. Tetapi kode ini tidak berfungsi dengan baik. Jika ada di PC, pasti tidak ada masalah, dan itu bekerja secara tidak normal. Tapi itu benar-benar tidak dikenal di embedded. Kolega itu sangat tertekan dan tidak tahu mengapa. Datang untuk meminta saya menyelesaikan masalahnya. Ketika saya melihat kode itu, saya bertanya kepadanya, bagaimana cara GetRxBuffCharNum () diimplementasikan? Buka dan lihat:


GetRxBuffCharNum yang tidak ditandatangani (batal)


{


cpu_register reg;


num yang tidak ditandatangani;


reg = interrupt_disable ();


num = gRxBuffCharNum;


interrupt_enable (reg);


return (num);


}


Jelas, karena dalam loop, antara interrupt_disable () dan interrupt_enable () adalah area kritis global, integritas gRxBufCharNum dijamin. Namun, karena di bagian luar do {} while (), CPU sering menutup dan membuka interupsi, kali ini sangat singkat. Bahkan, CPU mungkin tidak merespons interupsi UART secara normal. Tentu saja, ini ada hubungannya dengan baud rate uart, ukuran buffer perangkat keras, dan kecepatan CPU. Baud rate yang kami gunakan sangat tinggi, sekitar 3Mbps. Sinyal start uart dan stop signal menempati satu bit. Satu byte perlu mengkonsumsi 10 siklus. Baud rate 3Mbps membutuhkan sekitar 3.3us untuk mengirimkan satu byte. Berapa banyak instruksi CPU yang bisa kita jalankan? ARM 100MHz dapat menjalankan sekitar 150 instruksi. Akibatnya, berapa lama untuk menutup interupsi? Secara umum, ARM membutuhkan lebih dari 4 instruksi untuk mematikan interupsi, dan lebih dari 4 instruksi untuk menghidupkan. Kode yang menerima interupsi uart sebenarnya lebih dari 20 instruksi. Oleh karena itu, dengan cara ini, mungkin ada bug dari data komunikasi yang hilang, yang tercermin dalam tingkat sistem, yang merupakan komunikasi yang tidak stabil.


Memodifikasi kode ini sebenarnya sangat sederhana, cara termudah adalah memodifikasinya dari level tinggi. yang mana:


bExit = FALSE;


lakukan {


DelayUs (20); // Delay 20us, umumnya diwujudkan dengan instruksi loop kosong


num = GetRxBuffCharNum ();


jika (num> = 30)


    bExit = ReadRxBuff (buff, num);


} while (! bExit);


Dengan cara ini, CPU memiliki waktu untuk mengeksekusi kode yang terputus, sehingga menghindari eksekusi kode terputus yang disebabkan oleh seringnya gangguan dan hilangnya informasi yang dihasilkan. Dalam sistem embedded, sebagian besar aplikasi RTOS tanpa driver port serial. Saat mendesain kode sendiri, kombinasi kode dan kernel tidak sepenuhnya dipertimbangkan. Menyebabkan masalah yang mendalam pada kode. RTOS disebut RTOS karena responsnya yang cepat terhadap berbagai peristiwa, respons cepat terhadap berbagai peristiwa tergantung pada kecepatan respons CPU terhadap interupsi. Driver sangat terintegrasi dengan kernel dalam sistem seperti Linux dan berjalan dalam kondisi kernel bersama. Meskipun RTOS tidak dapat menyalin struktur Linux, ia memiliki signifikansi referensi tertentu.


Dapat dilihat dengan jelas dari contoh di atas bahwa pengembang yang disematkan perlu memahami semua aspek kode dengan jelas.


Contoh kedua:


Kolega mengemudikan chip 14094 serial-to-parallel. Sinyal serial disimulasikan oleh IO karena tidak ada perangkat keras khusus. Seorang kolega menulis driver dengan santai, dan setelah debugging selama 3 atau 4 hari, masih ada masalah. Saya benar-benar tidak tahan lagi, jadi saya pergi dan memeriksanya. Sinyal paralel yang terkontrol terkadang normal dan kadang tidak normal. Saya melihat kode, kode pseudo mungkin:


untuk (i = 0; i <8; i ++)


{


    SetData ((data >> i) & 0x1);


    SetClockHigh ();


    untuk (j = 0; j <5; j ++);


    SetClockLow ();


}


Kirim 8 bit data secara berurutan dari bit0 ke bit7 di setiap level tinggi. Itu harus normal. Tidak bisa melihat di mana masalahnya? Saya memikirkannya dengan cermat, membaca lembar data 14094, dan saya mengerti. Ternyata 14094 membutuhkan level tinggi jam untuk bertahan selama 10 ns dan level rendah bertahan selama 10 ns. Kode ini melakukan penundaan waktu tingkat tinggi, tetapi bukan penundaan tingkat rendah. Jika interupsi dimasukkan di antara level rendah agar berfungsi, maka kode ini OK. Tetapi jika CPU dieksekusi ketika CPU tidak terganggu pada level rendah, itu tidak akan bekerja secara normal. Baik dan buruk.


Modifikasinya juga relatif sederhana:


untuk (i = 0; i <8; i ++)


{


    SetData ((data >> i) & 0x1);


    SetClockHigh ();


    untuk (j = 0; j <5; j ++);


    SetClockLow ();


    untuk (j = 0; j <5; j ++);


}


Ini sangat normal. Tetapi ini masih merupakan kode yang tidak dapat ditransplantasikan dengan baik, karena begitu kompiler mengoptimalkan, ini dapat menyebabkan hilangnya dua loop penundaan ini. Jika hilang, persyaratan 10ns tingkat tinggi tingkat rendah yang tahan lama tidak dapat dijamin, dan itu tidak akan berfungsi secara normal. Oleh karena itu, untuk kode yang benar-benar portabel, loop ini harus dibuat menjadi DelayNs nanodetik (10);


Seperti Linux, saat dinyalakan, pertama-tama ukur berapa lama instruksi nop diperlukan untuk mengeksekusi, dan berapa banyak instruksi nop mengeksekusi 10ns. Jalankan instruksi nop tertentu. Gunakan kompiler untuk mencegah instruksi kompilasi optimisasi atau kata kunci khusus untuk mencegah penundaan loop dioptimalkan oleh kompiler. Seperti dalam GCC




__volatile__ __asm __ ("nop;");




Dapat dilihat dengan jelas dari contoh ini bahwa menulis kode yang baik membutuhkan banyak pengetahuan. Apa yang kamu katakan?


Sistem tertanam sering tidak memiliki dukungan sistem operasi, atau karena mereka memiliki dukungan sistem operasi, tetapi karena berbagai pembatasan, sistem operasi menyediakan sangat sedikit fungsi. Oleh karena itu, banyak kode tidak dapat digunakan seperti pemrograman PC. Hari ini saya akan berbicara tentang alokasi memori dan fragmentasi memori, yang mungkin akrab bagi semua orang. Namun, dalam sistem embedded, yang paling ditakuti adalah fragmentasi memori, yang juga merupakan pembunuh stabilitas sistem nomor satu. Saya pernah membuat proyek. Ada banyak mallocs dan membebaskan dalam sistem, dengan berbagai ukuran mulai dari lebih dari 60 byte hingga 64KB. Gunakan RTOS sebagai dukungan. Pada saat itu, saya punya dua pilihan, satu menggunakan malloc dan bebas dari perpustakaan sistem C, dan yang lainnya menggunakan alokasi memori tetap yang disediakan oleh sistem operasi. Desain sistem kami membutuhkan operasi yang stabil selama lebih dari 3 bulan. Bahkan, itu turun setelah 6 hari beroperasi terus menerus. Berbagai masalah telah dicurigai, dan keputusan akhir adalah pada alokasi memori, pada kenyataannya, itu adalah waktu yang lama, setelah sejumlah besar memori dialokasikan, memori sistem menjadi terfragmentasi dan tidak dapat berkelanjutan. Meskipun ada ruang besar, ruang kontinu tidak dapat dialokasikan. Ketika ada ruang besar untuk diterapkan, itu hanya bisa menjadi downtime. Untuk membuat sistem memenuhi persyaratan desain asli, kami mensimulasikan seluruh perangkat keras pada PC, menjalankan kode tertanam pada PC, dan malloc kelebihan beban dan bebas untuk membuat program statistik yang kompleks. Perilaku memori sistem statistik. Setelah berjalan selama beberapa hari, data diekstraksi dan dianalisis. Meskipun memori yang diminta mahal, masih ada beberapa aturan. Kami mengklasifikasikan yang di bawah 100 byte menjadi satu kategori, yang dengan 512B menjadi satu kategori, dan yang dengan 1KB menjadi satu. Kelas, 2KB diklasifikasikan ke dalam satu kategori, dan 64KB diklasifikasikan ke dalam satu kategori. Hitung jumlah setiap kategori dan tambahkan 30% margin ke dasar aslinya. Membuat aplikasi memori tetap sangat meningkatkan waktu bagi sistem untuk berjalan secara stabil dan terus menerus. Tertanam seperti ini, tidak takut pada metode asli, tetapi kinerjanya tidak sesuai dengan persyaratan.




Masalah memory overflow, masalah memory overflow, embedded system lebih dahsyat daripada sistem PC! Seringkali overflow tanpa disadari. Sulit untuk berpikir, terutama untuk pemula C / C ++, yang tidak terbiasa dengan pointer dan tidak bisa memeriksanya. Karena sistem PC memiliki MMU, ketika memori sangat disilangkan, itu dilindungi oleh MMU, yang tidak akan menyebabkan konsekuensi bencana yang serius. Namun, sistem tertanam sering tidak memiliki MMU, yang sangat berbeda, dan kode sistem masih dapat berjalan jika dihancurkan. Hanya Tuhan dan CPU yang tahu apa yang sedang berjalan. Mari kita lihat kode ini:


char * strcpy (char * dest, const char * src)


{


           menegaskan (dest! = NULL && src! = NULL);


           while (* src! = '�')


          {


                   * dest ++ = * src ++;




          }


          * dest = '�';


        return (dest);


}


Kode ini adalah kode salinan string, ditulis seperti ini di PC, pada dasarnya tidak masalah. Tapi ada satu hal tertanam yang harus diwaspadai, yaitu, src benar-benar berakhir dengan ''. Jika tidak, itu akan menjadi tragedi. Kapan itu akan berakhir? Oh, hanya abdi Allah yang tahu. Jika kode ini dapat dijalankan secara kebetulan, jangan berharap program berjalan normal. Karena area memori yang ditunjukkan oleh dest hampir hancur. Agar kompatibel dengan pustaka C / C ++ standar, sebenarnya tidak ada cara yang baik, jadi masalah ini hanya dapat diserahkan kepada programmer untuk memeriksa.


identik,


memcpy (dest, src, n);


Masalah yang sama dengan salinan memori, berhati-hatilah agar tidak memberikan nilai negatif untuk n. Ini adalah berapa banyak byte yang disalin, dan nilai negatif dipaksa untuk positif. Itu menjadi angka positif yang besar, menyebabkan semua memori setelah dest dihancurkan ...


Penunjuk memori dalam sistem tertanam harus benar-benar diperiksa sebelum dapat digunakan, dan ukuran memori juga harus benar-benar rusak. Kalau tidak, tragedi sulit dihindari. Seperti pointer fungsi, meskipun NULL, 0 ditugaskan di tertanam. Jika itu adalah ARM, bahkan tidak ada kesalahan abnormal, dan itu direset secara langsung, karena memanggil fungsi ini bahkan membuat kode dijalankan dari 0. Dan 0 adalah posisi kode pertama yang berjalan setelah ARM dinyalakan. Ini terutama berlaku pada ARM7. Tragedi semacam ini jauh lebih tragis daripada di PC, dan MMU harus memberikan kesalahan instruksi yang tidak ditentukan. Bangkitkan perhatian programmer. Dalam embedded, itu semua diserahkan kepada programmer untuk menemukannya.


Memory overflow terjadi setiap saat, berapa banyak tumpukan yang Anda alokasikan untuk seluruh sistem front-end dan back-end (atau sistem operasi)? Seberapa besar tumpukan itu? Dalam keadaan normal, berapa kedalaman panggilan sistem (berapa maksimum) dan berapa banyak tumpukan yang ditempati? Tidak cukup hanya dengan melihat fungsi program yang benar, Anda juga perlu menghitung parameter ini. Kalau tidak, selama ada overflow. Ini fatal bagi sistem. Sistem tertanam membutuhkan waktu kerja yang lama dan menuntut stabilitas dan keandalan. Butuh beberapa waktu untuk menggiling sistem ini dengan hati-hati.


Debugging dari sistem embedded seringkali sangat rumit, sarana yang tersedia tidak sebanyak pemrograman PC, dan biaya pengembangannya jauh lebih besar daripada biaya sistem PC. Metode utama debugging untuk sistem embedded hanya pelacakan satu langkah diwakili oleh JTAG dan printf untuk membunuh Dafa.


Kedua metode debugging ini tidak dapat menyelesaikan semua masalah di sistem tertanam. Jtag mengharuskan debugger memiliki perangkat debugging (yang mungkin mahal) dan terhubung ke sistem target. Gunakan perangkat lunak seperti GDB Client untuk masuk ke peralatan debugging dan melacak program yang sedang berjalan. Sejujurnya, metode ini adalah metode debugging terbaik untuk tertanam, dan juga merupakan metode debugging yang lebih baik. Namun, masih ada beberapa kekurangan, ketika ada terlalu banyak breakpoint, batas perangkat keras terlampaui, beberapa CPU low-end tidak mendukung breakpoint lebih lanjut, oleh karena itu, perlu menggunakan JTAG untuk menggunakan simulasi perangkat lunak atau perangkap perangkat lunak (interupsi lembut atau pengecualian). Terapkan breakpoints. Mekanismenya lebih rumit: Sederhananya, 1. Tidak bisa didebug dalam waktu lama, dan tidak stabil, 2. Dapat mempengaruhi perilaku program pada saat operasi, melalui efek waktu. Setelah sistem JTAG terhubung, breakpoint yang diimplementasikan perangkat keras tidak akan mempengaruhi kecepatan sistem, tetapi breakpoint yang diimplementasikan perangkat lunak harus mengorbankan beberapa kinerja. Keandalan harus dikompromikan