Menentukan ukuran heap dan stack untuk mikrokontroler ARM Cortex-M4?

11

Saya telah bekerja dan mematikan pada proyek sistem tertanam kecil dan mematikan. Beberapa proyek ini menggunakan prosesor basis ARM Cortex-M4. Di folder proyek ada file startup.s . Di dalam file itu saya mencatat dua baris perintah berikut.

;******************************************************************************
;
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Stack   EQU     0x00000400

;******************************************************************************
;
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Heap    EQU     0x00000000

Bagaimana cara menentukan ukuran tumpukan dan tumpukan untuk mikrokontroler? Apakah ada informasi spesifik dalam lembar data yang akan dipandu untuk sampai pada nilai yang benar? Jika demikian, apa yang harus dicari dalam lembar data?


Referensi:

Mahendra Gunawardena
sumber

Jawaban:

12

Stack dan heap adalah konsep perangkat lunak, bukan konsep perangkat keras. Apa yang disediakan oleh perangkat keras adalah memori. Menentukan zona memori, salah satunya disebut "tumpukan" dan salah satunya disebut "tumpukan", adalah pilihan program Anda.

Perangkat keras membantu dengan tumpukan. Sebagian besar arsitektur memiliki register khusus yang disebut stack pointer. Kegunaan yang dimaksudkan adalah bahwa ketika program membuat panggilan fungsi, parameter fungsi dan alamat kembali didorong ke stack, dan mereka muncul ketika fungsi berakhir dan kembali ke pemanggilnya. Mendorong ke tumpukan berarti menulis di alamat yang diberikan oleh penunjuk tumpukan dan mengurangi penunjuk tumpukan sesuai (atau bertambah, tergantung ke arah mana tumpukan tumbuh). Popping berarti menambah (atau mengurangi) penunjuk tumpukan; alamat pengirim dibaca dari alamat yang diberikan oleh penunjuk tumpukan.

Beberapa arsitektur (bukan ARM) memiliki instruksi panggilan subrutin yang menggabungkan lompatan dengan penulisan ke alamat yang diberikan oleh penunjuk tumpukan, dan instruksi pengembalian subrutin yang menggabungkan pembacaan dari alamat yang diberikan oleh penunjuk tumpukan dan melompat ke alamat ini. Pada ARM, penyimpanan dan pemulihan alamat dilakukan dalam register LR, instruksi panggilan dan pengembalian tidak menggunakan penunjuk tumpukan. Namun ada instruksi untuk memfasilitasi penulisan atau membaca banyak register ke alamat yang diberikan oleh stack pointer, untuk mendorong dan memunculkan argumen fungsi.

Untuk memilih ukuran tumpukan dan tumpukan, satu-satunya informasi yang relevan dari perangkat keras adalah jumlah total memori yang Anda miliki. Anda kemudian membuat pilihan tergantung pada apa yang ingin Anda simpan di memori (memungkinkan kode, data statis, dan program lainnya).

Suatu program biasanya akan menggunakan konstanta ini untuk menginisialisasi beberapa data dalam memori yang akan digunakan oleh sisa kode, seperti alamat bagian atas tumpukan, mungkin nilai di suatu tempat untuk memeriksa tumpukan kelebihan, batas untuk pengalokasi tumpukan , dll.

Dalam kode yang Anda lihat , Stack_Sizekonstanta digunakan untuk memesan blok memori di area kode (melalui SPACEarahan dalam perakitan ARM). Alamat atas dari blok ini diberi label __initial_sp, dan disimpan dalam tabel vektor (prosesor menggunakan entri ini untuk mengatur SP setelah reset perangkat lunak) serta diekspor untuk digunakan dalam file sumber lain. The Heap_Sizekonstan sama digunakan untuk cadangan blok memori dan label untuk batas-batasnya ( __heap_basedan __heap_limit) diekspor untuk digunakan dalam file sumber lain.

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

…
__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler

…

                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Tahukah Anda bagaimana nilai-nilai 0x00200 dan 0x000400 ditentukan
Mahendra Gunawardena
@ MahendraGunawardena Terserah Anda untuk menentukannya, berdasarkan apa yang dibutuhkan program Anda. Jawaban Niall memberikan beberapa tips.
Gilles 'SO- stop being evil'
7

Ukuran tumpukan dan tumpukan ditentukan oleh aplikasi Anda, bukan di mana pun di lembar data mikrokontroler.

Tumpukan

