VŠB - TU Ostrava, Fakulta elektrotechniky a informatiky

Semestrální práce do předmětu

OPERAČNÍ SYSTÉMY - TUOX

Petr Zajac, David Horák


Zadání:

Studie o začlenění matematického koprocesoru (možno se omezit na 8087) do systému a rozšíření jádra o přepínání kontextu koprocesoru.


Úvod:

Numerický koprocesor INTEL 80387DX:

Je koprocesor vyvinutý pro mikroprocesor INTEL 80386DX pro zvýšení výkonnosti systému při zpracování numerických výpočtů v pohyblivé řádové čárce. Koprocesor nemá vlastní program, ale instrukce jsou mu předávány prostřednictvím parametrů instrukce ESC mikroprocesoru INTEL 80386DX. Formáty dat využívané koprocesorem odpovídají normě IEEE 754. Vzájemné vztahy mikroprocesoru a koprocesoru jsou tak těsné, že se na tuto dvojici obvodů hledí jako na jeden numericky orientovaný mikroprocesor - takto lze tedy koprocesor chápat jako rozšíření mikroprocesoru o další instrukce a s nimi související typy dat a registry.

Stahnuti zdrojaku:

tuox.zip

Architektura koprocesoru :

Numerický koprocesor INTEL 80387DX obsahuje dva zřetězené subprocesory - řídící jednotku (CU - Control Unit, příjímá a dekóduje instrukce, čte a zapisuje operandy z/do poměti) a numerickou operační jednotku (NEU - Numeric Execution Unit, prostřednictvím aritmetické jednotky provádí výpočty).

Typy dat:

Koprocesor pracuje se 7 formáty dat : krátký (short) real, dlouhý (long) real, pomocný (temporary) real - rozšířená přesnost - koprocesorem výhradně používaný, dekadický BCD (dekadická čísla ve spakovaném tvaru), dlouhý integer, krátký integer a slovo integer. Kromě toho umí koprocesor zobrazovat i speciální hodnoty, které neodpovídají obvyklým reálným číslům : nekonečno (infinity), nenormalizované číslo (denormal number) a nečíselná informace (NAN - Not A Number - neplatná reprezentace čísla s plovoucí řádovou čárkou - je signalizovaná samými jedničkami v poli charakteristiky a v poli mantisy hodnotou jinou, než hodnotou reprezentující nekonečno. Existují 2 varianty - signalizující NAN (koprocesor při užití této hodnoty automaticky generuje chybový stav signálem ERROR) a klidový NAN (je generován chybnou operací)).

Soubor registrů :

Koprocesor obsahuje osm 80-tibitových datových registrů a několik registrů řídících. Na rozdíl od univerzálních registrů jsou datové registry koprocesoru adresovány jako zásobník. Aktuální vrchol zásobníku je indikována polem TOP stavového registru a adresován jako ST(0). Následující registr je pak adresován jako ST(1) atd.

Stavový registr (SWR - Status Word Register) je 16-ti bitový, zachycuje stav koprocesoru :

Řídící registr (CWR - Command Word Register) je 16-ti bitový, ale význam má pouze 8 bitů. Nastavením hodnot jednotlivých bitů jsou nastavovány některé parametry modifikující činnost koprocesoru při výpočtu. Význam jednotlivých bitů je následující :

Registr značek (TAG - Command Word Register) je třetím 16-ti bitovým registrem, který obsahuje osmici dvoubitových značek, které charakterizují obsah příslušného datového registru koprocesoru.

