Ich schreibe einen einfachen Aufgabenplaner und verwende die dynamische Speicherzuweisung auf meinem cc430F5137. Ich stimme zu, dass dies keine gute Praxis ist, aber nehmen wir vorerst an, dass es meine Anwendungsanforderung ist, die dynamische Speicherzuweisung zu verwenden.
In meiner OS.c-Datei
Ich habe zwei Strukturen,
typedef struct
{
task_t task;
uint8_t next;
uint8_t prev;
uint8_t priority;
} info_t;
typedef struct
{
task_t task;
uint8_t index;
} index_t;
Größe von info_t
ist 8 Bytes und Größe von index_t
ist 6 Bytes.
Ich auch
index_t* m_index;
info_t* m_info;
Dann muss ich die Funktion initialisieren, in der ich das mache
m_info = NULL;
m_index = NULL;
Jetzt habe ich eine Funktion registerTask(& task)
, die die Adresse der zu planenden Funktion übernimmt. In dieser Funktion tue ich
m_info = realloc(m_info,(num_registered_tasks + 1) * sizeof(*m_info));
und legen Sie dann die Werte von .priority, next,task und prev fest.
Dann mach ich
m_index = realloc(m_index,(num_registered_tasks + 1) * sizeof(*m_index));
und setze die Werte von task und index. und TUnum_registered_tasks++;
Meine Frage ist, wie sich realloc()
in dieser Hinsicht verhält.
Angenommen, mein Speicherplatz, First Task ist registriert, also hat er die ersten 8 Bytes für m_info[0]
und die nächsten 6 Bytes für m_index[0]
. Was passiert nun, wenn meine zweite Aufgabe diese Funktion aufruft? Ich vermute, dass m_info zuerst nach 16 Bytes kontinuierlicher Daten sucht und diese erst nach den ersten 14 Bytes findet, die Adresse von ändert und den m_info[0]
Inhalt kopiert und dann hinzufügt m_info[1]
. Und wenn m_index
aufgerufen wird, findet es nur 12 Bytes nach diesen (14 + 16) Bytes und Ort m_index[0]
und m_index[1]
hier.
Wenn dies wahr ist, wird dann mein gesamter bisheriger Speicherplatz verschwendet?
Wenn ich falsch liege, wie funktioniert diese Funktion dann?
Wie kann ich den bisherigen Platz zusätzlich nutzen?
Ich brauche index_t
struct, um eine Art Suchalgorithmus zu implementieren, also ist es auch notwendig
Die Verwendung der dynamischen Speicherzuweisung auf einer 16-Bit-MCU mit 4 KB RAM ist sehr schlechte Technik.
Nicht so sehr wegen der üblichen Probleme mit Speicherlecks. Nicht so sehr wegen der Fragmentierung des Heap-Speichers. Nicht einmal wegen des ziemlich hohen Ausführungszeitaufwands, der für die Zuordnungsroutinen benötigt wird. Sondern weil es völlig sinnlos ist und keinen Sinn ergibt.
Sie haben einige reale Anforderungen, die angeben, was Ihr Programm tun soll, und basierend auf diesen muss Ihr Programm genau x Bytes RAM verwenden, um das Worst-Case-Szenario zu bewältigen . Sie brauchen nicht weniger, Sie brauchen nicht mehr, Sie brauchen genau die Menge an RAM, die zur Kompilierzeit bestimmt werden kann.
Es macht keinen Sinn, einen Teil der 4 KB zu speichern und sie ungenutzt zu lassen. Wer wird sie benutzen? Ebenso macht es keinen Sinn, mehr Speicher als für das Worst-Case-Szenario benötigt zuzuweisen. Weisen Sie einfach statisch so viel Speicher wie nötig zu, Punkt.
Außerdem haben Sie ein Worst-Case-Szenario mit maximaler Stack-Nutzung, bei dem Sie sich tief in einem Call-Stack befinden und alle aktivierten Interrupts ausgelöst wurden. Dieses Worst-Case-Szenario kann zur Kompilierzeit berechnet oder zur Laufzeit gemessen werden. Eine gute Praxis besteht darin, mehr RAM als für den schlimmsten Fall erforderlich zuzuweisen, um Stapelüberläufe zu verhindern (der Stapel ist eigentlich auch ein dynamischer Speicherbereich). Oder besser gesagt: Verwenden Sie jedes einzelne Byte RAM, das von Ihrem Programm nicht verwendet wird, für den Stack.
Wenn Ihre Anwendung genau 3 KB RAM benötigt, sollten Sie die restlichen 1 KB für den Stack verwenden.
Die dynamische Zuordnung/der Computer-Heap ist für gehostete Anwendungen wie Windows oder Linux vorgesehen, bei denen jeder Prozess über eine feste Menge an RAM verfügt und zusätzlich Heap-Speicher verwenden kann, um so viel RAM zu verwenden, wie in der Hardware vorhanden ist. Es ist nur sinnvoll, die dynamische Zuordnung in solchen komplexen Mehrprozesssystemen zu verwenden, in denen riesige Mengen an verfügbarem RAM vorhanden sind.
In einem MCU-Programm, entweder Bare-Metal- oder RTOS-basiert, müssen Sie sich darüber im Klaren sein, dass Heap-Implementierungen wie folgt funktionieren: Reservieren Sie eine feste Menge von x KB RAM für den Heap. Wenn Sie die dynamische Zuweisung verwenden, erhalten Sie einen Teil dieser festen Speichermenge. Warum also nicht einfach die x kb nehmen, die Sie für die Zuweisung des Heaps verwenden würden, und sie stattdessen zum Speichern Ihrer Variablen verwenden?
Ich habe dies hier mit einer detaillierteren Erklärung und weiteren möglichen Problemen erweitert:
Warum sollte ich in eingebetteten Systemen keine dynamische Speicherzuweisung verwenden?
Dies wird als Speicherfragmentierung bezeichnet.
Es gibt alle möglichen ausgefallenen Algorithmen, um damit umzugehen, aber der Buchhaltungsaufwand macht sie für eine kleine MCU ungeeignet.
Stattdessen können Sie basierend auf Ihrem erwarteten Nutzungsmuster Ihre eigenen Speicherzuweisungsroutinen schreiben. Sie könnten zum Beispiel eine separate Routine für jeden Ausführungs-Thread haben. Wenn Sie garantieren können, dass free() in der entgegengesetzten Reihenfolge von alloc() geschieht, können Sie einen einfachen Zeiger auf das Ende des Speichers verwenden.
Oder Sie könnten eine Indirektionsebene verwenden: Greifen Sie nur über einen Zeiger auf einen Zeiger auf den Speicher zu. Dann können Sie den Speicher frei müllsammeln und die Zeigertabelle so anpassen, dass sie auf den Speicher zeigt, wo immer er endet.
Die beste Antwort für eingebettete Systeme besteht normalerweise darin, die dynamische Speicherzuweisung vollständig zu vermeiden.
Nach vielen, vielen Kommentaren und Antworten, keinen dynamischen Speicher zu verwenden, fand ich immer noch einen Weg, der mein Problem zur Zeit löste.
Ich habe das in meinem Code geändert
typedef struct
{
task_t task;
uint8_t next;
uint8_t prev;
uint8_t priority;
} info_t;
typedef struct
{
task_t task;
uint8_t index;
} index_t;
typedef struct
{
info_t m_info;
info_t m_index;
} combined_t;
combined_t* m_combined;
Dadurch weist nw my realloc() m_combined zusammenhängenden Speicher zu und mein Fragmentierungsproblem ist gelöst. (Nun, ich denke, weil nach der Analyse des Speichers Schritt für Schritt).
typedef struct { int a; } foo_t; foo_t bar;
ist schlechtes Design, während struct foo_s { int a; }; struct foo_s bar;
es gutes Design ist? Wenn ja, warum dann?
scharfer Zahn
brhans
Paul A. Clayton