Profil memori paling sederhana pada STM32 dan mikrokontroler lainnya

"Dengan pengalaman, pendekatan standar dan ilmiah untuk menghitung ukuran tumpukan yang tepat datang: ambil nomor acak dan harapkan yang terbaik."
- Jack Ganssle, β€œSeni Merancang Sistem Tertanam”

Halo, Habr!

Aneh seperti yang terlihat, di sebagian besar "STM32 primer" yang saya lihat khususnya dan mikrokontroler secara umum, umumnya tidak ada yang namanya alokasi memori, penempatan tumpukan, dan yang paling penting, mencegah meluapnya memori - akibatnya satu area berguncang dan semuanya runtuh, biasanya dengan efek mempesona.

Hal ini sebagian disebabkan oleh kesederhanaan proyek pelatihan yang dilakukan pada papan debug dengan mikrokontroler yang relatif berminyak, di mana sulit untuk terbang ke kekurangan memori dengan berkedip LED - namun, baru-baru ini, bahkan untuk amatir pemula, referensi untuk, misalnya, pengontrol tipe STM32F030F4P6 lebih banyak dan lebih umum. , mudah dipasang, bernilai satu sen, tetapi juga dengan unit memori kilobyte.

Kontroler semacam itu memungkinkan Anda melakukan hal-hal yang cukup serius untuk diri Anda sendiri (yah, di sini, misalnya, pengukuran yang sangat cocok dibuat untuk kami di STM32F042K6T6 dengan 6 KB RAM, dari mana sedikit lebih dari 100 byte tetap bebas), tetapi ketika berurusan dengan memori, Anda memerlukan sejumlah memori tertentu kerapian.

Saya ingin berbicara tentang akurasi ini. Artikel ini akan singkat, profesional tidak akan belajar sesuatu yang baru - tetapi untuk pemula, pengetahuan ini sangat dianjurkan.

Dalam proyek tipikal pada mikrokontroler berbasis pada inti Cortex-M, RAM memiliki pembagian bersyarat menjadi empat bagian:

  • data - data yang diinisialisasi dengan nilai tertentu
  • bss - data yang diinisialisasi ke nol
  • heap - heap (area dinamis dari mana memori dialokasikan secara eksplisit menggunakan malloc)
  • stack - tumpukan (wilayah dinamis dari mana memori dialokasikan oleh kompiler secara implisit)

Area noinit juga kadang-kadang dapat terjadi (variabel tidak diinisialisasi - mereka nyaman karena mereka mempertahankan nilai antara reboot), bahkan lebih jarang, beberapa area lain dialokasikan untuk tugas-tugas tertentu.

Mereka berada dalam memori fisik dengan cara yang agak spesifik - faktanya adalah bahwa tumpukan mikrokontroler pada core ARM tumbuh dari atas ke bawah. Oleh karena itu, ia terletak secara terpisah dari blok memori yang tersisa, di akhir RAM:



Secara default, alamatnya biasanya sama dengan alamat RAM terbaru, dan dari sana ia turun saat tumbuh - dan satu fitur yang sangat tidak menyenangkan dari tumpukan itu tumbuh dari itu: ia dapat mencapai bss dan menulis ulang atasnya, dan Anda tidak akan mengetahuinya secara eksplisit.

Area memori statis dan dinamis


Semua memori dibagi menjadi dua kategori - dialokasikan secara statis, mis. memori, jumlah total yang jelas dari teks program dan tidak tergantung pada urutan pelaksanaannya, dan dialokasikan secara dinamis, volume yang diperlukan tergantung pada kemajuan program.

Yang terakhir mencakup banyak (dari mana kita mengambil potongan menggunakan malloc dan kembali menggunakan gratis) dan tumpukan yang tumbuh dan menyusut "dengan sendirinya".

Secara umum, menggunakan malloc pada mikrokontroler sangat tidak disarankan kecuali Anda tahu persis apa yang Anda lakukan. Masalah utama yang mereka bawa adalah fragmentasi memori - jika Anda mengalokasikan 10 buah masing-masing 10 byte, dan kemudian membebaskan setiap detik, maka Anda tidak akan mendapatkan 50 byte gratis. Anda akan mendapatkan 5 buah 10 byte gratis masing-masing.

Selain itu, pada tahap penyusunan program, kompiler tidak akan dapat secara otomatis menentukan berapa banyak memori yang dibutuhkan malloc Anda (terutama dengan mempertimbangkan fragmentasi akun, yang tergantung tidak hanya pada ukuran dari potongan yang diminta, tetapi juga pada urutan alokasi dan rilisnya), dan oleh karena itu tidak akan dapat memperingatkan Anda jika pada akhirnya memori tidak cukup.

Ada beberapa metode untuk mengatasi masalah ini - implementasi malloc khusus yang bekerja dalam area yang dialokasikan secara statis, dan bukan keseluruhan RAM, penggunaan malloc dengan hati-hati dengan mempertimbangkan kemungkinan fragmentasi pada tingkat logika program, dll. - tetapi secara umum malloc lebih baik untuk tidak menyentuh .

Semua area memori dengan batas dan alamat terdaftar dalam file dengan ekstensi .LD, yang berorientasi pada tautan saat membangun proyek.

Memori yang dialokasikan secara statis


Jadi, dari memori yang dialokasikan secara statis, kami memiliki dua area - bss dan data, yang hanya berbeda secara formal. Ketika sistem diinisialisasi, blok data disalin dari flash, di mana nilai inisialisasi yang diperlukan disimpan untuk itu, blok bss hanya diisi dengan nol (setidaknya mengisinya dengan nol dianggap sebagai bentuk yang baik).