Registry ukazatele chyby (Error pointers) jsou další skupinou registrů, jejichž obsah je nastavován při zahájení zpracování každé instrukce koprocesoru. V případě, že provádění instrukce vyvolá přerušení, je z obsahu těchto registrů možno určit, která instrukce tento stav způsobila. Není sice k dispozici žádná instrukce přistupující přímo k obsahu těchto registrů, avšak instrukce instrukce FSTENV zkopíruje obsah všech registrů numerického koprocesoru do operační paměti, kde je jejich obsah zpřístupněn mikroprocesoru. Registry jsou přitom uvažovány jako čtveřice 32 bitových slov. První z těchto registrů - 32 bitový ukazatel instrukce FIP (Floating - point Instruction Pointer) je při zahájení zpracování instrukce naplněn obsahem registru EIP mikroprocesoru. S ním souvisí 16 bitový registr segmentu programu FCS (Floating - point Code Segment), který je naplněn odpovídajícím obsahem segmentového registru CS. Dalším z registrů ukazatele chyby je registr operačního kódu instrukce FOP (Floating - point OPcode), který je 16 bitový. Jeho 5 nejvyšších bitů je nulových, zbývajících 11 bitů specifikuje typ instrukce ESC, přesněji 3 nejnižší bity jejího prvního byte a druhý byte této instrukce. Poslední dvojice registrů koprocesoru určuje, v případě instrukce pracující s operandem v paměti, adresu tohoto operandu. 32 bitový registr ofsetu operandu FOO (Floating - point Operand Offset) přitom obsahuje ofset operandu a 16 bitový registr segmentu operandu FOS (Floating - point Operand Segment) pak obsahuje obsah segmentového registru použitého při určení adresy operandu instrukce v paměti.

Soubor instrukcí :

Instrukce numerického koprocesoru lze rozdělit do 4 skupin - instrukce pro přesun dat, aritmetické instrukce, instrukce pro výpočet funkcí a generování konstant a řídící instrukce.


Co dělá Borland C s koprocesorem

Inicializace:

Vlastní inicializace je popsána v souboru : crtl/EMU/fpinit.asm
Je pro nás důležitá rutina:

int2handler proc far

Což je vlastně obsluha přerušení NMI (02h), které ošetřuje chybu koprocesoru. Nastavení vektoru přerušení se také provádí v tomto souboru. O emulaci koprocesoru se nemusíme starat, protože předpokládáme alespoň 80387. Použití toho souboru by bylo možné, kdyby se změnily služby nastavení přerušení z dosovské varianty na tuoxovskou variantu.

Tohleto nás samozřejmě nezajímá, protože si volání přerušení koprocesoru zakážeme.

 

Ošetření chyby uživatelem

Pokud chce uživatel ošetřit chybu koprocesoru, tak musí provést implementaci této funkce:

int matherr (struct exception *a)

V případě, že se nám podaří ošetřit chybu, tak vrátíme 1, jinak vrátíme 0. Tohle to se volá jak u přerušení, tak i u matematických funkcí(cos,tan,sqrt). Pro naše potřeby nám zcela postačuje, když bude metoda vypadat takto:

int matherr (struct exception *a) {

return 1;

}

V případě, že neprovedeme zaimplementování, tak se nám na stderr vypisuje chybová hláška, navrat=0.

Poznámky :

Správná funkčnost koprocesoru by se měla ještě testnout v ostré distribuci tuoxu, protože je možné, že Borlandi tam provádějí ještě jiné zběsilosti.


