25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

51KB

Developing Your Own 32-Bit Operating System — Chapter-19: Memory Management Code

  • Source: /library/Developing Your Own 32-Bit Operating System/source-file.pdf
  • PDF pages: 399–426
  • Pages without text: 399

H Part PV ■ The Operating System Source Code The memory management code is contained in the file MemCode.ASM. It begins with data definitions that are used by all the private (local) and public functions. Memory Management Data Three important structures you modify and deal with are the page allocation map (PAM), page directories (PDs), and page tables (PTs). The variable naming conventions should help you follow the code. The task state segments are also used extensively. Listing 19. 1 is the data seg- ment portion of the memory management code. Listing 19.1. Memory management constants and data. .DATA .INCLUDE MOSEDF.INC .INCLUDE TSS.INC .INCLUDE JOB. INC PUBLIC _nPagesFree PUBLIC _oMemMax PUBLIC rgPAM PUBLIC SPAM sPAMmax DD 0 ;Number of free physical pages left MemExch pNextPT DD 000FFFFFh ; Default to 1 MB ;Page Allocation Map DB 2048 DUP (0) ;1 bit/4Kbytes - 2048 bytes = 64» DD 32 ;Deafult is 32 (1Mb) EQU 2048 ;Max is 2048 (64Mb) DD 00000000h ; Semaphore exchange for Mem Mgmt DD 00000000h ;We alloc the “next” page table ;in advance (MANDATORY) It was easier for me to define masks in assembly language. The following definitions are used to set memory pages for specific access rights: 20 Bit Address MEMALIAS EQU MEMUSERD EQU MEMUSERC EQU MEMSYS EQU ALIASBIT EQU PRSNTBIT EQU AVL00DA00UWP 100000000111b ;User Writable 111b ;User Writable (Data) 01b ;User Readable (Code) 01b ;System Read/Write 1 Internal Code The code is divided into two basic sections. The first section comprises all of the internal rou- tines used by the public functions users access. I call these internal support code. I will describe the purpose of each of these functions just before I show you the code. As with all assembler files for DASM, the code section must start with the .CODE command.

Chapter 19 B Memory Management Code s InitMemMgmt The InitMemMgmt function finds out how much memory, in megabytes, you have, by writing to the highest dword in each megabyte until a read-back test fails. It sets nPagesFree after find- ing out just how much you have. You assume 1MB to start, which means you start at the top of 2MB (IFFFFCh). It places the highest addressable offset in global oMemMax. You also calcu- late the number of pages of physical memory this is, and store that number in the global vari- able nPagesFree. See Listing 19.2. Listing 19.2. Memory management initialization code. .CODE ;IN: Nothing ;0UT: Nothing (except that you can use memory management routines now!) ;USED: ALL REGISTERS ARE USED. PUBLIC InitMemMgmt: MOV _nPagesFree, 256 MOV EAX, IFFFFCh XOR EBX, EBX MOV ECX.06D72746CH MEMLoop: MOV DWORD PTR [EAX] ,0h MOV DWORD PTR [EAX],ECX MOV EBX, DWORD PTR [EAX] CMP EBX.ECX JNE MemLoopEnd ADD EAX ,3 MOV oMemMax, EAX SUB EAX, 3 ADD EAX,100000h ADD _nPagesFree, 256 ADD SPAM, 32 CMP EAX,3FFFFFCh JAE MemLoopEnd XOR EBX, EBX JMP MemLoop MemLoopEnd: 1 Mb of pages = 256 top of 2 megs (for DWORD) ‘mrtl’ test string value for memory Set it to zero intially Move in test string Read test string into EBX See if we got it back OK NO! Yes, oMemMax must be last byte Set oMemMax Make it the last DWord again Next Meg Another megs worth of pages Increase PAM by another meg Are we above 64 megs Yes! Zero out for next meg test The page allocation map is now sized and zeroed. Now you must fill in bits used by OS, which was just loaded, and by the video RAM and boot ROM, neither of which you consider free. The code in Listing 19.3 also fills out each of the page table entries (PTEs) for the initial OS code and data. Note that linear addresses match physical addresses for the initial OS data and code. It's the law! Listing 19.3. Continuation of memory management initialization. This first part MARKS the OS code and data pages as used and makes PTEs.

Part TV ■ The Operating System Source Code Listing 19.3. continued IMM001 MOV EDX, OFFSET pTbll XOR EAX, EAX ;EDX points to OS Page Table 1 ;Point to 1st physical/linear page (0) MOV [EDX], EAX ;Make Page Table Entry ' AND DWORD PTR [EDX], 0FFFFF000h ; Leave upper 20 Bits OR DWORD PTR [EDX], 0001h ;Supervisor, Present MOV EBX, EAX CALL MarkPage ;Marks page in PAM ADD EDX, 4 ;Next table entry ADD EAX, 4096 CMP EAX, 30000h ; Reserve 192K for OS (for now) JAE SHORT IMM002 JMP SHORT IMM001 :Go for more Now you fill in PAM and PTEs for video and ROM slots. This covers A0000 through OFFFFFh, which is the upper 384K of the first megabyte. Right now you just mark everything from A0000 to FFFFF as used. The routine in Listing 19.4 could be expanded to search through the ROM pages of ISA memory, COOOOh-FFFFFh, finding the inaccessible ones and marking them as allocated in the PAM. Several chip sets on the market, such as the 82C30 C&T, enable you to set ROM areas as usable RAM, but I can't be sure everyone can do it, nor can I provide in- structions to everyone. Listing 19.4. Continuation of memory management initialization. IMM002: IMM003: MOV EAX, MOV EBX, SHR EBX, MOV EDX, ADD EDX, EAX 10 OFFSET pTbll EBX Points to 128K Video & 256K ROM area Make it index (SHR 12, SHL 2) EDX pts to Page Table MOV [EDX], EAX ;Make Page Table Entry AND DWORD PTR [EDX], 0FFFFF000h ; Leave upper 20 Bits OR DWORD PTR [EDX], 0101b ;Mark it “User” “Readonly” & MOV EBX, EAX ; Setup for MarkPage call CALL MarkPage ;Mark it used in the PAM ADD EDX, 4 ;Next PTE entry ADD EAX, 4096 ;Next page please CMP EAX, 100000h ;1Mb yet? JAE IMM004 ;Yes ‘Present’ JMP SHORT IMM003 ;No, go back for more The initial page directory and the page table are static. Now you can go into paged memory mode. This is done by loading CR3 with the physical address of the page directory, then read- ing CR0, logically ANDing it with 8000000h, and then writing it again. After the MOV CRO you must issue a JMP instruction to clear the prefetch queue of any bogus physical addresses. See Listing 19.5.