Kedua hal - menyalin dari flash dan mengisi dengan nol - dilakukan dalam kode program dalam bentuk eksplisit , tetapi tidak di main Anda (), tetapi dalam file terpisah yang dijalankan pertama kali, ditulis sekali dan hanya diseret dari proyek ke proyek.

Namun, ini bukan yang menarik minat kita sekarang - tetapi bagaimana kita akan memahami apakah data kita bahkan cocok dengan RAM controller kita.

Ia dikenali dengan sangat sederhana - oleh utilitas ukuran lengan-tidak-eabi-dengan satu parameter - file ELF yang dikompilasi dari program kami (sering kali panggilannya dimasukkan di akhir Makefile, karena nyaman):



Di sini teks adalah jumlah data program yang terletak di flash, dan bss dan data adalah area yang dialokasikan secara statis dalam RAM. Dua kolom terakhir tidak mengganggu kita - ini adalah jumlah dari tiga kolom pertama, tidak memiliki arti praktis.

Total, secara statis dalam RAM kita membutuhkan bss + byte data, dalam hal ini - 5324 byte. Pengontrol memiliki 6144 byte RAM, kami tidak menggunakan malloc, 820 byte tetap.

Yang seharusnya cukup untuk kita di tumpukan.

Tapi cukup? Karena jika tidak, tumpukan kami akan tumbuh menjadi data kami sendiri, dan kemudian yang pertama akan menimpa data, kemudian data akan menimpanya, dan kemudian semuanya akan macet. Selain itu, antara poin pertama dan kedua, program dapat terus bekerja tanpa menyadari bahwa ada sampah dalam data yang diprosesnya. Dalam kasus terburuk, itu akan menjadi data yang Anda tulis ketika semuanya sudah sesuai dengan tumpukan, dan sekarang Anda baru saja membaca - misalnya, parameter kalibrasi beberapa sensor - dan kemudian Anda tidak memiliki cara yang jelas untuk memahami bahwa semuanya buruk dengan mereka, Program ini akan terus berjalan, seolah-olah tidak ada yang terjadi, memberi Anda sampah di output.

Memori yang dialokasikan secara dinamis


Dan di sini bagian yang paling menarik dimulai - jika Anda mengurangi dongeng menjadi satu frase, maka hampir tidak mungkin untuk menentukan ukuran tumpukan di muka .

Secara teoritis , Anda dapat meminta kompiler untuk memberi Anda ukuran tumpukan yang digunakan oleh masing-masing fungsi individu, lalu memintanya untuk mengembalikan pohon eksekusi program Anda, dan untuk setiap cabang di dalamnya menghitung jumlah tumpukan semua fungsi yang ada di pohon ini. Ini saja untuk program yang kurang lebih kompleks akan membutuhkan waktu yang cukup lama.

Kemudian Anda ingat bahwa setiap saat gangguan dapat terjadi, prosesor yang juga membutuhkan memori.

Kemudian - bahwa dua atau tiga interupsi bersarang dapat terjadi, para penangannya ...

Secara umum, Anda mengerti. Mencoba menghitung tumpukan untuk program tertentu adalah kegiatan yang menarik dan umumnya bermanfaat, tetapi seringkali Anda tidak akan melakukannya.

Oleh karena itu, dalam praktiknya, satu teknik digunakan yang memungkinkan Anda untuk setidaknya memahami bagaimana segala sesuatu dalam hidup kita berkembang dengan baik - yang disebut "lukisan memori" (memori lukisan).

Apa yang nyaman dalam metode ini adalah tidak bergantung pada alat debugging yang Anda gunakan, dan jika sistem memiliki setidaknya beberapa cara untuk menghasilkan informasi, Anda dapat melakukannya tanpa debugging tool sama sekali.

Esensinya adalah bahwa kita mengisi seluruh array dari akhir bss ke awal stack di suatu tempat pada tahap awal pelaksanaan program, ketika stack masih sangat kecil, dengan nilai yang sama.

Selanjutnya, memeriksa di mana alamat nilai ini telah menghilang, kami memahami di mana tumpukan turun. Karena sekali warna yang terhapus itu sendiri tidak akan dipulihkan, pemeriksaan dapat dilakukan secara sporadis - itu akan menunjukkan ukuran tumpukan maksimum tercapai.

Tentukan warna cat - nilai spesifik tidak masalah, di bawah ini saya hanya mengetuk dengan dua jari tangan kiri saya. Hal utama adalah tidak memilih 0 dan FF:

#define STACK_CANARY_WORD (0xCACACACAUL)

- , -, :

volatile unsigned *top, *start;
__asm__ volatile ("mov %[top], sp" : [top] "=r" (top) : : );
start = &_ebss;
while (start < top) {
    *(start++) = STACK_CANARY_WORD;
}

? top , β€”  ; start β€”  bss (, , *.ld β€” libopencm3). bss .

:

unsigned check_stack_size(void) {
    /* top of data section */
    unsigned *addr = &_ebss;

    /* look for the canary word till the end of RAM */
    while ((addr < &_stack) && (*addr == STACK_CANARY_WORD)) {
        addr++;
    }
    
    return ((unsigned)&_stack - (unsigned)addr);
}

_ebss , _stack β€”  , , , , .

.

β€” - check_stack_size() , , , .

.

712 β€” 6 108 .

Word of caution


β€” , , 100-% . , , , , , . , , -, 10-20 %, 108 .

, , , .

P.S. RTOS β€” MSP, , PSP. , β€” .

Source: https://habr.com/ru/post/id443030/


All Articles