Consumer Thread mit Preemption, wie?



  • Hallo

    der Name des Threads ist ein bisschen irreführend, aber mir fiel beim besten Willen nichts passenderes ein.

    Ich schreib einen Echtzeit-mikrokernel auf einem Gumstix (intel pxa 255) und möchte eine Funktion der Art 'consume_cycles(uint32_t cycles)'. Ich habe bereits Funktionen wie sleep_mseconds, die ich aber nicht verwenden kann, weil diese mit verschachtelten For-schleifen geschrieben sind und nur bei 400 Mhz richtig arbeiten und da ich jetzt im Betrieb die Clock Frequenz ändere, sind diese Funktionen unbrauchbar. Aus diesem Grund möchte ich eine consume Funktion schreiben, die unabhängig von der Frequenz ist.

    Über den Coprocessor (CP14, register 0) kann ich auf einen Register zurgreifen, der die Anzahl der abgearbeiteten Zyklen speichert. Die einfachste Form wäre also:

    consume_execution_cycles(uint32_t x)
    {
       uint32_t start = CCNT; /* der CP14, reg 0 */
       uint32_t end = start + x; // ignoring overflow;
       while((CCNT >= start) || (CCNT <= end));
    }
    

    Ich hab den EDF Scheduler mit Preemptions implementiert und dann kann man diesen Code ebenfalls in die Tonne schmeißen. Ich sitze schon fast 1 Stunde dran und mir fällt keinen besseren Algorithmus, der auch auf mögliche Preemptions reagieren kann. Hat jemand nen guten Vorschlag, wie ich das machen könnte?



  • ^^kannst du keinen hardware-timer nehmen? der chip hat doch sicher welche, oder? wenn du die globale taktfrequenz veränderst und die timer davon abhängig sind, dann musste zwar neue teilerwerte für die timer-clock berechnen, aber das sollte machbar sein. wozu eigentlich so'ne cpu-zyklen-verbratefunktion? wär's nicht besser, eine sleep-funktion zu bauen, die eine task eine gewisse zeit pausiert und die freiwerdende rechenzeit anderen tasks zu gute kommen lässt?
    🙂



  • Besitzen deine Prozesse keine Laufzeitinformationen? Du solltest pro Prozess die abgearbeiteten Zykeln speichern, dann kannst du von diesen ausgehend zählen.
    Aber Zykelbasiert würde ich das nicht machen, ich würde ein milisekundenbasiertes oder nanosekundenbasiertes sleep() schreiben, welches dann über den Timer-Interrupt läuft, natürlich nicht direkt, sondern über einen entsprechenden Handler der alle schlafenden Prozesse versorgt.



  • ~fricky schrieb:

    ^^kannst du keinen hardware-timer nehmen?
    🙂

    nein, ich hätte vier zur Verfügung, die verwende ich aber bereits für den Scheduler.

    ~fricky schrieb:

    wenn du die globale taktfrequenz veränderst und die timer davon abhängig sind

    das ist zum Glück nicht der Fall, sonst könnte ich keinen frequency scaling machen.

    ~fricky schrieb:

    wozu eigentlich so'ne cpu-zyklen-verbratefunktion? wär's nicht besser,
    🙂

    eine timer(irq)-basierte Funktion habe ich bereits, aber das Problem mit Preemption besteht trotzdem und dieses will ich ja umgehen.

    ~fricky schrieb:

    die eine task eine gewisse zeit pausiert und die freiwerdende rechenzeit anderen tasks zu gute kommen lässt?
    🙂

    geht nicht, weil das die Definition des EDFs widerspricht. Ich brauche kein sleep in dem Sinne, dass jemand anders an die Reihe kommt, sondern will ich wirklich sagen 'while(1); und zwar n Millisekunden lang'. Der Grund: ich arbeite an Energie effizientes Scheduling und ich muss den Stromverbrauch bei volllast messen (bei verschiedenen CPU-Frequenzen und verschiedene utilization factors.

    Tippgeber schrieb:

    Besitzen deine Prozesse keine Laufzeitinformationen? Du solltest pro Prozess die abgearbeiteten Zykeln speichern, dann kannst du von diesen ausgehend zählen.

    Jein. Für jede Task ist für den Scheuler an sich nur die Periode (und damit ihre Deadline) bekannt. Die Rechenzeit ist für den "rohen" EDF Scheduler nicht von belang. Wie ich oben erwähnt habe, muss ich Benchmarks schreiben, die Tasks enthalten, die z.B. 300 ms lang etwas machen, von mir aus 'while(1);', deswegen kann ich mir nicht leisten, die Task zum Schlafen zu bringen. Aus diesem Grund brauche ich kein 'sleep' sondern 'consume'.

    Tippgeber schrieb:

    Aber Zykelbasiert würde ich das nicht machen, ich würde ein milisekundenbasiertes oder nanosekundenbasiertes sleep() schreiben, welches dann über den Timer-Interrupt läuft, natürlich nicht direkt, sondern über einen entsprechenden Handler der alle schlafenden Prozesse versorgt.

    siehe oben, darauf habe ich schon beantwortet 😉



  • supertux schrieb:

    ~fricky schrieb:

    ^^kannst du keinen hardware-timer nehmen?
    🙂

    nein, ich hätte vier zur Verfügung, die verwende ich aber bereits für den Scheduler.

    ~fricky schrieb:

    wenn du die globale taktfrequenz veränderst und die timer davon abhängig sind

    das ist zum Glück nicht der Fall, sonst könnte ich keinen frequency scaling machen.

    ~fricky schrieb:

    wozu eigentlich so'ne cpu-zyklen-verbratefunktion? wär's nicht besser,
    🙂

    eine timer(irq)-basierte Funktion habe ich bereits, aber das Problem mit Preemption besteht trotzdem und dieses will ich ja umgehen.

    ~fricky schrieb:

    die eine task eine gewisse zeit pausiert und die freiwerdende rechenzeit anderen tasks zu gute kommen lässt?
    🙂

    geht nicht, weil das die Definition des EDFs widerspricht. Ich brauche kein sleep in dem Sinne, dass jemand anders an die Reihe kommt, sondern will ich wirklich sagen 'while(1); und zwar n Millisekunden lang'. Der Grund: ich arbeite an Energie effizientes Scheduling und ich muss den Stromverbrauch bei volllast messen (bei verschiedenen CPU-Frequenzen und verschiedene utilization factors.

    Tippgeber schrieb:

    Besitzen deine Prozesse keine Laufzeitinformationen? Du solltest pro Prozess die abgearbeiteten Zykeln speichern, dann kannst du von diesen ausgehend zählen.

    Jein. Für jede Task ist für den Scheuler an sich nur die Periode (und damit ihre Deadline) bekannt. Die Rechenzeit ist für den "rohen" EDF Scheduler nicht von belang. Wie ich oben erwähnt habe, muss ich Benchmarks schreiben, die Tasks enthalten, die z.B. 300 ms lang etwas machen, von mir aus 'while(1);', deswegen kann ich mir nicht leisten, die Task zum Schlafen zu bringen. Aus diesem Grund brauche ich kein 'sleep' sondern 'consume'.

    Tippgeber schrieb:

    Aber Zykelbasiert würde ich das nicht machen, ich würde ein milisekundenbasiertes oder nanosekundenbasiertes sleep() schreiben, welches dann über den Timer-Interrupt läuft, natürlich nicht direkt, sondern über einen entsprechenden Handler der alle schlafenden Prozesse versorgt.

    siehe oben, darauf habe ich schon beantwortet 😉

    Ok, dann deaktiviere in consume die preemption doch einfach. Macht der Linux-Kernel auch beim "Big Kernel Lock".



  • das wäre zu schön, kann/darf ich leider nicht, weil es sich um ein RTOS handelt (ich muss ja echte Laufzeit simulieren, die unterbrochen werden kann).



  • Erweitere deinen Scheduler so, dass du für jeden Thread/Prozess die "consumed cycles" abfragen kannst.

    Der Scheduler ist ja der der Threads "umschaltet", also kann er beim Umschalten der Threads auch einfach das CP14 Register auslesen, und in der Thread-Struktur einen Zähler anpassen.

    Dann kannst du in deiner Funktion einfach die neue "GetThreadCycles()" Funktion anstelle des CP14 Registers verwenden.

    p.S.:
    was willst du eigentlich? Eine Funktion die 300ms lang was macht, auch wenn der Takt runtergeregelt wurde, und ihr von den 300ms z.B. 200ms durch anderen Threads gestolen wurden?

    Oder eine Funktion die einer Last entspricht die bei Original-Takt und ohne preemtion 300ms laufen würde? Oder... ?
    Deine Beschreibung ist echt irgendwie verwirrend.



  • supertux schrieb:

    ~fricky schrieb:

    die eine task eine gewisse zeit pausiert und die freiwerdende rechenzeit anderen tasks zu gute kommen lässt?
    🙂

    geht nicht, weil das die Definition des EDFs widerspricht. Ich brauche kein sleep in dem Sinne, dass jemand anders an die Reihe kommt, sondern will ich wirklich sagen 'while(1); und zwar n Millisekunden lang'. Der Grund: ich arbeite an Energie effizientes Scheduling und ich muss den Stromverbrauch bei volllast messen (bei verschiedenen CPU-Frequenzen und verschiedene utilization factors.

    du willst das ganze system für n ms einfrieren? es gibt bei einigen ARMs die möglichkeit, die CPU zu 'disablen', bis ein interrupt auftritt (such mal nach register CP15). beim nächsten scheduler-tick wacht das system dann wieder auf. das ganze hätte den netten nebeneffekt, dass während dieser sleep-periode der prozessor kaum energie verbraucht. mit solchen globalen pausen degradierst du dein RTOS übrigens zu einem kooperativen system, weil das gesamte zeitverhalten davon abhängig ist, wie lange und wie oft irgendwelche tasks alles lahmlegen.
    🙂



  • ~fricky schrieb:

    du willst das ganze system für n ms einfrieren? es gibt bei einigen ARMs die möglichkeit, die CPU zu 'disablen', bis ein interrupt auftritt (such mal nach register CP15). beim nächsten scheduler-tick wacht das system dann wieder auf. das ganze hätte den netten nebeneffekt, dass während dieser sleep-periode der prozessor kaum energie verbraucht. mit solchen globalen pausen degradierst du dein RTOS übrigens zu einem kooperativen system, weil das gesamte zeitverhalten davon abhängig ist, wie lange und wie oft irgendwelche tasks alles lahmlegen.
    🙂

    meine Güte, wie soll ich es denn erklären? Ich will Rechenzeit simulieren, ich will z.b. 300 ms Arbeit simulieren und dabei den Stromverbrauch messen (und das bei unterschiedlichen Frequenzen), das will ich nur für Tests und Benchmarks. Es geht um Tasksmnegen der Art:

    Task i, Laufzeit C_i, Periode T_i
    1, 300 ms, 700 ms
    2, 1500 ms, 4000 ms
    3, 50 ms, 300 ms
    usw
    

    und diese Laufzeit C_I will ich künstlich erzeugen.

    hustbaer schrieb:

    Erweitere deinen Scheduler so, dass du für jeden Thread/Prozess die "consumed cycles" abfragen kannst.

    Der Scheduler ist ja der der Threads "umschaltet", also kann er beim Umschalten der Threads auch einfach das CP14 Register auslesen, und in der Thread-Struktur einen Zähler anpassen.

    das habe ich mir auch überlegt, mit anderen Werten mache ich es genauso. Das Problem ist, man hätte nur die (bis zum letzten Context-Switch) verbrauchten Zyklen.

    hustbaer schrieb:

    was willst du eigentlich? Eine Funktion die 300ms lang was macht, auch wenn der Takt runtergeregelt wurde, und ihr von den 300ms z.B. 200ms durch anderen Threads gestolen wurden?

    siehe meine Antwort auf freakys letzten Post: ich will Rechenzeit simulieren. Da ich unterschiedliche Frequenzen habe, wollte ich es eben durch die Anzahl der verbrauchten Zyklen machen. Wie vielen Zyklen 100 ms bei Frequenz x MHz entsprechen, lässt sich leicht experimentell bestimmen.



  • supertux schrieb:

    meine Güte, wie soll ich es denn erklären? Ich will Rechenzeit simulieren, ich will z.b. 300 ms Arbeit simulieren und dabei den Stromverbrauch messen (und das bei unterschiedlichen Frequenzen), das will ich nur für Tests und Benchmarks. Es geht um Tasksmnegen der Art:

    Task i, Laufzeit C_i, Periode T_i
    1, 300 ms, 700 ms
    2, 1500 ms, 4000 ms
    3, 50 ms, 300 ms
    usw
    

    und diese Laufzeit C_I will ich künstlich erzeugen.

    das hättest du auch gleich sagen können. hab grad mal nachgeschaut: der os-timer im pxa255 wird direkt mit der 3.68Mhz clock angetriggert, während die CPU-clock (die du ja verstellen willst) von einer PLL kommt. die anzahl aller bisherigen clock-impulse der 3.68MHZ clock, kannst du aus dem register OSCR auslesen. damit hast du eine konstante zeitbasis, egal wie schnell die CPU ist. und dann machste einfach folgendes:

    delay_ms (unsigned time)
    {
       unsigned timeout = OSCR + time * CLOCKS_PER_MILLISECOND;  // ueberlauf mal nicht beruecksichtigt
       disable_interrupts_and_schedeuler();
       while (timeout >= OSCR)
       {
          // rechenzeit verbraten
       }
       enable_interrupts_and_schedeuler();
    }
    

    🙂



  • das weiß ich und diesen Ansatz habe ich bereits, aber der funktioniert nicht, wenn ich mehrere Tasks habe. disable_interrupts_and_schedeuler(); kann/darf ich mir nicht leisten, das ist genau das Problem.



  • supertux schrieb:

    hustbaer schrieb:

    Erweitere deinen Scheduler so, dass du für jeden Thread/Prozess die "consumed cycles" abfragen kannst.

    Der Scheduler ist ja der der Threads "umschaltet", also kann er beim Umschalten der Threads auch einfach das CP14 Register auslesen, und in der Thread-Struktur einen Zähler anpassen.

    das habe ich mir auch überlegt, mit anderen Werten mache ich es genauso. Das Problem ist, man hätte nur die (bis zum letzten Context-Switch) verbrauchten Zyklen.

    Wieso?
    Du kannst ja eine zusätzliche Scheduler-Funktion basteln:

    long GetThreadCycles()
    {
        while (1)
        {
            long used = *((volatile long*) &g_current_thread->used_clocks); // das muss halt atomic gehen
            long start = *((volatile long*) &g_current_thread->current_slice_start_clocks); // ... ebenso ...
            long cp14 = CP14;
            long used2 = *((volatile long*) &g_current_thread->used_clocks); // ...
    
            if (used == used2) // wenn used == used2 dann hat der scheduler nicht umgeschaltet zwischen dem lesen der werte
                return used + (cp14 - start);
        }
    }
    

    Damit hättest du immer den aktuellen Wert. Müsste IMO gehen. Musst du nichtmal kurzfristig den Scheduler anhalten dafür 🙂



  • hi danke, so etwas ähnliches habe ich heute mit meinem Betreuer rausbekommen. Die atomare Anfragen gehen leider nur über syscalls (was ich mit SWIs implementiere) und weiß nicht, ob ich das so will. Jedenfalls danke für die Antworten.


Anmelden zum Antworten