Postup:

  1. V souboru KERNEL/PROC_PUB.H jsme do struktury proc dodeklarovali pole pro uložení stavu koprocesoru p_FPU_registers[108] :

    /****************************************************************************/
    /* TUOX kernel: modul PROC_PUB.H */
    /****************************************************************************/
    /* Verejna cast vyclenena specialne pro ostatni ze souboru proc.h. */
    /* Tento hlavickovy soubor je includovan z hlavickoveho souboru proc.h. */
    /****************************************************************************/

    . . . . . . . . . . . . . . . . . . . . . .

    EXTERN struct proc {
    /*** Polozky se nesmi prehazet (k_save(), k_restore() vyuziva poradi **/
    reg_t p_reg[NR_REGS]; /* process' registers saved here */
    int p_sp; /* stack pointer */

    struct pc_psw p_pcpsw; /* pc and psw as pushed on interrupt */
    int p_flags; /* P_SLOT_FREE, SENDING, RECEIVING, etc. */
    struct mem_map p_map[NR_SEGS]; /* memory map */
    int* p_splimit; /* lowest legal stack value */
    pid_t p_pid; /* process id passed in from MM */

    real_time user_time; /* user time in ticks */
    real_time sys_time; /* sys time in ticks */
    real_time child_utime; /* cumulative user time of children */
    real_time child_stime; /* cumulative sys time of children */
    real_time p_alarm; /* time of next alarm in ticks, or 0 */

    struct proc *p_callerq; /* head of list of procs wishing to send */
    struct proc *p_sendlink; /* link to next proc wishing to send */
    message *p_messbuf; /* pointer to message buffer */
    int p_getfrom; /* from whom does process want to receive? */

    struct proc *p_nextready; /* pointer to next ready process */
    sigset_t p_pending; /* bit map for pending signals */

    /********** 24.4.1999 Petr Zajac, David Horak - Coprocesor************/
    char p_FPU_registers[108]; /* kompletni stav registru koprocesoru 80387*/ /*********************** end koprocesor ************************/
    };
    . . . . . . . . . . . . . . . . . . .


  2. V souboru KERNEL/MPX88.C jsme modifikovali jsme funkci k_save( ), realizující uschování registrů, a funkci k_restart( ), realizující obnovení registrů :

    :/****************************************************************************/
    /* TUOX kernel: modul MPXB88.C */
    /****************************************************************************/
    /*****************************************************************************
    Soubor s assemblerovymi rutinami nejnizsi urovne pro kernel
    *****************************************************************************/

    // Obsazene rutiny:
    // s_call ()
    // -SEND,RECEIVE,TRAP,DIE_TRAP
    // - trap od send/receive message (hlavni a jediny vstupni bod SW do jadra)
    // vola k_save, nachysta parametry a zavola sys_call a k_restart
    // tty_int () ... interrupt
    // - vstupni bod preruseni od klavesnice
    // vola k_save, keyboard(), k_restart
    // hdd_int () ... interrupt
    // - vstupni bod preruseni od disku
    // vola k_save, nachysta message, vola k_interrupt, k_restart
    // floppy_int () ... interrupt
    // - vstupni bod preruseni od FDD
    // vola k_save, nachysta message, vola k_interrupt, k_restart
    // clock_int () ... interrupt
    // - vstupni bod preruseni od hodin
    // vola k_save, nachysta message, vola k_interrupt, k_restart
    // k_save ()
    // - uschova registry do proc[cur_proc]
    // k_restart ()
    // - obnovi registry z proc[cur_proc] a obnovi beh procesu/tasku cur_proc.
    // Je-li cur_proc==IDLE : skoc na idle (WAIT, skok zpet na WAIT)

    #define AXR_O(o) [o+2*AX_REG] //jsou to prvni polozky v proc [], takze od nuly
    #define BXR_O(o) [o+2*BX_REG] //jeden registr za druhym
    #define CXR_O(o) [o+2*CX_REG]
    #define DXR_O(o) [o+2*DX_REG]
    #define SIR_O(o) [o+2*SI_REG]
    #define DIR_O(o) [o+2*DI_REG]
    #define BPR_O(o) [o+2*BP_REG]
    #define DSR_O(o) [o+2*DS_REG]
    #define ESR_O(o) [o+2*ES_REG]
    #define SSR_O(o) [o+2*SS_REG]

    #include "kernel\makra.h"
    #include "kernel\proc.h"
    #include "COMMON/com.h"
    #include "COMMON/glo.h"

    #include "test\kprintf.h"
    #include "test.cnf"
    . . . . . . . . . . . . . . . . . . . . .

    //=============================================================================
    // k_save ()
    // - uschova registry do proc[cur_proc]
    // - nastavi DS=DS jadra
    // Zachovava AX, BX a DX (kvuli makrum SEND/RECEIVE/TRAP)
    //=============================================================================

    void k_save ()
    {
    // prekladac prida: push bp, mov bp,sp, push si, push di

    asm push ds
    restoreDS();
    asm push ax
    asm push si
    asm mov si, word ptr proc_ptr //adresa struktury
    asm add sp,10+4

    /* 4 za push si,di */
    asm pop ax //prectu neprepsane bp
    asm mov BPR_O(si),ax

    asm mov BXR_O(si),bx
    asm mov CXR_O(si),cx
    asm mov DXR_O(si),dx
    asm mov DIR_O(si),di
    asm mov ax, es //prepisu ax kde byl BP
    asm mov ESR_O(si),ax
    asm mov ax, ss
    asm mov SSR_O(si),ax

    asm mov ax,sp //v teto chvili ukazuje sp na IP ?!,viz 3
    asm mov [si+NR_REGS*2],ax
    asm sub sp, 12+4
    asm pop ax //si-puvodni
    asm mov SIR_O(si),ax
    asm pop ax //ax-puvodni
    asm mov AXR_O(si),ax

    asm pop ax // ds-puvodni
    asm mov DSR_O(si),ax

    // flags,cs a ip p_pcpsw
    asm mov bp,sp
    asm mov ax,[bp+6+4] // ip ze stacku
    asm mov [si+NR_REGS*2+2],ax
    asm mov ax,[bp+8+4] // cs ze stacku
    asm mov [si+NR_REGS*2+4],ax
    asm mov ax,[bp+10+4] // flags ze stacku
    asm mov [si+NR_REGS*2+6],ax

    /****** 24.4.1999 Petr Zajac, David Horak - Coprocesor******/
    asm push cx /* asi zbytecne ,ale kdovi */
    _BX=(int) proc_ptr->p_FPU_registers;
    asm frstor[bx]
    asm pop cx
    /******************* end koprocesor ******************/

    asm mov ax,AXR_O(si)
    asm mov bx,BXR_O(si)
    asm mov dx,DXR_O(si)
    }

    //=============================================================================
    // k_restart ()
    // - obnovi registry z proc[cur_proc] a obnovi beh procesu/tasku cur_proc.
    //
    // Pri vstupu se predpoklada DS=DS jadra
    //=============================================================================

    reg_t new_DS,new_AX,new_BX;

    void k_restart ()
    // prekladac prida: push bp, mov bp,sp, push si, push di

    {
    /****** 24.4.1999 Petr Zajac, David Horak - Coprocesor******/
    _BX=(int) proc_ptr->p_FPU_registers;
    asm frstor[bx]
    /******************* end koprocesor ******************/

    asm add sp,4 ; /* zahodime si a di */
    asm mov si, word ptr proc_ptr
    asm mov bx, BXR_O(si)
    asm mov cx, CXR_O(si)
    asm mov dx, DXR_O(si)
    asm mov di, DIR_O(si)
    asm mov bp, BPR_O(si)
    asm mov ax, ESR_O(si)
    asm mov es,ax
    asm mov ax, AXR_O(si)
    asm push ax
    asm mov ax, SIR_O(si)
    asm push ax
    asm mov ax, DSR_O(si)
    asm mov new_DS,ax
    asm mov ax, SSR_O(si)
    asm push ax
    asm mov ax,[si+NR_REGS*2]
    asm push ax

    asm add sp,4 /* zpatky na si */

    asm pop si
    asm pop ax
    asm sub sp,8 /* a zase na SP */

    asm mov new_AX,ax /* schovat (jeste do kernel_DS) */
    asm mov new_BX,bx
    asm pop ax /* do ax dame sp */
    asm pop bx /* do bx dame ss */
    asm mov ss,bx
    asm mov sp,ax /* nastaven novy stack */

    /* dokud jeste mame stary DS (kerneluv), vybereme schovane AX, BX */
    /* a vlozime na NOVY stack. Nakonec prepiseme DS */

    asm mov ax,new_DS
    asm push ax
    asm mov ax,new_AX
    asm mov bx,new_BX
    asm pop ds

    /* UF! */

    asm iret
    }

    . . . . . . . . . . . . . . . . .


  3. V souboru KMAIN.C bylo nutno doplnit kód o incializaci koprocesoru :

    :#include "kernel\proc.h"
    #include "kernel\intrs.h"
    #include "test.cnf"

    #include "COMMON\com.h"
    #include "COMMON\const.h"
    #include "COMMON\glo.h"
    #include "test\kprintf.h"
    #include "test\execldr.h"
    #include "test\fsldr.h"

    #if (defined(TEST_EXIT_ON_KEYEND) && (TEST_PHASE != 0))
    #include "test\exitor.h"
    #endif

    #define N_MODULES 4

    Word k_code_segment,k_data_segment;

    extern void saveDS(void);
    typedef void (*handler_t)(void);

    #define MK_FP( seg,ofs )( (void _seg * )( seg ) +( void near * )( ofs ))
    void schovejInterrupty(void) {
    int i;
    char far* s=MK_FP(0,0);
    char far* d=MK_FP(k_data_segment,orig_vectors);
    asm cli;
    for (i=0; i<256*4; i++) *d++=*s++;
    asm sti;
    }

    int matherr (struct exception *a)
    {
    return 1;
    }

    void obnovInterrupty(void) {
    int i;
    char far* d=MK_FP(0,0);
    char far* s=MK_FP(k_data_segment,orig_vectors);
    asm cli;
    for (i=0; i<256*4; i++) *d++=*s++;
    asm sti;
    }

    void mysetvect(int intNo, handler_t handler) {

    asm {
    cli
    push es
    xor ax,ax
    mov es,ax
    mov bx,[intNo]
    shl bx,1
    shl bx,1
    mov ax,[handler]
    mov es:[bx],ax
    push cs
    pop ax
    add bx,2
    mov es:[bx],ax
    pop es
    sti
    }
    }
    . . . . . . . . . . . . . . . . . . . . .

    void addToProc(int procPos, Word csegment, void (*mainOffset)(void), int* paramsForMain, int numParams,
    int queueToAdd) {
    /*
    Pridani tasku nebo serveru do proc[ ] a do zadane fronty planovace (na konec).
    Je-li queueToAdd==-1, neprida se do zadne fronty
    */
    . . . . . . . . . . . . . . . . . . . . .

    /****** 24.4.1999 Petr Zajac, David Horak - Coprocesor******/
    /* (begin) inicializace koprocesoru*/
    _BX=(int) proc[procPos].p_FPU_registers;
    asm fsave[bx]
    CWR=*((unsigned int*) proc[procPos].p_FPU_registers);
    CWR |=/*0xFFE0*/ 1+2+4+8+16+32;
    *((unsigned int*) proc[procPos].p_FPU_registers)=CWR;
    _BX=(int) proc[procPos].p_FPU_registers;
    asm frstor[bx]
    _BX=(int) proc[procPos].p_FPU_registers;
    asm fsave[bx]
    /* (end) inicializace koprocesou*/
    /**************************************************/
    . . . . . . . . . . . . . . . . . . . . .


  4. Soubor TEST/TEST4.c jsme rozšířili o testovací kódy pro koprocesor :

    // TEST_PHASE 4
    /* Totez jako TEST_PHASE 3, ale doplneny 3 procesy, ktere bezi v dobe,
    kdy jsou vsechny tasky idle (cekaji na alarm). Tim se overi
    (1) - planovani procesu v dobe, kdy zadny task neni RUNNABLE
    (2) - preemptivni multitasking procesu
    */
    // Written by P.G.

    // MUSI BYT NASMEROVANO INT 08

    #include "kernel\makra.h"
    #include "COMMON\type.h"
    #include "COMMON\const.h"
    #include "COMMON\com.h"
    #include "clock\h_clock.h"
    #include <math.h>
    #include <stdlib.h>
    /* v inicialni fronte planovace v tomto poradi */
    #define TASK1 2
    #define TASK2 3
    #define TASK3 4
    . . . . . . . . . . . . . . . . . . . . .

    /* Tyhle procesy nejsou zase az tak regulernimi procesy: nemaji poradne
    pridelenu mapu pameti apod. Nas ale vlastnost "byt procesem" zajima jen
    s ohledem na planovac a protoze nevolame zadne sluzby z FS ani MM,
    tak to (snad) nevadi. */

    void proc1(void) {
    char far* pos=(char*) MK_FP(0xb800,10*80*2);
    int i;
    double a=5;
    double b;
    int iii;
    unsigned long p;
    a++;
    asm fld [a];
    a=-5;
    b=sqrt(a);
    b=log(a);
    a=0.0;
    b=log(a);


    do {
    for (i=0; i<80; i++) {
    //asm fxch ST(3);
    asm fst[b];
    iii=b ;
    *(pos+(i*2))= iii+'0';

    for (p=0; p<300000L; p++); // jen pro zpomaleni
    *(pos+(i*2))=' ';
    }
    } while(1);
    }

    void proc2(void) {
    char far* pos=(char*) MK_FP(0xb800,12*80*2);
    int i;
    unsigned long p;
    double a=7;
    double b;
    int iii;
    asm fld [a];

    do {
    for (i=0; i<80; i++) {
    asm fst[b];
    iii=b;
    *(pos+(i*2))=iii+'0';

    for (p=0; p<300000; p++); // jen pro zpomaleni
    *(pos+(i*2))=' ';
    }
    } while(1);
    }

    . . . . . . . . . . . . . . . . . . . . .


  5. Upravili jsme testovací fázi u TEST.CNF