Autor Thema: Warteschleife mit Zeitangabe (in Millisekunden)  (Gelesen 10186 mal)

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« am: 14. January 2006, 23:06 »
Ich möchte beim Laden meines OS das Lade-Logo ca. 3 Sekunden eingeblendet haben. Das heißt ich benötige eine Warteschleife die genau diese 3 Sekunden wartet. Da ich aber später vielleicht noch mehrere Warteschleifen einbauen möchte, die andere Werte brauchen, wäre eine Parameterangabe in z.B. Millisekunden genau das richtige. Aber ich habe noch nie soetwas gemacht. Bis jetzt hatte ich immer hohe Werte in (e)cx eingetragen und hatte mit loop eine Warteschleife. Diese ist aber Prozessorgeschwindigkeit-Abhängig. Also weiß einer von euch wie man das mit den Millisekunden macht? So soll es ungefähr aussehen:

... ;lade Logo
mov ax,3000 ;3000 Millisekunden = 3 Sekunden
call delay ;Funktion die Wartet
... ;Weiter machen


Danke!!!

bitmaster
In the Future everyone will need OS-64!!!

Legend

  • Beiträge: 635
    • Profil anzeigen
    • http://os.joachimnock.de
Gespeichert
« Antwort #1 am: 14. January 2006, 23:14 »
Benutz lieber den PIT.
Oder warte auf x Interrupts von der RTC.
*post*

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 14. January 2006, 23:22 »
Hier ist ist Beispiel für den lokalen APIC Timer und dem Timestamp Counter (TSC):
Der lokale APIC bietet den genausten Timer des x86, da er von der Prozessor Busgeschwindigkeit abhängt. Daher muss er vorher kalibriert werden, um die Busgeschwindigkeit herauszubekommen.
Der TSC gibt die Anzahl der vergangenen Taktzyklen zurück, seitdem man den Prozessor eingeschaltet hat. So kann man ihn auch für Delays benutzen.

Der folgende Code ist in C, ich hoffe mal, dass du ihn selbst in Assembler umsetzen kannst.
#define LAPIC_REG_ID 0x020
#define LAPIC_REG_VERSION 0x030
#define LAPIC_REG_TPRI 0x080
#define LAPIC_REG_EOI 0x0b0
#define LAPIC_REG_SIVR 0x0f0
#define LAPIC_REG_ESR 0x280
#define LAPIC_REG_ICR1 0x300
#define LAPIC_REG_ICR2 0x310
#define LAPIC_REG_LVTT 0x320
#define LAPIC_REG_LINT0 0x350
#define LAPIC_REG_LVT3 0x370
#define LAPIC_REG_ICRT 0x380
#define LAPIC_REG_CCRT 0x390
#define LAPIC_REG_TDCR 0x3e0

// der lokale APIC befindet sich _normalerweise_ an 0xFEE00000
// das kann unterschiedlich sein, steht in einem MSR und wird vom BIOS programmiert
// wenn man den wert auslesen will, müsste man die multiprozessor informationen lesen, den code dafür poste ich nicht, da er viel zu lang wäre
// der apic ist aber in allen mir bekannten system an 0xFEE00000 also kann man sich normalerweise darauf verlassen
// bochs emuliert den apic code nur, wenn es dafür kompiliert wurde, qemu garnicht.
// für deine zwecke ist rdtscll() warscheinlich besser geeignet.
unsigned long *g_mp_lapic = (unsigned long *)0xFEE00000;

unsigned long long g_cpu_speed;
unsigned long long g_cpu_speed_temp;

unsigned long g_lapic_tempcount;
volatile unsigned long g_lapic_divisor;

// isr_lapic_timer() macht garnichts (nur IRET), isr_lapic_calibrate() ruft ruft lapic_calibrate_irq() auf
extern void isr_lapic_timer();
extern void isr_lapic_calibrate();

