Xác định kích thước heap và stack cho vi điều khiển ARM Cortex-M4?


11

Tôi đã và đang làm việc trên và tắt trên dự án hệ thống nhúng nhỏ và tắt. Một số dự án này đã sử dụng bộ xử lý cơ sở ARM Cortex-M4. Trong thư mục dự án có tệp startup.s . Bên trong tập tin đó tôi lưu ý hai dòng lệnh sau.

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

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

Làm thế nào để xác định kích thước của heapstack cho vi điều khiển? Có bất kỳ thông tin cụ thể nào trong biểu dữ liệu để hướng dẫn đến đúng giá trị không? Nếu vậy, người ta nên tìm kiếm gì trong biểu dữ liệu?


Người giới thiệu:

Câu trả lời:


12

Stack và heap là các khái niệm phần mềm, không phải khái niệm phần cứng. Những gì phần cứng cung cấp là bộ nhớ. Xác định các vùng bộ nhớ, một trong số đó được gọi là ngăn xếp chồng và một trong số đó được gọi là vùng heap, là một lựa chọn của chương trình của bạn.

Các phần cứng không giúp đỡ với ngăn xếp. Hầu hết các kiến ​​trúc có một thanh ghi chuyên dụng được gọi là con trỏ ngăn xếp. Mục đích sử dụng của nó là khi chương trình thực hiện cuộc gọi hàm, các tham số chức năng và địa chỉ trả về được đẩy đến ngăn xếp và chúng được bật khi hàm kết thúc và trả về cho người gọi. Đẩy vào ngăn xếp có nghĩa là ghi vào địa chỉ được cung cấp bởi con trỏ ngăn xếp và giảm con trỏ ngăn xếp tương ứng (hoặc tăng dần, tùy theo hướng phát triển của ngăn xếp). Popping có nghĩa là tăng (hoặc giảm) con trỏ ngăn xếp; địa chỉ trả về được đọc từ địa chỉ được cung cấp bởi con trỏ ngăn xếp.

Một số kiến ​​trúc (không phải ARM) có lệnh gọi chương trình con kết hợp bước nhảy với ghi vào địa chỉ được cung cấp bởi con trỏ ngăn xếp và lệnh trả về chương trình con kết hợp đọc từ địa chỉ được cung cấp bởi con trỏ ngăn xếp và nhảy đến địa chỉ này. Trên ARM, việc lưu và khôi phục địa chỉ được thực hiện trong thanh ghi LR, các lệnh gọi và trả lại không sử dụng con trỏ ngăn xếp. Tuy nhiên, có các hướng dẫn để tạo điều kiện cho việc viết hoặc đọc nhiều thanh ghi đến địa chỉ được cung cấp bởi con trỏ ngăn xếp, để đẩy và đối số hàm pop.

Để chọn kích thước heap và stack, thông tin liên quan duy nhất từ ​​phần cứng là tổng số bộ nhớ bạn có. Sau đó, bạn đưa ra lựa chọn tùy thuộc vào những gì bạn muốn lưu trữ trong bộ nhớ (cho phép mã, dữ liệu tĩnh và các chương trình khác).

Một chương trình thường sử dụng các hằng số này để khởi tạo một số dữ liệu trong bộ nhớ sẽ được sử dụng bởi phần còn lại của mã, chẳng hạn như địa chỉ của đỉnh ngăn xếp, có thể là một giá trị ở đâu đó để kiểm tra ngăn xếp tràn, giới hạn cho cấp phát heap , Vân vân.

Trong mã bạn đang xem , Stack_Sizehằng số được sử dụng để dự trữ một khối bộ nhớ trong vùng mã (thông qua một lệnh SPACEtrong lắp ráp ARM). Địa chỉ trên của khối này được đưa ra nhãn __initial_spvà nó được lưu trong bảng vectơ (bộ xử lý sử dụng mục này để đặt SP sau khi thiết lập lại phần mềm) cũng như xuất ra để sử dụng trong các tệp nguồn khác. Các Heap_Sizehằng số được tương tự sử dụng để đặt một khối bộ nhớ và nhãn để ranh giới của nó ( __heap_base__heap_limit) được xuất khẩu để sử dụng trong các tập tin nguồn khác.

; 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

Bạn có biết cách xác định các giá trị 0x00200 và 0x000400 đó không
Mahendra Gunawardena

@MahendraGunawardena Tùy thuộc vào bạn để xác định chúng, dựa trên những gì chương trình của bạn cần. Câu trả lời của Niall cho một vài lời khuyên.
Gilles 'SO- ngừng trở nên xấu xa'

7

Các kích thước của ngăn xếp và đống được xác định bởi ứng dụng của bạn, không phải bất cứ nơi nào trong biểu dữ liệu của vi điều khiển.

Chồng