Tumpukan ini digunakan untuk menyimpan nilai-nilai variabel lokal di dalam fungsi, nilai-nilai sebelumnya register CPU digunakan untuk variabel lokal (sehingga mereka dapat dikembalikan saat keluar dari fungsi), alamat program untuk kembali ketika meninggalkan fungsi-fungsi tersebut, ditambah beberapa overhead untuk pengelolaan tumpukan itu sendiri.

Saat mengembangkan sistem tertanam, Anda memperkirakan kedalaman panggilan maksimum yang Anda harapkan, tambahkan ukuran semua variabel lokal dalam fungsi dalam hierarki itu, lalu tambahkan beberapa padding untuk memungkinkan overhead yang disebutkan di atas, lalu tambahkan lagi untuk setiap gangguan yang mungkin terjadi selama eksekusi program Anda.

Metode estimasi alternatif (di mana RAM tidak dibatasi) adalah untuk mengalokasikan ruang stack jauh lebih banyak daripada yang Anda perlukan, isi stack dengan nilai sentinel, kemudian pantau berapa banyak yang sebenarnya Anda gunakan selama eksekusi. Saya telah melihat versi debug runtime bahasa C yang akan melakukan ini untuk Anda secara otomatis. Kemudian, ketika Anda selesai mengembangkan, Anda dapat mengurangi ukuran tumpukan jika Anda mau.

Tumpukan itu

Menghitung ukuran tumpukan yang Anda butuhkan bisa jadi lebih sulit. Heap digunakan untuk variabel yang dialokasikan secara dinamis, jadi jika Anda menggunakan malloc()dan free()dalam program bahasa C, atau newdan deletedalam C ++, di situlah variabel-variabel tersebut tinggal.

Namun, khususnya di C ++, mungkin ada alokasi memori dinamis tersembunyi yang terjadi. Misalnya, jika Anda memiliki objek yang dialokasikan secara statis, bahasa tersebut mengharuskan destruktornya dipanggil saat program keluar. Saya mengetahui setidaknya satu runtime tempat alamat destruktor disimpan dalam daftar tertaut yang dialokasikan secara dinamis.

Jadi untuk memperkirakan ukuran tumpukan yang Anda butuhkan, lihat semua alokasi memori dinamis di setiap jalur melalui pohon panggilan Anda, hitung maksimum dan tambahkan beberapa lapisan. Runtime bahasa dapat memberikan diagnostik yang dapat Anda gunakan untuk memantau total penggunaan tumpukan, fragmentasi, dll.

Niall C.
sumber
Terima kasih atas tanggapannya, saya suka cara menentukan angka spesifik seperti 0x00400 dan sebagainya
Mahendra Gunawardena
5

Selain jawaban lain, saya ingin menambahkan bahwa ketika mengukir RAM antara stack dan heap space, Anda juga perlu mempertimbangkan ruang untuk data statis yang tidak konstan (misalnya file global, statika fungsi, dan program-lebar) global dari perspektif C, dan mungkin yang lain untuk C ++).

Cara alokasi tumpukan / tumpukan bekerja

Perlu dicatat bahwa file perakitan startup adalah salah satu cara untuk mendefinisikan wilayah; toolchain (baik lingkungan build Anda dan lingkungan run-time) kebanyakan peduli tentang simbol yang menentukan awal stackspace (digunakan untuk menyimpan pointer stack awal di Tabel Vektor) dan awal dan akhir ruang tumpukan (digunakan oleh dinamis pengalokasi memori, biasanya disediakan oleh libc Anda)

Dalam contoh OP, hanya 2 simbol yang didefinisikan, ukuran tumpukan di 1kiB dan ukuran tumpukan di 0B. Nilai-nilai ini digunakan di tempat lain untuk benar-benar menghasilkan ruang tumpukan dan tumpukan

Dalam contoh @Gilles, ukurannya didefinisikan dan digunakan dalam file assembly untuk mengatur ruang stack mulai dari mana pun dan berlangsungnya ukuran, diidentifikasi oleh simbol Stack_Mem dan menetapkan label __initial_sp di akhir. Demikian juga untuk heap, di mana spasi adalah simbol Heap_Mem (ukuran 0,5kiB), tetapi dengan label di awal dan akhir (__heap_base dan __heap_limit).