void lapic_calibrate() {
unsigned char byte;

g_lapic_tempcount = 0;
g_lapic_divisor = 0;
g_cpu_speed = 0;
g_cpu_speed_temp = 0;

// setup real time clock
// unmask IRQ 8
outByte(0x21, 0xFB);
outByte(0xA1, 0xFE);

// set interrupt gate
// interrupt gate an 0x28/0x80 setzen, mit pl 0
interrupt_setentry(0x28, isr_lapic_calibrate, 0);
interrupt_setentry(0x80, isr_lapic_timer, 0);

// set real time clock frequency
outByte(0x70, 0xA);
byte = inByte(0x71) & 0xF0;
outByte(0x70, 0xA);
outByte(0x71, byte | 0xF);

// setup apic timer
lapic_write(LAPIC_REG_LVTT, 0x80);
lapic_write(LAPIC_REG_TDCR, 0xA);
lapic_write(LAPIC_REG_ICRT, 0xFFFFFFFF);

// enable interrupt
outByte(0x70, 0xB);
byte = inByte(0x71);
outByte(0x70, 0xB);
outByte(0x71, byte | 0x40);

outByte(0x70, 0xC);
inByte(0x71);

while(g_lapic_divisor == 0) {
asm volatile ("nop");
}

// disable apic timer
lapic_write(LAPIC_REG_ICRT, 0);
lapic_write(LAPIC_REG_TDCR, 0xB);

// disable real time clock interrupt
outByte(0x70, 0xB);
outByte(0x71, byte & 0xBF);

debug_info("bus speed: %u hz\n", g_lapic_divisor);
// meine debug_info funktion kann keine long longs verarbeiten
debug_info("cpu speed: %u hz\n", (unsigned long)g_cpu_speed);
}

unsigned long long rdtscll() {
unsigned long long result;
asm volatile ("rdtsc" : "=A" (result));
return result;
}

void lapic_calibrate_irq() {
// read status register c
outByte(0x70, 0xC);
inByte(0x71);

unsigned long bus = lapic_read(LAPIC_REG_CCRT);
unsigned long long cpu = rdtscll();

if(g_lapic_tempcount == 0) {
g_lapic_tempcount = bus;
}else if(g_lapic_divisor == 0){
g_lapic_divisor = (g_lapic_tempcount - bus) * (128 / 2);
}

if(g_cpu_speed_temp == 0) {
g_cpu_speed_temp = cpu;
}else if(g_cpu_speed == 0) {
// wir haben nur eine halbe sekunde gewartet, daher * 2
// wenn du nicht weißt, wie du long longs multiplizieren und subtrahieren kannst in assembler, kann ich dir dafür einen codeschnippsel geben.
g_cpu_speed = (cpu - g_cpu_speed_temp) * 2;
}
}

// usecs microsekunden warten (1000 microsekunden = 1 millisekunde)
void lapic_delay(unsigned long usecs) {
unsigned long ticks = g_lapic_divisor / 1000000;

lapic_write(LAPIC_REG_ICRT, ticks * usecs);

while(1) {
if(lapic_read(LAPIC_REG_CCRT) == 0)
break;

asm volatile ("nop");
}
}

void lapic_write(unsigned short reg, unsigned long value) {
*(unsigned long*)((unsigned long)g_mp_lapic + reg) = value;
}

unsigned long lapic_read(unsigned short reg) {
return *(unsigned long*)((unsigned long)g_mp_lapic + reg);
}


Nach einem Aufruf von lapic_calibrate() steht in g_lapic_divisor der Bus Speed in HZ und in g_cpu_speed die Taskfrequenz in HZ.
Mit RDTSC kannst du dann solange warten, bis eine bestimmte Anzahl an Taken vergangen ist. Du musst warscheinlich viele Funktion an dein OS anpassen.^^
Der APIC hat den Vorteil, dass er auch Interrupts generieren kann. Den APIC kann man aber nur im PM benutzen, da er sich an einer Addresse befindet, die man im RM nicht ansprechen kann. Wenn du ihn nicht verwenden willst, kannst du einfach alle LAPIC spezifischen Codeteile löschen, er wird nicht zur Kalibrierung benötigt.

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #3 am: 17. January 2006, 21:44 »
Danke für die Antwort, aber ich verstehe den Code nicht so ganz. Wie gesagt ist C/C++ nicht so mein Gebiet. Also ich schreibe mal wie ich es bis jetzt verstanden habe: Ich gebe z.B. in ax die Millisekunden an. Dann rufe ich mit call meine Funktion auf. Diese sperrt alle IRQs außer den IRQ8. Dann schreibe ich im CMOS in das Statusregister A die Frequenz (verstehe ich nicht ganz). Wie es weiter geht weiß ich nicht. Wie muss denn der Code des IRQ8 lauten? Und wie muss die Frequenz lauten? Also so ganz verstehe ich das nicht. Bitte um Hilfe.

Danke!!!

