Fünf Ursachen für unliebsame Firmware-Fehler

Rezepte fürs Bugfixing

23. Oktober 2015, 12:59 Uhr | Michael Barr
Diesen Artikel anhören

Fortsetzung des Artikels von Teil 2

Nicht ablaufinvariante Funktionen

Technisch gesehen ist das Problem einer nicht ablaufinvarianten (non-reentrant) Funktion der Sonderfall einer Race Condition. Die Laufzeitfehler, die von einer nicht ablaufinvarianten Funktion hervorgerufen werden, sind deshalb ähnlich und treten ebenso wie Race Conditions auf nicht reproduzierbare Weise auf, sodass sich das Debugging entsprechend schwierig gestaltet. Leider ist eine nicht ablaufinvariante Funktion im Zuge einer Codeprüfung auch schwieriger aufzudecken als Race Conditions anderer Art.

passend zum Thema

Geteilte Ressourcen in einem TCP/IP-Protokoll-Stack
Bild 1. Geteilte Ressourcen in einem TCP/IP-Protokoll-Stack.
© Barr Group

Ein typisches Szenario ist in Bild 1 zu sehen. Bei den Software-Objekten, die hier einer Preemption unterliegen, also unterbrochen werden können, handelt es sich um RTOS Tasks. Diese Tasks manipulieren ein geteiltes Objekt in diesem Fall allerdings nicht direkt, sondern über den Umweg eines Funktionsaufrufs. Hierzu sei angenommen, dass Task A eine Protokollfunktion des Socket Layer aufruft, der wiederum eine Protokollfunktion des TCP Layer aufruft. Dieser wiederum ruft eine Protokollfunktion des IP Layer auf, von dem schließlich ein Ethernet-Treiber aufgerufen wird. Damit sich das System zuverlässig verhält, müssen alle diese Funktionen reentrant, d. h. ablaufinvariant sein.

Die Funktionen des Treibermoduls manipulieren jedoch dasselbe globale Objekt in Form der Register des Ethernet-Chips. Wenn während dieser Registermanipulation Preemptions zugelassen sind, kann Task B Task A unterbrechen, nachdem die Daten von Paket A in die Queue geladen, aber noch nicht abgeschickt wurden. In diesem Fall ruft Task B die Socket-Layer-Funktion auf, die dann die TCP-Layer-Funktion aufruft, die anschließend die IP-Layer-Funktion aufruft, die dann ihrerseits den Ethernet-Treiber aufruft. Dieser schließlich lädt Paket B in die Queue und sendet es ab. Wird danach die Kontrolle über die CPU wieder an Task A zurückgegeben, fordert diese Task das Absenden an. Je nach dem Design des Ethernet-Chips kann dies zum erneuten Absenden von Paket B führen oder einen Fehler generieren. In beiden Fällen aber sind die Daten von Paket A verloren, sodass sie nicht in das Netzwerk geschickt werden.

Damit die Funktionen dieses Ethernet-Treibers (beinahe) gleichzeitig von mehreren RTOS Tasks aufgerufen werden können, müssen sie ablaufinvariant sein. Solange jede Funktion ausschließlich Stack-Variablen verwendet, sind keine weiteren Maßnahmen erforderlich, da jede RTOS Task ihren eigenen privaten Stack besitzt. Treiber und bestimmte andere Funktionen aber sind nicht ablaufinvariant, sofern sie nicht mit besonderer Sorgfalt entwickelt werden.

Entscheidend dafür, Funktionen ablaufinvariant zu machen, ist es, keine Preemptions zuzulassen, während auf Peripherieregister, globale Variablen (darunter auch statische lokale Variablen), persistente Heap-Objekte und geteilte Speicherbereiche zugegriffen wird. Dies kann durch Sperren eines oder mehrerer Interrupts erfolgen, oder durch Abfragen und Freigeben eines Mutex.

Abhilfe: Erstellen und verbergen Sie einen Mutex in allen Bibliotheken oder Treibern, die nicht inhärent ablaufinvariant sind. Machen Sie die Abfrage dieses Mutex zur Vorbedingung für die Manipulation aller persistenten Daten oder geteilten Register, die innerhalb des gesamten Moduls verwendet werden. Derselbe Mutex kann beispielsweise auch genutzt werden, um Race Conditions mit den Registern des Ethernet-Controllers und einem globalen (oder auch einem lokalen statischen) Paketzähler zu verhindern. Sämtliche Funktionen des Moduls, die auf diese Daten zugreifen, müssen das Protokoll einhalten und vor der Manipulation dieser Objekte zunächst den Mutex abfragen.

Seien Sie sich auch der Tatsache bewusst, dass sich nicht ablaufinvariante Funktionen auch als Bestandteil von Middleware von Drittunternehmen, von Bestandscode oder von Gerätetreibern in Ihre Codebasis einschleichen können. Etwas irritierend ist, dass sogar die standardmäßige C- oder C++-Bibliothek Ihres Compilers nicht ablaufinvariante Funktionen enthalten kann. Wenn Sie beispielsweise den GNU-Compiler zum Erstellen von Echtzeit-Anwendungen nutzen, sollten Sie anstelle der vorgegebenen Bibliothek die ablaufinvariante Standard-C-Bibliothek ‚newlib‘ verwenden.


  1. Rezepte fürs Bugfixing
  2. Race Conditions
  3. Nicht ablaufinvariante Funktionen
  4. Prioritätsinversionen
  5. Speicherlecks

Lesen Sie mehr zum Thema


Das könnte Sie auch interessieren

Jetzt kostenfreie Newsletter bestellen!

Weitere Artikel zu Componeers GmbH

Weitere Artikel zu Software (M2M)