Chapter 19 ■ Memory Management Code \Tq\ Listing 19.5. Turning on paged memory management. IMM004: IM0005: MOV EAX, OFFSET PDirl ;Physical address of OS page directory MOV CR3, EAX ; Store in Control Reg 3 MOV EAX, CR0 ;Get Control Reg 0 OR EAX, 80000000h ;Set paging bit ON MOV CR0, EAX ; Store Control Reg 0 JMP IM0005 ;Clear prefetch queue Now you allocate an exchange that the OS uses as a semaphore to prevent reentrant use of any of the critical memory management functions. See Listing 19.6. Listing 19.6. Allocation of memory management exchange. LEA EAX, MemExch ;Alloc Semaphore Exch for Memory calls PUSH EAX CALL FWORD PTR _AllocExch PUSH MemExch ;Send a dummy message to pick up PUSH 0FFFFFFF1h PUSH 0FFFFFFF1h CALL FWORD PTR _SendMsg You must allocate a page table to be used when one must be added to a user PD or OS PD. This must be done in advance of finding out you need one, because you may not have a linear address to access it if the current PTs are all used up! It's a little complicated, I'm afraid. See Listing 19.7. Listing 19.7. Finishing memory management initialization. PUSH 1 ; 1 page for Next Page Table MOV EAX, OFFSET pNextPT ; PUSH EAX CALL FWORD PTR _AllocOSPage ; Get ‘em! RETN ;Done initializing memory managment FindHiPage The FindHiPage function finds the first unused physical page in memory from the top down and returns the physical address of it to the caller. It also marks the page as used, assuming that you will allocate it. Of course, this means that if you call FindHiPage and don't use the page, you must call UnMarkPage to release the page. FindHiPage reduces nPagesFree by one. See Listing 19.8.

382 Part PV ■ The Operating System Source Code Listing 19.8. Find highest physical page code. IN OUT USED FHP1 Nothing EBX is the physical address of the new page, or 0 if error EBX, Flags PUSH EAX PUSH ECX PUSH EDX MOV ECX, OFFSET rgPAM MOV EAX, SPAM DEC EAX CMP BYTE PTR [ECX+EAX] ,0FFh JNE FHP2 CMP EAX, 0 JE FHPn DEC EAX JMP SHORT FHP1 ;Page Allocation Map ;Where we are in PAM ;EAX+ECX will be offset into PAM ;A11 8 pages used? ;No ;Are we at Bottom of PAM? ;no memory left. . . ;Another Byte lower ;Back for next byte FHP3: MOV EBX, 7 XOR EDX, EDX MOV DL, BYTE PTR [ECX+EAX] BT EDX. JNC FHPf CMP EBX, 0 JE FHPn DEC EBX JMP FHP3 EBX ;Get the byte with a whole in it, ;Test bits ; FOUND ONE! (goto found) ;At the bottom of the Byte? ; Error (BAD CPU???) ;Next bit BTS EDX, EBX MOV BYTE PTR [ECX+EAX], DL SHL EAX, 3 ADD EBX, EAX SHL EBX, 12 DEC _nPagesFree POP EDX POP ECX POP EAX RETN ;Set the bit indexed by EBX ;Set page in use ;Multiply time 8 (page number base in byte) ;Add page number in byte ;Now EBX = Physical Page Addr (EBX*4096) ;One less available XOR EBX, EBX POP EDX POP ECX POP EAX RETN ;Set to zero for error FindLoPage The FindLoPage function finds the first unused physical page in memory from the bottom up and returns its physical address to the caller. It also marks the page as used, assuming that you will allocate the page. Once again, if you call FindLoPage and don't use the page, you must call UnMarkPage to release the page. FindLoPage reduces nPagesFree by one. See Listing 19.9.

Chapter 19 B Memory Management Code H Listing 19.9. Find lowest physical page code. ;IN ;OUT ;USED FLP1 FLP2: FLP3: FLPf FLPn: Nothing EBX is the physical address of the new page, or 0 if error EBX, Flags PUSH EAX PUSH ECX PUSH EDX MOV ECX, OFFSET rgPAM XOR EAX, EAX CMP BYTE PTR [ECX+EAX] ,0FFh JNE FLP2 INC EAX CMP EAX, SPAM JAE FLPn JMP SHORT FLP1 XOR EBX, EBX XOR EDX, EDX MOV DL, BYTE PTR [ECX+EAX] BT EDX JNC FLPf INC EBX CMP EBX, 8 JAE FLPn JMP FLP3 EBX ;Page Allocation Map ; Start at first byte in PAM ;A11 8 pages used? ;No ;Another Byte lower ;Are we past at TOP of PAM? ;no memory left. . . ;Back for next byte ;Get the byte with a whole in it, ;Test bits ; FOUND ONE! (goto found) ;Next bit ;End of the Byte? ;Error (BAD CPU???) BTS EDX, EBX ;Set the bit indexed by EBX MOV BYTE PTR [ECX+EAX], DL ;Set page in use SHL EAX, 3 ADD EBX, EAX SHL EBX, 12 DEC nPagesFree POP EDX POP ECX POP EAX RETN XOR EBX, EBX POP EDX POP ECX POP EAX RETN ;Multiply time 8 (page number base in byte) ;Add page number in byte ;Now EBX = Physical Page Addr (EBXM096) ;One less available ;Set to zero for error MarkPage Given a physical memory address, MarkPage finds the bit in the PAM associated with it and sets it to show the physical page in use. This function is used with the routines that initialize all memory management function. This reduces nPagesFree by one. See Listing 19.10.

