Autor Thema: C++ Kernel  (Gelesen 6369 mal)

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« am: 30. January 2011, 22:10 »
Ich habe mich mal in das Abenteuer C++ Kernel gestürzt (macht sich unter anderem ganz gut für meine C++ Prüfung ;) ) und bin natürlich auf einige Probleme gestoßen.

Ich bin gerade auch erst dabei C++ richtig zu lernen, also kann es passieren das ich hier Probleme schildere für die es eine simple Lösung gibt.

Erstes Problem weiß jemand wo ich Infos herbekomme wie ich Exceptions im Kernel zum Laufen bekomme?

Dann habe ich oft das Problem, das ein Konstruktor ja ein Objekt initialiseren soll, aber bei einigen Objekten müsste mein Konstruktor ne Exception schmeißen. Dafür bräuchte ich die aber erstmal und selbst dann habe ich noch ein Problem, weil das meistens auch noch Objekte betrifft die global definiert sind und wo der Konstruktor ja eigentlich schon beim Starten des Kernels aufgerufen wird (ist bei eigentlich allen Tutorials dazu so).

Jetzt könnte man sagen gut, in dem Fall kann man auch mal auf das OO-Konzept Konstruktor verzichten und einfach ne Friend-Funktion zum initialisieren nutzen, aber vllt habt ihr ja noch ne Idee wie man das lösen könnte.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 30. January 2011, 23:05 »
Steht alles hier: http://wiki.osdev.org/C%2B%2B#Full_C.2B.2B_Runtime_Support_Using_libgcc_And_libsupc.2B.2B

Aber willst du wirklich Exceptions im Kernel haben? Wenn globale Objekte in ihren Konstruktoren Exceptions werfen, solltest du diese Objekte einfach nicht global machen. Und warum friend-Funktionen? Früher nannten wir die Funktionen "init" und die waren Memberfunktionen.
« Letzte Änderung: 30. January 2011, 23:08 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 31. January 2011, 11:54 »
Zitat von: porkchicken
Steht alles hier: ...
Ich dachte jemand wüsste wo ich Infos finde wie die das bei GCC implementiert haben (zu MSC++ hatte ich sowas gefunden). Naja muss ich mich wahrscheinlich doch durch den Code kämpfen.

Zitat von: porkchicken
Aber willst du wirklich Exceptions im Kernel haben? Wenn globale Objekte in ihren Konstruktoren Exceptions werfen, solltest du diese Objekte einfach nicht global machen.
Naja, wenn ich der Meinung bin das mir die Exceptions was bringen bzw. die Umsetzung nicht alzu umfangreich/komplex wird. Dann würde ich die "mitnehmen". Ich habe keinen "normalen" Allocator, sondern nur nen SlabAllocator und warum sollte ich Speicher "wegwerfen", wenn ich das auch alles schon schön zur Compile-Zeit in der Datensektion zusammenpacken lassen kann.

Zitat von: porkchicken
Und warum friend-Funktionen? Früher nannten wir die Funktionen "init" und die waren Memberfunktionen.
Naja, wenn ich init nutze, dann kann ich mir eigentlich auch gleich C++ sparen, weil die Konstruktoren und Destruktoren nehmen mir halt Arbeit ab (und ich kann weniger vergessen)! Zu den friend-Funktionen ich habe Code der nur einmal (halt init Code) aufgerufen wird und der muss halt das Objekt (bzw. genau ein Objekt, weil die danach dann den allgemeinen Konstruktor nutzen können) initialisieren (und wird dann wenn der Kernel läuft auch freigegeben).

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 31. January 2011, 17:54 »
Ich habe bei mir selbst zwar noch keine Exceptions, aber wo liegt denn das Problem bei Exceptions in Globalen Konstruktoren? Du musst die doch sowieso von Hand aufrufen. Da kannst du dann auch einen try-Block drumherum packen.
Bzw. ich verstehe glaube ich das Problem generell nicht.

Generell für Exceptions im Kernel siehe: Using C++ exceptions in the Linux Kernel
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 31. January 2011, 18:13 »
@MNemo