bitmaster
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 17. January 2006, 22:19 »
Du arbeitest doch im RM, oder?
Dann kansnt du den APIC nicht verweden. Der TSC ist von der Vorgehensweise aber ähnlich: Mit dem Befehl rdtsc kannst du lesen, wie viele Takte schon seit dem einschalten vergangen sind. Da du aber nicht weißt, wie viele Takte/Sekunde der Prozessor hat, muss du das erst herauskriegen.

Das geht so:
- Du programmierst die Realtime Clock (oder nen anderen Timer) so, das sie zweimal (oder wie oft auch immer) pro Sekunde einen Interrupt auslöst.
- Dann wartest du bis der Interrupt auftritt.
- Im IRQ8 Handler führst du rdtsc aus. Jetzt hast du in EDX:EAX einen 64 Bit Wert, die Anzahl der Takte. Die speicherst du und wartest wieder auf einen Interrupt
- Nachdem der IRQ das zweite mal ausgeführt wurde, machst du das ganze nochmal, subtrahierst die Werte voneinander und multiplizierst sie mit 2 (da der interrupt ja nicht jede sekunde, sondern jede 0,5te Sekunde ausgeführt wurde.)
- Jetzt hast du die Anzahl an Takten pro Sekunde
- Die Interrupts kannst du wieder deaktivieren

Wenn du jetzt einen delay haben willst, gehst du so vor:
- Du multiplizierst die Anzahl der Millisekunden (oder Microsekunden, oder was auch immer), die du warten willst, durch (TAKTE_PRO_SEKUNDE / 1000) oder halt 1000000 bei Microsekunden.
- Jetzt hast du die Anzahl der Takte die du warten willst
- Jetzt führst du rdtsc aus, und addirest zu dem Wert die Anzahl der Takte. Jetzt weißt du in wie vielen Takten du fertig gewartet hast.
- Jetzt machst du solange garnichts, und ließt mit rdtsc ununterbrochen den TSC, und überprüfst ob du schon fertig bist mit warten. Wenn ja springst du einfach zum Caller zurück.

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #5 am: 17. January 2006, 23:03 »
Hi,

danke. Ich arbeite im RM und PM ;) . Mein OS/M ist im RM und meine GUI dafür schaltet in den PM. Ich möchte diese Sache für meiner GUI also im PM schreiben. Aber wenn man den (wie hieß der noch) nicht unbedingt brauch, dann lass ich es lieber. Aber was ist rdtsc? Ist das ein Unterprogramm? Wie lautet der Code?

Danke!!!

bitmaster

EDIT: Oops, rdtsc ist ein CPU Behl. Diesen Befehl gibt es aber erst seit dem Pentium (steht in meinem ASM Buch). Mein OS soll zwar auch auf früheren CPUs laufen, aber machen wir es erst mal so. Was soll ich machen wenn ich den Wert in edx:eax habe? *nichtkapiere*
In the Future everyone will need OS-64!!!

n3Ro

  • Beiträge: 288
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 17. January 2006, 23:11 »
Ne, RDTSC ist ein Maschinenbefehl, welcher seit dem Pentium existiert. Der liest einfach den internen Taktzyklenzähler nach edx:eax, ist also ein 64bit Wert.
Agieren statt Konsumieren!

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #7 am: 17. January 2006, 23:14 »
@SSJ7Gohan: Ah, ich glaube ich habe verstanden was du meinst. Jetzt habe ich aber das problem mit dem TSC bzw. RTC. Wie sorge ich dafür das der IRQ8 2 mal pro Sekunde aufgerufen wird?

Danke!!!
In the Future everyone will need OS-64!!!

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #8 am: 17. January 2006, 23:14 »
Zitat von: n3Ro
Ne, RDTSC ist ein Maschinenbefehl, welcher seit dem Pentium existiert. Der liest einfach den internen Taktzyklenzähler nach edx:eax, ist also ein 64bit Wert.
jo ich weiß, siehe edit ;)
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 18. January 2006, 11:55 »
// set real time clock frequency
outByte(0x70, 0xA);
byte = inByte(0x71) & 0xF0;
outByte(0x70, 0xA);
outByte(0x71, byte | 0xF);


Das sorgt dafür, das die Realtime Clock jede Sekunde 2x einen Interrupt generiert.

// read status register c
outByte(0x70, 0xC);
inByte(0x71);

Das musst du nach jedem Interrupt aufrufen, um der RTC zu zeigen, der der Interrupt verarbeitet wurde.