Ngăn xếp được sử dụng để lưu trữ các giá trị của các biến cục bộ bên trong các hàm, các giá trị trước đó của các thanh ghi CPU được sử dụng cho các biến cục bộ (để chúng có thể được khôi phục khi thoát khỏi hàm), địa chỉ chương trình để trở về khi rời khỏi các hàm đó, cộng với một số chi phí cho việc quản lý các ngăn xếp chính nó.

Khi phát triển hệ thống nhúng, bạn ước tính độ sâu cuộc gọi tối đa bạn mong muốn có, thêm kích thước của tất cả các biến cục bộ trong các hàm trong cấu trúc phân cấp đó, sau đó thêm một số phần đệm để cho phép chi phí được đề cập ở trên, sau đó thêm một số chi tiết khác cho bất kỳ gián đoạn nào có thể xảy ra trong quá trình thực hiện chương trình của bạn.

Phương pháp ước tính thay thế (trong đó RAM không bị hạn chế) là phân bổ không gian ngăn xếp nhiều hơn bao giờ bạn cần, lấp đầy ngăn xếp với giá trị sentinel, sau đó theo dõi số tiền bạn thực sự sử dụng trong khi thực hiện. Tôi đã thấy các phiên bản gỡ lỗi của thời gian chạy ngôn ngữ C sẽ tự động làm điều này cho bạn. Sau đó, khi bạn đã phát triển xong, bạn có thể giảm kích thước ngăn xếp nếu muốn.

Đống

Tính kích thước của heap bạn cần có thể là một khó khăn hơn. Đống được sử dụng cho các biến động phân bổ, vì vậy nếu bạn sử dụng malloc()free()trong một chương trình ngôn ngữ C, hay newdeletetrong C ++, đó là nơi những biến sống.

Tuy nhiên, trong C ++ đặc biệt, có thể có một số phân bổ bộ nhớ động ẩn đang diễn ra. Ví dụ, nếu bạn có các đối tượng được phân bổ tĩnh, ngôn ngữ yêu cầu các hàm hủy của chúng được gọi khi chương trình thoát. Tôi biết ít nhất một thời gian chạy trong đó địa chỉ của các hàm hủy được lưu trữ trong một danh sách được liên kết động được phân bổ.

Vì vậy, để ước tính kích thước của heap bạn cần, hãy xem tất cả phân bổ bộ nhớ động trong mỗi đường dẫn qua cây cuộc gọi của bạn, tính toán mức tối đa và thêm một số phần đệm. Thời gian chạy ngôn ngữ có thể cung cấp chẩn đoán mà bạn có thể sử dụng để theo dõi tổng mức sử dụng heap, phân mảnh, v.v.


Cảm ơn bạn đã phản hồi, tôi muốn làm thế nào để xác định số cụ thể như 0x00400 và vv
Mahendra Gunawardena

5

Ngoài các câu trả lời khác, tôi muốn thêm rằng khi khắc RAM giữa không gian ngăn xếp và vùng heap, bạn cũng cần xem xét không gian cho dữ liệu không cố định (ví dụ: toàn cầu tệp, thống kê chức năng và toàn chương trình toàn cầu từ góc độ C, và có thể là những người khác cho C ++).

Cách phân bổ stack / heap hoạt động

Điều đáng chú ý là tệp lắp ráp khởi động là một cách để xác định vùng; chuỗi công cụ (cả môi trường xây dựng và môi trường thời gian chạy của bạn) chủ yếu quan tâm đến các biểu tượng xác định bắt đầu stackspace (được sử dụng để lưu trữ con trỏ ngăn xếp ban đầu trong Bảng Vector) và bắt đầu và kết thúc không gian heap (được sử dụng bởi động cấp phát bộ nhớ, thường được cung cấp bởi libc của bạn)

Trong ví dụ của OP, chỉ có 2 biểu tượng được xác định, kích thước ngăn xếp ở 1kiB và kích thước heap ở 0B. Các giá trị này được sử dụng ở nơi khác để thực sự tạo ra không gian ngăn xếp và đống

Trong ví dụ @Gilles, các kích thước được xác định và sử dụng trong tệp lắp ráp để đặt không gian ngăn xếp bắt đầu ở bất cứ đâu và kéo dài kích thước, được xác định bởi biểu tượng Stack_Mem và đặt nhãn __initial_sp ở cuối. Tương tự như vậy đối với vùng heap, trong đó khoảng trắng là ký hiệu Heap_Mem (kích thước 0,5kiB), nhưng có nhãn ở đầu và cuối (__heap_base và __heap_limit).

Chúng được xử lý bởi trình liên kết, sẽ không phân bổ bất cứ thứ gì trong không gian ngăn xếp và không gian heap vì bộ nhớ đó bị chiếm dụng (bởi các biểu tượng Stack_Mem và Heap_Mem), nhưng nó có thể đặt những ký ức đó và tất cả các quả cầu bất cứ nơi nào nó cần. Các nhãn cuối cùng là các ký hiệu không có độ dài tại các địa chỉ đã cho. __Initial_sp được sử dụng trực tiếp cho bảng vectơ tại thời điểm liên kết và __heap_base và __heap_limit bởi mã thời gian chạy của bạn. Địa chỉ thực tế của các ký hiệu được chỉ định bởi trình liên kết dựa trên vị trí đặt biểu tượng.