Das "Problem" liegt darin, das man eigentlich alle Konstruktoren von globalen Objekte aufruft, noch bevor man in den Kernel springt, aber wie du schon richtig festgestellt hast, kann man diesen Code auch einfach weglassen und die Konstruktoren von Hand aufrufen (warum ich da nicht selbst draufgekommen bin). Das hat jetzt zwar noch den Nachteil, das es wieder Fehleranfällig ist, weil ich den Aufruf vergessen könnte, insbesondere wenn ich mal schnell ein neue Objekt hinzufüge, aber es wäre eine Lösung und ich könnte dann dort erfolgreich Exceptions einsetzen und mein anderes Problem, dass der Konstruktor auf Sachen zugreift die noch gar nicht initialisert sind, hat sich damit auch erledigt und ich kann dann auch noch die Friend-Funktionen einsetzen :D

Wie gesagt, das ich da nicht selbst drauf gekommen bin.

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 31. January 2011, 18:30 »
Wo ist den vor dem Kernel? Die Konstruktoren werden bei der Initialisierung des Kernels aufgerufen, ob das jetzt im ASM oder im C++ teil geschieht ist egal.

Mit dem per Hand aufrufen hatte ich das hier gemeint:
C++ Konstruktoren

Da kannst du eigentlich nichts vergessen.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 31. January 2011, 18:35 »
Dein Kernel hat meinetwegen ne main() Funktion und bevor du dir aufrufst, rufst du genau die von dir genannte Liste von Konstruktoren auf, aber genau das will ich ja nicht, weil die Konstruktoren auch Code enthalten der noch gar nicht verwendet werden kann.

Ich habe ja meine Source-Dateien wo dann irgendwelche globalen Objekte drin sind. Da würde ich dann von der KernelMain aus eine Init-Funktion aufrufen (was ja jetzt auch schon passiert), die sogar noch eine Friend-Funktion sein kann/wird und da rufe ich entweder den Konstruktor aller globalen Objekte per Hand auf oder aber ich initialisiere das Objekt ohne den Konstruktor (weil der halt Sachen verwendet die noch nicht funktionieren).

Dabei kann es natürlich zu Fehlern kommen, sprich ich vergesse ein globales Objekt oder vergesse das ich den Konstruktor per Hand aufrufen muss.

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 31. January 2011, 19:25 »
Ah…Ok, Objekte global zu machen, deren Konstruktoren von anderen Sachen abhängen, ist eine schlecht Idee.
Dann kann ich nur wieder holen: Entweder nicht global machen, oder den Konstruktor abspecken und den Rest in Member Funktion auslagern.

Zitat von: porkchicken
Und warum friend-Funktionen? Früher nannten wir die Funktionen "init" und die waren Memberfunktionen.
Naja, wenn ich init nutze, dann kann ich mir eigentlich auch gleich C++ sparen, weil die Konstruktoren und Destruktoren nehmen mir halt Arbeit ab (und ich kann weniger vergessen)! Zu den friend-Funktionen ich habe Code der nur einmal (halt init Code) aufgerufen wird und der muss halt das Objekt (bzw. genau ein Objekt, weil die danach dann den allgemeinen Konstruktor nutzen können) initialisieren (und wird dann wenn der Kernel läuft auch freigegeben).
Ich sehe da keinen Vorteil den friend gegenüber member hat. Es läuft exakt auf das gleich raus. Nur das die Funktion als Member logisch da ist wo sie hingehört.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 31. January 2011, 19:38 »
Zitat von: MNemo
Entweder nicht global machen, oder den Konstruktor abspecken und den Rest in Member Funktion auslagern.
Du hast aber nur einen Bildschirm (als Bsp. wo man ein Objekt global machen muss) und wenn ich einiges in Member-Funktionen auslagern muss, kann ich mir den Konstruktor auch komplett sparen! Aber das hatte ich unter anderem als Vorteil gesehen, das ich nen Konstruktor (und auch Destruktor) habe.

Zitat von: MNemo
Ich sehe da keinen Vorteil den friend gegenüber member hat. Es läuft exakt auf das gleich raus. Nur das die Funktion als Member logisch da ist wo sie hingehört.
Und was ist wenn diese Friend-/Member-Funktion nur ein einziges mal (über den ganzen Zeitraum wo der Kernel läuft) aufgerufen wird und der Code sogar nach dem Initialiseren freigegeben wird? Da will ich das nicht als Member-Funktion haben.

Problem ist halt, das Exceptions, je nach dem wie man sie Implementiert, Zeit und/oder Speicher kosten und beides nicht zu wenig. Desweiteren sind sie Compiler spezifisch, da sehe ich jetzt weniger das Problem, da ich eh einige sehr GCC spezifische Sachen nutzen, aber um eine für meine Zwecke ausreichende Exception Implementierung (die schneller ist und weniger Speicher braucht) müsste ich GCC modifizieren und das geht eigentlich weit darüber hinaus was ich machen wollte und was ich in der Lage bin zu tun ;)