outByte(0x70, 0xB);
outByte(0x71, byte & 0xBF);

Deaktiviert die RTC wieder.

Wenn der Code auch auf älteren Prozessoren laufen soll, kannst du Delays nur mit dem PIT oder der RTC machen. Die RTC kann auch so programmiert werden, das sie 1024 Interrupts pro Sekunde generiert, so kannst du auch ungenaue Delays verwirklichen.

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #10 am: 18. January 2006, 14:38 »
Also ich habe folgende Idee die ich machen wollte:

Ich übergebe in einem Register z.B. dx den Wert in Millisekunden. Dann rufe ich mit call die Funktion auf die, die Millisekunden warten soll. Ich setzte ein Register z.B. bx auf Null und sorge dafür das der IRQ8 jede Millisekunde aufgerufen wird. In dem IRQ8 inkrementiere ich bx um eins. In der Funktion prüfe ich dann ob bx = dx ansonsten IRQ8 noch mal. Wenn bx dann nun entlich = dx, springe ich mit ret aus der Funktion bzw. Warteschleife.

Würde das so funktionieren? Und wenn ja, wie bringe ich der RTC dazu den IRQ8 jede Millisekunde aufzurufen?

Danke!!!
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 18. January 2006, 15:13 »
Jede Millisekunde geht nicht, aber du kannst 1024x pro Sekunde Interrupts auslösen, also annähernd 1ms. Soweit ich weiß, ist 1024 HZ sogar die Normaleinstellung. Hier gibts Beschreibungen der RTC, wo alles genau drinnen steht: http://www.nondot.org/sabre/os/articles/MiscellaneousDevices/

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #12 am: 18. January 2006, 19:16 »
Hhmm... Aber wo genau muss ich denn diesen Code ausführen?// read status register c
outByte(0x70, 0xC);
inByte(0x71);
Direkt vor dem EOI im IRQ8 Code?

Danke!!!
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 18. January 2006, 19:19 »
Irgentwann wärend des Interrupthandlers, wann ist egal, du wirst nur keine neuen Interrupts mehr kriegen, bis du ihn gesendet hast.

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #14 am: 18. January 2006, 20:16 »
Zitat von: SSJ7Gohan
Irgentwann wärend des Interrupthandlers, wann ist egal, du wirst nur keine neuen Interrupts mehr kriegen, bis du ihn gesendet hast.
OK, ich habe ihn vor dem EOI gesetzt. Ich lasse aus spass mal mit dem Code des IRQ8 Pixel auf dem Bildschirm schreiben. Ich habe ihn auf 2 Hz sprich 500ms gesetzt. Aber das komische ist, dass er bei Bochs sofort ausgeführt wird unter VMware aber ewig brauch. Warum?

Danke!!!
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 18. January 2006, 20:23 »
Bei Bochs sind die Timer nicht genau.
Bei VMware sollten sie etwa die richtige Geschwindigkeit haben. Bei Qemu funktionieren sie auch richtig.

bitmaster

  • Troll
  • Beiträge: 1 138
    • Profil anzeigen
    • OS-64 = 64 Bit Operating System
Gespeichert
« Antwort #16 am: 18. January 2006, 21:59 »
Zitat von: SSJ7Gohan
Bei Bochs sind die Timer nicht genau.
Bei VMware sollten sie etwa die richtige Geschwindigkeit haben. Bei Qemu funktionieren sie auch richtig.
Ja, ich habs jetzt geschaft das es auf PCs und VMware richtig läuft. Aber unter Bochs wird der IRQ8 viel zu oft aufgerufen. Was muss ich machen damit es auch dort einigermaßen richtig ist?

Danke!!!
In the Future everyone will need OS-64!!!

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 18. January 2006, 22:03 »
Keine Ahnung, auf Bochs funktionieren die Timer (PIT, RTC, APIC) eh nicht richtig.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 19. January 2006, 15:59 »
sie funktionieren richtig, wenn man sie richtig einstellt.

mich wundert allerdings, dass du, bitmaster, die frage stellst: http://www.softgames.de/forum/frage117003.html
Dieser Text wird unter jedem Beitrag angezeigt.

SSJ7Gohan

  • Beiträge: 398
    • Profil anzeigen
Gespeichert
« Antwort #19 am: 19. January 2006, 16:32 »
Bei meiner Bochs Version funktioniert die RTC auch nicht richtig, wenn ich die Option angebe.

 

Einloggen