384 Part TV ■ The Operating System Source Code Listing 19.10. Code to mark a physical page in use. IN OUT USED EBX is the physical address of the page to mark Nothing EBX, Flags PUSH EAX PUSH ECX PUSH EDX MOV EAX, OFFSET rgPAM AND EBX, 0FFFFF000h MOV ECX, EBX SHR ECX, 15 SHR EBX, 12 AND EBX, 07h MOV DL, [EAX+ECX] BTS EDX, EBX MOV [EAX+ECX], DL ; DEC nPagesFree POP EDX POP ECX POP EAX RETN ;Page Allocation Map ; Round down to page modulo 4096 ;ECX is now byte offset into PAM ;Get Bit offset into PAM ;EBX is now bit offset into byte of PAM ;Get the byte into DL ;BitSet nstruction with Bit Offset Save the new PAM byte ;One less available UnMarkPage Given a physical memory address, UnMarkPage finds the bit in the PAM associated with it and resets \x. to show the physical page available again. This increases nPagesFree by one. See Listing 19.11. Listing 19.1 1 . Code to free a physical page for reuse. IN : EBX is the physical a OUT : Nothing USED: EBX Flags PUSH EAX PUSH ECX PUSH EDX MOV EAX, OFFSET rgPAM AND EBX, 0FFFFF000h MOV ECX, EBX SHR ECX, 15 SHR EBX, 12 AND EBX, 07h ADD EAX, ECX MOV DL, [EAX] BTR EDX, EBX MOV [EAX], DL INC nPagesFree POP EDX POP ECX POP EAX RETN ;Page Allocation Map ; Round down to page modulo ECX is now byte offset into PAM EBX is now bit offset into byte of PAM ;BitReset instruction ;One more available

Chapter 19 I Memory Management Code H LinToPhy The LinToPhy function looks up the physical address of a 32-bit linear address passed in. The JCB is used to identify whose page tables you are translating. The linear address is used to look up the page table entry that is used to get the physical address. This call is used for things like aliasing for messages, DMA operations, and so on. This also leaves the linear address of the PTE itself in ESI for callers that need it. This function supports the similarly named public routine. See Listing 19.12. Listing 19.12. Code to convert linear to physical addresses. OUTPUT: USED: EAX — Job Number that owns memory we are aliasing EBX — Linear address EAX — Physical Address ESI — Linear Address of PTE for this linear address EAX, EBX, ESI, EFlags Save Linear Leaves pJCB in EAX EAX now has ptr to PD! Move to shadow addresses in PD Shift out lower 22 bits leaving 10 bit offset *4 to make it a byte offset into PD shadow EBX/EAX now points to shadow EAX now has Linear of Page Table Get original linear back in EBX Save it again Get rid of upper 10 bits get rid of lower 12 to make it an index *4 makes it byte offset in PT EBX now points to Page Table entry! Save this address for caller Physical base of page is in EAX mask off lower 12 Get original linear Cut off upper 22 bits of linear EAX now has REAL PHYSICAL ADDRESS! PUBLIC LinToPhy: PUSH EBX CALL GetpJCB MOV EAX, [EAX+JcbPD] ADD EAX, 2048 SHR EBX, 22 SHL EBX, 2 ADD EBX, EAX MOV EAX, [EBX] POP EBX PUSH EBX AND EBX, 003FFFFFh SHR EBX, 12 SHL EBX, 2 ADD EBX, EAX MOV ESI, EBX MOV EAX, [EBX] AND EAX, 0FFFFF000h POP EBX AND EBX, 00000FFFh OR EAX, EBX RETN FindRun The FindRun function locates a contiguous run of free linear memory in one of the user or operating-system page tables. This is at either address base 0 for the operating system or the 1GB address mark for the user. The EAX register will be set to 0 if you are looking for OS memory; the caller sets it to 256 if you are looking for user memory. The linear address of the run is returned in EAX, unless no run that large exists, in which case you return 0. The linear

Part IV The Operating System Source Code run may span page tables if additional tables already exist. This is an interesting routine be- cause it uses two nested loops to walk through the page directory and page tables while using the SIB (scale index base) addressing of the Intel processor for indexing. See Listing 19.13. Listing 19.13. Code to find a free run of linear memory. IN : EAX PD Shadow Base Offset for memory (0 for OS, 256 for user) EBX Number of Pages for run OUT: EAX Linear address or 0 if no run is large enough EBX still has count of pages USED: EAX, EBX, EFlags (all other registers saved & restored) FindRun: PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI ;Holds count of pages (saved for caller) ; Keeps count of how many free found so far ; Index into PD for PT we are working on ;Address of PD saved here MOV ECX, EBX MOV EDX, EAX ;Copy number of pages to ECX. Save in EBX ; Index into shadow addresses from EAX CALL GetpCrntJCB MOV ESI, [EAX+JcbPD] ADD ESI, 2048 ; Leaves pCrntJCB in EAX ;ESI now has ptr to PD ;Move to shadow addresses FR0: FR1 FR2: FR3: MOV EDI, [ESI+EDX4] OR EDI, EDI JNZ FR1 XOR EAX, EAX JMP SHORT FREXIT XOR EAX, EAX CMP EAX, 1024 JB FR3 INC EDX JMP SHORT FR0 CMP DWORD PTR JNE FR4 DEC ECX JZ FROK INC EAX JMP SHORT FR2 ; Linear address of next page table into EDI ;Is the address NON-ZERO (valid)? ;Yes, go for it ; Return 0 cause we didn't find it! ;EAX indexes into PT (to compare PTEs) Are we past last PTE of this PT? No, keep testing Next PT! [EDI+EAX4], 0 ;Zero means it's empty (available) ;In use One more empty one! We found enough entries goto OK Not done yet, Next PTE Please. FROK: ;If we got here we must reset ECX for full count and ;go back and start looking again INC EAX ;Not empty, next PTE please MOV ECX, EBX ;We kept original count in EBX JMP FR2 ;If we got here it means that ECX has made it to zero and ;we have a linear run large enough to satisy the request.