Leider habe ich außer dem Code auch noch keine wirklichen Infos zur der Implementierung im GCC gefunden (scheint auch nichts außer den Code-Kommentaren zu geben :( ).

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 31. January 2011, 19:57 »
Ich würde davon ausgehen, dass die existierende Implementierung in GCC bzw. den dazugehörigen Bibliotheken bereits optimiert ist. Nimm die doch einfach.
Dieser Text wird unter jedem Beitrag angezeigt.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 31. January 2011, 20:06 »
Zitat von: PorkChicken
Ich würde davon ausgehen, dass die existierende Implementierung in GCC bzw. den dazugehörigen Bibliotheken bereits optimiert ist. Nimm die doch einfach.
Ersten will ich ja auch was lernen, würde also schon gerne probieren das selbst zu Implementieren und zweitens mag ich keine BlackBox in meinem Kernel ;)

Was noch dazu kommt, ist dass der Exception Code in GCC ziemlich allgemein gehalten ist (damit mit man in allen Sprachvarianten den gleichen Code nutzen kann und das ganze auch noch Architektur übergreifend funktioniert) und damit leider nicht so optimiert ist wie er sein könnte.

Ich überlege im Moment ob es nicht ausreichend ist, wenn ich in dem Fall von globalen Objekten, den Konstruktor einfach per Hand aufrufe und bei allen Objekten entweder eine Membervariable nutze oder eine globale Variable per Thread, ob etwas im Konstruktor (der als letztes aufgerufen wurde) fehlgeschlagen ist.

Was würdet ihr machen, wenn ich den Code eigentlich nicht aus dem Konstruktur in eine extra Member Funktion auslagern wollt?

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 31. January 2011, 20:33 »
Hallo,


man kann die Konstrukturen auch in einer bestimmten Reihenfolge aufrufen lassen so das all die Dinge von denen ein Konstruktur abhängig ist bereits vorher erledigt wurden. Wie das genau funktioniert weiß ich jetzt nicht auswendig aber das gcc-Manual sollte da helfen können. Wimre wird dieser Mechanismus bei eCos benutzt.

Da darf man natürlich keine Kreisabhängigkeiten in seinem Code habe, Henne-Ei-Probleme fallen nicht in den Zuständigkeitsbereich des Compilers. ;)


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 31. January 2011, 22:32 »
Zitat von: MNemo
Entweder nicht global machen, oder den Konstruktor abspecken und den Rest in Member Funktion auslagern.
Du hast aber nur einen Bildschirm (als Bsp. wo man ein Objekt global machen muss) und wenn ich einiges in Member-Funktionen auslagern muss, kann ich mir den Konstruktor auch komplett sparen! Aber das hatte ich unter anderem als Vorteil gesehen, das ich nen Konstruktor (und auch Destruktor) habe.
Für sachen die es nur einmal gibt nimmt man Singletons, nicht Globale globale Objekte. Und ob du es in Member oder in Friends auslagerst ist wie gesagt völlig egal.

Zitat
Zitat von: MNemo
Ich sehe da keinen Vorteil den friend gegenüber member hat. Es läuft exakt auf das gleich raus. Nur das die Funktion als Member logisch da ist wo sie hingehört.
Und was ist wenn diese Friend-/Member-Funktion nur ein einziges mal (über den ganzen Zeitraum wo der Kernel läuft) aufgerufen wird und der Code sogar nach dem Initialiseren freigegeben wird? Da will ich das nicht als Member-Funktion haben.
Wieso nicht als Member? Die Funktion gehört zu dieser Klasse ob er jetzt einmal oder mehrmals aufgerufen. Und freigeben kannst du es so oder so. Aber das ist deine Sache. Es wird sich halt nichts ändern als der Aufruf:
init(obj, …) vs. obj->init(…);