Ini diproses oleh linker, yang tidak akan mengalokasikan apa pun di dalam ruang stack dan heap space karena memori tersebut ditempati (oleh simbol Stack_Mem dan Heap_Mem), tetapi ini dapat menempatkan memori itu dan semua global di mana pun ia butuhkan. Label akhirnya menjadi simbol tanpa panjang di alamat yang diberikan. __Initial_sp digunakan langsung untuk tabel vektor pada waktu tautan, dan __heap_base dan __heap_limit oleh kode runtime Anda. Alamat simbol yang sebenarnya diberikan oleh penghubung berdasarkan tempat penempatannya.

Seperti yang saya singgung di atas, simbol-simbol ini sebenarnya tidak harus berasal dari file startup.s. Mereka dapat berasal dari konfigurasi linker Anda (Menyebarkan file di Keil, linkerscript di GNU), dan pada mereka Anda dapat memiliki kontrol yang lebih halus atas penempatan. Misalnya, Anda bisa memaksa tumpukan berada di awal atau akhir RAM, atau menjauhkan global Anda dari tumpukan, atau apa pun yang Anda inginkan. Anda bahkan dapat menentukan bahwa HEAP atau STACK hanya menempati RAM apa pun yang tersisa setelah global ditempatkan. Perhatikan bahwa Anda harus berhati-hati dalam menambahkan lebih banyak variabel statis sehingga memori Anda yang lain akan berkurang.

Namun, setiap toolchain berbeda, dan cara menulis file konfigurasi dan simbol apa yang akan digunakan pengalokasi memori dinamis Anda harus berasal dari dokumentasi lingkungan khusus Anda.

Ukuran Stack

Mengenai cara menentukan ukuran tumpukan, banyak toolchains dapat memberi Anda kedalaman tumpukan maksimum dengan menganalisis pohon panggilan fungsi program Anda, JIKA Anda tidak menggunakan rekursi atau pointer fungsi. Jika Anda menggunakannya, perkirakan ukuran tumpukan dan pra-mengisinya dengan nilai-nilai kardinal (mungkin melalui fungsi entri sebelum utama) dan kemudian periksa setelah program Anda berjalan untuk sementara waktu di mana kedalaman maksimumnya (di mana nilai-nilai kardinal akhir). Jika Anda telah sepenuhnya menjalankan program hingga batasnya, Anda akan tahu secara cukup akurat apakah Anda dapat mengecilkan tumpukan atau, jika program Anda macet atau tidak ada nilai kardinal yang tersisa, Anda perlu menambah tumpukan dan coba lagi.

Heap Sizing

Menentukan ukuran tumpukan lebih tergantung aplikasi. Jika Anda hanya melakukan alokasi dinamis selama startup, Anda bisa menambahkan ruang yang diperlukan dalam kode startup Anda (ditambah beberapa overhead untuk manajemen memori). Jika Anda memiliki akses ke sumber manajer memori Anda, Anda bisa tahu persis apa overhead-nya, dan bahkan mungkin menulis kode untuk menggerakkan memori untuk memberi Anda informasi penggunaan. Untuk aplikasi yang membutuhkan memori runtime dinamis (mis. Mengalokasikan buffer untuk frame ethernet masuk) yang terbaik yang bisa saya sarankan adalah dengan hati-hati mengasah stacksize Anda dan memberikan Heap segala yang tersisa setelah stack dan statika.

Catatan akhir (RTOS)

Pertanyaan OP ditandai dengan bare-metal, tapi saya ingin menambahkan catatan untuk RTOS. Seringkali (selalu?) Setiap tugas / proses / utas (saya hanya akan menulis tugas di sini untuk kesederhanaan) akan diberi ukuran tumpukan ketika tugas dibuat, dan selain tumpukan tugas, kemungkinan akan ada OS kecil tumpukan (digunakan untuk interupsi dan semacamnya)

Struktur akuntansi tugas dan tumpukan harus dialokasikan dari suatu tempat, dan ini akan sering berasal dari ruang tumpukan keseluruhan aplikasi Anda. Dalam kasus ini, ukuran tumpukan awal Anda sering tidak masalah, karena OS hanya akan menggunakannya selama inisialisasi. Saya telah melihat, misalnya, menentukan SEMUA ruang yang tersisa selama penautan dialokasikan ke HEAP dan menempatkan penumpukan tumpukan awal di ujung heap untuk tumbuh menjadi heap, mengetahui bahwa OS akan mengalokasikan mulai dari awal heap dan akan mengalokasikan tumpukan OS sebelum meninggalkan tumpukan initial_sp. Kemudian semua ruang digunakan untuk mengalokasikan tumpukan tugas dan memori yang dialokasikan secara dinamis lainnya.

John O'M.
sumber