Như tôi đã nói ở trên, những biểu tượng này thực sự không phải đến từ tệp startup.s. Chúng có thể đến từ cấu hình trình liên kết của bạn (tệp Scatter Load trong Keil, linkercript trong GNU) và trong những thứ bạn có thể kiểm soát chi tiết hơn đối với vị trí. Ví dụ: bạn có thể buộc ngăn xếp ở đầu hoặc cuối RAM hoặc giữ các quả cầu của bạn cách xa đống hoặc bất cứ thứ gì bạn muốn. Bạn thậm chí có thể chỉ định rằng HEAP hoặc STACK chỉ chiếm bất kỳ RAM nào còn sót lại sau khi đặt toàn cầu. LƯU Ý mặc dù bạn phải cẩn thận rằng việc thêm nhiều biến tĩnh mà bộ nhớ khác của bạn sẽ giảm.

Tuy nhiên, mỗi chuỗi công cụ là khác nhau và cách viết tệp cấu hình và những ký hiệu mà bộ cấp phát bộ nhớ động của bạn sẽ sử dụng sẽ phải đến từ tài liệu về môi trường cụ thể của bạn.

Kích thước ngăn xếp

Về cách xác định kích thước ngăn xếp, nhiều bộ công cụ có thể cung cấp cho bạn độ sâu ngăn xếp tối đa bằng cách phân tích các cây gọi hàm của chương trình của bạn, NẾU bạn không sử dụng đệ quy hoặc con trỏ hàm. Nếu bạn sử dụng chúng, hãy ước tính kích thước ngăn xếp và điền trước nó với các giá trị chính (có lẽ thông qua chức năng nhập trước chính) và sau đó kiểm tra sau khi chương trình của bạn chạy trong một thời gian có độ sâu tối đa (đó là nơi có giá trị chính kết thúc). Nếu bạn đã thực hiện đầy đủ chương trình của mình đến giới hạn của nó, bạn sẽ biết khá chính xác liệu bạn có thể thu nhỏ ngăn xếp hay không, nếu chương trình của bạn gặp sự cố hoặc không còn giá trị chính, bạn cần tăng ngăn xếp và thử lại.

Kích thước đống

Xác định kích thước heap phụ thuộc vào ứng dụng nhiều hơn một chút. Nếu bạn chỉ thực hiện phân bổ động trong khi khởi động, bạn chỉ có thể thêm không gian cần thiết trong mã khởi động của mình (cộng với một số chi phí để quản lý bộ nhớ). Nếu bạn có quyền truy cập vào nguồn của trình quản lý bộ nhớ, bạn có thể biết chính xác chi phí là gì và thậm chí có thể viết mã để chuyển bộ nhớ để cung cấp cho bạn thông tin sử dụng. Đối với các ứng dụng cần bộ nhớ thời gian chạy động (ví dụ: phân bổ bộ đệm cho các khung ethernet bên trong), điều tốt nhất tôi có thể đề xuất là cẩn thận trau dồi ngăn xếp của bạn và cung cấp cho Heap mọi thứ còn sót lại sau stack và statics.

Lưu ý cuối cùng (RTOS)

Câu hỏi của OP đã được gắn thẻ cho kim loại trần, nhưng tôi muốn thêm một ghi chú cho RTOSes. Thông thường (luôn luôn?) Mỗi ​​tác vụ / quy trình / luồng (tôi sẽ chỉ viết tác vụ ở đây cho đơn giản) sẽ được chỉ định kích thước ngăn xếp khi tác vụ được tạo, ngoài ngăn xếp tác vụ, có thể sẽ có một hệ điều hành nhỏ ngăn xếp (được sử dụng cho các ngắt và như vậy)

Các cấu trúc kế toán nhiệm vụ và các ngăn xếp phải được phân bổ từ một nơi nào đó và điều này thường sẽ từ không gian heap tổng thể của ứng dụng của bạn. Trong các trường hợp này, kích thước ngăn xếp ban đầu của bạn thường không thành vấn đề, bởi vì HĐH sẽ chỉ sử dụng nó trong quá trình khởi tạo. Tôi đã thấy, ví dụ, chỉ định TẤT CẢ không gian còn lại trong khi liên kết được phân bổ cho HEAP và đặt con trỏ ngăn xếp ban đầu ở cuối heap để phát triển thành heap, biết rằng HĐH sẽ phân bổ bắt đầu từ đầu heap và sẽ phân bổ ngăn xếp hệ điều hành ngay trước khi từ bỏ ngăn xếp ban đầu. Sau đó, tất cả không gian được sử dụng để phân bổ ngăn xếp nhiệm vụ và bộ nhớ được phân bổ động khác.

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.