Chapter 19 ■ Memory Management Code The starting linear address is equal to number of the last PTE we found minus ((npages -1) * 4096) EDX was index into PD, while EAX was index into PT. EBX still has count of pages. ;EDX is 10 MSBs of Linear Address ;EAX is next 10 bits of LA ;This is the linear address we ended at ;One less page (0 offset) ; Times size of page (* 4096) ;From current linear address in tables SHL EDX, 22 SHL EAX, 12 OR EAX, EDX DEC EBX SHL EBX, 12 SUB EAX, EBX FREXIT: POP EDI POP ESI POP EDX POP ECX POP EBX RETN AddRun The AddRun function adds one or more page table entries (PTEs) to a page table, or tables, if the run spans two or more tables. The address determines the protection level of the PTEs you add. If the address is less than 1GB, it means operating-system memory space that you will set to system. An address greater than 1 GB is set to user level protection. The linear address of the run should be in EAX, and the count of pages should be in EBX. This is the way FindRun left them. Many functions that pass data in registers are designed to complement each other's reg- ister usage for speed. See Listing 19.14. Listing 19.14. Adding a run of linear memory. IN : EAX Linear address of first page EBX Number of Pages to add OUT: Nothing USED: EAX, EFlags PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI MOV ECX, EBX MOV EDX, EAX SHR EDX, 22 SHL EDX, 2 PUSH EAX (save for caller) Copy number of pages to ECX (EBX free to use) LinAdd to EDX Get index into PD for first PT Make it index to DWORDS Save EAX thru GetpCrntJCB call

388 | Part PV ■ The Operating System Source Code Listing 19.14. continued AR0: CALL GetpCrntJCB MOV ESI, [EAX+JcbPD] POP EAX ADD ESI, 2048 ADD ESI, EDX MOV EDX, EAX AND EDX, 003FF000h SHR EDX, 10 MOV EDI, [ESI] ; Leaves pCrntJCB in EAX ;ESI now has ptr to PD! ;Restore linear address ;Offset to shadow address of PD ;ESI now points to initial PT (EDX now free) ;LinAdd into EDX again ;get rid of upper 10 bits & lower 12 ; Index into PD for PT (10 vice 12 -> DWORDS) ; Linear address of next page table into EDI ;At this point, EDI is pointing the next PT. ;S0 EDI+EDX will point to the next PTE to do. ;Now we must call FindPage to get a physical address into EBX, ;then check the original linear address to see if SYSTEM or USER ;and OR in the appropriate control bits, THEN store it in PT. CALL FindHiPage ;EBX has Phys Pg (only EBX affected) OR EBX, MEMSYS ;Set PTE to present, User Readonly CMP EAX, 40000000h ;See if it's a user page JB AR2 OR EBX, MEMUSERD ;Sets User/Writable bits of PTE AR2: MOV DWORD PTR [EDI+EDX], EBX ; EDX is index to exact entry DEC ECX ;Are we done?? JZ ARDone ADD EDX, 4 ;Next PTE please. CMP EDX, 4096 ;Are we past last PTE of this PT? JB AR1 ;No, go do next PTE ADD ESI, 4 ;Yes, next PDE (to get next PT) XOR EDX, EDX ; Start at the entry 0 of next PT JMP SHORT AR0 i ARDone POP EDI POP ESI POP EDX POP ECX POP EBX RETN AddAliasRun The AddAliasRun function adds one or more PTEs to a page table — or tables, if the run spans two or more tables — adding PTEs from another job's PTs, marking them as alias entries. Aliased runs are always at user protection levels even if they are in the operating-system address span. The new linear address of the run should be in EAX, and the count of pages should be in EBX. Once again, this is the way FindRun left them. The ESI register has the linear address you are aliasing, and the EDX register has the job number. See Listing 19.15.

Chapter 19 H Memory Management Code H Listing 19.15. Adding an aliased run of linear memory. EAX Linear address of first page of new alias entries (from find run) EBX Number of Pages to alias ESI Linear Address of pages to Alias (from other job) EDX Job Number of Job we are aliasing OUT: Nothing USED: EAX, EFlags AliasLin AliasJob EQU DWORD PTR [EBP -4] EQU DWORD PTR [EBP -8] AddAliasRun: PUSH EBP MOV EBP, ESP SUB ESP, 8 MOV AliasLin, ESI MOV AliasJob, EDX PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI (save for caller) ;This first section sets to make [ESI] point to first PT that ;we have to move the other guy's physical pages into MOV ECX, EBX MOV EDX, EAX SHR EDX, 22 SHL EDX, 2 ;Copy number of pages to ECX (EBX free to use) ;LinAdd to EDX ;Get index into PD for first PT ;Make it index to DWORDS PUSH EAX CALL GetpCrntJCB MOV ESI, [EAX+JcbPD] POP EAX ;Save EAX thru GetpCrntJCB call ; Leaves pCrntJCB in EAX ;ESI now has linear address of PD ;Restore linear address ADD ESI, 2048 ADD ESI, EDX ;0ffset to shadow addresses in PD ;ESI now points to first PT of interest ALR0: MOV EDX, EAX ;LinAdd into EDX again AND EDX, 003FF000h ;get rid of upper 10 bits & lower 12 SHR EDX, 10 ;Index into PD for PT (10 vice 12 -> DWORDS) MOV EDI, [ESI] ; Linear address of crnt page table into EDI ;At this point, EDI is pointing to the PT we are in. ;SO then EDI+EDX will point to the next PTE to do. ;Now we must call LinToPhy with Linear Add & JobNum ; to get a physical address into EAX. ;This is the Physical address to store in the new PTE. ;mark it MEMALIAS before adding it to PT. PUSH ESI Save for next loop (used by LinToPhy)

I
390 Part IV The Operating System Source Code Listing 19.15. continued MOV EAX, AliasJob MOV EBX, AliasLin ADD AliasLin, 4096 CALL LinToPhy Job we are aliasing Address we are aliasing Set up for next loop (post’ increment) ;EAX now has physical address for this page AND EAX, 0FFFFF000h OR EAX, MEMALIAS POP ESI ;cut off system bits of PTE ;Set system bits as ALIAS ;Restore ESI (LinToPhy used it) ;Now store it in new PTE MOV DWORD PTR [EDI+EDX], EAX EDX is index to exact entry DEC ECX JZ ALRDone ADD EDX, 4 CMP EDX, 4096 JB ALR1 ADD ESI, 4 XOR EDX, EDX JMP SHORT ALR0 ALRDone: POP EDI POP ESI POP EDX POP ECX POP EBX MOV ESP, EBP POP EBP RETN ;Are we done?? ;Next PTE please. ;Are we past last PTE of this PT? ;No, go do next PTE ;Yes, next PDE (to get next PT) ;Start at the entry 0 of next PT AddUserPT The AddUserPT function creates a new user page table, initializes it, and sticks it in the user's page directory. This will be in user address space (greater than 1GB). This is easier than AddOSPT, as shown in the following, because there is no need to update any- one else's PDs. This sets the protection on the PT to K^r-read-and-write. Individual PTEs will be set read-only for code. See Listing 19.16. Listing 19.16. Adding a page table for user memory. IN : Nothing OUT: 0 if OK or Error (ErcNoMem USED: EAX, EFlags AddUserPT: no free phy pages! )