Zitat
Problem ist halt, das Exceptions, je nach dem wie man sie Implementiert, Zeit und/oder Speicher kosten und beides nicht zu wenig. Desweiteren sind sie Compiler spezifisch, da sehe ich jetzt weniger das Problem, da ich eh einige sehr GCC spezifische Sachen nutzen, aber um eine für meine Zwecke ausreichende Exception Implementierung (die schneller ist und weniger Speicher braucht) müsste ich GCC modifizieren und das geht eigentlich weit darüber hinaus was ich machen wollte und was ich in der Lage bin zu tun ;)
Jo, exceptions sind ein wenig aufwendig. Aber in dem Artikel wird behauptet, dass die kosten drastisch reduziert werden konnten.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #13 am: 01. February 2011, 10:19 »
man kann die Konstrukturen auch in einer bestimmten Reihenfolge aufrufen lassen so das all die Dinge von denen ein Konstruktur abhängig ist bereits vorher erledigt wurden. Wie das genau funktioniert weiß ich jetzt nicht auswendig aber das gcc-Manual sollte da helfen können. Wimre wird dieser Mechanismus bei eCos benutzt.
Innerhalb einer Translation-Unit ist die Reihenfolge der Konstruktoraufrufe definiert. Die werden in der Reihenfolge der Definitionen aufgerufen. Wenn du also alle Definition in ein Sourcefile in der richtigen Reihenfolge auslagerst, sollte es also kein Problem sein. Dazu würde ich glaube ich tendieren.

Was auch geht ist folgendes:
class bla
{
  static bla& getInstance()
  {
    static bla inst;
    return inst;
  }
};
Dann wird der Konstruktor beim ersten Aufruf von getInstance aufgerufen (imho, ich bin mir gerade nicht 100% sicher). Unschön daran ist, dass Zyklen wohl zu einem Deadlock (oder zur Benutzung von nicht vollständig initialisierten Objekten) führen und man die Reihenfolge nirgends explizit sehen kann.

Ganz allgemein würde ich aber ehrlich gesagt vom Singletonpattern (unabhängig davon ob die Instanz jetzt global oder statisch ist) abraten. Ich finde es im Nachhinein - hatte es in lightOS auch benutzt - ziemlich nutzlos jedes Mal eine getInstance Methode aufzurufen. Da kann ich auch gleich auf einem globalen Objekt rumnudeln.
lightOS
"Überlegen sie mal 'nen Augenblick, dann lösen sich die ganzen Widersprüche auf. Die Wut wird noch größer, aber die intellektuelle Verwirrung lässt nach.", Georg Schramm

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 08. February 2011, 12:38 »
Hallo,


@bluecode oder auch andere die das wissen:
Was passiert eigentlich wenn getInstance() beim ersten Aufruf aus mehreren Threads parallel aufgerufen wird? Stellt der Compiler die Atomizität des Konstruktor-Aufrufs sicher?


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 08. February 2011, 13:24 »
Wo siehst du denn das Problem? Solange der Konstruktor keine Funktionen aufruft die atomar laufen müssen (welche ja eigentlich eh irgendwie gesichert sein müssen) ist das kein Problem. Jeder hat seinen eigenen Stack.
Daher muss ich auch sagen das ich das nicht gaz verstanden habe (das mit dem static innerhalb einer Funktion).

bluecode

  • Beiträge: 1 391
    • Profil anzeigen
    • lightOS
Gespeichert
« Antwort #16 am: 08. February 2011, 15:16 »
Was passiert eigentlich wenn getInstance() beim ersten Aufruf aus mehreren Threads parallel aufgerufen wird? Stellt der Compiler die Atomizität des Konstruktor-Aufrufs sicher?
Ja, er benutzt dafür aber Funktionen die man wiederum zur Verfügung stellen muss, siehe C++ ABI.

@FlashBurn: Das Objekt ist doch nicht auf dem Stack eines Threads, schau dir meinen Code nochmal genau an, da steht was von static.
lightOS
"Überlegen sie mal 'nen Augenblick, dann lösen sich die ganzen Widersprüche auf. Die Wut wird noch größer, aber die intellektuelle Verwirrung lässt nach.", Georg Schramm

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 08. February 2011, 19:18 »
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 10. April 2011, 08:49 »
Ich hole den Thread mal wieder hoch.

Da ich noch nicht glaube das ich das mit den Exceptions im Kernel hinbekomme, habe ich mir erstma eine Übergangslösung überlegt.

Ich will eine Klasse Error machen:
class Error {
protected:
 uint32 m_Error;
public:
 uint32 getError() {
  uint32 res= m_Error;

  m_Error= 0;

  return res;
 }
};

Alle Klassen sollen dann von dieser Klasse erben und jedes Mal wenn ich ein Objekt konstruiert habe oder eine Methode aufgerufen habe die normalerweise nen Fehlercode zurückgeben würde, müsste ich diese Funktion aufrufen, die mir den letzten Fehlercode zurückliefert und den Fehlercode des Objekts dann auf "0" setzt.

Was haltet ihr davon?

 

Einloggen