Ich bin mit mehreren Echtzeitkerneln vertraut: AVIX , FreeRTOS , TNKernel , und in allen haben wir 2 Versionen von fast allen Funktionen: eine zum Aufrufen von Tasks und eine zum Aufrufen von ISR.
Natürlich ist es sinnvoll für Funktionen, die den Kontext wechseln und / oder schlafen könnten: Offensichtlich kann ISR nicht schlafen, und der Kontextwechsel sollte auf andere Weise erfolgen. Es gibt jedoch mehrere Funktionen, die weder den Kontext wechseln noch schlafen: Sagen wir, es kann die System-Tick-Zählung zurückgeben oder den Software-Timer einrichten usw.
Jetzt implementiere ich meinen eigenen Kernel: TNeoKernel , der wohlgeformten Code hat und sorgfältig getestet wurde, und ich denke darüber nach, manchmal "universelle" Funktionen zu erfinden: diejenigen, die entweder aus dem Task- oder dem ISR-Kontext aufgerufen werden können. Aber da alle drei oben genannten Kernel separate Funktionen verwenden, fürchte ich, dass ich etwas falsch mache.
Angenommen, im Task- und ISR-Kontext verwendet TNKernel verschiedene Routinen zum Deaktivieren/Wiederherstellen von Interrupts, aber soweit ich sehe, besteht der einzig mögliche Unterschied darin, dass ISR-Funktionen als Optimierung "kompiliert" werden können, wenn die Zielplattform dies nicht unterstützt verschachtelte Interrupts. Wenn die Zielplattform jedoch verschachtelte Interrupts unterstützt, sieht das Deaktivieren/Wiederherstellen von Interrupts für Task- und ISR-Kontext absolut gleich aus.
Meine Frage lautet also : Gibt es Plattformen, auf denen das Deaktivieren / Wiederherstellen von Interrupts von ISR anders erfolgen sollte als von Nicht-ISR-Kontexten?
Wenn es solche Plattformen nicht gibt, würde ich lieber mit "universellen" Funktionen arbeiten. Wenn Sie Kommentare zu diesem Ansatz haben, sind diese sehr willkommen.
UPD: Ich mag es nicht, zwei Funktionssätze zu haben, weil sie zu einer bemerkenswerten Duplizierung und Komplikation des Codes führen. Angenommen, ich muss eine Funktion bereitstellen, die den Software-Timer starten soll. So sieht es aus:
enum TN_RCode _tn_timer_start(struct TN_Timer *timer, TN_Timeout timeout)
{
/* ... real job is done here ... */
}
/*
* Function to be called from task
*/
enum TN_RCode tn_timer_start(struct TN_Timer *timer, TN_Timeout timeout)
{
TN_INTSAVE_DATA; //-- define the variable to store interrupt status,
// it is used by TN_INT_DIS_SAVE()
// and TN_INT_RESTORE()
enum TN_RCode rc = TN_RC_OK;
//-- check that function is called from right context
if (!tn_is_task_context()){
rc = TN_RC_WCONTEXT;
goto out;
}
//-- disable interrupts
TN_INT_DIS_SAVE();
//-- perform real job, after all
rc = _tn_timer_start(timer, timeout);
//-- restore interrupts state
TN_INT_RESTORE();
out:
return rc;
}
/*
* Function to be called from ISR
*/
enum TN_RCode tn_timer_istart(struct TN_Timer *timer, TN_Timeout timeout)
{
TN_INTSAVE_DATA_INT; //-- define the variable to store interrupt status,
// it is used by TN_INT_DIS_SAVE()
// and TN_INT_RESTORE()
enum TN_RCode rc = TN_RC_OK;
//-- check that function is called from right context
if (!tn_is_isr_context()){
rc = TN_RC_WCONTEXT;
goto out;
}
//-- disable interrupts
TN_INT_IDIS_SAVE();
//-- perform real job, after all
rc = _tn_timer_start(timer, timeout);
//-- restore interrupts state
TN_INT_IRESTORE();
out:
return rc;
}
Wir brauchen also Wrapper wie die oben genannten für fast alle Systemfunktionen. Dies ist eine Art Unannehmlichkeit, sowohl für mich als Kernel-Entwickler als auch für Kernel-Benutzer.
Der einzige Unterschied besteht darin, dass verschiedene Makros verwendet werden: für Aufgaben sind dies TN_INTSAVE_DATA
, TN_INT_DIS_SAVE()
, TN_INT_RESTORE()
; für Interrupts sind dies TN_INTSAVE_DATA_INT
, TN_INT_IDIS_SAVE()
, TN_INT_IRESTORE()
.
Für die Plattformen, die verschachtelte Interrupts (ARM, PIC32) unterstützen, sind diese Makros identisch. Für andere Plattformen, die keine verschachtelten Interrupts unterstützen, werden TN_INTSAVE_DATA_INT
, TN_INT_IDIS_SAVE()
und TN_INT_IRESTORE()
auf nichts erweitert. Es ist also ein bisschen Leistungsoptimierung, aber die Kosten sind meiner Meinung nach zu hoch: Es ist schwieriger zu warten, nicht so bequem zu verwenden und die Codegröße nimmt zu.
Ich glaube, es gibt noch mehr treibende Kräfte dafür, zwei Gruppen von Funktionen zu haben, als Sie erwähnt haben.
Zusammenfassung: IMHO hat es zwei Sätze von Funktionen, die effektiv zwei verschiedene „Namensräume“ („Benutzeranwendung“ und „Systemkomponente“) bilden, und erleichtert es dem „Anwendungsentwickler“ und den Entwicklern, zusätzliche „privilegierte Systemkomponenten“ hinzuzufügen verwenden.
Es könnte auch einfacher sein, zwei verschiedene Funktionssätze zu haben, um die Verwaltung von Interrupts zu vereinfachen, die ausgelöst werden, während der Code in zwei verschiedenen Systemzuständen ausgeführt wird.
Es kann relativ einfach sein, die beiden Alternativen mit einigen sorgfältig durchdachten Präprozessor-Makros zu erstellen, um die Codeduplizierung zu minimieren.
Wenn Sie nicht beabsichtigen, dass jemand anderes Ihr Betriebssystem verwendet, oder Ihr Betriebssystem Ausnahmen behandelt, die im Benutzermodus und im Systemmodus auftreten, können Sie die Idee wahrscheinlich ignorieren.
Wenn Sie jedoch nicht wissen , wie das alles funktionieren könnte (zum Beispiel ist dies das erste Mal, dass Sie ein RTOS geschrieben haben) oder wenn Sie glauben, dass es sich ändern könnte, dann sollten Sie vielleicht sorgfältig darüber nachdenken, wie Sie es wieder einführen würden zwei 'Namensräume' auf halbem Weg durch die Entwicklung Ihres Betriebssystems.
Einige vom Hersteller bereitgestellte E/A-Bibliotheken haben ihre eigenen Methoden zum Aktivieren und Deaktivieren von Interrupts. In einigen Fällen sind sie auf eine unglückliche Weise geschrieben, die anfällig für Fehlfunktionen ist, es sei denn, der gesamte Code, der Interrupts aktiviert oder deaktiviert, tut dies über solche Bibliotheken. Wenn die E/A-Routinen solche Routinen verwenden, dann kann es notwendig sein, sicherzustellen, dass der gesamte andere Code systemweit den Anforderungen solcher Bibliotheken entspricht, es sei denn, die Routinen können gepatcht werden, um sich auf vernünftige Weise zu verhalten.
Ich bin mir nicht sicher, warum Anbieterbibliotheken nicht einfach eine Funktion bereitstellen, die den Interrupt-Aktivierungszustand erfasst, Interrupts deaktiviert und den erfassten Zustandswert zurückgibt, zusammen mit einer Funktion, die einen erfassten Zustand wiederherstellt, und das alles ohne statische oder globale Verwendung Objekte. Module, die diesen allgemeinen Ansatz verwenden, sind natürlich mit anderen Modulen kompatibel, die dies ebenfalls tun, ohne dass sie sich hinsichtlich der globalen Objektverwendung koordinieren müssen. Nichtsdestotrotz gibt es schlecht gestaltete Bibliotheken, und es ist manchmal notwendig, Programme zu schreiben, die sie berücksichtigen.
Ghellquist
Ghellquist
Ghellquist