Chapter 19 Memory Management Code H PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI ; (save for caller) AUPT01 AUPT02: MOV EAX, _nPagesFree OR EAX, EAX JNZ AUPT01 MOV EAX, ErcNoMem JMP AUPTDone CALL GetCrntJobNum MOV EBX, pNextPT CALL LinToPhy ;See if have enuf physical memory ; Sorry, out of physical mem ; Leaves job num in EAX (for LinToPhy) ;Pre allocated Page (Linear Address) ;EAX will have Physical address Put it in the User PD (and linear in shadow) Find first empty slot CALL GetpCrntJCB MOV EDI, JcbPD ADD EDI, EAX MOV ESI, [EDI] ADD ESI, 2048 MOV ECX, 511 ADD ESI, 4 MOV EBX, [ESI] OR EBX, EBX LOOPNZ AUPT02 ;pJCB in EAX ;Offset to PcbPD in JCB ;EDI points to UserPD Address ;ESI now points to PD ;ESI now points to upper 1 GB in PD ;Number of entries (at least 1 is already gone) ; Next possible empty entry ; Is it empty? ; No! (Try again) ESI now points to empty Slot Physical Address of new table is still in EAX Get Linear address back into EBX and put them into PD OR EAX, MEMUSERD MOV [ESI], EAX ADD ESI, 2048 MOV EBX, pNextPT MOV [ESI], EBX ;Set user bits (Read/Write) ; Physical address in lower half ;Move to shadow ; Linear add back into EBX ;Put in Linear address of PT (upper half) Now we now need another PreAllocated Page Table for next time. Get a run of 1 for next new page table ;size of request ;PD shadow offset needed by FindRun (( MOV EBX, 1 XOR EAX, EAX CALL FindRun OR EAX, EAX JNZ AUPT05 MOV EAX, ErcNoMem JMP AUPT05 ' SHORT AUPTDone MOV pNextPT, EAX CALL AddRun AUPTDone: POP EDI POP ESI ;was there an error (0 means no ;save pNextPT (the linear address) ;AddRun will return NON-ZERO on error

Part fV ■ The Operating System Source Code Listing 19.16. continued POP EDX POP ECX POP EBX RETN AddOSPT The AddOSPT function is more complicated than AddUserPT because a reference to each new operating-system page table must be placed in all user page directories. You must do this to ensure the operating-system code can reach its memory no matter what job or task it is run- ning in. Adding an operating- system page table doesn't happen often; in many cases, it won't happen except once or twice while the operating system is running. See Listing 19.17. Listing 19.17. Adding an operating system page table. IN : Nothing OUT: 0 if OK or Error (ErcNoMem USED: EAX, EFlags no free phy pages! ) AddOSPT: PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI (save for caller) AOPT01 MOV EAX, _nPagesFree OR EAX, EAX JNZ AOPT01 MOV EAX, ErcNoMem JMP AOPTDone MOV EAX, 1 MOV EBX, pNextPT CALL LinToPhy ;See if have enuf physical memory ;Sorry, out of physical mem ;0S Job Number (Monitor) ;Pre allocated Page (Linear Address) ;EAX will have Physical address Put it in the OS PD (and linear in shadow) Find first empty slot AOPT02: MOV ESI, MOV ECX, OFFSET PDirl 511 ADD ESI, 4 MOV EBX, [ESI] OR EBX, EBX LOOPNZ AOPT02 ESI points to OS Pdir Count of PDEs to check Next possible empty entry Is it empty? No! (Try again) ESI now points to empty PDir Slot EAX still has Physical Address of new table Get Physical Address back into EBX and put them into PDir

Chapter 19 ■ Memory Management Code OR EAX, PRSNTBIT MOV [ESI], EAX ADD ESI, 2048 MOV EBX, pNextPT MOV [ESI], EBX ;Set present bit ; Physical address in lower half ;Move to shadow ; Linear add back into EBX ;Put in Linear address of PT (upper half) Update ALL PDs from PDirl ! ! This doesn't happen often if it happens at all. The OS will usually not take 4 MBs even with ALL of its dynamic structures (except on a 32 Mb system or larger and when fully loaded) AOPT03: MOV EDX, nJCBs MOV EAX, EDX CALL GetpJCB MOV ECX, [EAX+JcbPD] OR ECX, ECX JZ AOPT04 ; # of dynamic JCBs ;Next JCB ;EAX now has pointer to a job's PD ;See if PD id zero (Inactive JCB) ;Is it a valid Job? (0 if not) ;No, Not a valid JCB (unused) ADD EAX, MOV EBX, JcbPD OFFSET PDirl ;EAX NOW points to PD of JCB ; Source of Copy PUSH EDX Save nJCB we are on PUSH EAX PUSH EBX Save values on stack PUSH EBX PUSH EAX PUSH 1024 CALL FWORD PTR _CopyData ;Source ;Destination ;Lower half of PD (Physical Adds) POP EBX POP EAX ;Get values from stack ADD EBX, 2048 PUSH EBX ADD EAX, 2048 PUSH EAX PUSH 1024 CALL FWORD PTR _CopyData POP EDX ;Move to shadow ;Move to shadow ;Upper half of PD (Linear Adds) ; Get back JCB number AOPT04 ; DEC EDX CMP EDX, 2 JA AOPT03 ;Jobs 1 & 2 use PDirl (Mon & Debugger) ;At this point the new table is valid to ALL jobs! ;We now need another PreAllocated Page Table for ;next time. Get a run of 1 for next new page table MOV EBX, 1 XOR EAX, EAX ;size of request ;PD shadow offset needed by FindRun (0)

[394 Part PV ■ The Operating System Source Code Listing 19.17. continued CALL FindRun OR EAX, EAX ;was there an error (0 means no mem) JNZ AOPT05 MOV EAX, ErcNoMem ; JMP SHORT AOPTDone AOPT05: MOV pNextPT, EAX ;save pNextPT (the linear address) CALL AddRun ; AddRun XOR EAX, EAX ;Set ErcOK (0) AOPTDone: POP EDI POP ESI POP EDX POP ECX POP EBX RETN Public Memory Management Calls You now begin the public call definitions for memory management. Not all of the calls are presented here because some are very similar. The calling conventions all follow the Pascal- style definition. AddGDTCallGate AddGDTCallGate( ) builds and adds a GDT entry for a call gate, allowing access to OS proce- dures. This call doesn't check to see if the GDT descriptor for the call is already defined. It assumes you know what you are doing, and it overwrites the descriptor if it is already defined. The selector number is checked to make sure you're in range. This is 40h through the highest allowed call gate number. See Listing 19.18. Listing 19.18. Adding a call gate to the GDT. IN: AX - Word with Call Gate ID type as follows: DPL entry of 3 EC0x (most likely) DPL entry of 2 CC0x (Not used in MMURTL) DPL entry of 1 AC0x (Not used in MMURTL) DPL entry of 0 8C0x (OS call ONLY) (x = count of DWord params 0-F) CX ESI Selector number for call gate in GDT (constants!) Offset of entry point in segment of code to execute OUT: EAX Returns Errors, else 0 if all's well USES: EAX, EBX, ECX, ESI, EFLAGS

Chapter 19 ■ Memory Management Code s PUBLIC _AddCallGate: CMP CX, 40h ;Is number within range of callgates? JAE AddCG01 ;not too low. MOV EAX, ercBadGateNum RETF AddCGOl AddCG02: MOVZX EBX, CX SUB EBX, 40 ;sub call gate base selector SHR EBX, 3 ;make index vice selector CMP EBX, nCallGates ;see if too high! JBE AddCG02 ;No. MOV EAX, ercBadGateNum ;Yes. RETF MOVZX EBX, CX ; Extend selector into EBX ADD EBX, GDTBase ;NOW a true offset in GDT MOV WORD PTR [EBX+02], 8 ;Put Code Seg selector into Call gate MOV [EBX], SI ;0:15 of call offset SHR ESI, 16 ;move upper 16 of offset into SI MOV [EBX+06], SI ;16:31 of call offset MOV [EBX+04], AX ;call DPL & ndParams XOR EAX, EAX ;0 = No Error RETF AddlDTGate AddIDTGate( ) builds and adds an interrupt descriptor table (IDT) trap, interrupt, or task gate. The selector of the call is always 8 for interrupt or trap; for a task gate, the selector of the call is the task state segment (TSS) of the task. See Listing 19.19. Listing 19.19. Adding entries to the IDT. IN: AX - Word with Gate ID type as follows: Trap Gate with DPL of 3 8F00 Interrupt Gate with DPL of 3 8E00 Task Gate with DPL of 3 8500 BX - Selector of gate (08 or TSS selector for task gates) CX - Word with Interrupt Number (00-FF) ESI - Offset of entry point in OS code to execute (THIS MUST BE 0 FOR TASK GATES) USES: EAX, EBX, ECX, EDX, ESI, EFLAGS PUBLIC _AddIDTGate: MOVZX EDX, CX ; Extend INT Num into EDX SHL EDX, 3 ;Gates are 8 bytes each (times 8) ADD EDX, OFFSET IDT ;EDX now points to gate MOV WORD PTR [EDX+4], AX ;Put Gate ID into gate

396| Part IV The Operating System Source Code Listing 19.19. continued MOV EAX, ESI MOV WORD PTR [EDX], AX ;Put Offset 15:00 into gate SHR EAX, 16 MOV WORD PTR [EDX+6], AX ;Put Offset 31 :16 into gate MOV WORD PTR [EDX+2], BX ;Put in the selector RETF AllocOSPage AllocOSPage ( ) allocates one or more pages of physical memory and returns a linear pointer to one or more pages of contiguous memory in the operating system space. A result code is re- turned in the EAX register. The steps involved depend on the internal routines described pre- viously. The steps are these: 1 . Ensure you have physical memory by checking nPagesFree. 2. Find a contiguous run of linear pages to allocate. 3. Allocate each physical page, placing it in the run of PTEs. You search through the page tables for the current job and find enough contiguous PTEs to satisfy the request. If the current PT doesn't have enough contiguous entries, you add another page table to the operating-system page directory. This allows runs to span table entries. AllocPageO and AllocDMAPage( ) are not shown here because they are almost identical to AllocOSPage ( ), but not close enough to easily combine their code. See Listing 19.20. Listing 19.20. Allocating a page of linear memory. Procedureal Interface AllocOSPage (dn4KPages,ppMemRet) : dError n4KPages ppMemRet EQU [EBP+10h] EQU [EBP+0Ch] ; These equates are also used by AllocPage PUBLIC _AllocOSPage: PUSH EBP MOV EBP, ESP PUSH MemExch MOV EAX, pRunTSS ADD EAX, TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX,0h JNE SHORT ALOSPExit MOV EAX,n4KPages OR EAX , EAX JNZ ALOSP00 Wait at the MemExch for Msg Put Msg in callers TSS Message Area ;Kernel Error?? ;Yes! Serious problem. I size of request ;More than 0? ;Yes

Chapter 19 Memory Management Code ALOSP00: ALOSP01 MOV EAX.ercBadMemReq JMP ALOSPExit CMP EAX, _nPagesFree JBE ALOSP01 MOV EAX, ErcNoMem JMP SHORT ALOSPExit MOV EBX,n4KPages XOR EAX, EAX CALL FindRun OR EAX, EAX JNZ SHORT ALOSP02 ;Can't be zerol ;See if have enuf physical memory ;Yes ; Sorry boss, we're maxed out ;size of request ;PD shadow offset needed by FindRun (0) ;(0 = No Runs big enuf) ;No Error! If we didn't find a run big enuf we add a page table CALL AddOSPT OR EAX, EAX JZ SHORT ALOSP01 JMP SHORT ALOSPExit ALOSP02: CALL AddRun MOV EBX, ppMemRet MOV [EBX], EAX XOR EAX, EAX ALOSPExit: PUSH EAX PUSH MemExch PUSH 0FFFFFFF1h PUSH 0FFFFFFF1h CALL FWORD PTR _SendMsg POP EAX MOV ESP, EBP POP EBP RETF 8 ;Add a new page table (we need it!) ;See if it's 0 (0 = NO Error) ;Go back & try again ; ERROR! ! EAX now has linear address EBX still has count of pages Does not return error EAX still has new linear address Get address of caller's pointer Give em new LinAdd No error Save last error Send a Semaphore msg (so next guy can get in) Get original error back (ignore kernel ere) AliasMem AliasMem( ) creates alias pages in the current job's PD and PTs, if the current PD is different than the PD for the job specified. This allows system services to access a caller memory for messaging without having to move data around. The pages are created at user protection level even if they are in operating memory space. This is for the benefit of system services installed in operating-system memory. Even if the address is only two bytes, if it crosses page bound- aries, you need two pages. This wastes no physical memory, however — only an entry in a table. The steps involved in the algorithm are these:

  1.  See  if  the  current  PD  equals  specified  job  PD.  If  so,  Exit.  No  alias  is 
    

needed.

I
398 Part IV ■ The Operating System Source Code 2. Calculate how many entries (pages) will be needed. 3. See whether they are available. 4. Make PTE entries and return alias address to caller. See Listing 19.21. Listing 19.21. Code to alias memory. Procedureal Interface : AliasMem(pMem, dcbMem, dJobNum, ppAliasRet): dError pMem is the address to alias. dcbMem is the number of bytes needed for alias access. JobNum is the job number that pMem belongs to. ppAliasRet is the address to return the alias address to. pMem EQU [EBP+24] dcbMem EQU [EBP+20] JobNum EQU [EBP+16] ppAliasRet EQU [EBP+12] PUBLIC _AliasMem PUSH EBP MOV EBP, ESP CALL GetCrntJobNum MOV EBX, [EBP+16] CMP EAX, EBX JNE ALSPBegin XOR EAX, EAX JMP ALSPDone Puts Current Job Num in EAX Get Job number for pMem Are they the same Page Directory?? No, alias it Yes, No Error Exit, we're done ALSPBegin: ;Now wait our turn with memory management ; PUSH MemExch ;Wait at the MemExch for Msg ; MOV EAX, pRunTSS ;Put Msg in callers TSS Message A ; ADD EAX, TSS Msg ; PUSH EAX ; CALL FWORD PTR WaitMsg ; CMP EAX,0h ;Kernel Error?? ; JNE ALSPDone ;Yes! Serious problem. ; We're IN! ALSP00: MOV EBX, [EBP+24] ;pMem into EAX AND EBX, 0FFFh ;MODULO 4096 (remainder) MOV EAX, [EBP+20] ; dcbMem ADD EAX, EBX ;Add the remainder of address ADD EAX, DWORD PTR [EBP+20] ;Add to dcbMem SHR EAX, 12 ;EAX is nPages -1 INC EAX ;EAX is now nPages we need! MOV ECX, EAX ;Save nPages in ECX

Chapter 19 ■ Memory Management Code H ;Now we find out whos memory we are in to make alias ;EAX is 256 for user space, 0 for OS ;EBX is number of pages for run CALL GetCrntJobNum CMP EAX, 1 JE SHORT ALSP011 MOV EAX, 256 JMP SHORT ALSP01 ;See if it is OS based service ;0S Job? ;Yes ;No, User memory ALSP01 XOR EAX, EAX MOV EBX, ECX CALL FindRun ;Set up for OS memory space ; Number of pages we need into EBX ;EAX has 0 or 256 ;EAX is now linear address or 0 if no run is large enough ;EBX still has count of pages OR EAX, EAX JNZ ALSP04 ;Was there enough PTEs’ ;Yes CALL GetCrntJobNum CMP EAX, 1 JE SHORT ALSP03 CALL AddUserPT JMP SHORT ALSP03 ;See if it is OS based service ;OS Job? ;Yes ;No! Add a new USER page table ALSP02: CALL AddOSPT ;No! Add a new OS p ALSP03 : OR EAX, EAX ;0 = NO Error JZ SHORT ALSP00 ;Go back & try again JMP SHORT ALSPExit ; ERROR!! ALSP04: ;EAX has linear address (from find run) Sve in EDI ;EBX still has number of pages to alias ;Set ESI to linear address of pages to alias (from other job) ;Set EDX job number of job we are aliasing MOV EDI, EAX MOV ESI, [EBP+24] MOV EDX, [EBP+16] CALL AddAliasRun ;Save alias page address base ;Address to alias ;Job number ;Now, take new alias mem and add trailing bits to address ;and return to caller so he knows address (EDI is lin add) MOV EAX, [EBP+24] AND EAX, 0FFFh ADD EDI, EAX MOV ESI, [EBP+12] MOV [ESI], EDI XOR EAX, EAX ; original pMem ;Get remaining bits ;pAliasRet ;Returned address to caller! ;Set to 0 (no error) ;We are done ALSPExit ;Save last error

/qq I Part TV ■ The Operating System Source Code Listing 19.21. continued PUSH MemExch ;Send a Semaphore msg (so next guy can get in) PUSH 0FFFFFFF1h ; PUSH 0FFFFFFF1h ; CALL FWORD PTR _SendMsg ; POP EAX ;Get original error back (ignore kernel ere) ALSPDone: MOV ESP, EBP POP EBP RETF 16 DeAliasMem DeAliasMem( ) zeroes out the page entries that were made during the AliasMem( ) call. You do not need to go through the operating system memory semaphore exchange because you are only zeroing out PTEs one at a time. This would not interfere with any of the memory alloca- tion routines. See Listing 19.22. Listing 19.22. Code to remove alias PTEs. Procedureal Interface : DeAliasMem (pAliasMem, dcbAliasBytes, JobNum) :ercType pAliasMem is the address to “DeAlias” dcbAliasBytes is the size of the original memory aliased pAliasMem EQU [EBP+20] dcbAliasBytes EQU [EBP+16] AliasJobNum EQU [EBP+12] PUBLIC _DeAliasMem PUSH EBP MOV EBP, ESP ;Calculate the number of pages to DeAlias DALM01 MOV EBX, [EBP+20] AND EBX, 0FFFh MOV EAX, [EBP+16] ADD EAX, EBX ADD EAX, DWORD PTR [EBP+20] SHR EAX, 12 INC EAX MOV ECX, EAX MOV EDI, ECX MOV EDX, [EBP+20] ;pMem into EAX ; MODULO 4096 (remainder) jdcbMem ;Add the remainder of address ;Add to dcbMem ;EAX is nPages-1 ;EAX is now nPages we need! ; Number of pages into EDI & ECX ;Save also in EDI (for compare) ; Linear Mem to DeAlias MOV EBX, EDX MOV EAX, [EBP+12] CALL LinToPhy ;Address of next page to deallocate ;Job num into EAX for LinToPhy ;Call this to get address of PTE into ESI

Chapter 19 H Memory Management Code |
401 ;Now we have Physical Address in EAX (we don't really need it) ;and pointer to PTE in ESI (We NEEDED THIS). ;See if PTE is an alias, if so just ZERO PTE. ;D0 NOT deallocate the physical page DALM01 1 MOV EBX, [ESI] TEST EBX, PRSNTBIT JNZ DALM02 CMP ECX, EDI JNE DALM011 MOV EAX, ErcBadLinAdd JMP SHORT DALMExit MOV EAX, ErcBadAlias JMP SHORT DALMExit ;Get PTE into EBX ;Is page present (valid)??? ;Yes, it's page is present ;NO! (did we do any at all) ;We did some. ;None at all! ;We dealiased what we could, ;but less than you asked for! DALM03 : TEST EBX, ALIASBIT JZ DALM03 ;Is page an ALIAS? ;NO - DO not zero it! ;If we got here the page is presnt and IS an alias ;so we zero out the page. XOR EAX, EAX MOV [ESI], EAX ADD EDX, 4096 LOOP DALM01 DALMExit MOV ESP, EBP POP EBP RETF 12 ;ZERO PTE entry ;Next linear page ;If we fall out EAX = ErcOK already DeAllocPage DeAllocPage ( ) frees up linear memory and physical memory that was acquired with any of the allocation calls. This will free physical pages only if the page is not marked as an alias. It will always free linear memory, providing it is valid. Even if you specify more pages than are valid, this will deallocate or dealias as much as it can before reaching an invalid page. See Listing 19.23. Listing 19.23. Deallocating linear memory. Procedureal Interface : DeAllocPage (pOrigMem, n4KPages) :ercType

Part TV ■ The Operating System Source Code Listing 19.22. continued pOrigMem is a POINTER which should be pointing to memory page(s) to be deallocate. The lower 12 bits of the pointer is actually ignored because we deallocate 4K pages. n4KPages is the number of 4K pages to deallocate pOrigMem EQU [EBP+10h] n4KPagesD EQU [EBP+0Ch] PUBLIC _DeAllocPage: ; PUSH EBP ; MOV EBP, ESP ; PUSH MemExch MOV EAX, pRunTSS ADD EAX, TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX,0h JNE DAMExit ;Wait at the MemExch for Msg ;Put Msg in callers TSS Message Area ;Error?? ;Yes! MOV EDX, pOrigMem AND EDX, 0FFFFF000h MOV ECX, n4KPagesD ; Linear Mem to deallocate ;Drop odd bits from address (MOD 4096) ; Number of pages to deallocate DAP01 MOV EBX, EDX CALL GetCrntJobNum CALL LinToPhy Address of next page to deallocate Leave Job# in EAX for LinToPhy ;Now we have Physical Address in EAX ;and pointer to PTE in ESI. ;See if PTE is an alias, if so just ZERO PTE, ;else deallocate physical page THEN zero PTE MOV EBX, [ESI] TEST EBX, PRSNTBIT JNZ DAP02 ;Get PTE into EBX ;Is page present (valid)??’ ;Yes, it's page is present CMP ECX, n4KPagesD JNE DAP011 MOV EAX, ErcBadLinAdd JMP SHORT DAMExit ;NO! (did we do any at all) ;We did some. . ;None at all! MOV EAX, ErcShortMem JMP SHORT DAMExit ;We deallocated what we could, ;but less than you asked for! DAP02 : TEST EBX, ALIASBIT JNZ DAP03 ;Is page an ALIAS? ;Yes, it's an Alias ;If we got here the page is presnt and NOT an alias ;so we must unmark (release) the physical page.

Chapter 19 M Memory Management Code H AND EBX, 0FFFFF000h CALL UnMarkPage ;get rid of OS bits XOR EAX, EAX MOV [ESI], EAX ADD EDX, 4096 LOOP DAP01 DAMExit: PUSH EAX PUSH MemExch PUSH 0FFFFFFF1h PUSH 0FFFFFFF1h CALL FWORD PTR _SendMsg CMP EAX, 0 JNE DAMExitl POP EAX DAMExitl : MOV ESP, EBP POP EBP RETF 8 ;ZERO PTE entry ;Next linear page ;If we fall out EAX = ErcOK already ;save Memory error ;Send a dummy message to pick up ; so next guy can get in ;Kernel error has priority ; over memory error ;get Memory error back QueryMemPages QueryMemPages() gives callers the number of remaining physical pages that can be allocated. You can do this because you can give them any physical page in the system. Their 1GB linear space is sure to hold what you have left. See Listing 19.24. Listing 19.24. Code to find number of free pages. Procedureal Interface : QueryMemPages ( pdnPagesRet ) : ercType pdnPagesRet is a pointer where you want the count of pages left available returned pMemleft EQU [EBP+0Ch] PUBLIC QueryPages: PUSH EBP MOV EBP, ESP MOV ESI, pMemLeft

Part PV ■ The Operating System Source Code Listing 19.24. continued. MOV EAX, _nPagesFree MOV [ESI], EAX XOR EAX, EAX ;No Error MOV ESP, EBP POP EBP RETF 4 GetPhyAdd GetPhyAdd ( ) returns the physical address for a linear address. This call would be used by device drivers that have allocated buffers in their data segment and need to know the physical address for DMA purposes. Keep in mind that the last 12 bits of the physical address always match the last 12 of the linear address because this is below the granularity of a page. See Listing 19.25. Listing 19.25. Public function for linear to physical conversion. Procedureal Interface : GetPhyAdd (JobNum, LinAdd, pPhyRet) :ercType LinAdd is the Linear address you want the physical address for pPhyRet points to the unsigned long where the physical address will be returned JobNum EQU [EBP+20] LinAdd EQU [EBP+16] pPhyRet EQU [EBP+12] PUBLIC GetPhyAdd: PUSH EBP MOV EBP, ESP MOV EBX, [EBP+16] Linear Address MOV EAX, [EBP+20] Job Number CALL LinToPhy MOV ESI, [EBP+12] pPhyRet MOV [ESI], EAX XOR EAX, EAX No Error MOV ESP, EBP POP EBP RETF 12

Powered by TurnKey Linux.