MMURTL V1.

0

Richard A. Burgess

Sensory Publishing, Inc. 2000 Jefferson Davis Hwy Suite D Alexandria, VA 22301 USA www.SensoryPublishing.com

MMURTL V1.0

-i-

RICHARD A BURGESS

MMURTL V1.0

ISBN 1-58853-000-0

Author: Richard A. Burgess Technical Editor: David McClarty, Atlanta, GA. Additonal editing by: Copyright © 1995 Richard A. Burgess, Copyright © 2000 Sensory Publishing, Inc. and Richard A. Burgess All Rights Reserved Third printing - Second formatted version - No content change Previously published by MacMillan Publishing/SAMS Computer Books as: Developing Your Own 32 Bit Operating System, ISBN 0-672-30655-7 Portions of the electronic and printed versions of this book may have been formatted with software produced by: Adobe Corporation for the PDF Acrobat Reader version, Overdrive Inc. for the Microsoft Reader version, Sensory Publishing, Inc. for the HTML, XML, and SGML versions. For the electronic version of this book: Please treat it as you would a printed copy of any book. You may pass a single copy of this text on to another individual or institution when you are through with it, and upon deleting your copy as if you no longer held a printed copy of the book. No portions of this book may be made publicly available by any electronic, or other means, to include but not limited to, web sites, ftp sites, Internet peer to peer sharing software, any other private or public existing networking software, or electronic methods not yet invented, without the express written permission of Sensory Publishing, Inc.

Trademarks
All terms in this book that are known to be trademarks or service marks are properly capitalized or noted as such with the symbols , or  as appropriate. Sensory Publishing, Inc. has attempted to validate this information with reasonable care but can not guarantee its accuracy. Use of a service mark or trademark in this text should not be regarded as affecting the mark, or claiming any rights whatsoever to the mark in question unless otherwise noted.

Limit of Liability Disclaimer
The publisher has made every effort to ensure that the information is this book is accurate. The publisher and the author make no representations or warranties as to the contents of this book. Neither the publisher or the author shall be responsible for loss of profit or any other commercial damages resulting from the use of the information contained in this book.

www.SensoryPublishing.com

MMURTL V1.0

-ii-

RICHARD A BURGESS

Forward – 2000

irst of all, I thank all of you for the continued interest in MMURTL. When the first printed copies of this book (then named Developing Your Own 32 Bit Computer Operating System) hit the bookstands in 1995, 32 bit was the “buzz word” of the day. Five years later the buzzword is the New Millennium; 2000+. But it still seems that operating system development is a hot topic - look at Linux go! - You GO Mr. Penguin, you go! Many people have tracked me down in an attempt to find more copies of the original edition of the book and I can tell you they’re very scarce. Another printing was denied by the original publisher which turned the rights to this book back over to me. Of the 10,000-plus copies sold, I have 5 printed copies left (which needless to say, I'm holding on to for sentimental reasons). But if you're reading this, you now know that the book is back in full print (and electronically) available once again. I had initially intended to put up a web site for everyone to share their MMURTL interests and findings, but little things like "earning a living" - "kids in college, etc." kept getting in the way. A friend of mine (an entrepreneur at heart) was itching to start another online business (e.g., Dot Com Mania) and I had always been interested in publishing - the not so simple art of information dissemination - and I offered some technical assistance. Now I am part of Sensory Publishing, Inc., the new publisher of this book. Sensory Publishing has built and runs a bulletin board on their servers where MMURTL readers/users can exchange information and ask questions. I will try to make as much time as possible to answer questions there, and I would also like to see everyone that has something to share about MMURTL add their two cents. I hope you use MMURTL V1.0 to learn, enjoy and explore. And now, my gift to you, the owners of a proper copy of this book or the former version from SAMS): You may use any of the code (with the exception of the C compiler or samples from Mr. Dunfield) for any project you desire - public or private - so long as you have purchased a proper copy of the book in this or its previous printings. That is my way of saying thanks. The only requirement is that you give some visible credit to MMURTL for the assistance with your project. No, this is not the same as “public domain,” but it allows you to use MMURTL in your projects without fear of a lawsuit from me, or the publisher. The only thing you can’t do is place MMURTL or the MMURTL source code on a web site or other electronic retrieval system where the public can get to it (protect it as you would any copyrighted material).

F

Richard A. Burgess Alexandria Virginia, 2000

MMURTL V1.0

-iii-

RICHARD A BURGESS

CONTENTS CHAPTER 1, INTRODUCTION.......................................................................................................................................... 1 OVERVIEW ............................................................................................................................................................................ 1 WHAT THIS BOOK IS ABOUT ................................................................................................................................................. 2 WHO SHOULD USE THIS BOOK ............................................................................................................................................. 2 MY BASIC DESIGN GOALS (YOURS MAY VARY) .................................................................................................................... 3 A BRIEF DESCRIPTION OF MMURTL ................................................................................................................................... 4 USES FOR MMURTL ............................................................................................................................................................ 4 SIMILARITIES TO OTHER OPERATING SYSTEMS ..................................................................................................................... 4 HARDWARE REQUIREMENTS ................................................................................................................................................. 5 CHAPTER 2, GENERAL DISCUSSION AND BACKGROUND ..................................................................................... 7 WHERE DOES ONE BEGIN?.................................................................................................................................................... 7 YOU ARE WHERE YOU WERE WHEN .................................................................................................................................... 7 THE DEVELOPMENT PLATFORM AND THE TOOLS .................................................................................................................. 8 THE CHICKEN AND THE EGG ................................................................................................................................................. 8 EARLY HARDWARE INVESTIGATION ...................................................................................................................................... 9 THE REAL TASK AT HAND (PUN INTENDED) ....................................................................................................................... 10 A STARTING POINT ............................................................................................................................................................. 11 THE CRITICAL PATH ............................................................................................................................................................ 11 WORKING ATTITUDE ........................................................................................................................................................... 12 CHAPTER 3, THE TASKING MODEL............................................................................................................................ 13 TERMS WE SHOULD AGREE ON .......................................................................................................................................... 13 RESOURCE MANAGEMENT .................................................................................................................................................. 14 TASK MANAGEMENT ........................................................................................................................................................... 14 SINGLE VS. MULTI USER ..................................................................................................................................................... 14 THE HARDWARE STATE ...................................................................................................................................................... 15 THE SOFTWARE STATE ........................................................................................................................................................ 15 CPU TIME ........................................................................................................................................................................... 15 SINGLE TASKING ................................................................................................................................................................. 16 MULTITASKING ................................................................................................................................................................... 16 COOPERATIVE MULTITASKING ............................................................................................................................................ 16 PREEMPTIVE MULTITASKING .............................................................................................................................................. 17 TASK SCHEDULING .............................................................................................................................................................. 17 WHO, WHEN, AND HOW LONG?.......................................................................................................................................... 17 SCHEDULING TECHNIQUES .................................................................................................................................................. 17 Time Slicing .................................................................................................................................................................... 18 Simple Cooperative Queuing.......................................................................................................................................... 18 Prioritized Cooperative Scheduling................................................................................................................................ 18 Variable Time Slicing ..................................................................................................................................................... 18 OTHER SCHEDULING FACTORS ............................................................................................................................................ 19 MIXED SCHEDULING TECHNIQUES ...................................................................................................................................... 19 Fully Time Sliced, Some Cooperation ............................................................................................................................ 20 Time Sliced, More Cooperative ...................................................................................................................................... 20 Primarily Cooperative, Limited Time Slicing................................................................................................................. 20 Primarily Cooperative, More Time Slicing .................................................................................................................... 21 THE TASKING MODEL I CHOSE ........................................................................................................................................... 21 INTERRUPT HANDLING ........................................................................................................................................................ 21 CHAPTER 4, INTERPROCESS COMMUNICATIONS................................................................................................. 23 INTRODUCTION .................................................................................................................................................................... 23 MESSAGING AND TASKS ...................................................................................................................................................... 23 SYNCHRONIZATION ............................................................................................................................................................. 23 SEMAPHORES....................................................................................................................................................................... 23 PIPES ................................................................................................................................................................................... 24 MESSAGES ........................................................................................................................................................................... 24
MMURTL V1.0

-iv-

RICHARD A BURGESS

Send and Wait .................................................................................................................................................................24 Request() .........................................................................................................................................................................27 Respond() ........................................................................................................................................................................28 Link Blocks......................................................................................................................................................................29 REENTRANCY ISSUES ...........................................................................................................................................................29 INTERRUPT LATENCY ..........................................................................................................................................................30 MEMORY MANAGEMENT ISSUES .........................................................................................................................................30 CHAPTER 5, MEMORY MANAGEMENT ......................................................................................................................31 INTRODUCTION ....................................................................................................................................................................31 BASIC TERMS ......................................................................................................................................................................31 MEMORY MODEL ................................................................................................................................................................31 Simple Flat Memory........................................................................................................................................................32 Figure 5.1, Simple Flat Memory .................................................................................................................................32 Paged Flat Memory ........................................................................................................................................................32 Figure 5.2 - Paged Flat Memory .................................................................................................................................33 Demand-Paged Flat Memory..........................................................................................................................................33 Figure 5.3 - Demand Paged Flat Memory...................................................................................................................34 Virtual Paged Memory....................................................................................................................................................34 Figure 5.4 - Virtual Paged Memory ............................................................................................................................35 Demand Paged Virtual Memory .....................................................................................................................................35 Demand-Paged Memory Management ...........................................................................................................................36 Segmented Memory.........................................................................................................................................................36 MEMORY MANAGEMENT .....................................................................................................................................................37 TRACKING LINEAR MEMORY ALLOCATION .........................................................................................................................37 BASIC MEMORY ALLOCATION UNIT ....................................................................................................................................37 LINKED LIST MANAGEMENT................................................................................................................................................37 MEMORY MANAGEMENT WITH TABLES ..............................................................................................................................41 TRACKING PHYSICAL MEMORY ...........................................................................................................................................41 INITIALIZATION OF MEMORY MANAGEMENT ......................................................................................................................42 MEMORY PROTECTION ........................................................................................................................................................42 AN INTEL BASED MEMORY MANAGEMENT IMPLEMENTATION ...........................................................................................43 A Few More Words On Segmentation.............................................................................................................................43 HOW MMURTL USES PAGING ...........................................................................................................................................43 Page Tables (PTs)...........................................................................................................................................................44 Page Directories (PDs)...................................................................................................................................................44 The Memory Map............................................................................................................................................................44 Table 5.1 - MMURTL Memory Map.........................................................................................................................45 Page Directory Entries (PDEs) ......................................................................................................................................46 Table 5.2 Page Directory Example ............................................................................................................................46 ALLOCATION OF LINEAR MEMORY ......................................................................................................................................46 DEALLOCATION OF LINEAR MEMORY .................................................................................................................................47 ALLOCATION OF PHYSICAL MEMORY ..................................................................................................................................47 LOADING THINGS INTO MEMORY ........................................................................................................................................47 MESSAGING AND ADDRESS ALIASES ...................................................................................................................................48 MEMORY AND POINTER MANAGEMENT FOR MESSAGING ...................................................................................................48 SUMMARY OF MMURTL MEMORY MANAGEMENT ............................................................................................................49 CHAPTER 6, HARDWARE INTERFACE .......................................................................................................................51 HARDWARE ISOLATION .......................................................................................................................................................51 THE CPU .............................................................................................................................................................................51 THE BUS STRUCTURE ..........................................................................................................................................................52 SERIAL I/O...........................................................................................................................................................................52 PARALLEL I/O .....................................................................................................................................................................53 BLOCK-ORIENTED DEVICES ................................................................................................................................................53 KEYBOARD ..........................................................................................................................................................................53 VIDEO ..................................................................................................................................................................................54 DIRECT MEMORY ACCESS (DMA) ......................................................................................................................................55 TIMERS ................................................................................................................................................................................63
MMURTL V1.0

-v-

RICHARD A BURGESS

PRIORITY INTERRUPT CONTROLLER UNIT (PICU)............................................................................................................... 63 CHAPTER 7, OS INITIALIZATION ................................................................................................................................ 65 GETTING BOOTED ............................................................................................................................................................... 65 BOOT ROM ......................................................................................................................................................................... 65 THE BOOT SECTOR .............................................................................................................................................................. 65 OPERATING SYSTEM BOOT IMAGE ...................................................................................................................................... 74 OTHER BOOT OPTIONS ........................................................................................................................................................ 74 BASIC HARDWARE INITIALIZATION ..................................................................................................................................... 74 STATIC TABLES AND STRUCTURES ...................................................................................................................................... 75 INITIALIZATION OF TASK MANAGEMENT ............................................................................................................................ 75 INITIALIZATION OF MEMORY............................................................................................................................................... 75 DYNAMIC TABLES AND STRUCTURES .................................................................................................................................. 76 CHAPTER 8, PROGRAMMING INTERFACES............................................................................................................. 77 APPLICATION PROGRAMMING INTERFACE ........................................................................................................................... 77 Documenting Your API................................................................................................................................................... 77 Procedural Interfaces ..................................................................................................................................................... 77 Calling Conventions ....................................................................................................................................................... 77 MECHANICAL DETAILS ....................................................................................................................................................... 78 PORTABILITY CONSIDERATIONS .......................................................................................................................................... 79 ERROR HANDLING AND REPORTING .................................................................................................................................... 79 SYSTEMS PROGRAMMING INTERFACE ................................................................................................................................. 79 INTERNAL COOPERATION .................................................................................................................................................... 80 DEVICE DRIVER INTERFACES .............................................................................................................................................. 80 A DEVICE-DRIVER INTERFACE EXAMPLE ........................................................................................................................... 80 Announcing the Driver to the OS.................................................................................................................................... 81 A Call to the driver ......................................................................................................................................................... 83 CHAPTER 9, APPLICATION PROGRAMMING........................................................................................................... 85 INTRODUCTION .................................................................................................................................................................... 85 TERMINOLOGY .................................................................................................................................................................... 85 UNDERSTANDING 32-BIT SOFTWARE ................................................................................................................................. 85 Table 9.1 - Memory Alignment ....................................................................................................................................... 85 OPERATING SYSTEM CALLING CONVENTIONS .................................................................................................................... 86 STACK USAGE ..................................................................................................................................................................... 86 MEMORY MANAGEMENT..................................................................................................................................................... 87 Table 9.2 - Basic Memory Map ...................................................................................................................................... 87 OPERATING SYSTEM PROTECTION ....................................................................................................................................... 88 APPLICATION MESSAGING................................................................................................................................................... 88 STARTING A NEW THREAD .................................................................................................................................................. 89 JOB CONTROL B LOCK.......................................................................................................................................................... 89 BASIC KEYBOARD ............................................................................................................................................................... 90 BASIC VIDEO ....................................................................................................................................................................... 91 Table 9.3 - Foreground Colors (Low nibble) ................................................................................................................. 91 Table 9.4 - Background Colors (High nibble) ................................................................................................................ 91 TERMINATING YOUR APPLICATION ..................................................................................................................................... 92 REPLACING YOUR APPLICATION ......................................................................................................................................... 92 CHAPTER 10, SYSTEMS PROGRAMMING.................................................................................................................. 93 INTRODUCTION .................................................................................................................................................................... 93 WRITING MESSAGE-BASED SYSTEM SERVICES ................................................................................................................... 93 INITIALIZING YOUR SYSTEM SERVICE ................................................................................................................................. 93 A SIMPLE SYSTEM SERVICE EXAMPLE ................................................................................................................................ 93 Listing 10.1. A System Service....................................................................................................................................... 94 THE REQUEST BLOCK.......................................................................................................................................................... 95 ITEMS IN THE REQUEST BLOCK ........................................................................................................................................... 95 THE SERVICE CODE ............................................................................................................................................................. 95 CALLER INFORMATION IN A REQUEST ................................................................................................................................. 95
MMURTL V1.0

-vi-

RICHARD A BURGESS

ASYNCHRONOUS SERVICES .................................................................................................................................................96 SYSTEM SERVICE ERROR HANDLING ...................................................................................................................................96 WRITING DEVICE DRIVERS ..................................................................................................................................................96 DEVICE DRIVER THEORY .....................................................................................................................................................97 BUILDING DEVICE DRIVERS ................................................................................................................................................97 HOW CALLERS REACH YOUR DRIVER .................................................................................................................................98 DEVICE DRIVER SETUP AND INSTALLATION ........................................................................................................................98 SYSTEM RESOURCES FOR DEVICE DRIVERS ........................................................................................................................99 Interrupts ........................................................................................................................................................................99 Direct Memory Access Device (DMA)............................................................................................................................99 Timer Facilities...............................................................................................................................................................99 MESSAGE FACILITIES FOR DEVICE DRIVERS ......................................................................................................................100 DETAILED DEVICE INTERFACE SPECIFICATION ..................................................................................................................100 Device Control Block Setup and Use............................................................................................................................100 Table 10.1-. Device control block definition.................................................................................................................100 STANDARD DEVICE CALL DEFINITIONS .............................................................................................................................102 DeviceOp Function Implementation .............................................................................................................................102 DeviceStat Function Implementation............................................................................................................................103 DeviceInit Function Implementation.............................................................................................................................103 INITIALIZING YOUR DRIVER ..............................................................................................................................................104 OS FUNCTIONS FOR DEVICE DRIVERS ...............................................................................................................................104 STANDARD DEVICE ERROR CODES ....................................................................................................................................105 CHAPTER 11, THE MONITOR PROGRAM.................................................................................................................107 INTRODUCTION ..................................................................................................................................................................107 ACTIVE JOB (VIDEO & KEYBOARD) ...................................................................................................................................107 INITIAL JOBS ......................................................................................................................................................................107 Listing 11.1. Sample Initial.Job file ..............................................................................................................................107 MONITOR FUNCTION KEYS ................................................................................................................................................108 Table 11.1. Monitor Function Keys ..............................................................................................................................108 MONITOR PROGRAM THEORY ............................................................................................................................................108 PERFORMANCE MONITORING ............................................................................................................................................108 MONITOR SOURCE LISTING ...............................................................................................................................................109 Listing 11.2. Monitor program source listing ...............................................................................................................109 CHAPTER 12, DEBUGGER .............................................................................................................................................127 INTRODUCTION ..................................................................................................................................................................127 USING THE DEBUGGER ......................................................................................................................................................127 Entering the Debugger..................................................................................................................................................127 Exiting the Debugger ....................................................................................................................................................127 Debugger Display .........................................................................................................................................................127 Debugger Function Keys ..............................................................................................................................................128 DEBUGGING YOUR APPLICATION ......................................................................................................................................129 DEBUGGER THEORY ..........................................................................................................................................................129 Table 12.1.Processor exceptions. .................................................................................................................................130 CHAPTER 13, KEYBOARD SERVICE ..........................................................................................................................133 INTRODUCTION ..................................................................................................................................................................133 AVAILABLE SERVICES .......................................................................................................................................................133 Table 13.1.Available Keyboard Services ......................................................................................................................134 Read Keyboard .............................................................................................................................................................134 Notify On Global Keys ..................................................................................................................................................134 Cancel Notify On Global Keys......................................................................................................................................135 Assign Keyboard ...........................................................................................................................................................135 KEY CODES AND STATUS ...................................................................................................................................................135 Alpha-Numeric Key Values...........................................................................................................................................135 Table 13.2 - .Shift State Bits..........................................................................................................................................136 Lock-State Byte (third byte) ..........................................................................................................................................136 Table 13.3 - Lock State Bits ..........................................................................................................................................136
MMURTL V1.0

-vii-

RICHARD A BURGESS

Numeric Pad Indicator ................................................................................................................................................. 136 KEY CODES ....................................................................................................................................................................... 137 Table 13.4 - .Keys Values ............................................................................................................................................. 137 Function Strip Key Codes............................................................................................................................................. 138 Table 13.5 - Function Key Strip Codes......................................................................................................................... 138 Numeric Pad Key Codes............................................................................................................................................... 139 Table 13.6 - Numeric Pad Key Codes........................................................................................................................... 139 Cursor, Edit, and Special Pad Key Codes .................................................................................................................... 139 13.7.Additional Key Codes ........................................................................................................................................... 139 YOUR KEYBOARD IMPLEMENTATION ................................................................................................................................ 140 CHAPTER 14, THE FILE SYSTEM SERVICE ............................................................................................................. 141 INTRODUCTION .................................................................................................................................................................. 141 FILE SPECIFICATIONS ........................................................................................................................................................ 141 NETWORK FILE REQUEST ROUTING .................................................................................................................................. 141 FILE HANDLES ................................................................................................................................................................... 142 FILE OPEN MODES............................................................................................................................................................. 142 FILE ACCESS TYPE ............................................................................................................................................................ 142 Block Mode................................................................................................................................................................... 142 Stream Mode................................................................................................................................................................. 142 LOGICAL FILE ADDRESS .................................................................................................................................................... 143 FILE SYSTEM REQUESTS .................................................................................................................................................... 143 Listing 14.1. Openfile request in C ............................................................................................................................... 143 Table 14.1 - File System Service Codes........................................................................................................................ 143 PROCEDURAL INTERFACES ................................................................................................................................................ 144 DEVICE ACCESS THROUGH THE FILE SYSTEM ................................................................................................................... 144 Table 14.2 - Device Stream Access............................................................................................................................... 144 FILE SYSTEM FUNCTIONS IN DETAIL ................................................................................................................................. 145 OpenFile ....................................................................................................................................................................... 145 CloseFile....................................................................................................................................................................... 145 ReadBlock..................................................................................................................................................................... 146 WriteBlock .................................................................................................................................................................... 146 ReadBytes ..................................................................................................................................................................... 147 WriteBytes..................................................................................................................................................................... 147 GetFileLFA................................................................................................................................................................... 148 SetFileLFA.................................................................................................................................................................... 148 GetFileSize ................................................................................................................................................................... 149 SetFileSize .................................................................................................................................................................... 149 CreateFile..................................................................................................................................................................... 150 RenameFile................................................................................................................................................................... 150 DeleteFile ..................................................................................................................................................................... 151 CreateDirectory............................................................................................................................................................ 151 DeleteDirectory ............................................................................................................................................................ 152 GetDirSector................................................................................................................................................................. 152 FILE SYSTEM THEORY ....................................................................................................................................................... 153 Internal File System Structures .................................................................................................................................... 153 File Control Blocks................................................................................................................................................... 153 File User Blocks ....................................................................................................................................................... 153 FAT Buffers.............................................................................................................................................................. 153 File Operations............................................................................................................................................................. 154 Read.......................................................................................................................................................................... 154 Write ......................................................................................................................................................................... 155 Close ......................................................................................................................................................................... 155 CHAPTER 15, API SPECIFICATION ............................................................................................................................ 157 INTRODUCTION .................................................................................................................................................................. 157 PUBLIC CALLS ................................................................................................................................................................... 157 Parameters to Calls (Args)........................................................................................................................................... 157 Table 15.1 - Size and Type Prefixes ......................................................................................................................... 157
MMURTL V1.0

-viii-

RICHARD A BURGESS

Table 15.2 - Additional Prefixes ...............................................................................................................................157 Table1 5.3 - Compound Prefixes ..............................................................................................................................158 Stack Conventions.........................................................................................................................................................158 Calls Grouped by Functionality....................................................................................................................................158 Messaging .....................................................................................................................................................................158 Job Management...........................................................................................................................................................159 Task Management .........................................................................................................................................................159 System Service & Device Driver Management .............................................................................................................159 Memory Management ...................................................................................................................................................159 High Speed Data Movement .........................................................................................................................................159 Timer and Timing..........................................................................................................................................................160 ISA Hardware Specific I/O ...........................................................................................................................................160 Interrupt and Call Gate Management...........................................................................................................................160 Keyboard, Speaker, & Video.........................................................................................................................................160 ALPHABETICAL CALL LISTING ..........................................................................................................................................161 AddCallGate .................................................................................................................................................................161 AddIDTGate..................................................................................................................................................................161 Alarm ............................................................................................................................................................................161 AliasMem ......................................................................................................................................................................162 AllocDMAPage .............................................................................................................................................................162 AllocExch ......................................................................................................................................................................162 AllocOSPage.................................................................................................................................................................163 AllocPage......................................................................................................................................................................163 Beep ..............................................................................................................................................................................163 Chain.............................................................................................................................................................................163 CheckMsg .....................................................................................................................................................................163 ClrScr............................................................................................................................................................................164 Compare .......................................................................................................................................................................164 CompareNCS ................................................................................................................................................................164 CopyData ......................................................................................................................................................................164 CopyDataR ...................................................................................................................................................................165 DeAliasMem..................................................................................................................................................................165 DeAllocExch .................................................................................................................................................................165 DeAllocPage .................................................................................................................................................................165 DeviceInit......................................................................................................................................................................166 DeviceOp ......................................................................................................................................................................166 DeviceStat .....................................................................................................................................................................166 DMASetUp ....................................................................................................................................................................167 EditLine.........................................................................................................................................................................167 EndOfIRQ .....................................................................................................................................................................167 ExitJob ..........................................................................................................................................................................168 FillData.........................................................................................................................................................................168 GetCmdLine ..................................................................................................................................................................168 GetCMOSTime..............................................................................................................................................................169 GetCMOSDate ..............................................................................................................................................................169 GetDMACount ..............................................................................................................................................................169 GetExitJob ....................................................................................................................................................................169 GetIRQVector ...............................................................................................................................................................170 GetJobNum ...................................................................................................................................................................170 GetNormVid ..................................................................................................................................................................170 GetPath .........................................................................................................................................................................170 GetPhyAdd ....................................................................................................................................................................171 GetpJCB........................................................................................................................................................................171 GetSysIn ........................................................................................................................................................................171 GetSysOut .....................................................................................................................................................................171 GetTimerTick ................................................................................................................................................................171 GetTSSExch ..................................................................................................................................................................172 GetUserName................................................................................................................................................................172 GetVidChar...................................................................................................................................................................172
MMURTL V1.0

-ix-

RICHARD A BURGESS

GetVidOwner ................................................................................................................................................................ 172 GetXY............................................................................................................................................................................ 172 InByte............................................................................................................................................................................ 173 InDWord ....................................................................................................................................................................... 173 InWord.......................................................................................................................................................................... 173 InWords ........................................................................................................................................................................ 173 InitDevDr...................................................................................................................................................................... 173 ISendMsg ...................................................................................................................................................................... 174 KillAlarm ...................................................................................................................................................................... 174 LoadNewJob ................................................................................................................................................................. 175 MaskIRQ....................................................................................................................................................................... 175 MicroDelay................................................................................................................................................................... 175 MoveRequest................................................................................................................................................................. 175 NewTask ....................................................................................................................................................................... 176 OutByte ......................................................................................................................................................................... 176 OutDWord .................................................................................................................................................................... 176 OutWord ....................................................................................................................................................................... 176 OutWords...................................................................................................................................................................... 177 PutVidAttrs ................................................................................................................................................................... 177 PutVidChars ................................................................................................................................................................. 177 QueryPages .................................................................................................................................................................. 177 ReadCMOS ................................................................................................................................................................... 178 ReadKbd ....................................................................................................................................................................... 178 RegisterSvc ................................................................................................................................................................... 178 Request ......................................................................................................................................................................... 178 Respond ........................................................................................................................................................................ 179 ScrollVid ....................................................................................................................................................................... 180 SendMsg ....................................................................................................................................................................... 180 SetCmdLine................................................................................................................................................................... 180 SetExitJob ..................................................................................................................................................................... 181 SetIRQVector................................................................................................................................................................ 181 Table 15.4 - Hardware IRQs..................................................................................................................................... 181 SetJobName .................................................................................................................................................................. 182 SetNormVid................................................................................................................................................................... 182 SetPath.......................................................................................................................................................................... 182 SetPriority..................................................................................................................................................................... 182 SetSysIn......................................................................................................................................................................... 183 SetSysOut...................................................................................................................................................................... 183 SetUserName ................................................................................................................................................................ 183 SetVidOwner................................................................................................................................................................. 183 SetXY ............................................................................................................................................................................ 183 Sleep ............................................................................................................................................................................. 184 SpawnTask.................................................................................................................................................................... 184 Tone .............................................................................................................................................................................. 184 TTYOut ......................................................................................................................................................................... 185 UnMaskIRQ.................................................................................................................................................................. 185 UnRegisterSvc .............................................................................................................................................................. 185 WaitMsg........................................................................................................................................................................ 185 CHAPTER 16, MMURTL SAMPLE SOFTWARE........................................................................................................ 187 INTRODUCTION .................................................................................................................................................................. 187 COMMAND LINE INTERPRETER (CLI) ................................................................................................................................ 187 Internal Commands ...................................................................................................................................................... 187 Cls (Clear Screen)..................................................................................................................................................... 188 Copy (Copy a file) ................................................................................................................................................... 188 Dir (Directory listing).............................................................................................................................................. 188 Debug (Enter Debugger)........................................................................................................................................... 188 Del (Delete a File) .................................................................................................................................................... 188 Dump (Hex dump of a file)....................................................................................................................................... 188
MMURTL V1.0

-x-

RICHARD A BURGESS

Exit (Exit the CLI) .................................................................................................................................................189 Help (List internal commands) .................................................................................................................................189 Monitor (Return to Monitor).....................................................................................................................................189 MD (Make Directory) ...............................................................................................................................................189 Path (Set file access path) .........................................................................................................................................189 RD (Remove Directory) ............................................................................................................................................189 Rename (Rename a file)............................................................................................................................................190 Run (Execute a .RUN file) ........................................................................................................................................190 Type (View a text file on the screen) ........................................................................................................................190 EXTERNAL COMMANDS .....................................................................................................................................................190 Commands.CLI File......................................................................................................................................................190 Global "Hot Keys" ........................................................................................................................................................191 CLI SOURCE LISTING ........................................................................................................................................................191 Listing 16.1. CLI Source Code Listing - Ver. 1.0..........................................................................................................191 A SIMPLE EDITOR ..............................................................................................................................................................207 Editor Screen ................................................................................................................................................................207 Editor Commands .........................................................................................................................................................207 Table 16.1. File Management and General Commands ..............................................................................................207 Table 16.2. Cursor and Screen Management Commands............................................................................................207 Listing 16.2. Editor Source Code..................................................................................................................................208 DUMBTERM .......................................................................................................................................................................231 Listing 16.3. DumbTerm source code ..........................................................................................................................231 PRINT .................................................................................................................................................................................234 Listing 16.4. Print source code .....................................................................................................................................234 SYSTEM SERVICE EXAMPLE...............................................................................................................................................238 Service.C Listing ...........................................................................................................................................................238 Listing 16.5 Simple Service Source Code ....................................................................................................................238 TESTSVC.C LISTING ..........................................................................................................................................................240 Listing 16.6. TestSvc.C.................................................................................................................................................240 CHAPTER 17, INTRODUCTION TO THE SOURCE CODE ......................................................................................241 COMMENTS IN THE CODE ..................................................................................................................................................241 CALLING CONVENTIONS ....................................................................................................................................................241 ORGANIZATION..................................................................................................................................................................241 BUILDING MMURTL ........................................................................................................................................................243 Listing 17.1. Assembler Template File..........................................................................................................................243 USING PIECES OF MMURTL IN YOUR OS.........................................................................................................................244 USING PIECES OF MMURTL IN OTHER PROJECTS ............................................................................................................244 CHAPTER 18, THE KERNEL..........................................................................................................................................245 INTRODUCTION ..................................................................................................................................................................245 Naming Conventions.....................................................................................................................................................245 KERNEL DATA ...................................................................................................................................................................245 Listing 18.1. Kernel data segment source .................................................................................................................245 LOCAL KERNEL HELPER FUNCTIONS .................................................................................................................................246 ENQUEUEMSG ...................................................................................................................................................................246 Listing 18.2. Queueing for messages at an exchange................................................................................................246 DEQUEUEMSG ...................................................................................................................................................................247 Listing 18.3. De-queueing a message from an exchange ..........................................................................................247 DEQUEUETSS....................................................................................................................................................................247 Listing 18.4. De-queueing a task from an exchange .................................................................................................248 ENQUEUERDY ...................................................................................................................................................................248 Listing 18.5. Adding a task to the ready queue.........................................................................................................248 DEQUEUERDY ...................................................................................................................................................................249 Listing 18.6. De-queueing the highest priority task ..................................................................................................249 CHKRDYQ .........................................................................................................................................................................250 Listing 18.7. Finding the highest priority task ..........................................................................................................250 INTERNAL PUBLIC HELPER FUNCTIONS .............................................................................................................................250 RemoveRdyJob..............................................................................................................................................................250
MMURTL V1.0

-xi-

RICHARD A BURGESS

Listing 18.8. Removing a terminated task from the ready queue ............................................................................. 250 GetExchOwner ............................................................................................................................................................. 252 Listing 18.9. Finding the owner of an exchange....................................................................................................... 252 SetExchOwner .............................................................................................................................................................. 253 Listing 18.10. Changing the owner of an exchange.................................................................................................. 253 SendAbort ..................................................................................................................................................................... 254 Listing 18.11. Notifying services of a job’s demise ................................................................................................. 254 PUBLIC KERNEL FUNCTIONS ............................................................................................................................................. 255 Request()....................................................................................................................................................................... 255 Listing 18.12. Request kernel primitive code ........................................................................................................... 255 Respond()...................................................................................................................................................................... 258 Listing 18.13. Respond kernel primitive code .......................................................................................................... 258 MoveRequest() .............................................................................................................................................................. 260 Listing 18.14. MoveRequest kernel primitive code.................................................................................................. 261 SendMsg()..................................................................................................................................................................... 262 Listing 18.15. SendMsg kernel primitive code......................................................................................................... 262 ISendMsg() ................................................................................................................................................................... 264 Listing 18.16. IsendMsg kernel primitive code ........................................................................................................ 264 WaitMsg() ..................................................................................................................................................................... 266 Listing 18.17. WaitMsg kernel primitive code ......................................................................................................... 266 CheckMsg()................................................................................................................................................................... 269 Listing 18.18. CheckMsg kernel primitive code...................................................................................................... 270 NewTask()..................................................................................................................................................................... 272 Listing 18.19. NewTask kernel primitive code......................................................................................................... 272 SpawnTask() ................................................................................................................................................................. 274 Listing 18.20. SpawnTask kernel primitive code ..................................................................................................... 274 AllocExch() ................................................................................................................................................................... 276 Listing 18.21. Allocate exchange code ..................................................................................................................... 276 DeAllocExch() .............................................................................................................................................................. 277 Listing 18.22. Deallocate exchange code ................................................................................................................. 277 GetTSSExch() ............................................................................................................................................................... 278 Listing 18.23. GetTSS exchange code...................................................................................................................... 279 SetPriority() .................................................................................................................................................................. 279 Listing 18.24. Set priority code ................................................................................................................................ 279 GetPriority() ................................................................................................................................................................. 280 Listing 18.25. Get priority code................................................................................................................................ 280 CHAPTER 19, MEMORY MANAGEMENT CODE ..................................................................................................... 281 INTRODUCTION .................................................................................................................................................................. 281 Memory Management Data .......................................................................................................................................... 281 Listing 19.1. Memory management constants and data............................................................................................ 281 INTERNAL CODE ................................................................................................................................................................ 281 InitMemMgmt ............................................................................................................................................................... 282 Listing 19.2. Memory management initialization code ............................................................................................ 282 Listing 19.3. Continuation of memory management init. ......................................................................................... 282 Listing 19.4. Continuation of memory management init. ......................................................................................... 283 Listing 19.5. Turning on paged memory management ............................................................................................. 283 Listing 19.6. Allocation of memory management exchange .................................................................................... 284 Listing 19.7. Finishing memory management init. ................................................................................................... 284 FindHiPage .................................................................................................................................................................. 284 Listing 19.8. Find highest physical page code.......................................................................................................... 284 FindLoPage .................................................................................................................................................................. 285 Listing 19.9. Find lowest physical page code ........................................................................................................... 285 MarkPage ..................................................................................................................................................................... 286 Listing 19.10. Code to mark a physical page in use ................................................................................................. 286 UnMarkPage ................................................................................................................................................................ 287 Listing 19.11. Code to free a physical page for reuse............................................................................................... 287 LinToPhy ...................................................................................................................................................................... 287 Listing 19.12. Code to convert linear to physical addresses ..................................................................................... 287
MMURTL V1.0

-xii-

RICHARD A BURGESS

............23........................................314 Listing 20...........................303 Listing 19...........................................13........................................................................................308 Listing 20.........................293 AddOSPT .......................316 Listing 20........307 Listing 20......................................................................................................17..............................................298 AliasMem .....................................................................................................18.......................................................................................................................................................................6........... Code to read the CMOS date ..................................................................299 DeAliasMem............................................... Loop in timer ISR ............. Code to find a free run of linear memory.............................................................................................................................................................................................................................................................................................290 AddAliasRun ...................................................................296 AddIDTGate..........................................................................................................................................................................................................................316 Listing 20................................. Deallocating linear memory.........................................................................................15................................317 Listing 20..............................................................................................................................................................................................................................................................................................................11.............5....................................................................................................299 Listing 19.............................................................8................................................................................................................................................................................................303 QueryMemPages.................................................. Public for linear to physical conversion..1................................................................294 Listing 19......................................................................317 MMURTL V1..4......288 AddRun ...........................288 Listing 19...........296 Listing 19......................................313 MicroDelay() ..............................................................................................................................................................294 PUBLIC MEMORY MANAGEMENT CALLS ........................................................................................................ Adding a call gate to the GDT ............................................................................................................................301 Listing 19.................................................................................13............................................................................................................ Code to produce a tone ............ Code to remove alias PTEs................................................................................307 INTRODUCTION ..............................................................................................................................................301 DeAllocPage .312 KillAlarm()..............308 TIMER CODE ......................................................................................3................. Data and constants for timer code......21................309 Sleep() ....19.......14............309 The Timer Interrupt .....307 CHOOSING A STANDARD INTERVAL ...................311 Listing 20...................................................................................................................................................................................................... Code for the MicrDelay function .....................................................................312 Listing 20..................................317 Listing 20........ Code to alias memory ..................................................................................................................................................................................................313 GetCMOSTime() ......................................................................................................................................307 TIMER DATA .........FindRun ...................................... Helper function for Beep() and Tone() .................................................................................................................................................... Adding a page table for user memory.........................................................................................................................309 Listing 20................................................................................................................. Adding an aliased run of linear memory...........................................................7........................................ Code to return the timer tick ........................... Adding a run of linear memory............................317 Tone().......................................316 Beep_Work() ............................................................. The timer interrupt service routine .304 GetPhyAdd ....................................................................................................................................................................................................................297 AllocOSPage......................................................................................................................................... Code for the Sleep function .................................................................................................................296 AddGDTCallGate ......297 Listing 19.......................................................................................315 Listing 20......................................................305 CHAPTER 20..........................................................10................................312 Listing 20......305 Listing 19.314 GetCMOSDate() ..................................309 Listing 20..16....25..... TIMER MANAGEMENT SOURCE CODE ...........................................................................................22.........................................................................313 Listing 20.....................................................................................................20..................................................... Code for the KillAlarm function..................................................................................................304 Listing 19..................................................................................................... Allocating a page of linear memory.......................................................................................................................291 AddUserPT ..............................................298 Listing 19......................................... Code for the Alarm function ...........................................................................................................................................9...........................................................................................2. Adding entries to the IDT ...............................................14...............................................289 Listing 19............................................................ External functions for timer code ... Code to produce a beep................................................................................................24.291 Listing 19......................................................................... Code to read the CMOS time............................315 GetTimerTick()..... Code to find number of free pages.................................................................... Adding an operating system page table .................0 -xiii- RICHARD A BURGESS ....................................................................................................................................................................293 Listing 19.......12..........................316 Beep()..................................................................................................................................................................................................................................................................................................................................................311 Alarm()...............................................................................................................................................................................

.............................................................................................................................................. 413 Listing 24................................................................................................................................ 389 Listing 24...................................................... 337 JOB MANAGEMENT LISTING ............... KEYBOARD SOURCE CODE..............1........................................................... 429 Listing 25............................ 398 Listing 24........................1................................................................................................................................................................................. 319 OPERATING SYSTEM ENTRY POINT ....................................... Keyboard ISR Code............................................6..................................................................... 319 OS GLOBAL DATA ................ 365 INTRODUCTION ............................. 436 Listing 25................................................................................ Exception Handlers for Entering Debugger.......5..................................................7...................... 409 Listing 24...................................... 415 Listing 24.......... 321 Listing 21........................................................................ 337 RECLAMATION OF RESOURCES ................................... 344 CHAPTER 23....................... 429 KEYBOARD CODE ............1................................................................................................................................................................................. 327 Listing 21......................................................................... 410 Listing 24................. Keyboard Data and Tables .......................CHAPTER 21.................. 328 Listing 21........... Continuation of OS initialization....................................................... 366 CHAPTER 24..........................................3......... 365 Listing 23.................................... 389 INTRODUCTION ............................. 365 DEBUGGER SOURCE LISTING .............................. Jobc......... 393 Listing 24......... 337 Listing 22............................................................. RS-232 Device Driver Code continued (interface) .........................................c source code..................................................... RS-232.........................................................4...... 436 Keyboard Translation................ 424 CHAPTER 25............................................................................................................................................................10....................................................................................................3.............................. 365 DEBUGGER INTERRUPT PROCEDURE ................................................................................................................... 389 Listing 24...................6.................................. 415 Listing 24........................................................................ Read Keyboard Buffer Code ............ 414 Listing 24........................................................................................................................................................ 417 Listing 24..............................0 -xiv- RICHARD A BURGESS .......................... INITIALIZATION CODE ......................2.7...... Continuation of IDE Driver Data (protos and defines) ................................................................ 389 IDE DISK DEVICE DRIVER .. IDE Device Driver Code (Initialization) ............................................................................................................... 331 CHAPTER 22..................................................................................................................3....................................................................................................................................... 442 Listing 25............. RS-232 Device Driver Code continued (status record) ...................................14............... 429 KEYBOARD DATA...............2........... 344 Listing 22...................................... Debugger Data and Code.......................................... DEBUGGER CODE ...................................................................................................................................................................4..................... Continuation of IDE Driver Code (Device Interface) ................................ 366 Listing 23........... RS-232 Device Driver Code continued (register bits) ........................................................................................................................ RS-232 Device Driver Code continued (DCB init)............... 405 THE RS-232 ASYNCHRONOUS DEVICE DRIVER............ 436 Keyboard ISR......................... SELECTED DEVICE DRIVER CODE............... Main Operating System Data ............................................................................................................................................................ JOB MANAGEMENT CODE ............................... RS-232 Device Driver Code (defines and externs) ................11................................................................................................................................. 409 Listing 24. End of Initialization Code.......... IDE Device Driver Data (Defines and Externs) ........................ OS Initialization Code and Entry Point............................................................................................... RS-232 Device Driver Code continued (ISR and code) .................................... 321 Listing 21....................................... 438 Listing 25......................................................................................................................................5..................................................................12....................................................................... 438 Reading the Final Buffer ............................................... Continuation of OS initialization............................ RS-232 Device Driver Code continued (DCB) ............................................................................ 337 JOB MANAGEMENT HELPERS .. 319 INTRODUCTION ..................................................... Job Management Subroutines.................................................................................................. Continuation of IDE Driver Data (data structures) ......................................................................................................... 326 Listing 21... 429 INTRODUCTION ........................... 395 Listing 24. Initialize call gates amd the IDT......................................................................................2....................................2................................................................. 337 INTRODUCTION ............... 327 INITIALIZATION HELPERS .................................................. 319 Listing 21...................................................................................................................................................................................................1................................................. Keyboard Translation Code................................................................................ Continuation of IDE Driver Code (code) ...............................................................................................................................................................................................................................................................................4....................................................................... 390 Listing 24.......9.................................................. Initialization Subroutines...................................... 325 Listing 21....................................................................................................................................................................................................................................................................................................................................................................... 442 MMURTL V1........2..8..............................................................1..........h Header File .......13....................

.................................................................... Code to Initialize Video ......................................................................................................462 Listing 26......................................................................474 CHAPTER 28.............................................................................457 VIRTUAL VIDEO CONCEPT ..............4................................................................................................6.......................................................................................................................................................................................................1....................................................................................................................................................................................................................................457 VGA TEXT VIDEO ..........................................................7....... Keyboard Service Initialization .....................539 USING DASM.....................................................................................................................................................................................................................................................................................................540 Listing 28..................473 Physical (Absolute) Sector Numbers...............540 Application Template File......................................................................................453 CHAPTER 26.............................................................................................................................469 Listing 26.... Code to Clear the Screen .........................541 List File Option ......... A More Complicated Template File ......................................................................................................................................................... Code to Set Cursor Position.................................................................466 Listing 26.......10.............................................................................................................. Code to get the Normal Video Attribute...............453 Listing 25..........................9...............448 Listing 25..........................474 Listing 27...............................................................................................................................5.............................................................................................10.............................................................................. Video constants and data ........... DASM...........................................................................443 Listing 25................. Support Code to Set Hardware Cursor Position.............................................................................................. Code for TTY Stream Output to Screen........................457 Listing 26......................................................................................................................................461 Listing 26....................................................... Low-level Keyboard Control Code............................................................................................................................539 DASM CONCEPTS ....................................461 Listing 26..............................................1.................459 Listing 26..........541 Command Line Options ....................................................................................465 Listing 26............................................... MS-DOS FAT-Compatible File System Code .................................................................................................................2....................................... Code to Edit a Line on the Screen...............................................................................................1...........8.....................................470 RELATING MMURTL’S VIDEO CODE TO YOUR OPERATING SYSTEM ........................................................................14.. VIDEO CODE .............. Simple Application Template File .........................................451 Keyboard Service Initialization ......................................................................................................................................................................................... Keyboard system service code.........................................................................................................................................458 Listing 26...............................................................460 Listing 26.........................................451 Listing 25.....................................................................................................3.......................................................15.................................................5.............................12.............................................................. Code to set Normal Video Attribute ....................................................................................................................542 Device Drivers ....473 Logical Block Address (LBA)...................................457 THE VIDEO CODE ....................................................................474 FILE SYSTEM LISTING .................................................. Code to Initialize the Keyboard Hardware ..................................................13....................473 HOW MMURTL HANDLES FAT .............443 Keyboard Procedural Interface ..............541 Errors Only Option ......9........16.....................................................................................................473 MS-DOS Boot Sector .................................................................452 Listing 25.. A 32-BIT INTEL-BASED ASSEMBLER ................................................................ Code to Screen Video Attributes ................................................................................................................................................................................................................................................ Code to Scroll an Area of the Screen ..................450 Listing 25....... Code to Return Current Cursor Position ..............472 CHAPTER 27.................... Debugger Read Keyboard Code ............................................................473 Hidden Sectors.................................................. Code to Place Video Chars Anywhere on the Screen .....457 INTRODUCTION ..................452 Hardware Helpers .......................................................................................... Code to Get the Current Video Owner...............................468 Listing 26..... Code to Get a Character and Attribute.....................................................539 INTRODUCTION .....................................................................................................................450 Keyboard Hardware Set Up.......................... FILE SYSTEM CODE....................540 Listing 28...........................................................................................................................448 Debugger Keyboard.................474 File System Initialization .............................................................................................464 Listing 26....542 MMURTL V1... Code to Set the Video Owner .................................................7............................................................................................................................................................................................................................................................460 Listing 26..........................................................................................................................................................................................................................6...542 Dynamic Link Libraries ...........................................................................................................................The Keyboard Service ........................................................................................0 -xv- RICHARD A BURGESS .....................................................................................................473 INTRODUCTION ...................464 Listing 26.......................................................458 Listing 26.............................................................................. Code for Blocking Read Keyboard call ..................................................................468 Listing 26...2..........................................................................11.......................................8.............................................................................................

............................................................................. 561 IMPORTANT INTERNAL DIFFERENCES ................................................................... Supported Library Functions ........... 562 FAR Calls .......................... 552 EXECUTABLE FILE FORMAT ......................1......................... 543 DASM versus DOS Assemblers .............. 543 Listing 28........................................................................................................................................................................................... 547 DASM Peculiar Syntax ............................................................................................................................................................................................................................................................DASM Source Files ............ Alternating data and code in a template file .............1 ..........................................................................................................................................................................................................................................................................................................................................................................................................................Tag Usage . Standard String Functions for MMURTL .......................................................................................................................................................................................... 552 Table 28...................... 565 Structure Limitations ............................................................................. 545 Storage Declarations.................................................................................................................................................................................................................................................................................................... 547 DW (Define Word) ........................................................................... 558 Your Own Assembler? ............................... 561 INTRODUCTION .......................................... A 32-BIT C COMPILER .................................................. 542 Memory Model...........................................................................................................2 ............................... 545 Table 28....................... 555 Table 28............................................................ 562 CM32 LANGUAGE SPECIFICS ..Supported Instructions ............ 563 Supported C Language Features ............................................................................................................................... 556 ERROR CODES FROM DASM ............................... 549 CALL and JUMP Addresses ..................................................................... CM32................................................................................................................................................................................................................................................3................................................... 548 PUBLIC Declarations............................ Standard file I/O for MMURTL ........................................................................................................ 542 Local (Non-Public) Declarations ..........................................................................................................................................Specifying Memory Operands..3 .................................................................................................................................... 569 Listing 29...... 562 Interrupt Functions.............................................................................................................................................................................0 -xvi- RICHARD A BURGESS .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................... 564 Limitations (compared to ANSI) ........................................................................................................................................................................................................................ 579 INDEX ............................................................................................................................................................................................................................................................................................................................................................................................................................................... 562 Variable Length Argument Lists ........................................................................................................................................................................................................................................ 575 APPENDIX A -.............................................................................................................................................................................................................................................................................. 545 DASM Addressing Modes .......................................................................................................................................................................................... 549 Segment and Instruction Prefixes ....................................... 568 USING CM32................ 569 Library Code Examples ... 556 Tag Descriptions.........................2..................................... 547 DF (Define Far Word) ...... 548 EXTRN Declarations.................................................................................................................................................................... 565 Far External Functions ..................................................................................................................................................................................................................... 547 DD (Define Double Word) .... 549 386 INSTRUCTION SET ............................................................................................................................................................................................................................................................................................................................................................................... 542 DASM Program Structure ....................................................................................................................................................... 543 DOT Commands ........................................................................................................ 570 Listing 29............................................ 565 Type Conversion and Pointers........................................................CD-ROM CONTENTS .......................... 566 Other Implementation Dependent Information......3................................................................................................................................................... 568 Library Functions ............................................................................................................................................................................................................................... 566 Initialization of Static Variables ..................................................................... 559 CHAPTER 29.................................................................................... 581 MMURTL V1............................. 548 Labels and Address............................ 561 MEMORY USAGE AND ADDRESSING .......................................................................................... 570 Listing 29...... 568 Command Line Options ................ 547 DB (Define Byte)........... 563 Library Header File Access....................................................................................................................................................................................

MMURTL V1.0 -xvii- RICHARD A BURGESS .

.

When I started out. It just seemed there should be a more "down to earth" book that documents what you need to know to get the job done. but very light on specifics. and begin coding. although I'm sure it would have increased my productivity a little while writing my own. Unfortunately. as viewed by the outside programmer." but the simplest items on the wish list for a piece of software can add years to the critical path for research and development. I did not intend to let compatibility issues drive my design decisions. and advice into the seemingly endless tasks facing someone that really wants to sit down and create one.Chapter 1. not his IQ. As coding began. memory model. When OS/2 was first developed. It's up to you. They use them. IBM. I thought the “specs” would be the easy part. My first list of desirables looked like a kid's wish list for Santa Claus. In the early days. may want compatibility if you intend to write your own operating system. NOT SO FAST THERE. You don't need to design your own. I have been involved in many large software projects where others conceived the. and they even work "around" them to get their jobs done. You also don't have to be Einstein to write one. Along my somewhat twisted journey of operating-system design and implementation. you were pretty much expected to recompile something if you wanted it to run on your particular flavor of the system. I'm willing to bet you've pondered changes you would make to operating systems you use. I'm sure you've noticed that this has changed substantially. especially if you have a huge programming team of one person. You. Writing my own operating system has turned into a very personal thing for me. I have Albert's absent-mindedness. One of the single most obvious concerns for commercial operating system developers is compatibility. because source-code compatibility was the main objective. What I discovered is that most of the books about operating system design are very heavy on general theory and existing designs. MMURTL V1.0 -1- RICHARD A BURGESS . If you write something that no one can use. or Sun. portability issues size and speed. are the tasking model. BUCKO! I may not have to tell you this if you've done some serious "code cutting. I set the ground rules. do some research on each of the desirables. or even thought about what you would build and design into one if you were to write your own. however. In the case of my own operating system. I would just make a list. You don't have to be Albert Einstein to figure out that many labor years go into writing a computer operating system. Each of the versions of MS-DOS that were produced had to be able to run software written for previous versions. It didn't provide the needed compatibility to run programs that business depended on. The most obvious of these. I've learned a lot from all that I've read. it's unlikely they'll use it. Realizing this. Even though I refer to it as unfinished. It's my belief that when a piece of software is "finished" it's probably time to replace it due to obsolescence or lack of maintenance. I hadn't been a good-enough boy to get all those things (and the average life expectancy of a male human being wouldn't allow it either). the "DOS-BOX" was a bad joke. That was part of my wish list. I decided what stayed and what went. Unix had a somewhat more luxurious life. I was not out to compete with the likes of Microsoft. Late in 1989 I undertook the task of writing a computer operating system. and the programming interface. and I was to turn them into working code and documentation. they work with them. But I wanted a small API. I determined the specifications for the final product. certain not-so-obvious ingredients came into play such as the OS-to-hardware interface. it's a complete system. I'm not knocking the authors of these books. If you're an experienced programmer. I have documented what it takes to really write one. code. Introduction Overview Computer programmers and software engineers work with computer operating systems every day. and I've included the "unfinished" product for you to use with very few limitations or restrictions. I whittled away to find a basic set of ingredients that constitute the real make-up of the "guts" of operating systems. One thing about desiring compatibility is that the interfaces are all well documented.

Who Should Use This Book This book is for you if you are a professional programmer or a serious hobbyist. Even though they have added speed. Throughout this book. Don't get me wrong I'm an avid C++ and OOP user. so I've removed the mystique.to 32-bit jump. even though the Pentium provides 64-bit access. hardware interface. along with all the tools necessary to build. and some serious "turbo" modifications. You may or may not want to do this for your system. I have included the complete source code to my 32-bit. the next quantum leap really hasn't been made. change it to suit your needs. For this reason. Serial and Parallel Ports. size and speed. The 486 and Pentium series have maintained compatibility with the 386-instruction set. For instance. Priority Interrupt Controller Units. One thing I didn't put on my wish list was documentation. This was completely intentional. The C Programming language (a C compiler is included Intel 80386/486/Pentium Paged memory operation and management using the processor paging hardware Intel 80386/486/Pentium 32-bit hardware and software task management using the processor hardware task management facilities. Well-commented code examples are worth ten times their weight in generic theory or simple text-based pseudo-algorithms. 80486 (486). message based operating systems PC Industry Standard Architecture hardware management including: DMA Controllers. It clouds far too many real issues these days. would help me write better application software for it. operating system (MMURTL) which is designed for the Intel 386/486/Pentium processors on the PC Industry Standard Architecture (ISA) platforms. written in 32-bit Intel based assembly language and C. If you know a fair amount about the Intel 32-bit processors. I'm sure it won't. I always thought that many systems lacked adequate documentation (and most of it was poorly written). I use the operating system I developed (MMURTL) to help explain some of the topics I discuss. the memory model. In my opinion. This was the 16. Once again. or all of it if you wish to write your own. and then finally discuss my code in great detail. Hardware Timers. MMURTL V1. multitasking. But that's not what this book is about. your goals may be different. I back up these discussions showing you what I decided on (for my own system). Embedded or dedicated systems using the 32 bit PC ISA architecture real-time. The hardware requirement section below tells you what you need to run MMURTL and use the tools I have developed. or write your own.What This Book Is About In this book I discuss the major topics of designing your own OS: the tasking model. I wanted to know more about how the system worked internally. a few more instructions. you'll notice the ever so popular term "object oriented" blatantly missing from any of my discussions. It's not really required if you decide on a different platform and simply use this book as a reference. I have completely documented every facet of my computer operating system and included it here. You can use pieces of it. and use the operating system are included on the accompanying CD-ROM. The source code. they are effectively super-386 processors. you know that a quantum leap was made between the 80286 (286) and 386 processors. and Pentium processors as if they were all the same. message based. ideas from it. redesign it to meet your requirements. but it was a "must" for me. and you have an interest in any of the following topics: Writing a 32-bit microcomputer operating system 80386/486/Pentium 32 bit assembly language (an assembler is included). which I felt. Hard/Floppy disk controllers File systems (a DOS File Allocation Table compatible file system in C is included) You may note that throughout this book I refer to the 80386 (386). Your wish list may not be exactly like mine. The only legal restriction placed on your use of the source code is that you may not sell it or give it away. real-time. will provide the information for you to use my system as is. The “Architecture and General Theory” section of this book along with the sections that are specific the MMURTL operating system.0 -2- RICHARD A BURGESS . programming interfaces. portability issues. In fact. I think you will find that it will help you if you intend to write your own. If you don't like the way something in my system is designed or implemented. modify.

or even sharing the processor between multiple programs loaded in memory.Those of you that program on the Intel processors know all about segmentation and the headaches it can cause (Not to mention Expanded. LOMEM. and also what I decided on. YOURMOTHERSMEM. I discuss some of your options in chapter 8. and the techniques may change based on your platform and processor of choice. HIMEM. yet allow us the power to do what we wanted. I didn't want to do this. Simple descriptive names of procedures and structures are used throughout the system. You can also gather some serious insight to my wish list in chapter 2. uncluttered Applications Programming Interface (API) specification. if we knew how. Easy programming . I discuss this in chapter 4 (Interprocess Communications). I'll discuss some of your options. and I stuck with a very simple paged model that made use of the Intel hardware. is with a procedural interface.My Basic Design Goals (yours may vary) My initial desires that went into the design of my own operating system will help you understand what I faced. I recommend you take care not to make it an "endless" list. We want the system to react to us. “Programming Interfaces. and task management native to the Intel 32-bit x86 series of processors.The most common 32-bit platform in the world is the PC ISA platform.. If you're going to write your own. How many do you think are out there? Millions! How much does a 386SX cost these days? A little more than dirt.” and chapter 8. Message agents could be added to any platform. This is also discussed in chapters 3 (Tasking Model). the right mix of capability and simplicity. and 4 (Interprocess Communications). but I think you'll see the push in industry is towards real-time systems. This could be a function of the hardware as well as the software. are outside events. Common affordable hardware platform with minimal hardware requirements . but real multi-threading . A message-based operating system seemed the best way to do this. If it is another processor.Not just task switching between programs. or start one from scratch. I added the Request and Respond message types to the more common Send and Wait found on most message based systems.Keep the system as simple as possible. the Request and Respond primitives could be removed. such as C or Pascal. I also wanted to maintain a small.The ability to react to outside events in real time. General Discussion and Background. you may have a different platform in mind. and what my end result was. I would shoot for less than 200 basic public functions.” Simplicity . archaic names and labels so often used in the computer industry.Many languages and operating systems ported between processors tend to ignore many of the finer capabilities of the target processor. Synchronization of the messages would be the basis for effective CPU utilization (the Tasking Model). or maybe a little less? Of course. but not all the options.” Protection from other programs . We. Computers using the Intel compatible 32-bit processors are cheap. yet powerful enough to get the job done. and most of them are grossly under used. Chapter 5 covers memory management. Client/Server design . I take a generic look at it in chapter 6. Here was my wish list: True Multitasking . but I don't recommend it. Extended. and 4 (Interprocess Communications). Many detailed books have been written on schemes for memory management. This design goal demanded MMURTL to be message based. I have attempted to isolate the API from the hardware as much as possible (for future source code portability). “The Hardware Interface. A procedural interface directly into the operating system (with no intermediate library) is the easiest there is. many of the items that I use and discuss may not apply. We also wanted to reduce the "jargon" level and minimize the number of terse. in chapters 3 (Tasking Model). Use the CPU Instruction Set as Designed . Power without BLOAT. UMBs. I wanted an OS that would prevent another program from taking the system down. “The Hardware Interface.The easiest way to interface to an operating system from a procedural language.0 -3- RICHARD A BURGESS .But not the programmer. The hardware interface may radically change based on your choice. I use the stack. and who knows what other memory schemes and definitions are out there). hardware paging. Real-time operation ..” Flat 32-Bit Virtual Memory Model .the ability for a single program to create several threads of execution that could communicate and synchronize with each other while carrying out individual tasks.The ability to share services with multiple client applications (on the same machine or across a network). LIM. I discuss this in chapter 6. You may start with my list and modify it. “Programming Interfaces. The messaging system would be the heart of the kernel. If you don't have this requirement. adding only the necessary calls to get the job done. MMURTL V1. You may want your operating system to run on another platform or even another processor. calling conventions. and for a very good reason. You could go nuts here and throw in the kitchen sink if desired. the humans. MMURTL would use the memory paging capabilities of 386/486 processors to provide an EASY 32-bit flat address space for each application running on the system. Real-time operation may not even be on your list of requirements.

Sections of the source code will. Dedicated. Message based. this means it will even run on an 80386SX with one megabyte of RAM (although I recommend 2Mb). Then again. it runs rather well on a Pentium with 24 MB of RAM too. be of value in your other programming projects as well. If you don't want to start from scratch. or you have an embedded application for MMURTL. operating system designed around the Intel 80386 and 80486 processors on the PC Industry Standard Architecture (ISA) platforms. complex equipment control is not beyond MMURTL's capabilities. but only a distant cousin. Real-Time. excluding dynamic allocation of system structures after loading. Its closest relative is CTOS (Unisys). Multitasking. make up your own! But. Similarities to Other Operating Systems MMURTL is not really like any other OS that I can think of. One of the goals was to keep the basic system under 192Kb (a maximum of 128K of code. I put together my own operating system. In the educational field. I'm sure. and you want to use MMURTL as is (or with minor modifications). and the initialization entry point jumped to. Your creation will also take on a personality of its own. and only because of the similar kernel messaging schemes. And of course. MMURTL (pronounced like the girl's name Myrtle) is a 32-bit. Vertical applications of any kind can use MMURTL where a character based color or monochrome VGA text interface is suitable. MMURTL is designed to run on most 32-bit ISA PCs in existence. MMURTL would also be suitable for ROM based embedded systems (with a few minor changes). The name is an acronym for Message based MUltitasking. MMURTL V1. If you're not worried about memory consumption. the code. or use any of the tools I included. The real-time nature of MMURTL makes it ideal to handle large numbers of interrupts and communications tasks very efficiently. MMURTL can be the basis or a good starting point for you very own microcomputer operating system. The OS could be moved from ROM to RAM. or as a dedicated interface between existing systems. expand it to your heart's desire. If you intend to run MMURTL. nor is it intended to directly replace them. the tools. The heavily commented source code and indepth theory covered in this book makes it ideal for a learning/teaching tool.A Brief Description of MMURTL From my wish list above.0 -4- RICHARD A BURGESS . kerneL. MMURTL can be the foundation for a powerful dedicated communications system. Uses for MMURTL If you are not interested in writing your own operating system (it CAN be a serious time-sink). no doubt. even if they aren't on ISA platforms or using message based operating systems. Real-Time. and 64K of data). or for general reference. MMURTL can be used to teach multitasking theory. It is an ideal learning and/or reference tool for programmers working in 32-bit environments on the Intel 32-bit. It is RADICALLY different in that SIMPLICITY was one of my prime design goals. and the information you need are all contained here so you can make MMURTL into exactly what you want. I will describe it to you here so that you can see what I ended up with based on my list. If you don't like my acronym. see the “Hardware Requirements” section later in this chapter. x86/Pentium processors. Yes. here's what I see MMURTL being used for: MMURTL can be considered a general-purpose operating system and dedicated vertical applications are an ideal use. MMURTL is not designed to be compatible with today's popular operating systems. I warn you: Acronyms are in short supply in the computer industry.

or one of the many clones in existence that executes the full 80386 instruction set. 2 MB (or more) is recommended. BIOS types and versions don’t matter. VGA videotext (monochrome or color) is required. but still not enough that you could ever confuse the two. You simply run the MMURTL OS loader from DOS and the system boots. MMURTL will handle up to 64 Megs of RAM. via the keyboard serial controller (most are. MMURTL V1. If you intend to build on a platform other that described here. The processor must be an Intel 80386. also – 2000). but still. Hence. Rebooting back to DOS is a few keystrokes away. some serious fun can had by writing a much better file system than I have included. Some RLL controllers will not work properly with the current hard disk device driver. Most I have found exceed this specification and are fully compatible. MMURTL itself uses about 300K after complete initialization. Other 16/32 bit bus designs may also work. The A20 Address Gate line must be controllable as documented in the IBM-PC AT hardware manual. A standard 101 key AT keyboard and controller (or equivalent) is required. IDE is preferred.0 -5- RICHARD A BURGESS . One Megabyte of RAM is required. The hardware (computer motherboard and bus design) must be PC ISA (Industry Standard Architecture). This makes it easy to use MMURTL. Full 32-bit device drivers control all hardware. Hardware Requirements This section describes the hardware required to run MMURTL (as included) and to work with the code and tools I have provided. The file system included with MMURTL uses the MS-DOS disk structures. No reformatting or partitioning your disks. IBM and other clone processors. MMURTL has its own loader to boot from a disk to eliminate the need for MS-DOS altogether. eases the development burden. not at all the same.5" are supported. MMURTL does NOT use the BIOS code on these machines. 80486. The parallel port should follow the standard IBM AT parallel port specification. Pentium. based on your system manufacturer's documentation (if you can get your hands on it). Note: PCI bus designs work well. The Floppy disk controller must be compatible with the original IBM AT controller specification (most are). If yours isn't. you can change the code as needed. 8250. you can ignore this section. There are some 100 million+ disks out there with MS-DOS FAT formats. and more importantly. AMD. MMURTL accesses the VGA text memory in color text mode (which is the default mode set up in the boot ROM if you have a VGA adapter).The flat memory model implementation is a little like OS/2 2. but minor changes to specialized interface hardware may be required. The hard disk controller must be MFM or IDE (Integrated Drive Electronics). Some of the public and internal calls may resemble Unix a little. that is. EISA (Ed.x (IBM). but only out of convenience. If you want to run DOS. (or even MS-DOS). This includes Cyrix. Both 5. but a few are not). It certainly wasn't because I liked the design or file name limitations.25" and 3. 16450 or 16550 serial controllers are required for the serial ports (if used). If you don't want to write a completely new operating system.

.

take away. You Are Where You Were When The heading above sounds a little funny (it was the title of a popular book a few years back. You know the adage (or a similar one): Take the time estimate. It was a 32-bit system. MMURTL V1.0 -7- RICHARD A BURGESS . Things change a lot in the industry in five years. I also give you an idea of the steps involved in writing an operating system. but I'm happy to find that many of the concepts I was interested in have become a little more popular (small tight kernels. A friend of mine and I began by day dreaming over brown bag lunches in a lab environment. Far too often. Fud’s Law of Software Design Time Estimates came into play rather heavily here. and it was very intriguing (even though punching in hundreds of octal codes on the front panel made my fingers hurt). no doubt. etc. The term "register" has a whole new meaning when you are looking for a bit that intermittently drops out in a piece of hardware that size. This lead to a fairly concrete set of design goals that I whittled down to a size I thought I could accomplish in two years or so. for no apparent reason). and just make it better if you have the desire. What I don't know fills the Library of Congress quite nicely. I have tried to stick with them as close as possible without scrimping on power or over-complicating it to the point of it becoming a maintenance headache. my methods may not seem obvious to the casual observer. my reading of various publications and periodicals. It usually also happens that software will expand to fill all available memory (and more!). which I took on-line from the University of Minnesota in 1982. We bantered back and forth about what we would build and design into a microcomputer operating system if we had the time and inclination to write one. It does. improve. and from there I did everything I could to get into the "computer science" end of life. client server designs. In fact. multiply by two and add 25 percent for the unexpected. my schooling. I was trained and tasked to maintain (repair and operate) a Honeywell DDP-516 computer system. This was a good while before I was ever introduced to PC-DOS or MS-DOS. That was 1989. “Overview. add. It whet my whistle. That worked out just about right. Five years later. My first introduction to computers was 1976 onboard a ship in the US Coast Guard. however. I bought many little computers (remember the Altair 8800 and the TRS-80 Model I? I know. merged). General Discussion and Background This section discusses the actual chore of writing an operating system. MMURTL's design is influenced by all of my experiences with software and hardware. Every little thing you've run across will. simplified memory management.000. As you can see. In Chapter 1. Reading this chapter will help you understand why I did things a certain way. It had a "huge" 8K of hand-wound core memory and was as big as a refrigerator (including icemaker). I wrote one over a period of almost 5 years. Burroughs and Sperry. as well what may influence your design. a piece of software becomes too complex to maintain efficiently without an army of programmers.Chapter 2. Imagine an 8086 based system with an eight-inch 10-MB hard drive costing $20. But the history of MMURTL and why certain decisions were made can be valuable to you if you intend to dig into the source code. affect its design. I hope this book will shave several years off of your development effort. or even just use MMURTL the way it is. Where Does One Begin? Where does one begin when setting out to write a computer operating system? It's not really an easy question to answer. and "Where You Were When" will certainly give you a different perspective on what you would want in your own system. you might not want to). has had an effect. here it is. the convergent hardware and the CTOS operating system reached the market at about the same time. It was an Intel 8086-based system and ran a multitasking. I would not let this happen. a.a. and what I have been introduced to by friends and coworkers.k. In 1981 I was introduced to a computer system made by a small company named Convergent Technologies (since swallowed by Unisys. write your own operating system. make a lot of sense. I might add). I have no doubt that your background would have a major effect on your own design goals for such a system. Even a course titled Structured Programming with FORTRAN 77. Borrow from mine. and borrowed with great respect.” I covered what my final design goals were.). I spent 20 years in the military and took computer science courses when time allowed (Uncle Sam has this tendency to move military people around every two or so years. real-time operating system from the very start (called CTOS).

however. Not having to start from scratch was a major advantage. but not many people were using them for 32-bit development five or six years ago on the Intel based platforms. I began with Microsoft's assembler (version 5. Nothing about my operating system is very new and exciting. in the case of having the source code to an assembler I was comfortable with. I wanted to eventually port them to the finished operating system. at times it was quite difficult. which at the time was still 16-bits anyway. Operating system developers. This lack was disappointing because I wanted a multitasking OS running on a piece of hardware I could afford to own! In this early introduction to CTOS. Some assemblers were available in the "public domain" but came with restrictions for use.x assembler and 2. maybe. but as you well know. MS-DOS and the capability for the Intel processors to execute some 32-bit instructions in real mode (while still in 16-bit code segments) made life a lot easier. There were actually several advantages to using MS-DOS in real mode during development. there was no such thing..0 -8- RICHARD A BURGESS . It was definitely a "chicken-and-egg" situation. and they didn't execute all the instructions I needed anyway. popular platform. MS-DOS was everywhere.all but ancient history now. The next major decision was the software development environment. compiler. combined with my experience on the Intel X86 processors.After I got involved with the IBM PC. I even called and inquired about purchasing limited source rights to MMURTL V1.0) and later moved to 5. I could actually experiment with 32-bit instructions without having to move the processor into protected mode and without having to define 32-bit segments. The ability for a programmer to determine what was important while the software was running. I really had no choice. It can be a little confusing and frustrating. It was limited to the small memory model. in the process of using these other tools. People are just now seeing the advantages of message-based operating systems. I had two computers at that time (386DX and a 386SX) still running MS-DOS and not enough memory or horsepower to run UNIX. The popularity of the Intel processor and the PC compatible systems was. Again. Message-based.x linker. I'm not a Borland salesman. I thought about other processors.1. a lure that could not be suppressed. I purchased technical documentation for Motorola's 68000 series. The Development Platform and the Tools My desire to develop an operating system to run on an inexpensive. but the tools just worked better. The messaging is based on theory that has been around since the early 1970's. one of the first tools required on any new system is the assembler. was a must. I also tasted "real-time" operation in a multitasking environment. clinched my decision on where to start: It would definitely be ISA 386-based machines (the 486 was in its infancy). I really do hate reinventing the wheel. National Semiconductor's N80000 series. but the tools worked. As you may be aware. I kept looking for the rich set of kernel primitives I had become accustomed to for interprocess communications and task management. Being in real mode and not having to worry about circumventing protection mechanisms to try out 32-bit processor instructions actually lead to a reduced development cycle in the early stages. these problems pretty much forced the move to all Borland-tools. people porting UNIX systems and those building 32-bit DOS extenders were the bulk of the users. It is just now becoming popular. I wanted the finished product to have its own development environment . or even OS/2. Trying to see the exact development path was not always easy. The 32-bit capabilities were added to these assemblers to take advantage of the capabilities of the new processors. I found some serious bugs in them when used for 32-bit development. Having assembler's available for the processor from the start made my job a lot easier. like a bad virus. however.. yet you need the 32-bit tools to develop the 32-bit OS. It's the place to start. How many of you have heard of or remember ISIS or PLM-86? These were some of the very first environments for the early Intel processors . Memory access was limited. The Chicken and the Egg The egg is first. and a few others. Of course. Two years later I ran into problems with the Microsoft linker and even some of the 32-bit instructions with the assembler. modular micro kernels seem to be coming of age.including assembler. Once again. or ported to a system. I even put a 32-bit C compiler in the public domain for MS-DOS as freeware. Then those were the 2. Utilities and compilers are usually the next things to be designed. You need the 32-bit OS to run the 32-bit tools. and utilities . and also the luxury of changing tasks based on outside events in a timely manner. but it worked nonetheless. In fact. There was no turning back. access and familiarity played a role in deciding the tools that I would use.yet I needed to have them in the MS-DOS environment to build the operating system.

But it gave me some serious insight into the operation of the Intel processor. which is 32 bits for the most part. I do so because its bus. “DASM: A 32-Bit Intel-Based Assembler. The IBM PC-AT and Personal System/2 Technical Reference Manuals were a must. The source code to this assembler (DASM) is included with this book on the CD-ROM. and a few other minor utilities." It's not that the programmers that put it together aren't top quality. Now THAT'S a digital nightmare. and technical manuals were used throughout the early development of my operating system. There is no linker.” Assembly language has its place in the world. All of the ISA type computer systems use VLSI integrated circuits (Very Large Scale Integration) that combine and perform most of the functions that were done with smaller. It's simply not needed. Communications controllers. you will need to read chapter 28. want or need. Most of the machine-dependent items are left completely external to the language in libraries that you may. Many different books. which is fed to the assembler. Nothing is harder to troubleshoot than a stack gone haywire. the external bus interface on the ISA machines is still 16 bits. It is tied to the internal bus. I dealt with both the electrical and timing characteristics. Developing this assembler was. a major task. (Unless you were the person that wrote that particular manual. DC. and ancillary logic design form the basis for the 32-bit ISA internal architecture. I plead the "Fifth" on this. I have worked for years with various forms of hardware. IBM published theirs. and the called function cleans the stack. such as Chips & Technologies.some tools from the major software vendors. I started with a public-domain version of Small-C (which has been around quite a while). But C's big strength lies in its capability to be ported easily. I think many people started there. the cost was prohibitive (they mentioned dollar figures with five and six digits in them. even though ANSI standards have fixed much of it. no linker. I mention this now to explain my need to do some serious C compiler work. in itself. it's the limitations of size (memory constraints) and possibly the need to protect trade secrets that create the spaghetti-code effect. interface design. The thought of controlling all of the hardware (Timers. at least. took a good look at Dave Dunfield's excellent Micro-C compiler. Some very important pieces are still missing from the compiler. In fact. or may not. discreet devices in the IBM AT system. articles. TTL. Following superfluous JMP instructions is not my idea of a pleasant afternoon. But there is nothing like a high level language to speed development and make maintenance easier. C can be made almost unreadable by the macros and some of the language constructs. That set me back four months. but it leads to some serious trouble on occasion. These documents are usually free. SX MMURTL V1. The C compiler produces assembly language. Those that have to maintain code or read code produced by others understand the advantages if they get past the "popularity concept". Besides.) This lead to the development of my own 32-bit assembler. I was into Pascal long before I was into C. That's right. but another port is in the works. etc. you know that it's usually NOT everything you need. you understood it perfectly) You will need to accumulate a sizable library of technical documentation for your target hardware platform if you intend to write an operating system.) in the machine was actually a little exciting. DMA. They give you the overall picture of how everything in the system ties together.0 -9- RICHARD A BURGESS . including most of the digital logic families (ECL. This is obviously for variable length parameter blocks. MMURTL uses the Pascal conventions (refereed to as PLM calling conventions in the early Intel days). supply excellent little documents that give even finer details on their particular implementation of the AT series ISA logic devices. as you may understand. Needless to say. etc. documents. To fully understand this. “CM32: A 32-Bit C Compiler. If you've ever looked though technical manuals that are supposed to give you "all" of the information you need to work with these integrated circuits. as well as the software control of these devices. I keep referring to the IBM AT system even though it was only an 80286 (16-bit architecture). Parameters are passed left to right. I'm not going to tell you I did some disassembly of some BIOS code. The biggest problem was accumulating all of the technical documentation in adequate detail to ensure it was done right. Computer chip manufacturers. in which case. I actually prefer Pascal and Modula over C from a maintenance standpoint. parameters passed from right to left and the fact that the caller cleans the stack.. BIOS code brings new meaning to the words "spaghetti code.). The prohibitive costs also led to the development of a 32-bit C compiler.” Early Hardware Investigation I taught Computer Electronics Technology for two years at the Computer Learning Center of Washington. but I pretty much built from scratch. I don't like the calling conventions used by C compilers.. The details of CM-32 (C-Minus 32) are discussed in chapter 29. a disassembler. or even port MMURTL to it.

from a 16-bit code segment. There was no loader. The largest of which would mean that all memory access would be based on 48-bit (far) pointers. But as you may be aware. This would have given us a zero-based addressable memory area for all code and data access for every program. It was proven that "PC" style computer manufacturers that deviated from this hardware design to the point of any real low-level software incompatibility usually weren't around too long. aliasing is also required. but it's not at as complicated.two segments per program and two for the OS. gentler" environment I was after. The Real Task at Hand (Pun intended) All of the hardware details aside.x. this is a year you won’t have to go through.] Much of the early development was a series of small test programs to ensure I knew how to control all of the hardware such as Direct Memory Access (DMA). because of what you get on this book’s CD-ROM.). Many bus designs now out take care of this problem (PCI. etc. If you use Intel-based platforms. My initial thought on memory management centered on segmentation. as you will see. Memory Management. All of the early test programs were in assembler. and I went straight to fully paged memory allocation for the operating system. Windows version 3. the BIOS code is not 32-bit (not even on 32-bit machines). This simple model could allocate and manage memory.. It has turned out very nicely. A variety of more recent operating systems go directly to a lot of the hardware just to get things done more efficiently.g. all implementations of UNIX). you must create an alias selector to be used by one of the programs. etc. but this is transparent to the programmer. In a paged system. It is also not designed to be of any real value in most true multitasking environments. It was a "kinder. easy to use system with a tremendous amount of documentation. but it presented several serious complications. but necessary to prove some of my theories and hunches. [Thunking is when you interface 32. timers. I touched on what I wanted in the overview.). all based on their own selectors. In chapter 5. This generally involves manipulating the stack or registers depending on the calling conventions used in the destination code. but not a fully segmented model . It took almost two years to get a simple model up and running. no way to get test programs into the system. During this time. and the Priority Interrupt Controller Unit (PICU). I discuss several options you have. powerful. The segmented memory idea was canned. even the operating system writer. If you start from scratch on a different processor you will go through similar contortions. It would have appeared as a flat memory space to all the applications. anyway (e. a speed penalty is paid because of the hardware overhead (loading shadow registers.and 16-bit code and data. I was also building the kernel and auxiliary functions that would be required to make the first executable version of an operating system. The thought was rather sickening. my original goal was a multitasking operating system that was not a huge pig. An example would be making a call to a 32-bit section of code. and had only the most rudimentary form of messaging (Send and Wait). If two programs in memory must exchange data and they are based on different selectors.machines still have 16-bit memory access limitations. I had to meet my design goals. EISA. but putting in on paper and getting it to run are two very different things. There wasn't even a file system! Test programs were actually built as small parts of the operating system code. Even machines that were technically superior in most every respect died horrible deaths (remember the Tandy 2000?).0 -10- RICHARD A BURGESS . and the primary emphasis was on the kernel and all that that implies. When the 386/486 loads a segment register.x. "Thunking" was out of the question. If you write your own system. If you intend to use the Intel processors and start with MMURTL or the information presented in this book. The second six months was spent moving in and out of protected mode and writing small programs that used 32-bit segments. This was timeconsuming. I knew from the start that this would be a necessity. One other consideration on the memory model is ADDRESS ALIASING. MMURTL V1. and the entire system was rebuilt for each test. you'll be ahead of the game. and some initial testing showed that it noticeably slowed down program execution. but instead a lean. of course. anyway. This easily consumed the first six months. don't lose sight of the forest for the trees. MS-DOS didn't really have to worry about the specific hardware implementation because the BIOS software/firmware writers and designers took care of those details. as well as the stack storage. Some processors (such as the Intel 32-bit X86 series) force a specific element size for the stack. OS/2 version 2. This made the IBM designs the natural place to start.

If you are writing an operating system for the learning experience (and many people do). In chapter 5 (Memory Management) I give you some options. the actual application may not be so important. I say this from experience. But. I'll give you a list of these items in the approximate order that I found were required while writing my system. If this has never happened to you. Many times I have taken a piece of software that I didn't fully understand and "gutted" it. but I'm very serious. Get to know your assemblers. The books I have purchased on operating system design gave me a vast amount of insight into the major theories. Know your desired application. I was really interested in communications. The theory behind what you want is the key to a good design. This includes the tasking model. it is by no means the only operating system source code available. But I'll try. Try to ensure it will still be in production when you think you'll be done with your system. Memory management is next. you'll find that there are major items to be accomplished. and overall concepts concerning operating system design. the rest is real work. There are so many platforms out there. you will no doubt find bugs. I have provided such an operating system with this book. the discoveries . Even though you may think that the programmers that wrote those tools know all there is to know about them. and there will also be background noise. but there wasn’t. or theories must be proved in order for you to continue along the projected path. at the beginning of the yellow brick road. you know that certain items in a project must work. You may need to choose a hardware platform prior to making your memory model concrete. I wish there had been some concrete information on really where to start. "You start here. memory model and basic programming interface. but whatever you decide on. Chapter 6 (The Hardware Interface) will give you some hints on this. This is called the critical path. compilers and utilities. you may want to concentrate on the device and hardware interface in more detail. of course. how much knowledge you have.0 -11- RICHARD A BURGESS . Go for the kernel. Things change pretty rapidly in the computer industry. it may be CISC. If you've gotten past the fun parts (kernel and memory). unless you go with the same hardware platform I did. The Critical Path Along your journey. Investigate your tools. hints are about all I CAN give you. Chapter 3 and 4 (Tasking Model and Interprocess Communications) will give you plenty of food for thought on the tasking model. This can be tricky because you may be playing with hardware and software in equal quantities.A Starting Point If you have truly contemplated writing an operating system." It is difficult for me to do this also. You can pretty much take these pieces everything from device drivers. You'll probably be able to see that as you read further into this book. garbage collection. Document the problems. and overall program management – as you see fit. Operating systems use processor instructions and hardware commands that applications and even device drivers simply never use. you know that it can be a very daunting task. If you choose to modify an existing operating system. The level of complexity. if you are interested in embedded applications for equipment control. It will be worth the time spent. get to know it very well. Play with the hardware Ensure you understand how to control it. This includes understanding some of the more esoteric processor commands. Of course.anything that you think you'll need to remember. and also dive into some more challenging possibilities. If you've ever done any large-scale project management. only to find out that it had incorporated many of the pieces I needed – I simply hadn’t understood them at the time. I'm not going to recommend any others to you. study what you have and make sure you completely understand it before making modifications. This may sound a little humorous. I envy you. and you may find you really have something when you're done with it. because I don't know exactly where you're coming from. You can then “port” the prototype. MMURTL V1. For example. you'll NEVER really be done with it. Or at least when you get it working. but they just didn't say. I'll tell you the easiest. will depend on the memory model you choose. If you have to build a prototype running under another operating system. I blamed myself several times for problems I thought were mine but were not. Select your hardware platform. Decide on your basic models. The easy way out would be to take a working operating system and modify it to suite you. or how much time you really have to invest. It may be RISC. do it. Your target application will help you determine what is important to you.

I actually wrote major portions of my programming documentation before the system was even usable. Don't wish for a Porche if what you need is a good. But really. As I mentioned above. Nothing is worse than finding out that the foundation of your pyramid was built on marshmallows. but it gave me great insight into what a programmer would see from the outside. sweat and maybe some tears. I spent a good six months playing with assemblers.Working Attitude Set your goals. it's worth the time. I lay out my notes for what I want. Even if you have to write four or five small test programs to ensure each added piece functions correctly. Sure the Porche is nice…but think of the maintenance (and can you haul firewood in it?)Make sure your tools work. Make certain you understand the capabilities and limitations of them. I write the documentation. Sounds backwards? It is. Work with small. and plenty of "theory food" for the engine (your brain). MMURTL V1. Document as you go. I'll admit that I had to do some major backtracking a time or two. dependable pickup truck. I had my wish list well in advance. When writing my memory-allocation routines I must have written 30 small programs (many that intentionally didn't act correctly) to ensure I had what I thought I had. easily digestible chunks. and then I write the code. compilers. I actually had to switch assemblers in midstream. But don't set them too high.0 -12- RICHARD A BURGESS . Problems with programming tools were something I couldn't see in advance.” Once satisfied. These tools include documentation for the hardware. Creating a realistic wish list may be one of the hardest jobs. and utilities to ensure I was “armed and dangerous. Good luck. I moved on from there. it won't be luck. It will be blood. all the way.

Before I begin a general discussion or cover your options on a tasking model. and I'm not talking about programming languages. MMURTL V1. at least temporarily. so I imagine most of you will be working with it. The preemptive multitasking I'm talking about. and non-multitasking systems use them to steal time from programs and simulate true multitasking (e. I use the same definition as the Intel 80386 System Software Writer's Guide and the 80386 and 80486 Programmer's Reference Manuals. it’s PREEMPTIVE. Terms We Should Agree On One thing the computer industry lacks is a common language. The Intel based hardware is by far the most available and least expensive. It shouldn't be. may use other words that mean the same thing such as "interprocess communications. The operating system functions that directly affect CPU tasking are called kernel primitives. My definition of Preemptive Multitasking: If an operating system has the ability to stop a task while running before it was ready to give up the processor. it probably isn't much more than two or three kilobytes of code. You can transform the same 50 processor instructions in memory into 20 independent tasks.Terminate and Stay Resident programs). more highlighter and chicken scratching than you can imagine).g. You may not agree with the definitions. There are many ways to do this in a computer operating system. After all. CPU utilization. but I hope you will accept them. The word preempt simply means to interrupt something that is currently running. Some operating systems call a task a “process. KERNEL . it is responsible for the way the CPU's time is allocated.” while others call it a “thread. but doesn't expect the interrupt. all the grumbling and heated discussions I see on the on-line services. but it is. but some of it can also be attributed to laziness (yes. This is convenient if you want to refer to these documents while reading this book. In other words. I have seen the term thread applied to additional tasks all belonging to one program. Most of the discussion centers on resource management. coffee and soda spilled on them." which means communications between tasks. while your reading this book or working with MMURTL. It also seems that a lot of people use the term and they don't even know what it is.” In most instances I call it a TASK. resource management is supposed to be what a computer operating system provides you. Some documentation. Hence. This makes it a very small kernel. but its tasking model may not be the correct one for the job you want to accomplish (or you may not be using it correctly).0 -13- RICHARD A BURGESS . and most people refer to. then start another task running. This might allow me to use the latest techno-buzz-word "microkernel" if I were into buzzwords. One type of preemption that occurs all the time is hardware interrupts.This is a hotly disputed term (or phrase) in the industry. One computer term may mean six different things to six different people. When a task is suspended from execution.A task is an independently scheduled thread of execution. I admit some guilt here too). It really depends on the documentation with the operating system you are using. however. be specific about it.. and more specifically. But this is not cast in stone either. but because they are the lowest level entry points into the kernel code of the operating system. PREEMPTIVE MULTITASKING . its hardware and software state are saved while the next task's state is restored and then executed. or at least coming to agreement on them. Most of the problem is caused by hype and deceptive advertising. I have to define some terms just to make sure we're on common ground. A single computer program can have one or more tasks. The Tasking Model This chapter discusses the tasking model and things that have an affect on it. It's not because they're from the days of Neanderthal computing. MS-DOS TSRs .Chapter 3. An operating system that you use may be preemptive. save its state. Generally. but it is used a lot in the computer industry.This term is not disputed very much. How and when it does this is driven by its tasking model. is the ability for an operating system to equitably share CPU time between several well defined tasks currently scheduled to run on the system (even tasks that may be a little greedy with the CPU). In its executable form. People use terms in everyday conversation without really trying to find out what they mean. If you write your own. TASK . The kernel of an operating system is the code that is directly responsible for the tasking model. I'm not including them in this discussion because they generally return control of the processor back to the same interrupted task. I literally destroyed two copies of each of these books while writing MMURTL (pages falling out. MMURTL has a very small amount of code that is responsible for the tasking model. Tell the programmers what you mean.

"Everything should be as simple as possible. execute programs as the new users desire. the ability to spawn additional tasks to handle requirements for simultaneous operations came in very handy.In other words. etc. How tasks are handled. From this information you will be able to judge what's right for you. "simple software for simple minds. In a system that is designed for multiple terminals/users. All of these things must be taken into consideration when managing tasks. Quite obviously. On the other hand. Probably the best way to get this point across is to describe what I decided. My operating system is designed for microcomputers. DMA. each program had a set of things that it had to accomplish to be successful (satisfy the user). These resources must be managed properly for all these things to work effectively in a multitasking environment. Consider your resources when planning your operating system. yet effective fashion is paramount to building a system you can maintain and work with. This is where all of the resources for my tasks are managed. One of my friends suggested the motto. and why. disk. when I look back at the various jobs this software had to handle. and communications makes use of these basic resources to do their jobs. One of my major goals in designing MMURTL was simplicity. but not simpler" (otherwise it wouldn't work!). take a good look at the TSS (Task State Segment). I have written many commercial applications that span a wide variety of applications. and some are user-oriented software. Single User. including video." but needless to say. I have attempted to live up to that motto while building my system as a resource manager. Multitasking. some deal with equipment control (interaction with a wide variety of external hardware). It is not a UNIX clone by any stretch of the imagination. This is its only real purpose in life. but it may not be so easy for you. and how resources are assigned to these tasks was easy for me to decide.When you determine exactly what you want for a tasking model. Interrupt Controller Units. When I wrote these programs. In other words. You may need to expand on this structure quite a bit if you decide that each and every task is an entity unto itself. The resources include CPU time sharing (tasking model). or the user was done. Tasks crash. Nor did I want it to be.). In each case. memory management. keyboard. When you get to Section IV (The Operating System Source Code). Tasks end. Resource Management An operating system manages resources for applications and services.” In some operating systems each task may be considered a completely independent entity. there seemed to be no purpose to have additional tasks that continued to run. Input/Output (I/O) devices. tasks may be created to monitor serial ports. when the mission was accomplished. rather than a mini or mainframe environment (although this line is getting really blurry with the unbelievable power found in micro CPUs these days). you will see the rest of your operating system fit around this decision.0 -14- RICHARD A BURGESS . or even network connections for logons. Some are communications programs (FAX software. each of the functions dealt with a specific well-defined mission. it didn't sit too well with me. These are the real resources. If you decide that a single task may exist on its own two feet. and each of the places these programs executed also had a CPU . You may MMURTL V1. Everything else uses these resources to accomplish their missions. good old Albert). The operating system I wrote is not multi-user. and it may even run after the task that created it is long gone. and hardware (Timers. then still be there after all the child processes are long gone (for the next user). it may be completely dependent on its parent task or program. It may have memory assigned to it. Multi User One of the determining factors on how your tasks are managed may be whether or not you have a true multi-user system. The largest factor was that I had a CPU in front of me. Task Management Tasks begin. I deal with one user at a time. But in almost all of these systems. keeping track of tasks is no simple “task. then you must make sure that your task management structures and any IPC mechanisms take this into account. Single vs. To paraphrase a famous scientist (yes. Managing these resources in a simple. comms programs).

There were so few factors to be concerned with on that early system. An example of an item that would be part of the software state is a simple variable in your operating system that you can check to see which task is running. The hardware context in its simplest form consists of the values in registers and processor flags at the time the task stopped executing. and single thread of instructions suited me just fine. you change this variable to tell you which task is currently executing. or it may be dozens of things that have to be saved and restored with each task switch. In a multitasking operating system. I had my very own processor. "It's 11:00PM. it was a breeze. The hardware state may also consist of memory context if you are using some form of hardware paging or memory management. The software state can consist of almost nothing.0 -15- RICHARD A BURGESS . you switch to its stack and pop the registers off. "What is that thing doing?" When I used my first multiuser system. You will have to determine this from the technical manuals you accumulate for your system. If you must do it yourself. The Hardware State When a task switch occurs. I also knew exactly how many microseconds it took to switch between them. It was 1980 that I wrote my first time slicer which was built into a small program on an 8-bit processor. MMURTL V1. Then all you have to do is switch stacks. Do you know where your CPU is?" A funny question. I didn't worry about how long the operating system took to do something unless it seemed like it wasn't fast enough. and I could pretty much figure out what it was doing most of the time. In the case of hardware interrupts. CPU Time CPU time is the single most important resource in a computer system. This will depend on the processor you are using and possibly on external paging circuitry. The CPU takes care of it for you. there are instructions on some processors to save all of the necessary registers onto the stack. ensuring that the CPU time is properly shared is the job of kernel and scheduling code. We were each getting a piece of the pie. It's actually easier than it sounds. the task that is running must have its hardware context saved. I knew exactly how long each of my two tasks were going to run. It was crude. Microcomputers spoiled me. Then I asked. When you restore a task. but effective. they generally assume that it's the only thing running on the computer system. but an important one. Some processors (such as the Intel series) let you define software state items in a memory structure that is partially managed by the operating system. The number of things you have to save may also depend on whether or not the processor assists you in saving and restoring the software-state. albeit not a very big piece it seemed. it would seem to return at its leisure. Yet. Your decision in this matter will have a large effect on how you manage tasks or whole programs that are comprised of one or more tasks. Many of the programs I wrote didn't require multitasking. What WAS that thing doing? It was being shared with a few dozen other people staring at their terminals just like I was. this is usually a no-brainer. My first programs were written with that attitude. there were many times that I thought it had died and gone to computer heaven. then simply change a pointer to the current state structure for the task that is executing. What instructions are executing? Where is it spending all its time? And why? When an application programmer writes a program. The Software State The software state can be a little more complicated than the hardware state and depends on how you implement your task management.want to deal with many. This will be the heart and brains of the system. When you change tasks. This is where the hardware-state is saved. You can define a structure to maintain the items you need for each task.

This is done by tacking your code onto other code that is executed as a result of an interrupt.0 -16- RICHARD A BURGESS . Meanwhile. if the system allows this (a sleep or delay function). or to pause when it has nothing to do. If the operating system uses this period of time to suspend your task and execute another. programs (actually the programmer) may have to be aware of things that must be done to allow a task switch to happen. 10ths of seconds or even seconds. you pass a parameter that indicates how long the program should pause before resuming execution. each task runs until it decides to give up the processor. They can. This implies that the programmer does part of the scheduling of task execution. a program that is running makes an operating system call to get the time of day. In fact. he then monopolizes the CPU. There is a major problem with a fully cooperative system. And yes. MMURTL V1. if necessary. the operating system decides which of the tasks will run next (if any are ready to run at all). In a single tasking operating system. Cooperative Multitasking In a solely cooperative environment. and also can be done with mini-multitasking kernels you include in your programs. but usually to wait for a resource that is not immediately available. Many methods have been devised to allow singletasking operating systems to share the CPU among pseudo-tasks. A task that has the processor gives it up. and basic input/output. and the computer looks dead for all practical purposes. you basically have a hardware manager. it is a good idea to have some form of a preemptive system. This may be in milliseconds. In many single tasking systems. The major point that you must understand here is that there are really only two ways a task switch will occur on ANY system. that's a waste of good CPU time if something else can be done. It is then making wise use of your request to delay or sleep. You may also be aware that multitasking is simulated quite often in single tasking systems by stealing time from hardware interrupts. you are cooperating with the operating system. or a combination of the two. An important point here is that in a cooperative system. or it is preempted (the processor is taken away from the task). and you have used a library or system call with a name such as sleep() or delay()." From there. The operating system may be written in such a fashion that this (or any call into the operating-system code) will suspend that task and execute another one. With these calls. The only thing for the programmer to worry about is how efficient the operating system is at its other jobs such as file handling. "Hey .I'm done with the CPU for now. I'm sure you've run across the need to delay the execution of your program for a short period of time. If this isn't the task that the user is involved with. It may not always be obvious when it happens. many operating-system tasking models are a combination. For instance. and in some cases. the screen and everything else looks frozen.Single Tasking What if you don't want a multitasking system? This is entirely possible. Cooperative and Preemptive. however. interrupt servicing. however. do an adequate job in many cases. management of CPU time is easy. a call of this nature may do nothing more than loop through a series of instructions or continually check a timer function until the desired time has passed. There are other important factors that apply to these two types of multi-tasking. a single threaded system may be the best solution. If a programmer writes code that doesn't give up the CPU (which means he doesn't make an operating system call that somehow gets to the scheduler). Because such "CPU hogs" are not only possible but also inevitable. These forms of CPU timesharing suffer from the disadvantage that they are not in complete control of all resources. This can be for a number of reasons. These have been in the form of special languages (CoPascal). there's some task in there calculating pi to a zillion digits. In this case. Multitasking I categorize forms of multitasking into two main groups. the ability to cut a task off at the knees. This is in the form of the task saying to the operating system. which I cover in this chapter. An operating system's tasking model can be one of these.

This means there is often some desire or necessity to have certain jobs performed in a timely fashion so there is some synchronization with outside events. As you can see. For the preemptive system. They are also. or it may be for something extremely critical. The rest of the tasks are waiting for their shot at the CPU. there is hardware (programmable support circuitry) that allows you to cause a hardware interrupt either at intervals. When do we switch tasks. The currently running task is preempted. To expand on that definition. outside events. is a preemptive system. such as ensuring your coffee is actually brewed when you walk out for a cup in a maniacal daze looking like Bill The Cat (I DID say important!). Most of the time.communicating. a program. it's only the hardware state which includes registers.). or waiting for outside resources such as I/O. This can occur between any two CPU instructions that it is executing (with some exceptions discussed later). One of the most important hardware interrupts and one of the most useful to an operating system designer is a timer interrupt. and maybe (if we're nice) the user.0 -17- RICHARD A BURGESS . These parties include the programmer. The next section is the tricky part. there is going to be a desire. hardware timer.g. the entire "state" of your task is not saved.. On most platforms. which means the processor is taken away from the task without its knowledge. In fully cooperative systems. the scheduler simply determines who's next. Also. it doesn't expect it. The timer can help determine when to preempt. quite often. disk drive. However. only one task is running at a time (excluding the possibility of multi-processor systems). This synchronization may be required for human interaction with the computer which will not be that critical. I defined preemptive at the beginning of this chapter. connected to the outside world . hardware interrupts occur to pass information to a device driver. Generally. Task Scheduling Given the only two real ways a task switch can occur. for some form of communications between several parties involved in the scheduling process. or the operating system from an external device (e. when a task is preempted. and maybe memory context. This is where the scheduling fun really begins. controlling. to which task do we switch. As I also mentioned earlier. and how long does that task get to run? Who. This is an operating system that can stop a task dead in its tracks and start another one. the scheduler. This is usually until it willingly surrenders the processor. The programmer (or the task itself) determines how long a task will run. In a preemptive system. or a few other hardware-related things. communications device. when a hardware interrupt occurs on most hardware platforms. It does nothing to prepare for it. MMURTL V1. after the interrupt is finished. the CPU is returned to the task that was interrupted. Just to review these ways (to refresh your RAM). this adds the requirement of timing. the task that's running. Scheduling Techniques In a multitasking system. or after a set period of time. or monitoring. and How Long? The procedure or code that is responsible for determining which task is next is usually called the scheduler. When.Preemptive Multitasking The opposite end of the spectrum from a fully cooperative system. these two reasons are: A task that has the processor decides to give it up. the scheduler also determines how long a task gets to run. etc. we must now decide which task runs next. How long does the current task run before we suspend it and start the next one? One thing that we can't lose sight of when considering scheduling is that computer programs are generally performing functions for humans (the user). or even a requirement. this actually happens every time a hardware interrupt is activated.

When a task's time is up. Who determines which task gets more time. Time Slicing Time slicing means we switch tasks based on set periods of execution time for each task. Some form of signaling can even increase time allocation based on other outside events. The others may see a slow down. each task will get a fixed amount of time . only the very highest would ever get to run. In some systems. each task gets 25 milliseconds. it can make for some pretty choppy user interaction. Each task can be assigned a number (highest to lowest priority) indicating its importance.) Simple Cooperative Queuing The very simplest of cooperative operating-system schedulers could use a single queue system and execute the tasks on a first-come/first-serve basis. This is usually not very effective. and the next task in line is restored and executed. But it may be just fine in some circumstances. or by the programmer (or user) telling the scheduler it needs it. They can be waiting for something such as I/O. (the others will be flipping 8. This way you can picture what each technique has to offer. This also means we just can't assign simple priorities in a time sliced system. the one with the highest priority will be executed. but they used a time slicing scheduler based on a hardware timer to divide CPU time amongst the tasks that were ready to run. a multitasking system that is handling simple point of sale and ordering transactions at Ferdinand's Fast Food and Burger Emporium may be just fine as an equitably time sliced system. Many early multitasking operating systems were like this. The customer (and the greasy guy waiting on you behind the counter) would never see the slowdown as the CPU in the back room did a simple round-robin between all the tasks for the terminals.0 -18- RICHARD A BURGESS . if we preempted a task without it telling us it was OK to do so. it can serve admirably. simple time slicing may be adequate or at least acceptable. but it won't be on their terminals. In the simplest time sliced systems. Not quite that simple. Variable Time Slicing In a preemptive system. In a cooperative system. This also implies that some may not be ready at all. And the programmer can't even determine in advance how much time he or she will need. In a time sliced preemptive system." This implies the system is capable of preempting tasks without their permission or knowledge. A simple queuing system suffices. or how much time there is MMURTL V1. Time slicing is an old technique. The tasks that need more time can get it by asking for it. it is suspended and goes to the end of the queue. "a slice of time. Prioritized Cooperative Scheduling A logical step up from a simple first-in/first-out cooperative queue is to prioritize the tasks. When a task indicates it is done by making certain calls to the operating system. But it has some major drawbacks.000 burgers. than to execute the tasks in a sequence of equal time segments based solely on a priority assignment.We're going to look at several techniques as if they were used alone. When combined with an adequate form of interprocess communications or signaling (discussed in Chapter 4).all tasks will get the same slice . this means that of all the tasks that are ready to run. For example. This gives the scheduler something to work with. For example. it makes more sense to increase the time allocation for more important tasks. or which task will run more often? You generally can't ask the user if they need more time (They will ALWAYS say yes!). It can look at the priorities of those tasks that are ready-to-run and select the best based on what the programmer thinks is important. its complete hardware and software state is saved. otherwise. and if a user interface is involved.and they will be executed in the order they were created. you must assume that it's ready to run immediately. This would even take care of the situation where your favorite employee falls asleep on the terminal and orders 8000 HuMonGo Burgers by mistake.

when something that is out of the immediate control of the programmer (and the CPU) must be handled within a specific period of time. The programs that run on this computer may have some snap decisions to make based on information from outside sensors. It's pretty simple to calculate how much time you'll need for a communications program running at full capacity at a known data rate. If the programs have an equal time slice and equal buffer sizes. Operating systems that share the CPU among tasks by just dividing up a period of time and giving each task a share respond poorly to outside events. This is a simple example (actually a common problem). For instance. it requires the ability to juggle tasks on the system in real-time. but it's not dramatic enough. and we want our machines responsive to us. These are communications and user interface issues.. This IS real-time stuff. things are never as simple as they seem.g. the possibility of being preempted before a certain important function was accomplished always exists. dependable. Some of the best examples of real-time computing are in robotics and equipment control. In the case of the nuclear reactor operating system.0 -19- RICHARD A BURGESS . The programmer on this type of system must also be able to indicate to the operating system that certain programs or tasks are more important than others are. A computer in charge of complete climate control for a huge building may be a good example. clean communications. If you have ever had the pleasure of writing communications-interrupt service routines on a system that has a purely timesliced model. Two very important areas of computing have been moving to the forefront of the industry. This is not to say that time-sliced systems have poor interrupt latency. hardware interrupt service routines) and the scheduler. outside events MUST be able to force (or at least speed up) a task switch. but one channel is handling five times the data. computers are everywhere. Being forced to wait 200 or more milliseconds to perform a critical safety task may spell certain disaster. (should turning on the coffeepot for the midnight shift take a priority over emergency cooling-water control?) But how many of us will be writing software for a nuclear reactor? I thought not. and maybe you can even see where it would be advantageous to combine a cooperative system (where the programmer can voluntarily give up the CPU) with a preemptive time-sliced system. Mixed Scheduling Techniques As you can see. I would have spent more time on the tasking model and also on interrupt service routines. The buffer will overflow. To know in advance if it will actually be used at that rate is another story entirely. Because it is still based on a preset period of time. Many of these things depend on real-time interaction between the machine and the world to which it is connected. Now. the ISR can send a message to the program servicing it to tell it the buffer is almost full ("Hey. The reaction is always near meltdown proportions. the program servicing the busier port may lose data if it can't get back to the buffer in time to empty it. But I opted to "get real" instead.to go around on the system. In a message based system. If I thought my operating system would be used in this fashion. Other Scheduling Factors You know as well as I do. This implies that some form of communication is required between the sensors (e. They do so many things for us that we simply accept them in our everyday lives. Thus. you have many options for scheduling techniques: MMURTL V1. or his FAX is garbled because buffers went a little bit too long before being emptied or filled. There are also many tasks inside the machine to perform of which the user has no concept (nor do they care!) In case you haven't noticed. I think you can see the advantages of a priority based system. but it makes the point. but they have problems with unbalanced jobs being performed by the tasks. Let's look at one that controls a nuclear reactor and all other events at a nuclear power plant. take two communications programs that handle two identical ports with two independent Interrupt Service Routines (ISRs). you know that it can lead to serious stress and maybe even hair loss. come do something with this data before I lose it"). one of the most volatile reaction I can think of is when a user losses data from a communications program. we definitely want to be able to preempt a task. and prioritization of these tasks will be critical. We want good. or the user. Therefore.

In addition. But even when the task has nothing to do. To be honest. Some Cooperation In this type of model. This would put the task back into the queue to be executed when surrendered the CPU. A task may. Users on a larger. All of these ways require some form of signaling or inter-process communications.0 -20- RICHARD A BURGESS . More Cooperative Building on the previous mix (some cooperation). Writing communications tasks for these types of systems has always been a challenge (and that's being nice about it!). time-slicing all the tasks is suitable (even desired) to accomplish what was initially intended by the system builders. This means that the programmer sets the priority of the task and the scheduler abides by those wishes. You may opt to give certain tasks a larger execution time based on external factors or the programmer's desires. In a system that was designed for multi-user application sharing (such as UNIX originally was). all tasks will get to run. Primarily Cooperative. A very simple. This could be in the form of a system call to indicate to the scheduler that this task should get a larger percentage of the available CPU time. There would have to be additional mechanisms to prevent poorly written tasks from simply gobbling up the CPU. This could be in the form of a maximum percentage of CPU time allowed. all tasks will be scheduled for execution. it would still be scheduled to execute. and even help define the tasking model. the task that is executing was always the highest priority found on the queue. MMURTL V1. See what works for your applications. Only in circumstances where multiple tasks of the same priority are queued to run would time slicing be invoked. No doubt. and I discuss all of that in the next chapter. additional signaling or messaging could be incorporated in the system to allow a task to indicate that it requires more time to execute. I didn't want to cloud the tasking issue with the required communications methods. based on the number of tasks scheduled. If such were the case (nothing to do) it would simply surrender the CPU again. you have a few ideas of your own. all of the examples in the next sections have preemptive capabilities. In the next sections we'll look at a few ways to mix them. give up the CPU if it’s no longer needed. and will execute for a predetermined amount of time. That's the only way you'll really know for sure. although such systems exist. I invite you to experiment with your own combinations. however. In this type of system. I can't picture a system without it. tasks are prioritized and generally run until they surrender the CPU. rapid calculation would hold the reins on a CPU hog.Cooperative task scheduling Preemptive time slicing Cooperative prioritized task scheduling Variable/prioritized time slicing ANY AND ALL COMBINATIONS OF THE ABOVE! The are as many ways to mix these two basic types of task scheduling as there are copies of this book out there. Keep in mind that on a prioritized cooperative system. Limited Time Slicing In this type of system. even though these methods are very important. Fully Time Sliced. These tasks would always be the highest priority task queued on the system. This type of system would also have the capability to preempt a task to execute a higher priority task if one became queued (messaging from an Interrupt Service Routine). Time Sliced. solely timesliced system will see the system slowdown as the time is equitably divided between an increasing number of users or tasks. You can probably picture in your head how this could get complicated as several tasks begin to tell the scheduler they need a larger chunk of time.

goes into a "waiting" state. I never wanted to miss outside events. To briefly explain my tasking model.PERIOD. file access. For instance. The timer-interrupt routine. I decided that MMURTL ISRs would execute in the context of the task that they interrupted. then you can make your own choice based on my experiences. To me. and they will also be used for execution timing on some preemptive systems. Go for the fastest option . it will initiate a task switch after a set period of time. and no. but to use the programmer's desires as the primary factor. I have learned two very valuable lessons concerning ISRs. and another one to return it to the task that was interrupted. This will usually be during the allocation of critical shared operating system resources and certain hardware I/O functions. It allows you to recognize a mistake when you make it again. and the next task with the highest priority executes. In such a system. I wanted a priority based. real-time operating system." I borrowed that quote from a sugar packet I saw in a restaurant. the outside events are even more important than placating the user. it wasn't Ferdinand's Burger Emporium. More Time Slicing This is similar to the previous model except you can slice a full range of priorities instead of just the highest priority running. prioritization of tasks is VERY important. Limited Time Slicing category. In chapters 18 and 20 (The Kernel and Timer Management) I discuss in detail how I melded these two models. and also the possibility of memoryMMURTL V1. An outside event (an interrupt) sent a message to a task that has an equal or higher priority than the currently running task. "Experience is a wonderful thing. preemptive. Interrupt Handling Hardware Interrupt Service Routines (ISRs) have always been interesting to me. My goal was not just to equitably share the processor among the tasks. multitasking. The less time the better. After all. With the Intel processor's you even have a choice of whether the ISR executes as an independent task or executes in the context of a task that it interrupted. Certain critical functions in the operating system kernel and even some functions in device drivers will require you to suspend interrupts for brief periods of time. It's faster – actually. This has the desired effect of ensuring that the highest priority task is running when it needs to and also those tasks of an equal priority get a shot at the CPU. The task based ISR is slower. timer services. monitors the current task priority. Keep them short. When it detects that a higher priority task is in a ready-to-run state.0 -21- RICHARD A BURGESS . you may get it!" Let me repeat that my goal was a real-time system. This is the only "time-slicing" that MMURTL does. This is a speed issue.Primarily Cooperative. I bring them up in this chapter because how an operating system handles ISRs can affect the tasking model. the user IS an outside event! This put my system in the Primarily Cooperative. much faster. The Tasking Model I Chose I'll tell you what I wanted. as well as checking for the highest priority task that is queued to run. This is the preemptive nature of MMURTL. If it detects that one or more tasks with priorities equal to the current task are in the ready queue. and in reality. This could have presented some protection problems. MMURTL task switches occur for only two reasons: The currently running task can't continue because it needs more information from the outside world (outside its own data area) such as keystrokes. Other processors may offer similar options. if you have 256 priorities. Remember the saying: "Be careful what you ask for. and limit the functionality if at all possible. it will initiate a task switch as soon as it finds this. 0-15 (the highest) may be sliced together giving the lowest numbers (highest priorities) the greatest time slice. A complete processor task change must occur for the ISR to execute. I made MMURTL listens to the programmer. which provides a piece of the scheduling function. In such a case it sends a "request" or non-specific message. This in itself does not immediately cause a task switch. or whatever.

only the user knows. In a cooperative environment. When you get to chapter 4 (Interprocess Communications) you'll how messaging ties into and even helps to define your tasking model. Who will determine how long it actually takes to transfer several sectors from a disk device? There are to many factors involved to even prepare for it. who's to say when Mr. (PICU) The real effect on the tasking model comes from the fact that some interrupts may convey important information that should cause an eventual task switch. and MMURTL's specific methods in chapter 19. I discuss memory management in chapter 5. User is going to press a key on the keyboard? That's right . This was due to MMURTL's memory management methods.management headaches. After a period of time has elapsed. This means that your operating system must be prepared at all times to be interrupted. It is determined by hardware that may be completely out of your control. you switch tasks. An important thing you should understand is that you don't have a big choice as to when an ISR executes. the generic clock interrupt may be the basis of most task switches. and all programs on the system share this common OS-base addressing. Your only option (when you need one) is to suspend the interrupts either through the processor (which stops all of them except the non-maskable variety). MMURTL V1. the problems didn't surface. but because ISRs all execute code at the OS level of protection. it may possibly be some form of intelligent message from an ISR that causes a task switch (if an ISR causes a task switch at all).0 -22- RICHARD A BURGESS . On systems that are primarily time sliced. For instance. and they are surely out of your control most of the time. or Ms. or to suspend one or more through the Priority Interrupt Controller Unit. or even possibly the users.

This identifier is generally associated with the ID of the task that created it. or is suspended when it can't run. Semaphores One common IPC mechanism is Semaphores.0 -23- RICHARD A BURGESS . On a single CPU." MMURTL V1. everyone can allocate memory in an operating system. Interprocess Communications Introduction This chapter introduces you to some of your options for Interprocess Communications (IPC).Chapter 4. and finally destroy (deallocate) the semaphore and its associated structures when no longer required. Messaging and Tasks A task is a single thread or series of instructions that can be independently scheduled for execution (as described earlier). I can't get away from it here because it's tied in very tightly with Inter Process Communications. there are public calls that allocate semaphores. but more than likely it will be managed in such a way that it can be handed out to only one task or program at a time. This is one place where the IPC mechanism comes into play and is very important. As the name implies. and also how it is scheduled will depend on the messaging facilities provided by the system. Some resources may be available to more than one task at a time. and more than likely will be used to control access to one or more functions that the owner task provides. Looking at this synchronization from the application programmer's point of view. If you remember the discussion in chapter 3 (Task Scheduling) then you know that a task switch can occur at just about anytime. Whether a task is ready to run will depend on information it receives from other tasks (or the system) and also how many other tasks are ready to run (multiple tasks competing for CPU time). If the memory-allocation routine was in the middle of a critical piece of code (such as updating a linked list or table) and the second task called this same routine. An example of a resource that may only be available for one task at time is system memory allocation routine. you should have good idea what your operating system will be used for before you determine important things like its tasking model and what forms of interprocess communications it will use. The arguments (or parameters) to these calls usually include the semaphore ID (or where to return it. portions of the code will not be reentrant. semaphores are used for signaling and exchanging information. the IPC mechanisms play a very important part in the overall makeup of the system. Synchronization Synchronization of the entire system is what IPC all boils down to. I discuss reentrancy in a later section in this chapter. Typical semaphore system calls are semget() which allocates a semaphore. where it waits. When the programmer makes a simple system call for a resource or I/O. This means that unless interrupts are suspended while one task is in the middle of a memory-allocation routine. while others are only available to a single task at any one point in time. However. Of course. In operating systems that use semaphores. As I described in earlier chapters. it could cause some serious problems. and a set of flags that indicate specific desires or conditions that your task wants to adhere to such as one that says "DON'T SUSPEND ME IF I CAN'T GET BY YOU" or "I'LL WAIT TILL I CAN GET IT. what operation you want to perform. nor should they have to. semctl() which controls certain semaphore operations and semop() which performs operations on a semaphore. perform operations on the semaphore structures. They also are used to signal the beginning or ending of an event. How the operating system decides when a task is ready to execute. this task could be suspended and another task could be started. In other words. he or she doesn't see most of the action occurring in the system. some systems allow private semaphores that only related tasks can access. You will also notice that even though I covered task scheduling in the previous chapter. A semaphore is usually assigned a system-wide unique identifier that tasks can use to select the semaphore when they want to perform an operation on it. You can't suspend interrupts for extended periods of time! I also discuss this in detail later in this chapter. if it's being allocated). Semaphores are code routines and associated data structures that can be accessed by more than one task. This allows you to lock out tasks as required to prevent simultaneous execution on non-reentrant code (they play traffic cop). As you will see in this chapter. only one task can execute at a time. their task may be suspended until that resource is available or until another task is done with it.

They are normally stream-oriented devices and the system calls will look very much like file system operations. All of the functions that they performed are available with simpler messaging facilities. In a message-based system. Pipes are a pseudo input/output device that can be used to send or receive data between tasks. and WritePipe(). With the send() and wait() types of messaging. I initially considered public named pipes for MMURTL to allow data transfer between system services and clients. the application programmer won't even know that a semaphore operation is being performed. it is waiting at that semaphore for a condition to change before proceeding (being restarted to execute). tasks are not wholly independent. It is even possible to implement messaging routines using semaphores and shared memory. destroy tasks. you should study how they are implemented in the more popular systems that use them (UNIX. only the basic form of messaging is implemented. In my system. OpenPipe(). Common system calls for pipe operations would be CreatePipe(). The best way to describe messaging is to provide the details of my own messaging implementation. One thing to note is that portability may be an issue for you. The parameters to these calls would be very similar to equivalent file operations. you should study how they are used on other systems.0 -24- RICHARD A BURGESS . which will consist of one or more tasks. They rely on a certain data contained in structures that are assigned to a program or job. I opted not use semaphores in my operating system. a program is made up of one or more tasks. Messages Messaging methods can be implemented several different ways (although not in as many different ways as semaphores from my experience). Another reason I avoided semaphores is that are usually associated specifically with tasks (processes). or similar ones. etc. I have added a second. As you can see. which means they can be given names so that unrelated tasks can access them. ReadPipe(). You will see these commands. Pipes can be implemented as public devices. They also don't have to be so tightly coupled with the kernel code. or schedule tasks for execution. If you intend to implement pipes as part of your IPC repertoire. I think they are best implemented as tightly integrated pieces of the kernel code. In fact. the semaphore mechanism would have to be coupled with all of the other kernel code. some systems allow standard I/O to be redirected through pipes. tasks exchange information and synchronize their execution by sending messages and waiting for them. I studied how they were used in UNIX systems and in OS/2. This would provide the needed synchronization for task management on the system. When a semaphore operation is performed and the calling task is suspended. I am describing this because you may decide to implement tasks as wholly independent entities. I refer to a program as a “job. on most real-time operating systems. ClosePipe(). OS/2 is one such system. But once again. This will afford an overview of messaging for the novice as well as the details for the experienced systems programmer. or to ensure some other required condition on the system is met before the calling task is scheduled to execute.).In most cases. He or she will make a system call for some resource. The best method to implement messaging (and I don't think I'll get an argument here) is to make it an integral part of the kernel code in your system. Send and Wait The most rudimentary form of messaging on any system is a pair of system calls generally known as send() and wait().” The initial task in a program may create additional tasks as needed. and was my opinion that they added an unnecessary layer of complexity. and the library code or code contained in the system will perform the semaphore operation which ensures synchronized access to its code (if that's its purpose). In my humble opinion. In fact. public pipes are very handy. If you're not worried about size or complexity in your system. such as MMURTL. Semaphore functions on some systems are actually implemented as an installable device instead of as an integrated part of the kernel code. but I used a second form of messaging instead. OS/2. In the system I've included for you (MMURTL). such as those procedures that create new tasks. form of messaging which adds two more kernel message MMURTL V1. This is much more efficient. the entire kernel should be built around whatever messaging scheme you decide on. These new tasks inherit certain attributes from the initial task. more powerful. If you intend to implement semaphores. Pipes Another popular IPC mechanism is called a pipe. the only other resource required is somewhere to send a message and a place to wait for one (usually the same place). I didn't. Quite often they are not public and are used to exchange data between tasks that are part of one program. On many message-based systems for smaller embedded applications. you must keep the code maintenance aspects in mind for whatever implementation you choose.

long dMsgPart1. or just about any other name you like. I have implemented this as SendMsg(). It returns 0 if all goes well. The request/respond concept is what I consider the key to the client-server architecture which was one of my original goals. The only reason it's discussed with the rest of the kernel primitives is because of its importance. give it the message and away it goes. Tasks are started with the kernel primitives SpawnTask or NewTask. CheckMsg(). Sending and receiving messages in any message-based system is not unlike messaging of any kind (even phone messages). All the other tasks are WAITING somewhere. Other operating systems use this type of messaging scheme also. a task is born. dExch is the destination exchange. Did you notice (from my definitions above) that not only messages wait at exchanges. an exchange point. You also know about Exchanges and the Ready Queue. In Bob's case he will get the message from his secretary." This is an example of a one way message. It is a place where you send messages or wait for them to exchange information. In MMURTL's case. and NewTask() are five very important kernel primitives. The task is the human. You can leave a message on the machine (at the exchange) if no one (no task) is waiting there. the message will wait there until a task waits at the exchange or checks the exchange with CheckMsg(). One key element in a message system is WHERE you leave your messages. The C definitions for these calls and their parameters as I have implemented them are: unsigned long SendMsg(long dExch. Operating system calls to send a message come in several varieties. a bin. Zork to forget it. You tell it what exchange. There are only two places for a task to wait in MMURTL: At an Exchange or on the Ready Queue. Now I have some very important terms and functions . the message is received right away. In your system you will provide a function to allocate a mailbox. WaitMsg(). only one task is actually executing instructions. char *pMsgRet). consider this: In a single processor system that is executing a multitasking operating system. his offer is the pits. The parameter pdExchRet points to a dword (32-bit unsigned variable) where the exchange number is returned. I don't consider it part of the kernel because it has no effect on the tasking model (CPU time allocation).not in detail yet. It is called AllocExch() and is defined like this in C: unsigned long AllocExch(long *pdExchRet). but large enough for a couple of pointers. The most common is the send() function I described earlier.functions. and VIOLA. Now. Consider the phone again. but tasks can wait there too? This is an extremely important concept. In MMURTL I call this WaitMsg(). request() and respond(). Yes. You point to a piece of code.0 -25- RICHARD A BURGESS . long dMsgPart2). unsigned long WaitMsg(long dExch. The function return value is the kernel error if there is one (such as when there are no more exchanges). one which is "request" for services which should receive a response. In an operating system's case. If a human is there waiting (a task is at the exchange waiting). the task that you want to get the message is "waiting" at the exchange by calling the wait() function I described earlier. it is generally an allocated resource that can be called a mailbox. or whatever you call it. Note that my messages are two 32 bit values. I called it an Exchange. char *pMsgRet). If you're lucky. AllocExch is an important auxiliary function. pMsgRet points to an eight-byte (2 dwords) structure where the message will be placed. You can send one way messages to a person. SpawnTask(). dExch is the exchange where we will wait to check for a message. This is an added messaging function to allow you to check an exchange for a message without actually waiting. the answering machine is the exchange. You also need a way to send the message. provide some other pieces of information. In order to send or receive a message you must have an exchange. it's a little more complicated than that. unsigned long CheckMsg(long dExch. "Tell Mr. for which you expect no response. It does just what it says . but we have enough to finish the basic theory. but enough that we can talk about them. The Ready Queue is the line-up of tasks that are in a ready-to-run state but are not running because there's a higher priority task currently executing. This now gives MMURTL two forms of messaging. If there is no task waiting at that exchange. such as. MMURTL V1. dMsgPart1 and dMsgPart2 are the two dword in a message. One more quick topic to round off what we've already covered. SendMsg().it sends a message. MMURTL provides a call to allocate exchanges for jobs. and a non-specific "message" that doesn't expect a response. Not too large to move around.

What it's doing isn't important. In this example.. It's still task 2. very small. Task2 is still running. You now have enough information under your belt to provide a scenario of message passing that will help enlighten you to what actually occurs in a message based system. Table 4. Kernel makes Task2 run Task2 is running.. You start with a single task executing. Task1 is running Task1 sends a message to Exch2 MMURTL V1. Kernel places Task2 on Exch2. Just kidding. just the "kernel" (a small tribute to Rod Serling. Kernel checks for a task waiting at Exch1. Kernel attaches message to Exch1. it's not called the kernel zone. Kernel makes Task1 run. Kernel checks for a message waiting at Exch2. As we move down the table in our example. Task2 sends a message to Exch1. Table 4. I'll try to liven it up some later on. time is passing. Task2 allocates Exch2. Task2 is higher.1 Task and Kernel Interaction Task Action Task1 is Running Task1 allocates Exch1 Task1 calls SpawnTask (to start Task2) Kernel checks priority of new task. Task1 is ready to run.). Task2 calls WaitMsg at Exch2.0 Kernel Action -26- RICHARD A BURGESS .1 (Task and kernel Interaction) shows actions and reactions between a program and the operating system. whenever you call a kernel primitive you enter the KERNEL ZONE. None are waiting. None found. Kernel places Task1 on the Ready Queue. Kernel evaluates Ready Queue. Kernel evaluates Ready Queue to see who runs next.I apologize that none of the items I've introduced have names that are confusing or buzzwordish.

A fully loaded system may have 5. the processor is activated and it checks the Ready Queue. The interface to the Request primitive is procedural. It doesn't need to because the Request interface is identical for all services. Message based services are used to provide shared processing functions that are not time critical (where a few hundred microsecond delay would not make a difference). BBS systems. With MMURTL. The functions and what they do are defined by the service. each service installed is given a name. it will be made to run. I didn't do this with MMURTL. you could implement named pipes (discussed earlier in this chapter) as a system service. and if the task that just received the message is a higher priority than the task that sent it. the kernel attaches the message to an exchange. I actually HALT the processor with interrupts enabled. You will have to dig into the technical aspects of the processor you are using to ensure a scheme like this will work. it registers its name with the OS Name Registry and tells the OS what exchange it will be serving. 10 or even 15 system services. The Request() and Respond() messaging primitives are designed so you can install a program called a System Service that provides shared processing for all applications on the system. If there is one. Actually it really does absolutely nothing. you will more than likely not use these types in your system. FAX services. From the simple example in table 4. The MMURTL FAT-file system and keyboard are both handled with system services. Kernel places task1 on Ready Queue Kernel makes Task2 run (It's a Higher priority) Task2 is running . MMURTL V1. Request() I have added two more messaging types in my system.. e-mail services. printing services. It gives task2 the message. They are ideal for things like file systems. If there is a task at the exchange. keyboard input. The OS knows nothing about the service codes (except for one discussed later).. they must be waiting for something. the list could go on and on.1. If the interrupt that caused it to wake up sent a message.533 different functions as identified by the Service Code. When the service is first installed. More than likely it is something from the outside world such as a keystroke. This may not work on all types of processors. The processing is carried out by the service. unsigned int wSvcCode.Kernel checks for task at Exch2 and find Task2 there. only the service name. This low priority task can even be made to do some chores such as garbage collection or a system integrity check. Each time an interrupt occurs. there should be a task sitting on the ready queue. the user of the system service doesn't need to know the exchange number. dummy tasks are created with extremely low priorities so there is always a task to run.0 -27- RICHARD A BURGESS .the processor suddenly finds itself with nothing to do. In some systems. What would happen if both Task1 and Task2 waited at their exchanges and no one sent a message? You guessed it . The name must be unique on the machine. but has quite a few more parameters than SendMsg. In fact. From the example you can also see that when you send a message. Each service can provide up to 65. you can see that the kernel has its job cut out for it. You can also see that it is the messaging and the priority of the tasks that determines the sharing of CPU time (as was described in previous chapters). . The Ready Queue is immediately reevaluated. They are dedicated types.. Look at it prototyped in C: unsigned long Request(char *pSvcName. the message is associated with that task and it is placed on the Ready Queue. queued file management.. This can simplify the scheduler. If everyone is waiting at an exchange. The exchange number may be different every time it's installed. the receiving task is made to run. If a very small embedded kernel is your goal. Request() and Respond() provide the basis for a client server system that provides for identification of the destination exchange and also allows you to send and receive much more than the eight-byte message used with the SendMsg() primitive. and also need to be shared with multiple applications. and a response (with the results and possibly data) is returned to the requester. This way. but the name will always be the same.

dnpSend would be 1. Respond() The Respond() primitive is much less complicated than Request().0 -28- RICHARD A BURGESS . Second. In many functions you will not even use pData1. pData1 points to the file name (data being sent to the service).is the number (0. For instance. in the file system OpenFile() function. then it calls WaitMsg() and sits at an exchange waiting for a reply. long dData2 ). If it is 80000000h or above. dStatRet .Number of the specific function you are requesting. or pData2 to send data to the service. char *pData2SR. The service name is eight characters. long dcbData2SR. *pData2 . This will be explained later in memory management. and pSend2 was not used or was pointing to your memory area for the service to fill. The parameters are also a little less intimidating than those for Request(). you'll need to know which one is responding. This is needed because you can make multiple requests and direct all the responses to the same exchange. dcbData2 . If both data items were being sent to you from the service then this would be 0. At first glance. dcbData1 . *pData1 .How many bytes the pDataSend points to. They are described below: dRqHndl . wSvcCode . There's still a little work to do. long dData0. as the plot unfolds you will see just how powerful this messaging mechanism can be (and how simple it really is).Exchange that the service will respond to with the results (an exchange you have allocated). The convention is the value of the first dword in the message. long *pdRqHndlRet. long dStatRet). but network transport mechanisms do not. This may even point to a structure or an array depending on how much information is needed for this particular function (service code). But.Pointer to the service name you are requesting. This doesn't mean the system service has it easy. dData1. long dnpSend char *pData1SR. you'll see that the call for making a request is a little bit more complicated than SendMsg(). This is possible because the kernel provides alias pointers to the service into your data area MMURTL V1. *pdRqHndlRet . Using the Open File() example.the handle to the request block that the service is responding to. the second dword is the status code or error from the service. it is NOT a response. The content of the eight-byte message tells you if it is a message or a response. and dData2 . if the request was serviced (with no errors). the message that comes in to an exchange is an eight-byte (two dwords) message. If this is the response. 1 or 2) of the two data pointers that are moving data from you to the service. Two questions arise: 1. and spacepadded on the right.the status or error code returned to the requester. long dcbData1SR. although its exact meaning is left up to the service. A job (your program) calls Request() and asks a service to do something. long dData1. this would be the size (length) of the filename.long dRespExch. The service already knows this. How do we know that this is a response or just a simple message sent here by another task? 2. Zero (0) usually indicates no error. Where is the response data and status code? First. this dword should match the Request Handle you were provided when you made the Request call (remember pRqHndlRet?). the data has already been placed into your memory space where pData1 or pData2 was pointing. dRespExch . but will simply fill in a value in one or more of these three dwords.Pointer where the OS will return a handle to you used to identify this request when you receive the response at your exchange. These are documented independently by each service. dnpSend .This is a second pointer exactly like pData1 described above. If pSend1 was a pointer to some data the service was getting from your memory area.Pointer to memory in your address space that the service must access (either to read or write as defined by the service code). These can never be pointers to data. If you remember. Lets look at each of the parameters: pSvcName . Second. all capitals. If you made more than one request. Here is the C prototype: unsigned long Respond(long dRqHndl.These are three dwords that provide additional information for the service.This is the same as dcbData1. dData0.

An exchange is a small structure in memory. As you know. I guess you could use Elmer's Glue. You will need some way (a method) to attach messages to your mailbox. it is preempted. it sends back the message so the next task can get in.0 -29- RICHARD A BURGESS . pointers are off the ends of arrays. But when it reaches PointX. I opted to use something called a Link Block (LB). So. What's usually not so simple for the operating-system writer is ensuring you identify and protect all of the NON-reentrant code. I say "almost" because really only one task is running at a time. there was only one buffer left. The call to manage and check the semaphore would actually be part of the allocation code itself. So the code goes in. This can add up to hundreds in a real hurry. This can lead to serious problems if the code that is being executed is not designed to be reentrant. Reentrancy Issues A multitasking system that lets you simply point to a set of instructions and execute them means that you can actually be executing the same code at almost the same time. only one task can really be running at a time. right? But let me guess . Not very big. I'll use a pseudo text algorithm to define a series of instructions that are NON-reentrant. The code has already checked and there was 1 buffer left. it's a mess. When each task is done with the allocation routine. and WHAMO . but still very important. and the service has already read your data and used it as needed. checks the semaphore. but this would slow things down a bit. The example is a function that allocates buffers and returns a pointer to it. MMURTL V1. For the sake of understanding this. Also. A Link Block is a little structure (smaller than an exchange) that becomes a link in a linked list of items that are connected to an exchange. lets look at an example. It's called GetMeSomeMem() In this example there are two variables that the code accesses to carry out its mission. Now. Reentrant means that if a task is executing a series of instructions and gets preempted. Each task will wait() at the exchange until it has a message. Variable nBuffersLeft Variable pNextBuffer GetMeSomeMem() If (nBuffersLeft > 0) (A place between instructions called Point X) { (Allocate a Buffer) Decrement nBuffersLeft Increment pNextBuffer Return pNextBuffer } else Return Error Lets suppose two tasks are designed to call this function. and show you what can happen. To really understand this issue. Not as difficult as you expect. task1 calls GetMeSomeMem(). it successfully makes it all the way through and gets that last buffer. Now. Each task that enters the code.to read or write data.it's crash time. As you can see. or pauses in the middle of them. The buffer is gone. it's OK for another task to enter and execute the same code before the first task is done with it. Link Blocks I keep referring to how a message or a task just "sits" or "waits" at an exchange. (You will find out how important they are if you run out of them!) There is one link block in use for every outstanding (unanswered) request and one for every message waiting at an exchange. the kernel has aliased this pointer for the service as well. A little further into this chapter we cover memory management as it applies to messaging which should clear it up considerably. He is restarted by the scheduler and starts up at PointX.this aliasing thing with memory addresses is still a bit muddy. task1 gets to run again. It's really quite simple. Allocate a mailbox or exchange and send it one message. if you were sending data into the service via pData1 or 2. even in a multitasking system. and will be suspended until the last task is done with it. It will no doubt be a linked list of sorts. some method is required to prevent two tasks from being inside this code at the same time. Semaphores are used on many systems. Messages can also provide exactly the same functionality. task2 is running and calls GetMeSomeMem().

If you implement independent-linear memory arrays through paging hardware (as I did with MMURTL).” MMURTL V1.0 -30- RICHARD A BURGESS . and maybe even not often enough.400 * 8 because they will interrupt for every byte (8 bits). You must take into consideration that there are more interrupts than just that communications channel. It will be the only way. The typical processor running at 25 MHz executes most of its instructions in 2 or 3 system-clock periods. If you disable interrupts too long then you will suffer interrupt latency problems. You will have to do some serious testing on your system to ensure interrupt latency is not going to be a problem. this means you could execute as many as 1730 instructions in the interrupt interval. they're doing some things I personally consider impossible. In theory. This means you can freely pass pointers in messages back and forth (even between different programs) and no one gets confused. but my testing shows I did OK.200 baud in Windows 3. Why are task switches considered "the biggie?" A task switch can consume as many as 30 or 40 microseconds.x? You will lose data. I could probably tighten mine a whole lot. I mentioned in the above section that you shouldn't disable interrupts for too long a period of time. but you will still have interrupts disabled for a rather scary period of time. then you have to find away to translate (alias) addresses that are contained in messages that will be passed between different programs.400 bits per second will interrupt every 208 microseconds.000 * 3). You will lose data. This means that if paging is used along with the segmentation.but it will increase the headaches elsewhere. it will be even a greater chore to translate addresses that are passed around on the system. The timer interrupt will be firing off every so often. Fully segmented models allow any memory allocation to be zero-based. Have you ever run a communications program for an extended period of time at 19. Good tight code in the kernel is a must. You should always know how long this will be and it shouldn't be too long. “Memory Management. let's look at a real-world example. and also "the biggie" .that was only in theory! Now we have to do a reality check. That would be an average of 120 nanoseconds (1/25.If the instruction sequence is short enough. You didn't disable interrupts. The next sections discuss this. you know you locked out multiple users with the kernel messaging (or semaphores). You can find out about some of your memory management options in chapter 5. Don't blame Windows though. Segmented memory models will also suffer from this aliasing requirement to even a greater extent. Just to give you an idea of what periods of time we are talking about. it will simplify IPC greatly . you can't use the kernel code to lock things out of the kernel! You MUST disable interrupts in many sections. In the kernel code. It doesn't sound like it would be a problem. Also. but it can be depending on how you implement your memory model. A nonbuffered communication UART (Universal Asynchronous Receiver Transmitter) operating at 38. I recommend you avoid a fully segmented model. A single huge linear array of memory in your system that all programs and task share means that they all understand a pointer to any memory address. The actual amount of time will depend on whether you use hardware task switching or do it yourself. The communications interrupt itself will have interrupts disabled for a good period of time. If you use a completely flat model for the entire system. if you remember the GetMeSomeMem() example above. WHOA THERE . Memory Management Issues The last hurdle in Interprocess Communications is memory.task switches. I started with one and abandoned it early on. you can simply disable interrupts for the critical period of time in the code.000. This is 1/38. Interrupt Latency Interrupt latency is when interrupts don't get serviced fast enough.

you know that you are basically asking the operating system for chunks of memory which you will breakdown even further as required for your application. but hits the important things): How the processor handles addressing. this refers to how and where programs are loaded.0 -31- RICHARD A BURGESS . Whether or not you use paging hardware. so does an operating system. Logical memory is more an Intel term than a generic term that can be applied across different manufacturers processors. I am addressing the second byte of physical memory. Basic Terms Once again. MMURTL V1. Memory management code in an operating system is affected by the following things (this list is not all inclusive. it's all basically the same. Memory Model Do not confuse my term "memory model" for the MS-DOS/Intel term. Memory Management Introduction This chapter provides you with some fairly detailed ideas for memory management from an operating system's perspective. a protected-mode program's memory is always referenced to a selector which is mapped in a table to linear memory by the operating system and subsequently translated by the processor. Just as your memory management routines keep track of what they hand out. The memory model you choose. If you've ever written memory-allocation routines for compiler libraries. This will depend on whether or not some form of hardware translation takes place. This greatly affects the next three items. For instance. An operating system's view of memory management is quite different than that of a single application. I will discuss segmentation in greater detail in this chapter for those of you who may want to use it. Either way. Address 0 is the first. Some do it with internal paging hardware. I do make some assumptions about your processor. It does not refer specifically to size or any form of segmentation. if available. Instead. These addresses may or may not be the same as the physical addresses. Whether you allow variable-sized chunks of memory to be allocated. and how the operating system allocates and manages processor memory space. I can do this because most of the processors afford roughly the same memory management schemes.Chapter 5. Intel uses this term to describe memory addresses that have been translated by paging hardware. or another complicated applications. If I put address 00001 on the address bus of the processor. These difference vary considerably based on how you choose to handle the memory on the system. With the Intel processors. There are some major differences though. Physical memory is the memory chips and their addresses as accessed by the hardware. I need to define some terms to ensure we are talking about the same thing. This is the memory that programs deal with and is based around a "selector" (a 16-bit number that serves as an index into a table). Linear memory is the address space a program or the operating system sees and works with. some do it with an external page memory management unit (PMMU).

they are partitioned off into their own space with a lower and upper bound. the operating system is usually loaded at the very bottom or very top of memory. Simple Flat Memory Unless some form of protection is used.There are several basic memory models to choose from and countless variations. They can still share and get to each other's address space if no protection is applied. The bad part is that an invalid pointer will more then likely cause some serious problems. The good part is that no address translations are required for any kind of interprocess communications. any program can reach any other program's memory space (intentionally or unintentionally). MMURTL V1. Paging hardware usually affords some form of protection. With this scheme.1. and bad. No address translations take place in hardware. different memory allocation schemes (top down. In this type of system. This would keep all of the program's memory in one range of linear addresses. This can be good. Figure 5. As programs are loaded. at least for the operating system. Paged Flat Memory This is a variation on simple flat memory. bottom up. chunks of physical memory called pages. and that physical addresses and linear addresses are the same. As programs are loaded. If the paging hardware only provides two levels of protection. You may be restricted from using some of these options. Considerations and variations of this simple model include paging. Partitioning means you would allocate additional memory for a program it would use as its unallocated heap. are allocated and assigned to be used as a range of linear memory. Figure 5. I'll start with the easiest to understand and work from there. This is still a flat scheme because all the programs will use the same linear addresses. and possible partitioning for each program. depending on what your processor or external memory management hardware allows. they are given a section of memory (a range of address) to use as their own.1 (Simple Flat Memory) shows this very simple-shared memory arrangement. etc. Hardware memory paging would possibly provide a solution to the bad pointer problem.0 -32- RICHARD A BURGESS . Simple Flat Memory Simple Flat Memory means that the operating system and all programs share one single range of linear memory space. Keep in mind that the easiest to understand may not necessarily be the easiest to implement.). This scheme requires the use of paging hardware. this still means that program A can destroy program B as long as they both share the same linear space and are at the same protection level.

Figure 5.3 (Demand Paged Flat memory) shows an example of the page translations that can occur. With paged memory. direct access storage device. For instance. more than likely a hard disk drive. For example. the physical memory can come from anywhere in the physical range to fit into a section of linear address space. and brought back into real physical address space on demand or when required. but only 16 Megabytes of this linear address range may be active if that's all the physical memory your system has. The word "demand" means that they are sent out to the disk when not needed. the linear address range can also be much larger than the amount of physical memory. or even 64 MB). This gives the operating system some options for managing memory allocation and cleanup. MMURTL V1. your system's actively-allocated linear address range may actually be extended to a much greater size (say 32.Paged Flat Memory Demand-Paged Flat Memory This is a major variation on Paged Flat Memory.2 . if you have 16 MB of physical memory.The additional pages of physical memory will actually be contained on an external. Figure 5.0 -33- RICHARD A BURGESS . Figure 5. With demand-paged memory.2 (Paged Flat Memory) shows how the blocks of physical memory are turned into addressable chunks for the system. Figure 5. With this scheme. but you can't actively allocate more linear address space than you have physical memory. your linear address space may run from 0 to 1 Gigabytes.2 also implies that the physical pages for the program were allocated from the "top down" to become its addressable area from the "bottom up" which is perfectly legitimate. you extend the active size of your linear memory beyond your physical memory's limits.

and indeed you are. it means that you're lying to your programs. Virtual Paged Memory The term virtual usually means IT'S NOT REAL.3 .0 -34- RICHARD A BURGESS .Demand Paged Flat Memory In this scheme. You could start your addressing for applications at the 3 GB mark if you desired. and the hardware allows it. but it sure looks like it is. you're already lying to them when you map in a physical page and it's translated to a linear page address that they can use. Address 30000h is the same to all programs and the operating system. With paging hardware. they are operating in "parallel" linear spaces. all the programs still use the same linear space.4 (Virtual Paged Memory) for an example that will help you understand this technique. Look at Figure 5. My definition of virtual is telling a BIG lie (not a little one).Figure 5. MMURTL V1. This means that you can tell two programs that they are both using linear address ranges that are the same! In other words. So you can really call any form of memory management where address translations take place "virtual memory. Something else to note is that the actual values of these linear addresses may extend to the highest values allowed by the paging hardware. as in virtual reality." Some of the older systems referred to any form of demand paging as virtual memory. The minute you apply this term to memory management.

Notice I said "may not" and not "will not.Virtual Paged Memory Like the Paged Flat memory.4 you can see that both programs see the operating system pages at the same addresses in their linear spaces. Demand Paged Virtual Memory Just like demand-paged flat memory. which is the idea. any memory they allocate can be theirs alone. In Figure 5. The largest will be when you implement interprocess communications. you will think of. there are many possible variations. but linear address 30000h to one program may not be the same physical memory addressed as linear address 30000h to another program (same address. To a single program with its own address space it may appear flat. physically-allocated pages can be placed anywhere in a programs linear address space. The same type of on-demand external storage is used as with demand-paged Flat Memory. The options here are almost mind boggling. This means you can share operating system memory between all your programs. Note the advantages here. No doubt." This is because you can make the physical addresses match the linear addresses for two programs if you desire. You can have all programs share a section of the same physical memory in a portion of their linear space. different data or code). parallel linear spaces. In order to allow multiple. but the same linear range of addresses where program code and data are stored are completely independent. Once again. You will have to do some homework for your processor of choice.4 . A way around this to use a shared memory area. but make all the rest of their linear space completely independent and inaccessible by any other program. You will need some form of translation between addresses that are passed from one linear space to another. Note also the disadvantages.0 -35- RICHARD A BURGESS . memory is NOT flat. Unlike Paged Flat Memory. The major difference is that these pages can be for any one of several independent linear address spaces.Figure 5. MMURTL V1. demand-paged virtual memory is when you can allocate more pages than you physically have on your system. and implement some of your own. the memory management hardware must support it. yet.

Demand-Paged Memory Management Management of demand-paged memory is no trivial task. They are not limited to 64K.0 -36- RICHARD A BURGESS . This was actually several registers. or overlapping 64-K sections of memory called segments. However. OS/2 or Windows). This means you can save the time rewriting it. They used something called a segment register. they needed a way to use all of the 8-bit processor code that used 16-bit addressing. This made it possible to manage these segments with something called a “selector. they made sure that it was downward compatible with code written for the 16-bit processors. MMURTL V1. the pages that are moved out to make room are the ones that haven't been used recently. it is usually termed as "dirty. 8088. When Intel built the first of its 32-bit processors (80386). The segment registers in the 16-bit processors were a simple extension to the address lines. Segmentation is really a hold over from the past. and you need to understand the implications. It also meant that when they built processors with more address lines. when they built the 16 bit processors (8086. and still reference it as if it were at offset zero. and the popular Zilog Z80). In fact. This compatibility may be in the form of the ability to use code or object modules from other systems that use segmentation. To make this work. paging hardware will tell you if a page has been accessed only for reading or has actually been modified. The paged memory management hardware will tell you which ones have been used and which ones have been sitting idle since they were paged in or created. we had some pretty limited address spaces. one for code and several for data access. This is a very handy feature. If you had read in a page and it was accessed for reading. It is really a form of virtual memory itself. The most common. and 80286). or even an 8-bit one. The descriptor tables allow you to set up a zero based address space that really isn't at linear address zero. If you need to move someone's physical pages back into their linear space. What they needed was a transparent way to use the extra four bits to give you access to a full 1 Mb of RAM." The idea of a page being dirty has some significance. The most common method is with a Least Recently Used (LRU) algorithm.” A selector is a number that is an index into a table that selects which segment you are working with. The selectors are managed in tables called the Global Descriptor Table (GDT) or Local Descriptor Tables (LDT). This meant you could have a whopping 64K of RAM. so I won't go into further detail. This can be a big time saver when you're talking about thousands of pages being swapped in and out. 80186. you may already have a "clean" copy on the disk. The 8-bit processors had 16 address lines. A multitude of software was written for 16-bit addressing schemes. is to use a separate thread or task that keeps track of how often pages are used. The segment registers gave you an addressable granularity of 16 bytes. they added a very important concept called Protected mode. or page creation (memory allocation) requirements. The FAR pointer is a pointer that includes the selector as part of the address. and generally the most efficient. the 16bit processors were also compatible with object code for the 8-bit processors (8080 and 8085. Likewise.g. You may even want to use it to make your system compatible with some of the most popular operating systems in use (e. When this was a 16-bit world. this introduction leads us to another very important point about segmentation that you need to understand. When a page has been written to. All of the Intel products are very well documented. They allowed multiple sequential. the processor uses the entry in the table indicated by the selector and performs an address translation. Another major change in the 32-bit processors that deal with segments is the fact that they can now be as large as all available memory. This means you can locate code or data anywhere you want in the 4 GB of linear memory. and maybe to use the Intel Object Module format. This additional task will try to keep a certain percentage of physical space cleared for near term page-in.. When you use a selector. What they came up with is called Segmentation. but it can become cumbersome to manage because it can force you to use something called FAR pointers to access almost everything. Segmented Memory I will discuss segmented memory because the Intel processors allow it. When the 80286 came on the scene. that would identity which particular 64-K addressable area you were working with. 20 to be exact. It's just what it sounds like. they needed to identify and manage those segments. The LRU schemes have many variations. Protected mode operation with selectors carried over into the 32-bit processors. and now you want to move it out to disk because it hasn't been used lately.

I'll discuss each of these things. Tracking client linear memory allocations. you should decide on your basic allocation unit. You can let applications allocate as little as one byte. This can be a real hassle. MMURTL V1. Tracking physical memory if paging is used. Management of memory involves the following things: Initialization of memory management. Basic Memory Allocation Unit The basic allocation unit will define the smallest amount of memory you will allow a client to allocate. along with the allocated size and maybe the owner of the memory block. The easiest and most efficient way is to store the link at the beginning of the allocated block. you ARE the operating system. Allocating memory for applications. and it will be based on many factors. Other factors include how much memory you have. The links are small structures that contain pointers to other links. I recommend you track which client owns each link for purposes of deallocation and cleanup. but it's a very realistic part of the equation. and one of the easiest ways to manage memory. it is simply terminated and the resources it used are recovered. You'll have to make a decision. and how much time and effort you can put into to the algorithms. This will simplify many things that your memory management routines must track. or force them to take 16 Kilobytes at a time.0 -37- RICHARD A BURGESS . They are “linked lists” and “tables. There have been hundreds of algorithms designed for linked list memory management. I don't know of any theoretical books that take into account how much time it takes to actually implement pieces of an operating system. it usually takes the whole system down. One of the important ones will be whether or not you use hardware paging. Linked List Management Using linked lists is probably the most common. but not in the order I listed them. This is because you have to figure out how you'll manage memory before you know how to initialize or track it. Allocating memory to load applications and services. you have one or more linked lists that define allocated and/or free memory. one person).With a 32-bit system. this makes a far pointer 48 bits. Remember. It also slows things down because the processor must do some house keeping (loading shadow registers) each time the selector is changed. I will give you a good idea what it's like. which will be defined by the paging hardware. then it's up to you to do some serious research to find the best method for your system. This type of algorithm is most common when there is a large linear address space shared by one or more applications. But in many systems. Tracking Linear Memory Allocation Two basic methods exist to track allocation of linear or physical memory. such as I described earlier with the Simple Flat Memory Model. if your hardware supports it. you may want to make the allocation unit the same size as a page. and my advice to you is not to bite of more than you can chew. each block holds its own link. the type of applications you cater to. The linked lists are setup when the operating system initializes memory. you'll face many decisions on how to manage it. This way. If you do. I learned this the hard way. and can be located almost instantly with the memory reference. In operating systems that don't provide any type of protection. when one application crashes. Memory Management Once you've chosen a memory model. With this scheme.” Before you decide which method to use. You make the rules. How you store the links in this list can vary. when one application crashes. and Handling protection violations. especially if you have a very small programming team (say.

The word cleanup also implies that the memory space will become fragmented as callers allocate and deallocate memory. pFree and pMemInUse.size is large enough.size = 0. Ignoring the fact that MS-DOS uses segmented pointers. pMemInUse. For my description. let's assume we have 15 megabytes of memory to manage beginning at the 1 Mb address mark (100000h) and all of it is free (unallocated). /* " " */ struct MemLinkType *pNextLink. you follow the links beginning at pFree to find the first link large enough to satisfy the request. */ Backlink to pFree */ 15 Megs-16 for the link */ OS owns it now */ -38- RICHARD A BURGESS . } When a client asks for memory. MMURTL V1.pNext until pLink. which means you can't honor the request. MS-DOS appears to use links in the allocated space. pMemInUse. you would set up the two base link structures. pFree. you have two structure variables defined in your memory management data. Of course. struct MemLinkType *pLink. but I'll touch on it some.pPrev = 0. /* to work with links in memory */ struct MemLinkType *pNewLink. In the following simple linked list scheme. or you reach a link where pNext is null. pLink->size = 0xf00000 . pMemInUse. pMemInUse. No next link yet.pNext = 0x100000.size must be at least as large as the requested size PLUS 16 bytes for the new link you'll build there. /* /* /* /* /* /* /* /* first link in 15 megabyte array */ No backlink */ No size for base link */ OS owns this */ No mem blocks allocated yet */ No backlink */ No size for base link */ OS owns this */ /* Set up first link in free memeory */ pLink = 0x100000. pFree. /* " " */ long Client = 0. struct MemLinkType pMemInUse. In the process of memory management initialization. One points to the first linked block of free memory (pFree) and the other points to the first linked block of allocated memory (pMemInUse). pLink->pPrev = &pFree.size = 0. pLink->owner = 0. You now have an idea where they went. I can tell this from the values of the pointers I get back when I allocate memory from the MS-DOS operating system heap. struct MemLinkType pFree. long owner.owner = 0. }. pLink->pNext = 0.0 /* /* /* /* /* Set structure pointer to new link */ NULL.pNext = 0.owner = 0. This is done by following pLink. /* Memory Management Variables */ struct MemLinkType /* 16 bytes per link */ { char *pNext. These are the first links in your list. you can see that there is a conspicuous few bytes missing from two consecutive memory allocations when you do the arithmetic. I haven't disassembled their code.16. /* " " */ struct MemLinkType *pPrevLink. char *pPrev. /* a backlink so we can find previous link */ long size. you must remember that pLink. This is another topic entirely.pPrev = 0. You would set up the base structures as follows: void InitMemMgmt(void) { pFree. pFree.

or a NULL pointer if we can't honor the request. The function returns the address of the new memory. Client is the owner of the new block. Granted. I've gone back to code I did five or six years ago and spent 30 minutes just trying to figure out what I did.WantSize . I would suggest you leave it as is for maintenance purposes and take the hit in memory usage. if (pLink->size >= (WantSize + 16)) /* This one will do! */ { /* Build a new link for the rest of the free block */ /* then add it two the free list. /* Start at the base link */ /* Keep going till we find one */ /* As long as we have a valid link. but it sure didn't seem like it was worth the headaches.*/ /* Next link please */ while (pLink->pNext) { pLink = pLink->pNext. /* This is who owns it now */ return(&pLink+16).. /* Point to the new link */ pLink->size = WantSize. pLink->size = WantSize. WantSize is the size the client has requested. pNewLink->owner = 0.The following case of the very first client will show how all allocations will be. pPrevLink->pNext = &pNewLink. pNewLink->pNext = pLink->pNext. When they hand you a pointer to MMURTL V1.16. The deallocation routine is even easier than the allocation routine. pNewLink->size = pLink->size . pLink->pNext = &pNewLink. The following example code is expanded to show each operation. /* Backlink */ /* Fix size of pLink */ /* Remove pLink from the pFree list and put it in the */ /* allocated links! */ pPrevLink = pLink->pPrev. This divides the free */ /* block into two pieces (one of which we'll allocate) */ /* Set up new link pointer. /* How much we allocated */ pLink->owner = Client. /* OS owns this for now */ /* Hook it into free links after pLink */ pNewLink->pPrev = &pLink.no mem available */ /* Return address of the NEW memory */ You will require a routine to free up the memory and also to clean up the space as it gets fragmented. Let's assume the first request is for 1 MB. it WILL get fragmented. char *AllocMem(long WantSize) { pLink = &pFree.pNext = &pLink. it was in an effort to save memory and make the code tight and fast. You could make this type of operation with pointers almost "unreadable" in the C programming language. /* get previous link */ /* Unhook pLink */ pLink->pNext = pMemInUse. } } return(0). } /* Sorry . And believe me. /* Put pLink in USE */ pMemInUse.0 -39- RICHARD A BURGESS . fix size and owner */ pNewLink = &pLink + (WantSize + 16)..pNext. If you use this code.

/* /* /* /* /* /* point to the link to free up */ Some form of error checking should go here to ensure they */ are giving you a valid memory address. This provides instant cleanup. We will stop when */ /* we find the link we belong between. Here is a deallocation routine that defragments the space as they free up memory. You have the linear address.deallocate. */ pNewLink = MemAddress . which returns memory to the free pool. Even though the links themselves are next to each other in memory. change its owner. But you can't depend on this. and you insert it there. "walks" up the links and finds its place in linear memory order.16. but cleanup will be a snap. then move it to the pFree list. This makes the most sense to me. /* this will be handy later */ /* If memory isn't corrupted. /* scan till we find out where we go. You should notice that the allocation algorithm ensured that there was always a link left at a higher address than the link we allocated. He's a dummy */ if (pNextLink->pPrev != &pFree) MMURTL V1. A simple scan of the list can be used to combine links that are next to each other into a single link. You can add code in the allocation and deallocation routines to make it easier. Combining blocks can be done at the same time. and that they really own the */ memory by checking the owner in the link block. the links may end up next to each other in the free list anyway.0 /* Can't be pFree */ -40- RICHARD A BURGESS .pNext. You can do things like checking to */ ensure the pointer isn't null. In other words. but a lot depends on the patterns of allocation by your clients. If they tend to allocate a series of blocks and then free them in the same order. I personally have not done any research on the fastest methods. depending on the order of deallocation. you go to that address (minus 16 bytes). */ /* but that doesn't help us cleanup if we can */ /* First let's see if we can combine this newly freed link */ /* with the one before it. This is where the fragmentation comes it. */ /* Start at pFree */ pNextLink = pFree. This will add time to deallocation routines. But we can't add our size to pFree. This would be the link */ validation I talked about. if at all possible. There are several thoughts on cleanup. you simply walk the list until you find the links that the newly freed memory belongs between. int FreeMem(char *MemAddress) { /* We will use pNewLink to point to the link to free up. The cleanup of fragmented space in this type of system is not easy. do a simple insertion at the proper point in the list. they can end up anywhere in the pFree list. This means we would simply add our */ /* size + 16 to the previous link's size and this link would */ /* disappear. validate the link. */ while ((pNextLink->pNext) && (&pNextLink < &pNewLink)) { pNextLink = pNextLink->pNext. because we can assume there will always will be a link at a higher address. Another method is to keep the pFree list sorted as you add to it. we should be there! */ /* We could just stick the link in order right here. */ /* It's actually a MemInUse link right now. We try to combine the newly freed link with the one before it or after it. } pPrevLink = pNextLink->pPrev. This starts at the first link pFree is pointing to. or you can do it all after the fact. This will be useful in the following routine.

This means your operating system will be tracking linear and physical memory at the same time. */ /* In this case. /* fix backlink */ } /* If we didn't combine the new link with the last one. In the memorymanagement routines earlier. /* operating system owns this now!*/ /* Now we'll try to combine pNext with us! */ if ((&pNewLink + pNewLink->size + 16) == &pNextLink) { /* We can combine them. pNewLink->pNext = pNextLink->pNext. pNewLink->owner = 0. we couldn't be combined with the previous. you will have two memory spaces to track. Tracking Physical Memory If you use some form of paging. */ /* we just leave it were we inserted it and report no error */ return(0). we assumed we were working with a flat linear space. you can still look at the setup of the tables for paging. I go into great detail on paging hardware and tables when I describe MMURTL memory management. } } /* If we got here. MMURTL V1. The two must be synchronized. then go away! */ pPrevLink->size += pNewLink->size + 16. Tables are not as elegant as linked lists. Quite often you resort to brute processor speed to get things done in a hurry. you will be forced to use tables of some kind anyway. In the case of the paging hardware. pNewLink->pNext = pNextLink. and all the information you need to use paging hardware on the Intel and work-alike systems. If you don't use the paging hardware. you will simply have to ensure that when you hand out a linear address. return(0). You can still use paging underneath the system described earlier. pNewLink->pPrev = pPrevLink.0 -41- RICHARD A BURGESS . It should give you a great number of ideas. If you want to use tables and you're not using paging hardware. pNext will go away! */ pNewLink->size += pNextLink->size + 16. It combines the use of managing physical and linear memory with tables. pNextLink->pPrev = pNewLink. there are physical pages assigned to it. if (pLink) /* the next could have been the last! */ pLink->pPrev = &pNewLink. Paging hardware will define how the tables are organized. you can decide how you want them setup. } Memory Management With Tables If you use paging hardware . pLink = pNextLink->pNext. we must insert ourselves in the free list */ pPrevLink->pNext = pNewLink.or you decide to make your operating system's allocation unit a fixed size and fairly large you can use tables for memory management. You can expand on them if needed.{ if (&pNewLink == (&pPrevLink + (pPrevLink->size + 16))) { /* add our size and link size (16).

such as battery backed-up CMOS memory space. It will be the page’s size. I recommend you to leave the initial operating system memory block in an address range that matches physical memory. “OS Initialization. If your processor gives you these types of options. You could also use linked lists to manage the physical memory underneath the linear address space if you like.0 -42- RICHARD A BURGESS . Most paging hardware also requires that the non-paged addresses you are executing during initialization match the paged linear addresses when you turn on paging. and turn on the paging hardware. This can get complicated. The hardware paging is usually not active when the processor is reset. it becomes allocated memory right away. In some cases. and you should take that into consideration. This is one of the first things you will do during initialization of memory management. Some processors use memory addresses from your linear space for device I/O. Intel has a different scheme called Port I/O. MMURTL V1. You must set up tables. you need to ensure that you initialize memory so that it takes into account all of memory used by the OS code and data. fault or other signaling method employed by the processor or paging hardware and determine how you will handle all those that apply to the type of system you design. It generally means the application has done some bad pointer math and probably would be a candidate for shut-down. It will be greater if you keep a certain number of pages grouped together in a cluster. you have a lot of work to do during the initialization process. Something to remember about working with physical memory when paging is used. add the translations you need and are already using. it will generally be in a block of addresses. If you use paging. Batteries fail. This signaling will be in the form of a hardware trap or interrupt. From there. demand-paged systems must have a way to tell you that some application is trying to address something in a page that isn't in memory right now. Initialization of Memory Management I won't discuss loading the operating system here because it's covered in chapter 7. you will have independent tables for each application. The trap may not actually be signaling a problem. is that you can't address it until it has been identified in the hardware page tables that you manage. You may not always be able to depend on this to be accurate. it will generally be associated with page tables that you are using to manage your linear address space. “the Hardware Interface. You may want your operating system code and data at the very top of you linear memory address space.When you work with paging hardware. For instance. you may also have to take into account any hardware addresses that may not be allocated to applications or the operating system. This will almost certainly lead to some form of table use. you will have to move it all. Your operating system is responsible to ensure they are updated properly. Some hardware platforms have initialization code stored in ROM (executed at processor reset) that will find the total and place it somewhere you can read. Another chore you'll have is to find out just how much memory you have on the system. trap. you will deal with a fixed size allocation unit.” If you use a processor that uses hardware I/O from the memory space. Memory Protection Protected memory implies that the processor is capable of signaling you when a problem is encountered with memory manipulation or invalid memory address usage. But it's up to you.” but once the operating system is loaded. You must consider where the operating system loads. This means that if you want your operating system code and data in a linear address beyond the physical range. You will have to investigate each type of interrupt. or load the rest of it after you turn on paging. but may be part of the system to help you manage memory. Paging hardware uses tables to track the relationship between physical and linear memory. I'll discuss that in Chapter 6. Wherever you load it. Other forms of traps may be used to indicate that an application has tried to access memory space that doesn't belong to it. You will be allocated these up front.

Getting it straight took only half my natural life MMURTL really doesn't provide memory management in the sense that compilers and language systems provide a heap or an area that is managed and cleaned up for the caller. This means the only 48-bit pointers you will ever use in MMURTL are for an operating system call address (16-bit selector. but will have no effect on applications. I do use a very small amount of segmentation. 32-bit offset). you know that with MS-DOS. I use a Virtual Paged memory model as described earlier. set them all to one value and forget them. and Huge models to accommodate the variety of segmented programming needs.An Intel Based Memory Management Implementation I chose the Intel processors to use for MMURTL. I use almost no segmentation. The selector values must be a multiple of eight. and even larger with demand page memory. The "selectors" (segment numbers for those coming from real mode programming) are fixed. The operating system code segment is 08h. Large. It can be very complicated to use on any system. The program memory model I chose is most analogous to the small memory model where you have two segments. I depend on the paging hardware very heavily. The concept of hardware paging is not as complicated as it first seems. but how I use it may give you some ideas I haven't even thought of. For instance. The fact that the operating system has its own code segment selector is really to make things easier for the operating system programmer. Small. programs are quite often broken down into the code segment. the application code segment. and many others. The operating system and all applications use only 3 defined segments: The operating system code segment. but it serves my purposes quite well without it. This may sound like a restriction until you consider that a single segment can be as large as all physical memory. If you are familiar with segmented programming. and one data segment for all programs and applications. Making the operating system code zero-based from its own selector is not a necessity. I may add demand paging to it at some later point in time. programs generally had one data segment which was usually shared with the stack. This was too complicated for me. How MMURTL Uses Paging MMURTL uses the Intel hardware-based paging for memory allocation and management. I figured that an operating system should be a "wholesale" dealer in MMURTL V1. and is no longer necessary on these powerful processors. In the 80x86 Intel world there are Tiny. These will never change in MMURTL as long as they are legal on Intel and work-alike processors. The common data segment is 10h. the concept of program segments is an old one. MMURTL's memory management scheme allows us to use 32-bit data pointers exclusively. This could change in future versions. Compact. This was commonly referred to as a "Medium Memory Model" program. and I chose them to reside at the low end of the Global Descriptor Table. This greatly simplifies every program we write. One is for code and the other is for data and stack. The only selector that will change is the code selector as it goes through a call gate into the operating system and back again. If you have no desire to use them. the initialized data segment.0 -43- RICHARD A BURGESS . uninitialized data segment. but in a very limited fashion. Medium. so the best thing I can do is to provide you with a very detailed description of the memory model and my implementation. A Few More Words On Segmentation Even though I continue to refer to Intel as the "segmented" processors. It also speeds up the code by maintaining the same selectors throughout most of the program's execution. I wanted only one program memory model. but nice to have. and for protection within the operating system pages of memory. The user code segment is 18h. and one or more code segments. and I even use the Intel segment registers.

If you want just a few bytes. Every Job gets its own PD. A page is Four Kilobytes (4Kb) of contiguous memory. you'll see that this allows you to map the entire 4 GB linear address space. each representing 4Kb of physical memory. The map is identical for every Job and Service that is installed. Why so high? This gives the operating system one gigabyte of linear memory space. but most of the operating system code – specifically. This is done with something called a page directory (PD). one 4K page table can manage 4MB of linear/physical memory. which can have 1024 entries. Right? Wrong. This 4K page of memory becomes addresses 4096 through 8191 even though it's really sitting up at a physical 16MB address if you had 16 MB of RAM. Each PDE is also four bytes long. This means we can have 1024 PDEs in the PD. The kernel itself is never scheduled for execution (sounds like a "slacker. Each PDE holds the physical address of a Page Table. go to your language libraries for this trivial amount of memory. The Map of a single job and the operating system is shown in Table 5. You could designed your system with only one page directory if you desire. its not magic. Paging allows us to manage physical and linear memory address with simple table entries. Each PTE is four bytes long. (Aren't acronyms fun? Right up there with CTS . MMURTL V1. The operating system is shared by all the other jobs running on the system. These table entries are used by the hardware to translate (or map) physical memory to what is called linear memory. is that you really do need most of this capability. If you get our calculator out. My thought was for simplicity and efficiency.0 -44- RICHARD A BURGESS .memory. we can take the very highest 4K page in physical memory and map it into the application's linear space as the second page of its memory. That's not too much overhead for what you get out of it. 1024 * 1024 * 4K (4096) = 4. an application of a hundred megabytes or more is even conceivable.967.294. Because of this. the kernel ." huh?). Each entry in a PD is called a Page Directory Entry (PDE). Each entry in a PT is called a page table entry (PTE).1. Besides.Carpal Tunnel Syndrome). the operating system really doesn't own any of its memory. A job's memory space actually begins at the 1GB linear memory mark. Sure. Here's the tricky part (like the rest was easy?). Each of the PDEs points to a PT. It is always on a 4Kb boundary of physical as well as linear addressing. but it’s close. but you need it in pieces.runs in the task of the job that called it. With 1024 entries (PTEs) each representing 4 kilobytes.296 (4 GB) You won't need this capability any time soon. and each application the same thing. I hand out (allocate) whole pages of memory as they are requested. The Page Tables that show where the operating system code and data are located get mapped into every job's memory space. The operating system itself is technically not a job. because I haven't explained it yet. There are 1024 PTEs in every PT. Linear memory is what applications see as their own address space. Page Directories (PDs) The paging hardware needs a way to find the page tables. if I add demand paging to MMURTL's virtual memory. For instance. No. What you don't see. Page Tables (PTs) The tables that hold these translations are called page tables (PTs). I manage all memory in the processor's address space as pages. and return them to the pool of free pages when they are deallocated. it has code and data and a task or two. The Memory Map The operating system code and data are mapped into the bottom of every job's address space.

Besides. The processor translates these linear (fake) addresses into real (physical) addresses by first finding the current Page Directory. but it wasn't needed. I built the PD and stored the physical address in the Task State Segment field for CR3. Well. the operating system has to know where to find all these tables that are allocated for memory management. Sounds like a lot of work. or even 30 jobs running it starts to add up. In the scheme of things. This is fine until you have to update or change a PDE or PTE. Sure. I wanted to keep the overhead down. certainly less overhead than this explanation).0 -45- RICHARD A BURGESS . The PTE is the physical address of the page it's after. It needs a fast way to get to them for memory management functions.MMURTL Memory Map Description Linear Top Dead address space Linear Max. I could have built a separate table and managed it. possibly several for each application. I make this upper 2K a shadow of the lower 2K. Now it's got the PTE. Job Allocated Memory Data Code Initial Stack Job Memory DLLs (loadable shared code) Device Drivers OS Allocated memory OS Memory Linear Base Address Range 4Gb -1 byte 2Gb – Linear Top 2Gb (Artificial maximum limit) 1Gb + Job Memory 1Gb + Stack Page(s) + Code Pages 1Gb + Stack Page(s) 1Gb 1Gb (Initial stack.1 . Data) 0Gb + operating system Memory 0Gb 0Gb Now the pundits are screaming: "What about the upper 2 gigabytes – it’s wasted!" Well. you can't just take a physical address out of a PDE and find the PT it points to. I keep the linear address of all the PTs there. then I put the linear address of the PD in the Job Control Block.Table 5. Read on and see what I did. This is how I use the upper 2 Kb of the page directories. Exactly 2048 bytes above each real entry in the PD is MMURTL's secret entry with the linear address of the PT. the secret is out. I didn't give it to the Red Cross). Finding the PD for an application is no problem. It does this by looking at the value you (the OS) put into Control Register CR3. yes. Likewise. When I started the application. these entries are marked "not used" so the operating system doesn't take a bad pointer and try to do something with it. MMURTL V1. The operating system uses linear addresses (fake ones) just like the applications do. 20. If you remember. Of course. You can't just get the value out of CR3 and use it to find the PT because it's the physical address (crash – page fault). each PDE has the physical address of each PT. MMURTL needs to know the physical address of a PT for aliasing addresses. and it needs it fast. 2K doesn't sound like a lot to save. This is fine for one address per job. But it was for a good cause (No. However. The operating system has no special privileges as far as addressing physical memory goes. but it does this with very little overhead. it uses the upper 10 bits of the linear address it's translating as an index into the PD. but when you start talking 10. now we're talking dozens or even hundreds of linear addresses for all the page tables that we can have. The processor then uses the next lower 10 bits in the linear address it's translating as an index into the PT. CR3 is the physical address of the current PD. in a word. Now that it knows where the PD is. Code. The entry it finds is the physical address of the Page Table (PT).

What does this make the page directory look like? Below.. I don't keep duplicate operating system page tables for each job. This assumes the operating system consumes only a few PTEs (one-page table maximum). This is 1Gb to 2Gb. This is accomplished with three different calls depending what type of memory you want. in fact. in Table 5. If I desired.0 -46- RICHARD A BURGESS . The pages are all initially marked with the System protection level Read/Write.. 768 769 . AllocPage().2.. The pages are all initially marked Read/Write with the System protection level and the entries automatically show up in all job's memory space because all the operating system page tables are listed in every job's page directory. but it ensures that these pages are below the 16MB physical address boundary. As shown. but I was more interested in conservation at this point in time.Page Directory Entries (PDEs) I know you're trying to picture this in your mind.2 Page Directory Example Entry # 0 1 . MMURTL V1. all of the entries with nothing in them are marked not present.. Something else the example doesn't show is that the entry for the physical address of the operating system PT (0) is actually an alias (copy) of the page tables set up when memory management was initialized. but you get the idea. This is 0 to 1Gb. 1023 Description Physical address of operating system PT (PDE 0) Empty PDE Physical address of Job PT Empty PDE Linear Address of operating system PT (Shadow –marked not present) Empty Shadow PDE Linear Address of Job PT (Shadow –marked not present) Empty Shadow PDE Last Empty Shadow PDE This table doesn't show that each entry only has 20 bits for each address and the rest of the bits are for management purposes. The pages are all initially marked with the user protection level Read/Write. The low-order 12 bits for a linear address are the same as the last 12 bits for a physical address. Now you need to know how MMURTL actually allocates the linear space in each job or for the OS. They simply don't exist as far as the processor is concerned. Direct Memory Access hardware on ISA machines can't access physical memory above 16MB. Allocation of Linear Memory You now know the mechanics of paging on the Intel processors. AllocPage() allocates contiguous linear pages in the Jobs address range. 256 257 ... It's 20 bits because the last 12 bits of the 32-bit address are below the granularity of a page (4096 bytes). Table 5. all the shadow entries are marked not present. it would be transparent to applications anyway. and how I use the processor's paging hardware.. AllocDMAPage() also returns the physical address needed by the user's of DMA. AllocOSPage(). That would really be a waste.. If I decided to do it. I could move the shadow information into separate tables and expand the operating system to address and handle 4Gb of memory.1. and AllocDMAPage() are the only calls to allocate memory in MMURTL. AllocDMAPage() allocates contiguous linear pages in the operating system address range. AllocOSPage() allocates contiguous linear pages in the operating system address range. is a list of the entries in the PD for the memory map shown in Table 5. 512 513 .

Each byte of the array represents eight 4Kb pages (one bit per page). If the physical memory exists. Each application (job) gets it own PD. The caller will never know.Managing physical memory. This ensures that even if you install a device driver that uses DMA after your applications are up and running. if pages of memory in a particular job are physically next to each other (with the exception of DMA). They are actually loaded into the operating system's memory space with freshly allocated operating system pages. I've discussed how MMURTL handles linear addresses. and to keep up family relations I told her I named this array after her). with the least significant bit of byte 0 representing the first physical 4K page in memory (Physical Addresses 0 to 4095). but the memory will not be available for later use. The main goal of physical memory management is simply to ensure you keep track of how much physical memory there is. the operating system will attempt to deallocate as many pages as requested which may run into memory that was allocated in another request. and data. and whether or not it's currently in use. The current version of MMURTL is designed to handle 64 MB of physical memory which makes the PAM 2048 bytes long. If so. For AllocDMAPage() you allocate physical memory from the bottom up. where the physical memory is located with the exception of DMA users (device drivers). only that number will be deallocated. They are simply new jobs. Now comes that easy part . it's unlikely that it won't find a contiguous section of PTEs. initial stack. With a 1Gb address space. This is allocated along with the new Job Control Block (JCB). nor do you even care. which is also my sister's name. MMURTL V1.0 -47- RICHARD A BURGESS . The caller is responsible for ensuring that the number of pages in the DeAllocMem() call does not exceed what was allocated. Message-based system services are exactly like applications from a memory standpoint. Allocation of Physical Memory The fact that the processor handles translation of linear to physical memory takes a great deal of work away from the OS. The PAM is similar to a bit allocation map for a disk. They become part of the operating system in the operating-system address space accessible to everyone. It also gets as many pages as it needs for its code. but only from this caller's memory space. To get this information we must go to the PDs and PTs. must all be loaded into memory somewhere. there will be no error. The PAM only shows you which pages of memory are in use. If it does. System Services. nor should it try to find out. Device Drivers have code and data. If fewer pages than were allocated are passed in. For AllocPage() and AllocOSPage(). Physical memory allocation is tracked by pages with a single array. or will return an error if it's not available. Now if I could only afford 64 MB of RAM. from a previous AllocPage() call. The array is called the Page Allocation Map (PAM. Loading Things Into Memory Applications. If enough free PTEs in a row don't exist. They become part of the operating system and are reached through the standard entry points in the call gate). It's more likely you will run out of physical memory (the story of my life). This means the PAM would be 512 bytes long for 16 Mb of physical memory. along with the number of pages to deallocate. Device drivers. it will create a new PT. The PAM is an array of bytes from 0 to 2047. the caller passes in a linear address.All AllocPage() calls first check to see if there are enough physical pages to satisfy the request. you allocate physical memory from the top down. It is not important. Deallocation of Linear Memory When pages are deallocated (returned to the operating system). All AllocPage() calls return an address to contiguous linear memory. and DLLs. there will be physical memory below 16MB available (if any is left at all). It does not tell you whom they belong to. It's loaded into these initial pages. but no stack. then they must find that number of pages as contiguous free entries in one of the PTs.

This request block is allocated in operating system memory space. Of course. But this gets away from memory management. Memory and Pointer Management for Messaging If you haven't read chapter 4 (Interprocess Communications). then you can also alias addresses using selectors. If it tries to deallocate them. but that's trivial (a few instructions). DLLs are also loaded into operating system address space. The Intel documentation has a very good description of this process. I wanted them to be fully re-entrant with no excuses. the kernel allocates a Request Block from the operating system and returns a handle identifying this request block to the caller. This is 8 Kb of initial memory management overhead for each job. Messaging and Address Aliases If you design your memory management so that each application has its own independent range of linear addresses. Some systems allow DLLs to contain data. how could they be swapped?). and the service doesn't even know that the pages don't actually belong to him as they "magically" appear inside its linear address space. the second job has access to other job's data. This means they can be accessed from the user's code with near calls. are aliased into the services memory area and are placed in the request block. you'll have to tackle address aliasing. They share physical memory.0 -48- RICHARD A BURGESS . Memory overhead per application is one thing you will have consider in your design.Dynamic Link Libraries are the weird ones. Instantly. Aliasing will only occur for certain operating system structures and messaging other than the aliased page tables in the users Page Directory for the operating system code and data. but the pages they occupy are marked executable by user level code. the kernel copies a PTE from one job's PT to another job's PT. Paging makes messaging faster and easier. This will make more sense if you do. This can introduce re-reentrancy problems. There is no new allocation of physical memory. It will still only be 8Kb for a 4Mb application. you should do it before you read this section. When an application requests a service. They have only code. If you decide you want to use the full segmentation capabilities of the Intel processors. Operating System page tables are aliased as the first tables in each job's PD and marked as supervisor. no data. PDs and PTs are always resident (no page swapper yet. The memory management aspects of the request/respond messaging process work like this: The caller makes a request The Request() primitive (on the caller's side) does the following: Allocates a request block Returns a request handle to the caller Places the following into the RqBlk: linear address of pData1 (if not 0) linear address of pData2 (if not 0) sizes of the pData1 and 2 pointer to caller's Job Control Block MMURTL V1. Of course they probably won't be at the same linear address. A PTE that is aliased is marked as such and can't be deallocated or swapped until the alias is dissolved. but at the user's protection level so the service can access it. This means you will have to translate addresses for one program to reach another's data. an alias address is actually just a shared PTE. With a page based system such as MMURTL. an error will occur. If two programs need to share memory (as they do when using the Interprocess Communications such as Request/Respond messaging). which means you have to fix-up a pointer or two in the request block. They are there to be used as shared code only. This also means that the loader must keep track of the PUBLIC names of each call in a DLL and resolve references to them after we load the applications that call them. The user's two pointers pData1 and pData2. and no stack.

Jobs can deallocate one or more pages at a time. When it's done its work. Memory is allocated in 4Kb increments (pages). The Wait() primitive (back on the caller's side): passes the response message to the caller.Service Code Response Exchange dData0 and dData1 Places a message on the Service's exchange with the Request handle in it Schedules the service for execution Reevaluates the ready queue. Reevaluates the ready queue (switch tasks if needed). They don't need to know any of this at all to write a program for MMURTL. For MMURTL. What does all this mean to the applications programmer? Not much I'm afraid. How they see the interface is very important. Physical memory is tracked with a bit map. OS PTs are MAPPED into every Job's Page Directory by making entries in the Job's PD. it responds. Linear address of the PD is kept in the Job Control Block. Jobs can allocate one or more pages at a time.0 -49- RICHARD A BURGESS . use it. Only those of you that will brave the waters and write your own operating system will have to consider documentation directed at the programmers. Places the message on the caller's response exchange. switching tasks if needed The Wait() primitive (on the service's side): Adds aliases to the service's Page Table(s) for pData1 and 2 Places aliased linear addresses into RqBlk Returns the message to the service The service does its thing using the aliased pointers. Summary of MMURTL Memory Management The key points to remember and think about when pondering my memory-management scheme (and how you can learn from it. The programmer tracks his own memory usage. The respond() primitive (on the service's side) does the following: Removes the aliased memory from the service's PTs. OS uses upper 2Kb of each PD for linear addresses of PTs. the application programmer needs to know the following: The minimum amount of allocated memory is 4Kb (one page). One or more PTs for the OS. reading & writing data to the caller's memory areas. One or more Page Tables (PT) for each Job. or use pieces of it) are: One Page Directory (PD) for each job. MMURTL V1.

.

is the interrupt or trap. I haven't run into any timing problems I could attribute to caching. and physical device nomenclatures to the code that will be below this abstraction layer (which is actually controlling the hardware). The Intel and compatible CPUs have been referred to as "backward" in this respect. the operating system provides access to hardware for the programs on the system. you can port the operating system to almost any platform. Other methods may be in the form of registers that you can read to gather information on the state of the processor.Chapter 6. jumps where you tell it. memory-to-memory DMA transfers. The most common. The implementation of this hardware isolation layer also means that you must thoroughly investigation all of the platforms you intend to target. Timing issues are also involved with the CPU. Adding a somewhat bulky layer between a program and the video hardware definitely slows down operation. Hardware Isolation The concept of isolating the operating system (and the programmer) from directly accessing or having to control the hardware is a good idea. or tasks that are running (or running so poorly). Your assembler and compilers will take care of all the details of this for you. MMURTL V1. This affects everything from the programmatic interfaces. This is often referred to as the "Big-ENDian. But there's always more than meets the eye. or for specific timing requirements. You need to define logical device nomenclatures from the operating system's point of view. and speed. in theory. Don't use any fancy non-standard functions that might not be supported on some platforms – for example. The two that are obvious are code size. The most obvious place this is evident is in the video arena. it implies an additional programmatic interface layer between the hardware and the parts of the operating system that control it. I don't recommend you try to implement a complete hardware isolation layer without having all of the documentation for the target platforms you intend to support. You provide a common. Hardware Interface As a resource manager. It executes your instructions. although I never thought so. there are drawbacks to this idea. Some processors store the least significant byte of a four byte integer at the highest memory address of the four bytes. This chapter discusses your approach to hardware interfaces and highlights some of he pitfalls to avoid. and it simply runs. The interface can be designed above or below the device driver interface. aside from the instructions you give it.how it is accessed in memory. You can keep your device interfaces as hardware non-specific as possible.0 -51- RICHARD A BURGESS . The CPU The interface to the Central Processor Unit (CPU) seems almost transparent. As good as it sounds. Not even device driver writers have to know about the hardware aspects. to interoperability with other systems. These types of caches can affect timing of certain OS-critical functions. Little-ENDian" problem when dealing with the exchange of information across networks or in data files. and if you've worked with the processor for any length of time it will be second nature. and the most significant at the lowest. Any additional layers between the users of the hardware and the hardware itself adds to size and reduces the speed of the interface. as long as it has similar hardware to accomplish the required functions. however. This concept is called hardware abstraction or hardware isolation. The most elegant method (and the most difficult) is when it is done below the device driver code. This well-defined interface has no hardware-specific information required for its operation from the point of view of the operating system. The CPU has well defined methods of communicating with the operating system. well defined interface to do things like move data to and from a device. Therefore. An important consideration is how the processor stores and manipulates its data . but that doesn't mean that you won't. Most 32-bit CPUs have relatively large internal instruction and data caches. One other thing this may affect is how you manipulate the stack or access data that is on it. I have learned how to enable and disable caching on the systems I work with for the purposes of debugging.

With some CPUs you can actually switch tasks on a hardware interrupt instead of just calling a procedure. Multibus. This is called the main bus. The connections between busses are usually through gated devices that will be turned on and off depending on who is accessing what bus. they are fairly explicit about the effect of certain instructions concerning some of these signal lines. the control bus usually carries a wide variety of signals. Your interpretation of their documentation may be superior to that of those who write secondary books on these topics (like mine). The interface bus may not carry all of the data. These actions are under hardware control so you generally don't have to worry about them. MMURTL V1. no matter which device you must code. in that case. including read and write access lines for memory and I/O.some proposed. but I found the most valuable. the main bus may be composed solely of the CPU signals. The RS232 device driver included with the MMURTL operating system should prove valuable to you. It would help you to be familiar with the basic bus structures on your target hardware platform. the CPU bus and the main bus are the same. such as those dealing with interrupts. The most obvious to note is the bus from the CPU to the rest of the computer system. There are many extensions to this bus . The first set is the data bus. along with things like processor interrupt lines and clock signals. On smaller systems. Switching the complete hardware and software context of a task for every interrupt will simply slow you down. On the PC-ISA platforms. They are actually very forgiving devices. Other devices besides communications channels may also be controlled through UARTs. or ISA Bus. the PC-ISA bus is really quite crippled compared to the CPU bus. Before you plan some grand interface scheme. Bus designs are usually based on standards and given names or numbers such as IEEE-696. which means not being able to handle all your interrupts in a timely fashion. make sure you understand the capabilities of the platform you're working with. The CPU bus will not usually be the bus that is connected directly to the external interface devices that you must control. just to name a few. PCI. EISA. The CPU bus is usually comprised of three distinct sets of signals for most processors. and some that are available now. which carries the values that are read from and written to memory or I/O devices. Serial I/O Many forms of serial Input/Output exist. Finally. address. but in some of the processor documentation. The bus structure on any hardware platform is usually comprised of several buses. These may include the keyboard or a mouse port to name few. and not all of the I/O addresses or interrupt lines can be accessed on this bus.An important point concerning the CPU interface is how you handle interrupts. ISA. or control lines that are found on the main or CPU bus. I refer to this bus as the interface bus.0 -52- RICHARD A BURGESS . asynchronous serial communications (RS-232). The most common is the relatively low-speed. Only half of the data lines make their way out to this bus (it's really a 16-bit bus). I read many books about my target processor. I've tried both methods. and especially the interface bus. the external interface bus is called the Industry Standard Architecture. internal bus. For instance. and most useful. and they take much of the drudgery away from the programmer that is implementing serial communications. or CPU bus. Most varieties of these devices are very similar. I've had to "drop back 10 yards and punt" a time or two. but it can consume a good deal of valuable bandwidth. This is handy. Quite honestly. The second is the address bus. The Bus Structure A bus is a collection of electrical signals that are routed through a computer system between components. You really don't need to know the exact hardware layouts for these busses and signals. The CPU bus is usually connected to the main bus. The electronic devices that handle asynchronous communications are called UARTs (Universal Asynchronous Receiver Transmitters). were purchased directly from the processor manufacturer. which determines where in memory is being read from or written to or which I/O device is being accessed. There is no source of information like the manufacturer's documentation to familiarize yourself with all of the aspects of the CPU. and I recommend interrupt procedures over interrupt tasks if at all possible. This gets into the issue of interrupt latency.

Therefore. They have no way. the infamous Centronics interface). which is discussed in detail later in this chapter. to determine when one byte ends and the next begins. MMURTL V1. The major difference between these forms of block-data transfer is the consumption of CPU time (bandwidth).0 -53- RICHARD A BURGESS . Buffered USARTs assist with this issue. In fact.g. Another way is programmed I/O. This block movement of data is accomplished in one of several ways. Block-Oriented Devices Block-oriented devices can include disk drives. Parallel I/O Parallel Input/Output may include devices for printer interface (e. DMA uses the least CPU bandwidth because it's a hardware device designed to transfer data between memory and devices. Another method is shared memory blocks – a process in which a hardware device actually takes up a portion of your physical address space. Keyboard The keyboard may or may not be a part of the hardware to control on your system. The interfaces to these devices enable large volumes (blocks) of data to be moved between the devices and memory in a rapid fashion. or HDLC. They work with frames or packets of data. a delay causes them to abort the frame they're working on and send an error instead. the method is determined by the interface hardware (the disk or tape controller hardware. I recommend that you forget about polling and stick with interrupts. USARTs generally expect the device driver to provide these packets with very little delay. or even memory to memory. SDLC. You will usually not have a choice as to which method you use. Quite often. except for the hardware clock timing. even the main bus may be accessible as a parallel interface of sorts (this is most common on laptops and notebook computers). you must ensure that you can get out the entire frame (byte by byte) without a large delay factor involved between each byte. All these synchronous communications standards have one thing in common from the operating system writer's point of view: critical timing.. This is how the Integrated Drive Electronics (IDE) interface works on the PC-ISA systems. during unused portions of the CPU clock cycle. are synchronous communications such as those used with X. Parallel buses can move data faster. or network card). These devices provide interrupt-driven capabilities just like the serial devices. network frame handlers. The processor may even have some form of high-speed string instructions to enable you send blocks of data to this array of pseudo addresses. they are much easier to control than serial devices because they have fewer communications options. in many cases. but how your operating system implements its tasking model and its ISRs (interrupt service routines) will have a large effect on its ability to handle these types of devices. Programmed I/O uses special instructions to send data to what appears to be an address. and tape drives. Unlike UART devices. simply because they are transferring information one byte or one word at a time instead of a single bit at a time in a serial fashion. This is done by repetitively reading the status registers on the UART/USART in a loop. and all you do to transfer the block is write or read those addresses then tell the device you're done. If you have a platform that communicates with its users through external terminals. you may simply have to deal with a serial device driver.25 (an international link and network layer standard). but still very important. and on some systems. Direct Memory Access (DMA) is one method. The issue of polling versus interrupt-driven control of communications devices crops up in documentation you read about UARTs and USARTs. The concept of polling a device means that your device driver or program always is there to continually check on the device to see when it needs attention.Less commonly found. If you intend to design a true multitasking system. to name a few. Concentrate on the efficiency of all the Interrupt Service Routines (ISRs) on your system. USART (the added "S" is for synchronous) devices are very timing critical. but is really a device connected to the bus. which is available on some processors such as the Intel series. the IEEE-488 General Purpose Interface Bus (GPIB).

500 bytes of memory space. Video Like the keyboard. and even the device manufacturer's manuals (builders of the integrated circuits). In character-based systems. However. The two basic forms of video display are character-based and bit-mapped graphics. 600 x 500 divided by 8(size of a byte in bits)=37. Keyboard users prefer a familiar set of codes to work with such as ASCII (American Standard Code for Information interchange). This is known as planar pixel access. Bit-mapped graphics-based systems are much more complicated to deal with. A common resolution on many systems is 1280 x 1024. You can even perform a CPU hardware reset through these same control ports. Then they break video memory into four or more sections. The keyboard can usually be treated as just another device on your system as far as a standardized interface. the keyboard hardware is tightly integrated into the system. and they give you a way to switch between the arrays. On the PC-ISA platform. this only consumes 4000 bytes. the keyboard is an integral part of the hardware. This consumes the least amount of your address space. One of these is how to assign and distinguish between multiple programs asking for keystrokes. All the output would be via a serial port.g. If this is the case. Keyboard handling in a multitasking system can present some pretty interesting problems. You are responsible for the translations required from the keyboard device if they aren't already in the desired format. the keyboard serial device allows more than just keyboard interaction. and can consume a much larger area of your address space. If you want 16 colors for each bit. The keyboard on many systems is preprogrammed to provide series of bytes based on some translation table within the keyboard microprocessor. Manufacturers provide methods to allow these arrays to occupy the same physical address space. It's worth repeating that having the manufacturer's documentation for the platform. Another method is to make each nibble of a byte represent the four color bits for each displayed bit on-screen. Quite often. and MMURTL V1. One byte per character and maybe one byte for the character's color or attribute is required. Your tasking model and your programmatic interfaces will determine how this is accomplished. Many books have been written on control of video hardware. In a monochrome system with a decent resolution. you have direct access to "video memory" somewhere in the array of physical addresses that your CPU can reach. and you will need to know how to set it up and operate the device. In most cases. Graphics systems must have an address for each bit or Pixel (Picture element) of information you wish to manipulate or display. which is a relatively new multi-byte international standard. This hardware is usually a small processor or a UART/CPU combination. This could consume a vast amount of memory. Interrupts are the normal method for the keyboard device to tell you that a key has been struck. Video hardware manufacturers realize this.On many systems. this video memory will contain character codes (e. I suppose you can imagine the veins popping out on my forehead when I reach the huge 15 keystroke limit of MS-DOS. you may not have any video hardware at all if your user I/O is through external terminals. or Unicode. ASCII) that the video hardware translates into individual scan lines on-screen to form the characters. but you must realize the importance of this device.0 -54- RICHARD A BURGESS . devices perform more than one function on a particular platform. the keyboard hardware will be accessed through I/O port (or memory address). this would require three additional arrays to provide the independent color information. Each of these requires a completely different approach to how you interface video with your operating system. Nothing infuriates an experienced user more than an undersized typeahead buffer or losing keystrokes. Each bit array is on its own plane. In fact. There are certain aspects of video hardware you need to understand because the direct hardware interaction on your platform will affect things like memory-management techniques that you must design or implement.. is worth more than I can indicate to you. this can be 600 or more bits across and as many as 500 vertically. This requires an interrupt service routine. most platforms have the video hardware built in or directly accessible through the interface bus. This is especially true for the IBM PC-AT compatible platforms. On an 80-column-by-25-line display.

you need to take graphics into consideration even if you build a character-based system first. DMA devices have several control lines that connect to the CPU and to the devices that use it. This includes cursor positioning. Video-handling code may even be provided in the form of a BIOS ROM (Basic Input/Output System). The concept of a message-based operating system plays well into event-driven program control (such as a windowing system). “Video Code. I did. I was interested in character-based. MMURTL V1. you'll have no problems. How much memory you require will be determined by what methods your video hardware uses.” describes each of the calls I designed for the video interface. Make certain that the video documentation you acquire documents hardware access and not simply access to the code found in ROM. The video hardware must usually be programmed to tell it where this memory is located. One thing I should mention is that many video subsystems may provide video code in ROM to control their hardware. DMA hardware also requires that the programmer know the physical addresses with which they are working. The DMA hardware calls are SetUpDMA(). I considered future video and graphics requirements with MMURTL. and timing when you write to the video display area. In this age of graphics-based systems and graphical user interfaces. Only one plane of the total memory consumes your address space. I recommend you purchase a book that details the video hardware that you intend to control long before you implement your memory management. and GetDMACount(). Your graphics implementation might have an effect on your memory management design or even your tasking model.000 bytes.g.0 -55- RICHARD A BURGESS . I took this into consideration also. Other aspects of the video subsystem will also require your attention. this would be 16 planes at 37. Chapter 26. Programmers that write device drivers have often faced the chore of learning all the intricacies of DMA hardware. In a multitasking system that may have several DMA users. The code for these two calls is shown below. The efficiency of your video routines has a large effect on the apparent system speed as seen by the user. this code will be of little use to you after the system is booted. These methods of displaying video information require you to allocate an array of OS-managed memory that is for the video display subsystem. “Memory Management. interprocess communications. In some cases. I provided a method to allocate memory that returns the physical address they require which I touched on in chapter 5. If the programmers all follow the rules for programming the DMA hardware to the letter. multiple.allow you to switch between them. which is hardwarecontrolled on character based systems. For a 256-color system. Even though they have paralleled access to this memory. Direct Memory Access (DMA) You'll find DMA hardware on almost all platforms you work with. the operating system must ensure harmony between these users. because the video interface cards have RAM that they switch in and out of your address space. In many cases. however. which takes care of the synchronization and details of DMA programming. These control lines provide the necessary "handshakes" between the device and the DMA hardware. With minimal changes you could use this code on almost any system. and your tasking model. taking into account the differences in control registers and commands on the target system's DMA hardware. it is still a vast amount of memory to access. A better plan is to provide users with a programmatic interface to use the DMA hardware. I even cheated a little and left the video subsystem set up the way the boot ROM left it because it suited my purposes.” I also gave users high level calls to set up the DMA hardware for a move. and I also allocated memory for large graphics arrays during initialization. have to research hardware cursor control. except maybe an embedded system board. non-overlapping video users. 16-bit code in a 32-bit system). This is known as packed pixel access. I'll show you what I provided for users of DMA. Direct memory access is a hardware device that operates outside of the virtual address translations that may be taking place within the paging hardware of the processor or external PMMU (paged memory management unit). That equates to 600.500 bytes (using the 600 x 500 size sample). and to query the status of the move.. but this means that the programmers must be well versed on the DMA hardware-control requirements. the video ROM's only purpose is to test and setup the video hardware during the testing and initialization phases of the boot process. Your requirements will more than likely be different. This code may also be useless if it will not execute properly in your system (e.

Read/Write DMA Rq Register .Read/Write DMA Rq Mask Register .INC .Ch 2 Address DMA12Cnt EQU 05h .Rd clears mode reg count/Wr Clr ALL mask bits .Read Status/Write Command DMA1RqReg EQU 09h .CODE With 8-bit DMA.Read Command/Write Single bit mask DMA1Mode EQU 0Bh .Read Command/Write Single bit mask . and the page register is the next most significant byte (bits 23-16).Ch 3 Address . the lower word (bits 15-0) is placed into the address registers of the DMA.Writing this address clears byte ptr flip flop DMA1Clear EQU 0Dh .) MMURTL V1.0 -56- RICHARD A BURGESS .Read/Write DMA Rq Mask Register .DATA .Ch 1 Address .The code is larger than it could be because I have expanded out each channel instead of using a simple table for the registers. while 23-17 are put in the page register. There really is no data segment for DMA.DMA DACK0 Page register . .Ch 2 Word Count .Read/Write Mode register DMA1FF EQU 0Ch . The page registers determine which 64K or 128K section of memory is being accessed.INC is used for error codes that may be returned to the caller. This is called a cascaded device.Ch 3 Word Count . etc. With word.Ch 2 Address . One of the channels from the second device is fed into a channel on the first device. address bits 16-1 are placed in the address registers.Ch 3 Word Count DMA1StatCmd EQU 08h . etc.Write causes MASTER Clear (Read from Temp Reg) DMA1ClrMode EQU 0Eh .DMA Page register by DRQ/DACK number DMAPage0 DMAPage1 EQU 87h EQU 83h . but the standard include file MOSEDF.Read Status/Write Command . The following equates are the register addresses for these two devices on ISA hardware. Bit 16 is ignored by the page register.Ch 1 Word Count DMA12Add EQU 04h .Ch 1 Word Count .Writing this address clears byte ptr flip flop . There are two 4-channel DMA devices. DMA 2 Port addresses DMA20Add DMA20Cnt DMA21Add DMA21Cnt DMA22Add DMA22Cnt DMA23Add DMA23Cnt DMA2StatCmd DMA2RqReg DMA2RCmdWbm DMA2Mode DMA2FF DMA2Clear DMA2ClrMode DMA2MskBts EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 0C0h 0C2h 0C4h 0C6h 0C8h 0CAh 0CCh 0CEh 0D0h 0D2h 0D4h 0D6h 0D8h 0DAh 0DCh 0DEh .Write causes MASTER Clear (Read from Temp Reg) .Ch 3 Address DMA13Cnt EQU 07h . Optimize it as you please. DMA moves (channels 5-7).Ch 0 Word Count . DMA 1 Port addresses and page registers DMA10Add EQU 00h .INCLUDE MOSEDF.Rd clears mode reg count/Wr Clr ALL mask bits DMA1MskBts EQU 0Fh . .Ch 2 Word Count DMA13Add EQU 06h .Ch 1 Address DMA11Cnt EQU 03h . DACK1 (etc.Ch 0 Word Count DMA11Add EQU 02h .Read/Write Mode register .Ch 0 Address .========== DMA Equates for DMA and PAGE registers ========= .Ch 0 Address DMA10Cnt EQU 01h .Read/Write DMA Rq Register DMA1RCmdWbm EQU 0Ah .

the segment is wrapped around.6 and 7. AL OUT DMA2StatCmd. AL OUT DMA2Mode. This includes the cascade mode for generic channel 4. PUBLIC InitDMA: MOV AL. Out. 43h OUT DMA1Mode. as you might assume).DMAPage2 EQU 81h DMAPage3 EQU 82h DMAPage5 EQU 8Bh DMAPage6 EQU 89h DMAPage7 EQU 8Ah The following internal call sets up the initial DMA channel values for both chips to most probable use. even on word moves.Master disable OUT DMA1StatCmd. dChannel. and data is moved into the lower part of the current 64K segment (not into the next segment. dType. .CH 2 DMA 1 & 2 .CH 0 DMA 2 . AL MOV OUT MOV OUT AL. DMA is crippled because it can't move data across 64K physical boundaries for a byte-oriented move. 40h DMA1Mode. AL OUT DMA1Clear.MASTER CLEAR (same as hardware reset) .CH 3 DMA 1 & 2 . AL OUT DMA2Mode. (Cascade Mode) MOV AL. . . 04 . AL OUT DMA2Clear.CH 1 DMA 1 & 2 . AL MOV AL. dMode) EBP+ 28 24 20 16 12 dPhyMem is physical memory address sdMem is nBytes to move (STILL bytes for word xfers.0 -57- RICHARD A BURGESS . we do math) MMURTL V1. If they do. . AL OUT DMA2Mode. .Enable ALL DMA 1 Channels . AL XOR AL. The PUBLIC call DMSASetUp() follows: . AL XOR AL.Enable ALL DMA 2 Channels The PUBLIC call DMASetUp() sets up a single DMA channel for the caller.AL OUT DMA1ClrMode. AL OUT DMA2StatCmd. . The caller sets the type of DMA operation (In. AL RETN . 0C0h DMA2Mode. 41h OUT DMA1Mode. . AL OUT DMA1StatCmd. This is called one time during initialization of the operating system. AL . or 128K boundaries for a word move. DmaSetUp(dPhyMem. .CH 0 DMA 1 . so they always specify byte count for the setup call. AL AL. .All commands set to default (0) . AL XOR AL. I do this for the caller. I left it up to the caller to know if their physical addresses violate this rule. For channels 5. sdMem. AL OUT DMA2ClrMode. which is DMA channel 0 on DMA chip 2. Verify). . AL MOV AL. the address and count must be divided by two for the DMA hardware. . 42h OUT DMA1Mode.

0 = Verify. Clear FlipFLop (Val in AL irrelevant) . 3 JE DMA3 CMP EAX. [EBP+28] DMA10Add. PUBLIC __DMASetUp: PUSH EBP . AL DMA1FF. 1 Single Cycle. AL EAX. dPhyMem .0 -58- RICHARD A BURGESS . 1 = In (Write Mem). get dType AND BL. 2 = Out (Read memory) . In? (Write) JE DMAWrite . 1 . Hi Byte . [EBP+20] CMP EAX. OR with MODE . MOV EBP. [EBP+16] . . 00001000b . ErcDMAMode JMP DMAEnd DMAModeOK: SHL EAX. channel 0 Set Mask for DRQ . [EBP+12] . AL . 0 .Jump table for channel selection . OR read command to BL (OUT) JMP DMASelect DMAWrite: OR BL. MMURTL V1. AL EAX. 2 JE DMA2 CMP EAX. Mode must be < 4 JB DMAModeOK MOV EAX.2. 0 JE DMA0 CMP EAX. 04 . 00000100b . 00000000b AL. 1 JE DMA1 CMP EAX. BL DMA1Mode. 6 JE DMA6 CMP EAX. Set to Verify by default (low bits 0) CMP EAX. (Floppy Disk I/O uses 1) .3. Check fType (Verify?) JE DMASelect . MOV EAX. 7 JE DMA7 MOV EAX.ESP . 6 . ErcDMAChannel JMP DMAEnd DMA0: CLI MOV OUT MOV OR OUT OUT MOV OUT SHR OUT AL. dMode CMP EAX. 8 DMA10Add. AL . AL AL. Yes (if no then fall thru to Read) DMARead: OR BL. . dMode . Move Mode bits to 6 and 7 MOV BL.7) .6. 2 Block. 3 Cascade . Lo byte address . 5 JE DMA5 CMP EAX. Put it in BL MOV EAX. OR write command to BL (IN) DMASelect: MOV EAX.5. dChannel (0.1. dType . CH0 (CH is last 2 bits) . 00000100b DMA1RCmdWbm. Yes CMP EAX. 0C0h ..0 Demand Mode.

[EBP+24] EAX DMA12Cnt. Highest byte (to page register) . 8 DMA11Add. AL EAX. dPhyMem . AL EAX. AL EAX. 00000110b DMA1RCmdWbm. Lo byte address . AL EAX. BL DMA1Mode. 00000001b DMA1RcmdWbm. Hi Byte . AL EAX. channel 0 Clear Mask for DRQ AL. AL AL. Highest byte (to page register) . EAX DMAEnd . AL AL. AL EAX. [EBP+28] DMA11Add. AL AL. OR with MODE/TYPE . 8 DMAPage2. channel 2 Clear Mask for DRQ MMURTL V1. AL EAX. EAX DMAEnd . 00000001b AL. channel 2 Set Mask for DRQ .SHR OUT MOV DEC OUT SHR OUT MOV OUT STI XOR JMP DMA1: CLI MOV OUT MOV OR OUT OUT MOV OUT SHR OUT SHR OUT MOV DEC OUT SHR OUT MOV OUT STI XOR JMP DMA2: CLI MOV OUT MOV OR OUT OUT MOV OUT SHR OUT SHR OUT MOV DEC OUT SHR OUT MOV OUT STI EAX. CH1 . Clear FlipFLop (Val in AL irrelevant) . OR with MODE . BL DMA1Mode. AL DMA1FF. AL DMA1FF. Highest byte (to page register) . 8 DMA11Cnt. CH2 . AL AL. [EBP+28] DMA12Add. Clear FlipFLop (Val in AL irrelevant) . Hi Byte . 00000101b DMA1RCmdWbm. AL EAX. dPhyMem . 8 DMAPage1. AL . AL EAX. 00000010b AL. AL EAX. 00000010b DMA1RcmdWbm. AL EAX. channel 1 Set Mask for DRQ . AL EAX. Lo byte address . 8 DMA12Cnt. [EBP+24] EAX DMA10Cnt. channel 1 Clear Mask for DRQ AL. AL EAX. 00000000b DMA1RcmdWbm.0 -59- RICHARD A BURGESS . AL EAX. 8 DMA10Cnt. 8 DMAPage0. sdMem . [EBP+24] EAX DMA11Cnt. AL AL. 8 DMA12Add. sdMem .

Clear FlipFLop (Val in AL irrelevant) . . . EAX JMP DMAEnd DMA3: CLI MOV OUT MOV OR OUT OUT MOV OUT SHR OUT SHR OUT MOV DEC OUT SHR OUT MOV OUT STI XOR JMP AL. Hi Byte . AL EAX. 00000001b DMA2RcmdWbm. EBX EAX. AL EAX. OR with MODE . CH3 . OR with MODE . Lo byte address . AL EAX. We only need 23-17 for the page Highest byte (to page register) sdMem DIV by 2 for WORD Xfer One less word . 8 DMAPage3. . AL DMA2FF. BL DMA2Mode. AL EAX. EAX DMAEnd . . . 1 EAX DMA21Cnt. Hi Byte . AL EAX. 8 DMA13Add. 00000011b DMA1RcmdWbm. 00000101b DMA2RCmdWbm. EAX DMAEnd . AL DMA1FF. [EBP+24] EAX. AL EAX. 00000001b AL. channel 3 Clear Mask for DRQ . 00000111b DMA1RCmdWbm. 1 DMA21Add. [EBP+28] EBX. Highest byte (to page register) . AL EAX. [EBP+28] DMA13Add. 8 DMA13Cnt. 8 DMA21Add. . CH1 on DMA 2 .XOR EAX. channel 1 DMA2 Set Mask for DRQ . AL EAX. channel 3 Set Mask for DRQ . AL EAX. AL EAX. AL AL. MMURTL V1.NOTE: DMA channels 5-7 are on DMA2 and numbered 1-3 for chip select purposes DMA5: CLI MOV OUT MOV OR OUT OUT MOV MOV AND SHR OUT SHR OUT MOV SHR OUT MOV SHR DEC OUT SHR OUT MOV OUT STI XOR JMP . 0FFFFh EAX. AL AL. AL EAX.0 AL. AL EAX. . 8 DMA21Cnt. AL AL. . AL AL. EAX EAX. 00000011b AL. Clear FlipFLop (Val in AL irrelevant) dPhyMem Save EBX for page Rid of all but lower 16 DIV by 2 for WORD Xfer (bits 16-1) Lo byte address . BL DMA1Mode. 15 DMAPage5. . dPhyMem . sdMem . [EBP+24] EAX DMA13Cnt. channel 1 Clear Mask for DRQ -60- RICHARD A BURGESS .

15 DMAPage6. AL EAX. AL DMA2FF. Highest byte (to page register) . AL AL.0 AL. [EBP+28] EBX.EBP MMURTL V1. 00000010b AL. 1 EAX DMA22Cnt. EAX EAX. AL EAX. 0FFFFh EAX. 0FFFFh EAX. EAX DMAEnd . DIV by 2 for WORD Xfer . EBX EAX. Hi Byte . 8 DMA23Cnt. EAX EAX. 00000010b DMA2RcmdWbm. 8 DMA22Cnt. 8 DMA23Add. OR with MODE . AL EAX. -61- RICHARD A BURGESS . CH2 on DMA 2 . BL DMA2Mode. OR with MODE . AL EAX. AL EAX. [EBP+24] EAX. Rid of all but lower 16 . AL EAX. Rid of all but lower 16 . CH3 on DMA 2 . AL EAX. channel 2 Set Mask for DRQ . EAX . 00000011b AL. channel 2 Clear Mask for DRQ AL. 1 DMA22Add. 8 DMA22Add. 15 DMAPage6. DIV by 2 for WORD Xfer . AL AL. Hi Byte . DIV by 2 for WORD Xfer . AL EAX. DIV by 2 for WORD Xfer . AL EAX. channel 3 Clear Mask for DRQ . AL DMA2FF. AL AL. EBX EAX. AL AL. DMA7: CLI MOV OUT MOV OR OUT OUT MOV MOV AND SHR OUT SHR OUT MOV SHR OUT MOV SHR DEC OUT SHR OUT MOV OUT STI XOR DMAEnd: MOV ESP. 00000111b DMA2RCmdWbm. 1 DMA23Add. Highest byte (to page register) . Lo byte address . 00000110b DMA2RCmdWbm. 00000011b DMA2RcmdWbm. channel 3 Set Mask for DRQ . sdMem . 1 EAX DMA23Cnt. dPhyMem . Clear FlipFLop (Val in AL irrelevant) . AL EAX. Lo byte address . dPhyMem . [EBP+28] EBX.DMA6: CLI MOV OUT MOV OR OUT OUT MOV MOV AND SHR OUT SHR OUT MOV SHR OUT MOV SHR DEC OUT SHR OUT MOV OUT STI XOR JMP . [EBP+24] EAX. sdMem . AL EAX. Clear FlipFLop (Val in AL irrelevant) . AL EAX. BL DMA2Mode.

. you may not always be moving a fixed-size block. 7 MMURTL V1. .1. PUBLIC __GetDMACount: PUSH EBP MOV EBP.0 . You should note that this value will read one less byte or word than is really left in the channel. The count is number of WORDS-1 . . This is because 0 = 1 for setup purposes. DMA12Cnt JMP SHORT DMACDoIt DMAC3: CMP EAX. In this case. To move 64K. DMA22Cnt JMP SHORT DMACDoIt DMAC7: MOV DX. you actually set the channel byte count 65.Return address for count . . 20 bytes of junk to dump After the DMA call has been made.2. GetDMACount() returns the number of bytes or words left in the DMA count register for the channel specified. for channels 5-7 and BYTES-1 for channels 0-3. [EBP+16] MOV ESI. you will have to check the DMA controller and see just how many bytes or words were moved when you receive the interrupt.ESP MOV EAX.POP EBP RETF 20 . this is bytes. 3 JNE SHORT DMAC5 MOV DX.5. In cases where you always move a fixed size block such as a floppy disk. 1 JNE SHORT DMAC2 MOV DX. .7) . this will be the number of words. 0 JNE SHORT DMAC1 MOV DX. 6 JNE SHORT DMAC7 MOV DX. GetDMACount(dChannel. 2 JNE SHORT DMAC3 MOV DX.3. . you can assume that the block was moved if the device status says everything went smoothly (e. DMA21Cnt JMP SHORT DMACDoIt DMAC6: CMP EAX. . dChannel (0. the count will be returned. . . DMA11Cnt JMP SHORT DMACDoIt DMAC2: CMP EAX. . For channels 5 through 7. EBP+ 16 12 . no error from the floppy controller). DMA23Cnt CMP EAX.g. pwCountRet) ..Channel . 5 JNE SHORT DMAC6 MOV DX. For channels 0 through 3. On some devices. .535: . -62- RICHARD A BURGESS .6. the device you are transferring data to or from will usually interrupt you to let you know it's done. [EBP+12] DMACSelect: CMP EAX. DMA10Cnt JMP SHORT DMACDoIt DMAC1: CMP EAX. DMA13Cnt JMP SHORT DMACDoIt DMAC5: CMP EAX. . pwCountRet is a pointer to a Word (2 byte unsigned value) where .

be responsible to program these dedicated timers. In the process of an interrupt. The operating system may. ErcDMAChannel MOV ESP.DX MOV CL. Your job is to know how to program the PICU. This interval or time period can range from microseconds to seconds. . No Error . Internally. however. and not available to the operating system interrupt mechanisms directly.AL STI MOV WORD PTR [ESI].EBP POP EBP RETF 8 DMACDoIt: CLI IN AL.0 -63- RICHARD A BURGESS . If you have timers that perform system functions for the hardware. One or more of these timers may be dedicated to hardware functions on the system board. . but you must generally set values to indicate what will be placed on the bus. . CX XOR EAX. Usually. 8 bytes to dump from the stack Timers Hardware timers are a very important piece of any operating system. they will more than likely be initialized at boot time and you can forget them. many devices interrupt the processor. these devices allow you to read this register to obtain the countdown value at any time.No such channel! . EAX MOV ESP. the processor has one or two electrical signal lines that external hardware uses to indicate an interrupt. and read as required. and ways to tell the PICU how to prioritize all of the interrupt lines that it services. These timers may be for things like DMA refresh or some form of external bus synchronization. some form of multiplexing that takes place on the CPU interrupt signal line. or at least make sure it doesn't interfere with them after they have been set up by ROM initialization prior to boot time. ways to tell the PICU to block a single interrupt. A hardware timer is basically a clock or stopwatch-like device that you can program to interrupt you either once. A hardware interrupt is a signal from a device that tells the CPU (the operating system) that it needs servicing of some sort. . This is not very complicated. or at set intervals.EBP POP EBP RETF 8 . . The PICU takes care of this for you.DX MOV CH. Some platforms may provide multiple hardware timers that you can access. Priority Interrupt Controller Unit (PICU) The PICU is a very common device found on most platforms. . the CPU must know which device is interrupting it so it can dispatch the proper Interrupt Service Routine (ISR). which determines the interrupt interval. and eventually feeds one signal to the CPU is the PICU. As you can imagine (or will have realized). Therefore. The device that handles all of the devices that may interrupt. hardware timers have a counting device (a register) that is usually decremented based on a system-wide clock signal. and also has the logic to select one of these incoming signals to actually interrupt the CPU. There are instructions to tell the PICU when your interrupt service routine is finished and interrupts can begin again. MMURTL V1. . 20 bytes of junk to dump . . A PICU has several lines that look like the CPU interrupt line to the devices.JE SHORT DMACDoIt MOV EAX. Almost everything you do will be based around an interrupt from a hardware timer.AL IN AL. Quite often.CX has words/bytes left in DMA . You can generally set a divisor value to regulate how fast this register is decremented to zero. or even hours in some cases. program. This ISR is usually performed by the CPU which places a value on the data lines when the CPU acknowledges the interrupt. but it is critical that it's done correctly. .

0 -64- RICHARD A BURGESS . MMURTL V1. You will need the hardware manual for code examples.All of this information will be very specific to the PICU that your platform uses.

To be honest. the boot sector code may load another. because this address is executed by a boot ROM. Boot ROM When a processor is first powered up. Your operating system’s tasking and memory models may differ from MMURTL’s. where boot ROMs usually reside. memory. The boot ROM is read-only memory that contains just enough code to do a basic system initialization and load (boot) the operating system. (whatever they call it for your particular processor) tells the processor which instruction to execute first. The question is. OS Initialization Initializing an operating system is much more complicated than initializing a single program. It is usually the first logical sector of the disk. what instruction does it execute first? The instruction pointer register or instruction counter. more complicated. For this reason. the first executed address is usually somewhere at the high end of physical memory.) The operating system writer may not really need to know the processor’s initial execution address. This small amount of boot sector code has to know how to get the operating system loaded and executed. you'll have to do some research. this is known as the Boot Sector of the disk or diskette." that's exactly what I mean. then jumping to the first address of the sector. If you're using a PC ISA-compatible platform. be organized with a file system. Processor designers are very specific about what state they leave the processor in after they accomplish their internal hardware reset. MMURTL V1. then go through each section of the basic initialization. we start with loading the operating system. 512 bytes (standard hardware sector size on many systems) is not a heck of a lot of code. In other words. Getting Booted If you've ever done any research on how an operating system gets loaded from disk. loader program that in turn loads the operating system. The processor's purpose is to execute instructions. I've done all your homework. The operating system will be located on a disk or other block-oriented device.0 -65- RICHARD A BURGESS . I'm going to give you a good idea what you need to know. If this is true. how does it load a file (the operating system)? It generally doesn't . This is known as a multistage boot. and it's not exactly easy. it executes from a reset state. This device will. it's not everyday someone wants to write an operating system. The only hardware platform I've researched for the purpose of learning how to boot an operating system is the IBM-PC ISA-compatible system. you'll know there is no definitive publication you can turn to for all the answers. In the PC world.Chapter 7. and important structures. Consider that the boot ROM code knows little or nothing about the file system. This can be done in more than one step if required. including hardware. which you can apply to most systems in a generic fashion. In this chapter. This amounts to reading a sector into a fixed address in memory. but the basic steps to get your operating system up and running will be very similar. then execute it. This information can be found in the processor hardware manual. This will give you a very good head start on your studies. no doubt. tasks.at least not as a file from the file system. The Boot Sector The boot ROM code knows only enough to retrieve a sector or two from the disk to some location in RAM. If you're using another platform. When I say "just enough code. or in the processor programming manual from the manufacturer. (The initial address that is executed will also be documented in the processor manual.

This lead to two relocation efforts. it may have loaded it right where you want to put your operating system. the BOOT ROM expects your boot sector at what it considers is Logical Sector Number Zero for the first drive it finds it can read. You only need enough hardware and file-system knowledge to read the sectors from disk to the location you want them in RAM.If the operating system you are loading is located in a special place on the disk. If your operating system's first execution address varies each time you build it. There are usually a few things you must do before you actually execute the operating system. the hardware commands.g. then you can use the BIOS calls to load the operating system or first-stage boot program. I wanted mine at the very bottom and it would have overwritten this memory address. 8. My operating system data and code must start at address 0 (zero). or BIOS calls needed to load it into memory). But it gets more complicated. but the standard order on ISA systems is drive A. Some systems enable you to change the boot order for drives. Where this boot sector is loaded and executed. How you find your operating system on disk will depend on the type of file system. as were also the active interrupt vector table and RAM work area for the BIOS. Where the boot ROM expects your boot sector or sectors to be on disk. and whether or not you have to move it after you load it. so you can begin execution. I was lucky with the PC ISA systems. How to find your operating system or first-stage boot program on disk from the boot sector code once it's executed. and they have some knowledge of the drive geometry from their initialization routines. 7. Where your operating system's first instruction will be. 5. which they store in a small work area in RAM.0 -66- RICHARD A BURGESS . so I relocated my boot sector code (moved it and executed the next instruction at the new address). 6. and I also go into 32-bit protected mode (Intel-specific) before executing the very first operating system instruction. 7. 3. Keep in mind that the PC ISA systems have a BIOS (Basic Input/Output System) in ROM. and runs contiguously for about 160Kb. then Drive C. 3. Second. 8. This was a double problem. This was the case with my system. Just what these things are depends on the hardware platform. I read in the operating system above the actively used memory then relocate it when I didn't need the BIOS anymore. such as a whole track). I know the address of the first operating system instruction because my design has only two areas (Data and Code). then life is easy. This means the boot sector code has to relocate itself before it can even load the operating system. When the boot ROM loaded your boot sector. In my case. MMURTL V1. This dynamic relocation can eat up some more of the precious 512 bytes of code space in the boot sector (unless your boot ROM lets you load more than one sector. 4. The boot sector was in the way (at 7C00h). Whether there are any additional set-up items to perform before executing the operating system. If you intend to load from a wide variety of disk types. Each boot sector provides a small table with detailed disk and file system parameters that allow you to update what the BIOS knows about the disk. 5. I will go over each of the eight items on the above list to explain how it's handled.. because they provide the BIOS. Where your operating system will reside in memory. The factors you will have to consider when planning how your operating system will boot are: 1. 1. 2. I moved the boot sector code way up in memory. then executed the next instruction at the new address. such as a fixed track or cylinder. I have to turn on a hardware gate to access all the available memory. First. and the code segment loads at a fixed address. and where you stored it. The 7C00 hex address may be fine for your operating system if you intend to load it into high memory. 4. In an ISA system. 6. How the boot sector code will read the operating system or first-stage boot program from disk (e. 2. A single boot sector (512 bytes) is loaded to address 7C00 hex which is in the first 64Kb of RAM. Whether you can leave the boot sector code where the BOOT ROM loaded it (dynamic relocation may be needed if the boot sector is where your OS will load). But I'm sure you can gain some insight on what you'll have to do if I give a detailed description of what it's like on a IBM PC-AT-compatible systems. it may not always be in the same place if it's located after variable-sized structures on the first portion of the disk. I don't pretend to know how to boot your system if you're not using the platform I chose. and also with MS-DOS boot schemes. you may need to store this offset at a fixed data location and allow your boot sector code to read it so it knows where to jump. then I'll show you some boot sector code in grueling detail.

0 -67- RICHARD A BURGESS . ES:Nothing 0h JMP SHORT Boot up NOP ORG . I stick the stack anywhere in the first megabyte of RAM I desire.This 59 byte structure follows the 3 jump bytes above .Root Dir entries max .nHidden Sectors (first whole track on HD) .11 bytes for volume name . The boot sector code also .needs some of this information if this is a bootable disk. Ext Boot Signature (always 29h) .386P directive is required because I use protected . One thing you'll notice right away is that I take over memory like I own it.Resvd sectors .nHeads .The . This contains additional information about the file system . DS:CSEG.386P .nFATs . and are not part of the above boot sector data structure. . (140 sectors max) Was Volume ID number ' .nSect in FAT .nTotalSectors if > 32Mb .nBytes/Sector .Drive boot sector came from .a loadable image beginning at cluster 2 on the disk. There's no operating system here now.Padding to make it 3 bytes .BASE (Linear) GDTptr MMURTL V1.The following are pointers to my IDT and GDT after my OS loads .instructions.stage boot of the MMURTL OS which is about 160K stored as . Why not? I do own it.Sect/Cluster . .The actual number of sectors is stored in the data param nOSSectors.Used for temp storage of Sector to Read .media desc. IDTptr DW 7FFh DD 0000h DW 17FFh DD 0800h .be stored contiguously in each of the following logical sectors. (worthless) .LIMIT 256 IDT Slots .LIMIT 768 slots .disks.nTotal Sectors (0 = <32Mb) . Examine the following code: . Herald nBytesPerSect nSectPerClstr nRsvdSect nFATS nRootDirEnts nTotalSectors bMedia nSectPerFAT nSectPerTrack nHeads nHidden nTotalSect32 bBootDrive ResvdByte ExtBootSig nOSSectors ResvdWord Volname FatType DB DW DB DW DB DW DW DB DW DW DW DD DD DB DB DB DW DW DB DB 'MMURTLv1' 0200h 01h 0001h 02 00E0h 0B40h 0F0h 0009h 0012h 0002h 00000000h 00000000h 00h 00h 29h 0140h 0000h 'RICH 'FAT12 ' . CSEG SEGMENT WORD 'Code' USE16 ASSUME CS:CSEG.The following code is a boot sector to load an operating system from a floppy on a PC ISA-compatible system.nSectors/track . The OS must .on this disk that the operating needs to know.8 bytes for FAT name (Type) .and is found on all MS-DOS FAT compatible .This boot sector is STUFFed to the gills to do a single . It must be assembled with the Borland assembler (TASM) because the assembler I have provided (DASM) doesn't handle 16-bit code or addressing.BASE (Linear) .

7C0h DS.The BIOS left a pointer to it at the 1E hex interrupt . vector location. It works. AX MOV DS. 0Fh . DS:[BX] MOV MOV BYTE PTR [SI+4].0 -68- RICHARD A BURGESS . .9000h SS.Offset to new location (Next PUSH) . AX SI.Make DS correct again . CL BYTE PTR [SI+9]. 09000h PUSH AX MOV AX.This is where we jump from those first 3 bytes BootUp: .Move this boot sector UP to 90000h Linear (dynamic relocation) MOV MOV XOR MOV MOV XOR MOV REP AX. 512 MOVSB .Int 1E FDC Params! LDS SI.Clear interrupts . AX DI. at the beginning of this code CLI .. Now set DS equal to ES which is 9000h PUSH ES POP DS .This is the Boot block's first instruction from the initial jump .Now we must update the BIOS drive parameter . AX MOV BX. then "return" to it. return address. Its pseudo jump . 09000h ES.Get this while DS is correct XOR AX. 8000h .Save DS through the reset PUSH ES POP DS PUSH DS MMURTL V1. 6Fh PUSH AX RETF . AX SP.Source segment . some of the parameters to it from the boot sector. SI CX. 0078h . . that I calculated after assembling it once. Push the . table so it can read the disk for us.This is done by finding out where it is and writing . Now we "jump" UP to where we moved it.Stick the stack at linear 98000h (an arbitrary location) MOV MOV MOV AX. DI AX.Destination segment .Segment for new location . nSectPerTrack . MOV AX. MOV CX.

stage one of the a two stage loader should be).Must set interrupts for BIOS to work . Additional Reserved sectors (optional) . MOV CX. (3rd cluster is really the first allocated .STI MOV DL. now let's read some stuff!! .so we are really at cluster 2 like we want to be.Reset failed.Number of OS sectors to read JMP SHORT ContinueBoot . MOV MUL MOV DIV ADD AX.Required for Disk System Reset .waits for a key to reboot (or tries to) via int 19h BadBoot: MOV SI. nOSSectors . but cluster 0 and 1 don't exist .Reset Disk Controller (DL has drive num) . nFATS MUL WORD PTR nSectPerFAT ADD AX. WORD PTR nHidden+2 ADD AX. nBytesPerSect BX AX... ADC DX. AX INT JC 13h SHORT BadBoot . We need the space for other code. Hidden Sectors (optional) . AX .to the first allocated sectors (this is where the OS or .logical sector order. cluster because the first 2 are unused). nRsvdSect MOV CX. Root Directory (n Sectors long) XOR AX. AX MOV AL. .0020h .AX is at sector for cluster 0.What we do now is calculate our way to the third cluster .on the disk and read in the total number of OS sectors in .CX now has a Word that contains the sector of the Root .The layout of the Disk is: .0 -69- RICHARD A BURGESS . OFFSET MsgLoad CALL PutChars . CX . MOV SI.Bad boot goes here and displays a message then .The controller is reset.Save in CX . WORD PTR nHidden . bBootDrive XOR AX.really IS the OS.Calculate the size of the root directory and skip past it . FATS (1 or more) . OFFSET MsgBadDisk CALL PutChars MMURTL V1.Size of Dir Entry WORD PTR nRootDirEnts BX. POP DS . Boot Sector (at logical sector 0) .We are gonna skip checking to see if the first file .

512 bytes for segment .XOR INT INT AX.Number of sectors AH. before we move it down to 0000h .This is a single sector read loop .AX 16h 19h .Leaves Head in DL.Cyl into CX CL.OR with Sector number AL. BX AX . . DX . 20h ES.Low 8 bits of Cyl to CH. DL . ES BX. DH .I increment ES and leave BX 0 . but works well.Divide LogicalSect by nSectPerTrack DL . CH . 6 .Sys Reboot PutChars: LODSB OR AL.Read 13h . Hi 2 bits to CL CL. BX NextSector: PUSH AX PUSH CX PUSH DX PUSH ES XOR BX.Head to DH.BX will be zeroed at top of loop . AX . shifted to bits 6 and 7 CL.0 -70- RICHARD A BURGESS .Read that sucker! SHORT BadBoot MOV SI. 06000h MOV ES. . It's slow.0007 INT 10h JMP SHORT PutChars Done: RETN ContinueBoot: MOV BX. OFFSET MsgDot CALL PutChars POP POP POP POP MOV ADD MOV INC ES DX CX AX BX.Sector numbering begins at 1 (not 0) ResvdByte. Drive to DL CX. BYTE PTR ResvdByte . Cyl in AX DH.Wait for keystroke . Read a . 1 . nSectPerTrack SI .This is segment where we load the OS.Logical Track left in AX WORD PTR nHeads . 0Eh MOV BX.Next Sector MMURTL V1.Sector to read DX.AL JZ SHORT Done MOV AH. MOV DIV INC MOV XOR DIV MOV XCHG MOV XCHG SHL OR MOV MOV INT JC . 2 . bBootDrive DL. ES:BX points to sector read address logical sector to ES:BX Logical Sector Number SI. AX has . BX .

move .DI CX.0D1h OUT 64h.64h TEST AL.LOOP NextSector . AX ES.CX IBEmm0: IN AL. DX MOVSW .64h TEST AL.AL XOR CX. SI MMURTL V1. 8000h .AX DI. BX SI. Move first 64K code chunk from linear 70000h to 10000h MOV MOV XOR MOV MOV XOR MOV REP BX. BX SI. SI AX.from 60000h linear up to about 80000h linear. turn on the A20 line.02h LOOPNZ IBEmm1 MOV AL.DI CX.0DFh OUT 60h. Move 64K data chunk from linear 60000h to linear 0 MOV MOV XOR XOR MOV XOR MOV CLD REP BX.the OS down to address 0.CX IBEmm1: IN AL.CX IBEmm2: IN AL.Now we disable interrupts. set protected mode and JUMP! CLI XOR CX.A20 line should be ON Now . 07000h DS.0 .WORD move .DX is 8000h anyway -71- RICHARD A BURGESS .02h LOOPNZ IBEmm0 MOV AL.AX DI.1000h ES.So move the OS MOV DX. .64h TEST AL.02h LOOPNZ IBEmm2 . SI AX. 06000h DS. Move last code (32K) from linear 80000h to 18000h MOV DS.WORD move . DX MOVSW .At this point we have the OS loaded in a contiguous section . DX XOR SI.AL XOR CX.

lst). To assemble and link this into a usable boot sector.BX GS.BYTE move MOV BX. 'Bad Boot Disk!'. 00h ' MsgNone MsgBadDisk MsgLoad MsgDot BootSig CSEG DW 0AA5Fh ENDS END . As you change and experiment with the boot sector. 0Ah.We define a far jump with 48 bit pointer manually . I left it in.map). 9000h MOV DS.EXE header.BX SS. 00h 0Dh. DX MOVSB . The segment and code size escape . such as the dynamic relocation address (if you needed to relocate).referencing a 32 bit segment with a 32 bit instruction. To generate a list file from the assembly (Bootblok.'.AX DI. bBootDrive LIDT FWORD PTR IDTptr LGDT FWORD PTR GDTptr MOV EAX. use TASM as follows: MMURTL V1.call JMP FAR 08h:10000h.MS-DOS used this.OS can find boot drive in DL on entry .BX .EXE file exactly 1024 bytes in length.MOV MOV XOR MOV REP AX. 10h DS.2000h ES.Control Register . 'Loading MMURTL'. It's worthless.BX FS. The first 512 bytes is the . EDX MOV DL.Set protected mode bit .0 -72- RICHARD A BURGESS . BX XOR EDX.Load Processor ITD Pointer . use Borland's Turbo Assembler: TASM Bootblok.EAX JMP $+2 NOP NOP MOV MOV MOV MOV MOV MOV BX.asm <Enter> This will make a . you need to inspect the actual size of the sector and location of some of the code.DI CX.1 MOV CR0.Load Processor GDT Pointer . 0Ah.Set up segment registers .Clear prefetch queue with JMP . The last 512 bytes is the boot sector binary code and data in a ready-to-use format. and a map file from the link (Bootblok.BX ES.CR0 OR AL.bytes (66h and 67h) are required to indicate we are .by defining the bytes that would make the FAR jump . DB DB DB DD DW 66h 67h 0EAh 10000h 8h DB DB DB DB ' This is actually padding! 0Dh. 00h '.

bin in the root directory of drive C.TASM Bootblok Bootblok Bootblok <Enter> You need some additional information to experiment with this boot sector if you are on the PC ISA platforms. To read the boot sector from a disk. Format a disk with MS-DOS 5. find out where Debug's work buffer is by doing an Assemble command (just type A while in Debug). Most operating systems provide utilities to make a disk bootable. This is easy to do. and WITHOUT the /S (System) option. you should not do this to your active development drive. Keep in mind. -W 219F:0000 0 0 1 <Enter> After the boot sector is on the disk. It's quite easy to do with the Load. The results can be disastrous. C:\>Debug BootBlok.) This will dump the first 128 bytes and provide the address you need to the Write command. They may do this as they format the disk. and Write commands as follows: N C:\Bootblok. The following code loads the boot sector from the disk to address 219F:0000 (or whatever address you were shown): -A <Enter> 219F:0000 <Enter> -L 219F:0000 0 0 1 <Enter> From there you can write this sector to another disk as a file with the Name. Press enter without entering any instructions and use the address shown for the Load command along with the drive. Then copy the operating system image to the disk. and number of sectors following it. Some operating systems may force you to make a decision on whether the disk will be bootable before you format it if they pre-allocate space on the disk for the operating system. I used the MS-DOS Debug utility to read and write the disk boot sector while experimenting. and Name commands. you only need to put the operating system where it expects to find it.0 <Enter> -73- RICHARD A BURGESS . type in the Debug command followed by the name of your new executable boot sector. After you have assembled your boot sector. The boot sector code above expects to find the operating system in a ready-to-run format at the first cluster on the disk and running contiguously in logical sectors until it ends. then write it to the disk. or after the fact. If you wanted the boot sector from your hard disk you would have used the Load command as follows: -L 219F:0000 2 0 1 <Enter> The only difference was the drive number which immediately follows the segmented address. Debug refers to the drives numerically from 0 to n drives. From the MS-DOS prompt. you can use Debug to load it as a . After that you can copy any thing you like to the disk. The commands are: C:\> Format A: /U MMURTL V1.exe <Enter> -D <Enter> 219F:0000 EB 3E 90 (etc. Register.0 or higher using the /U (Unconditional) option. To find where Debug loaded it.EXE file and write the single 512 sector directly to the disk or diskette. Write. It will be the first file on the disk and will occupy the correct sectors. logical sector number.bin R BX <Enter> BX:0 <Enter> R CX <Enter> CX:200 <Enter> W 219F:0000 The boot sector from drive 0 is now in a file named BootBlok. This will show you a segmented address. you can use the Dump command to display it. such as how to write the boot sector to the disk or read a boot sector from the disk and inspect it.

Hopefully. I have a program (also provided on CD-ROM) that reads. It is called MMLoader. In a non-segmented processor (such as the 68x0 series) this wouldn't even be necessary. In a segmented processor (such as the Intel series). the Direct Memory Access hardware. Other Boot Options If the operating system you write will be run from another environment. along with reading the operating system from its native executable format (a RUN file).C:\> Copy OS. I'm happy. understands. If no address fix-ups are required in your operating system executable. interrupts are usually disabled because there are no addresses in the interrupt vector table. and loads the image into RAM then executes it. “Initialization Code. My MakeImg utility is included on the CD-ROM and must be built with the Borland C compiler. This is how I run MMURTL most of the time. it may not be in a form you can load directly from disk. I couldn't. you may face the chore of booting it from another operating system. This single source file program (MMLoader. All of the hardware that is resident in the system .c) to properly build it. They must simply be initialized to their default states.0 -74- RICHARD A BURGESS . At this point. The data and code is aligned where it would have been had it been loaded with a loader that understood my executable format. This means you must provide a utility to turn the operating system executable into a directly executable image that can be booted and run by the small amount of code in a boot sector. “The Hardware Interface.00 loader.” MMURTL V1.C) should also be built with the Borland C compiler as it has imbedded assembly language. This reads the MMURTL executable and turns it into a single contiguous executable image to be placed on the disk. you might be able to avoid a need for a utility like this.IMG A:\ <Enter> The new boot sector and the operating system are on the disk and ready to go. there will be a header and possibly fix-up instructions contained in it. If it is in an executable format from a linker. It has some additional debugging display code that you can easily eliminated if you like.must be initialized before you can set up the rest of the system (the software state). Basic Hardware Initialization One of the very first things the operating system does after it's loaded and the boot code jumps to the first address is to set up a simple environment. up to the point we jump the monitor program. It is all contained in the source file Main. Chapter 21. Good luck.asm on CD-ROM.” contains all of the code from MMURTL that is used to set up the operating system from the very first instruction executed. If so. the boot code may have already set up the segment registers. Operating System Boot Image Depending on the tools you use to build your operating system. It should help give you a picture of what's required. I've saved you some serious hours of experimentation. some of the hardware-related tasks you may have to accomplish are discussed in chapter 6. It does everything that the boot sector code does. I have provided a utility called MakeImg. This program basically turns MS-DOS into a $79. but it's not necessary. This includes setting up the OS stack. and basic timers . Additionally. Your operating system may already have some of the addresses hard-coded in advance. Instructions are included in the single source file (MakeImg. Disabling interrupts is one of the chores that my initialization code handles. I only had to do it thirty or forty times to get it right. With MMURTL.such as the programmable interrupt controller unit.

the A20 address line serves no purpose. This is enough bits to cover a one full megabyte.the first of which may be to eliminate it from the operating system after it is executed so it doesn't take up memory. MMURTL V1. you can let this first thread of execution just die once you have set up a real first task. What you do may simply depend on how much time you want to spend on it. It seems that memory address calculations wrapped around in real mode (from 1Mb to 0). The option is usually up to you. you must understand that the code you are executing on start up can be considered your first task. As most of you are aware (if you're programmers). or not even initially allocate it with your memory-initialization code. If you use one more bit. you can address above 1Mb. you just learn to live with it. If you want to eliminate it. It may be to turn on or off a range of hardware addresses. Initialization of Task Management Initialization of task management. Either way. These address lines are numbered A0 through A19. you may realize that most of this code may never be executed again.” discussed some of your options for a tasking model. While in real mode. the array of call gates (how users get to OS calls).Static Tables and Structures How your operating system handles interrupts and memory. Its job is to set up the hardware and to get things ready to create additional tasks. as well as one to switch to. but why not just let it become a real task that can be switched back to? Chapter 21 shows what I have to do using the Intel hardware task management. This leaves you with a few options . because this will determine whether their initialization occurs before or after memory-management initialization. is not trivial. What is the A20 Line Gate? It all stems back to IBM's initial PC-AT design. This is one example I can give to help you understand that there may be intricacies on your platform that will drive you nuts until you find the little piece of obscure documentation that explains it in enough detail to get a handle on it. on the Intel processors. but may also depend on hardware task management if you use it. but you may also be performing your own tasks management. Chapter 3. Chapter 21 shows how I go about this. you can position it at the end of your code section and simply deallocate the memory for it. and programmers wanted the same problem to exist in the AT series of processors. A third option is to make the code reusable by breaking it up into small segments so that the code can be more generic and used for other things. I didn't want to waste a perfectly good TSS by just ignoring it. This depends on the platform. I think it's kind of like inheriting baldness. It's not that big. For instance. These first tables are all static tables and do not require any memory allocation. setting up for multitasking. The processor you're working with may provide the task management in the form of structures that the processor works with. Additional task-management accomplishments may include setting up linked lists for the execution queue (scheduler) and setting variables that might keep track of statistical information. or even to set up the physical range of video memory if your system has internal video hardware. the real mode addresses are 20 bits wide. Initialization of Memory During the basic hardware initialization. You'll have think about which tables are static and which are dynamically allocated. Once the basic hardware and tables are set up. Because I used the Intel task management hardware I really had no choice. “the Tasking Model. One particular item that falls into this category on the PC ISA hardware platform is the A20 Address Line Gate. you've got your job cut out for you. I had to set up the basic interrupt vector table. I just left it alone.0 -75- RICHARD A BURGESS . Being a little conservative. Another option is just to forget the code if it doesn't take up too much space. you need to set up a Task State Segment (TSS) to switch from. and also some basic variables. you may be required to send a few commands to various pieces of hardware to set up physical addressing the way you need it. If you desire. I suppose that this line even caused some problems because programmers provided a way in hardware to turn it off (always 0). I could have gone back and added code to reuse it. In all of the cases that were discussed. and how you designed your basic operating system API and calling conventions determines how many tables and structures you must setup before you can do things like task and memory management.

After all of the dynamic structures are allocated and set up.My goal was not to discover why it existed. Meanwhile. You don't know how much you have. you can begin to allocate and set up any dynamic tables and structures required for your operating system. but one I'm glad I didn't face. Know where your OS loaded and allocate the memory that it is occupying. Set up the basic linked list structures if you will be using this type of memory management. If you went with a simple flat model then you've got a pretty easy job. memory areas for future tables that can't be allocated on the fly. This seems to work on most machines. you have basic tables set up and tasks ready to go. but you aren't cognizant of memory yet. otherwise. If your machine doesn't support this method. Most of the list depends on what memory model you chose for your design. or any number of things. and ended up sticking with the way it was documented in the original IBM PC-AT documentation.” shows what I did using the Intel paging hardware. I tried several methods. If you went with a full blown demand paged system. This program will generally be part of the operating system and it will do things like set up the program environments and load device drivers or system services. Something of importance to note about the Intel platform and paged memory-management hardware is that your physical addresses must match your linear addresses when you make the transition into and out of paged memory mode. It's not that much of a headache. it may mean force dynamic relocation of your entire operating system. To make a transition to a paged memory environment. that you couldn't allocate memory until memory-management functions were available. Dynamic Tables and Structures After memory management has been initialized. “Memory Management Code. You may need to consider the following points: Find out how much physical memory you have. you may have to do some tweaking of the code. The following list is not all-inclusive. or is at least loaded. These structures or tables may be allocated linked lists for resources needed for interprocess communications (IPC).0 -76- RICHARD A BURGESS . you're ready to execute a real program (of sorts). The point is. but to ensure it was turned on and left that way. At this point you usually load a command-line interpreter. I took the easy way out (totally by accident) when I left the entire static portion of my operating system in the low end of RAM. some which didn't work on all machines. This piece of code may have a lot to do. or maybe even load up your windowing environment. Allocate other areas of memory for things like video or I/O arrays if they are used on your system. back to the initialization (already in progress). although some people may still have problems with it. where the address match will occur. set up the basic tables that will manage memory. It is controlled through a spare port on the 8042 keyboard controller (actually a dedicated microprocessor). MMURTL V1. This is where you call the routine that initializes memory management. If you chose not to do this. and you can't manage or allocate it. but gives you a good idea of what you may need to do. At this stage of the initialization. It is explained in a fair amount of detail. if you used paging hardware and your processor doesn't start out that way (Intel processors don't). Chapter 19. this could be a fairly large chunk of code. This is important because it forces you to have a section of code that remains.

With newer operating systems always looking for more speed (for us power MMURTL V1.” shows you how I documented my calls. Procedural Interfaces Procedural interfaces are the easiest and most common type of interface for any API. including the mechanics behind some of the possible ways to implement them. but there are actually many decisions to be made before you begin work. it used the standard defined C calling conventions. Quite often though. they do. I can only imagine what will happen to someone walking into it from a cold start.Chapter 8. UNIX was written with C. are there alignment restrictions imposed by hardware? If registers are used. the resources it manages must be accessible to the programmers who will implement applications for it. Programming Interfaces For an operating system to be useful. may be similar to another documented call on the system. No kidding! With UNIX. an embedded system. In any and all of these cases. the function does its job and returns. The functions you provide will be a foundation for all applications that run on your system. but it turned out to be a time consuming and very important task for me. In the following sections. Documenting Your API Your operating system may be for a single application. You place the name of the call and its parameters in a parenthetical list and it's done. “API Specification. I wanted to provide a good code example for each call. The call is made. The programmers must be able to understand what the system is capable of and how to make the best use of the system resources. and leave undocumented. what do you do with the excess parameters if you run out of register? Who cleans the stack (resets the stack pointer) when the call is completed? This can get complicated. what order are they in? If the stack is used. Application Programming Interface The Application Programming Interface (API) is how a programmer sees your system. The very largest was the calling conventions of the interface. Calling Conventions As a programmer you're aware that not every language and operating system handles the mechanics of the procedural call the same. but time simply wouldn't allow it (at least for my first version). Even though I designed each of the calls for the operating system. If this happens to me. Entire books have been written to describe “undocumented system calls” for some operating systems. The design and implementation of an API may seem easy at first. I'm still not entirely happy with it. that on occasion. or your own consumption and enjoyment. you'll need to properly document the correct use of each call. life was easy in this respect. This can happen when operating system developers add a call that they think may not be useful to most programmers – or even confusing. I found. public distribution. I'll go over the complications that have crossed my path. I describe both the application programming interface and the systems programming interface. because the call they add. Chapter 15. This may sound like a no-brainer. What could be easier? I can't imagine. If the parameters are on the stack. But there are so many underlying complications for the system designer it can give you a serious headache. Some of the considerations include: Where the parameters are placed (on the stack or in registers?).0 -77- RICHARD A BURGESS . This is really a layered problem that application programmers shouldn't have to worry about. I had questions about some of the very parameters I selected when I went back to use them.

Parameters are pushed onto the stack from right to left and the caller (the one that made the OS call) is responsible to clean the stack. Some use registers.0 -78- RICHARD A BURGESS . Second. especially when it comes to speed. Mechanical Details Aside from the not-so-simple differences of the calling conventions. It's up to you. You may be using your favorite compiler and it will. you will surely want to do some research to ensure there is full compatibility in the convention that you choose. I had no need for variable-length parameter blocks into the operating system. the interrupt vector table). that is linked into your code that performs some translation before the actual operating system code is executed. This causes the instruction pointer to be loaded with a value from a table (e.(also known as PLM conventions in some circles) Parameters are pushed left to right. Pascal .. and also the fact that operating system code relocation doesn't affect the address of the call because its a value in the interrupt vector table.junkies). The advantages of using the software-interrupt function include speed (from assembly language only). and hardware requirements are the big considerations that drive the need to deviate from a standardized calling convention. there are many additional details to think about when designing the API. However. The address of the operating system calls may change every time you load it. Your opinion may vary on this. but the stack is not used for parameters. There are two reasons this library may be required. There is a plethora of not-so-standard conventions out there. You may not want to go to the lengths that I did to support a specific calling convention on your system. Not many calls are made recursively or in a tightly looped repetitive fashion to many operating system functions. This device is extremely hardware dependent. and the called function is responsible for making the stack correct. If you want to use code generated by several different compilers or languages. First. this is MMURTL V1. and you want to maintain a standard procedural calling convention for the sake of the high-level language interface. more complicated calling conventions have been designed and put in place. will the call actually cause the processor to change the instruction pointer to the code you wrote for that particular call? Your answer will more than likely be no. I'm fairly opinionated on this issue. Many systems use an intermediate library. for example. When a high-level language has its own calling convention. code size. so the C calling conventions had no appeal to me. support several conventions. I used an Intel-specific processor hardware function for my calls (Call Gates). or at least every time it's rebuilt. no doubt. In other words. and it required a fixed number of parameters. MS-DOS uses such a device on the Intel-compatible processors. the actual execution of the call may depend on a hardware mechanism to actually execute the code. some use the stack. it won't be a problem if this language also provides a method to reach the operating system if its conventions are different. I decided on the Pascal (PLM) conventions. It leads to a lot of confusion if you're not prepared for it when you go use a mixed-language programming model. Even the IBM C/Set2 compiler has its very own unique calling conventions that only work with CSet/2 (no one else supports it). The hardware requirements may also be one of the deciding factors. and forces an intermediate library to provide a procedural interface. Will your call go directly into the operating system? In other words. I have even run across commercial development libraries (DLLs) that used this convention which forced a specific compiler even though I preferred another (Borland's. An example of a hardware-calling mechanism is the Software Interrupt. A special instruction is provided to return from the software interrupt. OS/2. The stack is used to save the return address just like a CALL instruction.g. has three or four calling conventions. I don't think the few microseconds saved (that's all it really is) by redesigning the mechanics of a standardized interface is really worth it when it comes to an operating system. A software interrupt is a method provided by some processors that allow an pseudo-interrupt instruction to trigger a form of a call. This makes porting a C compiler a little hectic. your code expects all of the data to be in registers. The two most popular procedural calling conventions include: Standard C . some use both. but I wasn't going to let that drive my design. Execution speed. faster. I'm sure you know of several. as if you couldn't guess).

I recommend you avoid a register-driven interface if at all possible. but this is the computer industry of the 90s. When you need to return data from the call. Systems Programming Interface I divide the systems programming interface into two distinct categories. If portability is near the top of your wish list. MMURTL V1.0 -79- RICHARD A BURGESS . the hardware will be obsolete. I never considered the second aspect until I wrote my own operating system. especially with multiple threads that may share common data areas or a single data segment. but the portability of the operating system source code itself. Error Handling and Reporting So many systems out there make you go on some kind of wild-goose chase to find out what happened when the function you called didn't work correctly. They aid in the interface but don't detract from portability because they were designed to support a high-level calling convention in the first place (procedural interfaces). The portability I speak of is not the source-code portability of the applications between systems. The intermediate library would fill in this value. I really hope not.not sufficient reason to use this type of interface. how you design the internal mechanisms of the system calls may save you a lot of time if you want to move your system to another platform. However. things like software interrupts and register usage leave you with subtle problems to solve on a different platform. A simple table fixed in memory to use for indirect call addresses. When the software-interrupt mechanism is used. This may be a personal issue for me. have the caller provide a pointer as one of the parameters. right. Murphy's law says that as soon as you get the system completed. Portability Considerations Looking at portability might not seem so important as you write your system for one platform. and the call gates are really not a problem. Error reporting has special implications in a multitasking environment. then execute the interrupt instruction. For the operating system source. The first is the interface seen by programmers that write device drivers or any kind of extension to the operating system. I can remember reading the phrase "30 or 40 megahertz will be the physical limit of CPU clock speeds. you may only need one interrupt for all of the calls in the operating system. but if I could have any influence in this industry at all. it would be to have all systems return a status or error code directly from the function call that would give me some intelligent information about why my call didn't work." Yeah. For application source code portability. or some other processor hardware capability can provide this functionality without all the registers directly involved. I considered this before I used the Intel-specific call gates. Give this some serious thought before you begin coding. They either make you go find a public variable that you're not sure is named correctly for the language you're using. the procedural interface is the key. However. This is accomplished by using a single register to identify the call. or they make you call a second function to find out why the first one failed. The second is how internal calls are made from inside the operating system to other operating system procedures (public or internal). I recommend that you consider providing a status or error code as the returned value from your system functions that really tells the caller what happened. A single public variable linked into your data to return an error code doesn't work well anymore.

fully functional device driver interface I could imagine. They work with the hardware. You'll need a way to dynamically make all the device drivers accessible to outside callers without them knowing where the code is in advance. Its purpose is to work generically with external hardware. I ended up with two underscores for the public names that would be accessed by outside callers. For instance. I was familiar with two different device driver interface schemes before I started writing my own system. I had to deviate from my plan quite often as you'll see.0 -80- RICHARD A BURGESS . You also need to investigate exactly how public names are modified by the compiler(s) you intend to use. but there was at least a plan to go back to when all else failed. timers. I knew that there were four basic requirements when it came to device drivers: Installation of the driver Initialization of the driver Control (using) the driver. so you can't put the code entry point addresses into a table in advance. In your system you will also have to consider how to make the device appear at least somewhat generic. If this is the case. or maybe it was none if a particular compiler switch was active. and Statusing the driver. Some device drivers will load after the operating system is up and running. Select a register-usage plan and try to stick with it. You must also realize that it isn't just the interface alone that makes things easier for the device driver programmer. and so much synchronization is required with the tasking and memory-management code. You more than likely will. A Device-Driver Interface Example With the four basic requirements I mentioned above. Many operating systems now are written almost entirely with high-level languages. that it's almost impossible to separate them. Is it one underscore before the name? Maybe it was one before and one after. Do you see what I mean? Internally. naming conventions and register usage can become a nightmare. Device Driver Interfaces For all practical purposes.Internal Cooperation The second aspect I mentioned above is not as easy as it sounds. Certain system calls may have to be designed to allow them to concentrate their programming efforts on the device and not the hardware on the platform. Suddenly. but underneath them somewhere is some assembly language coding for processor control and status. device drivers actually become part of the operating system. You need to consider these requirements in your design. I set out to design the simplest. or hardware that appeared external from the operating system's standpoint. This capability requires some form of indirect calling mechanism. and this wasn't my original plan. and certain memory-management functions. all of your device drivers may be so deeply embedded in your code that you may not want a separate layered interface. This mechanism may be a table to place addresses in that the operating system can get to when a standardized entry point is made by an application. This is especially true if you intend to use assembler and high-level languages at the same time. can the programmer read a sector from a floppy disk drive or a hard disk drive without caring which one it really is? That's something to think about when you approach your design. The possibility exists that you may be writing a much more compact system than I did. You may want to satisfy these requirements by adding calls to isolate them from some of the system hardware such as DMA. The interface calls I came up with to satisfy my design were: MMURTL V1.

” I provide detail on the use of these calls.Valid DCB number? -81- RICHARD A BURGESS . EAX InitDev00: CMP dDevX. You'll note that a pointer to its DCB is provided by the driver.Set up local vars . The first call listed in this section. . When you look at the code. InitDevDr(). This structure is filled out with items common to all drivers before the driver tells the operating system that it is ready to go into business serving the applications. dLBA.ASM. dOpNum. A requirement for the operating system to know things about each device driver led me to require that each driver maintain a common data structure called a Device Control Block (DCB). What I want to discuss is the underlying code that allows them to function for multiple device drivers in a system. . dfReplace):dError EBP+16 EBP+12 sParam 16 [EBP-4] [EBP-8] [EBP-12] [EBP-16] PUBLIC __InitDevDr: PUSH EBP MOV EBP. .ESP SUB ESP. It's included in this chapter to emphasize the concepts and not to directly document exactly what I've written. this particular device must be protected from someone else trying to use it. 16 MOV EAX. EAX MOV EAX. pInitData. You'll see certain validity checks.Local vars dDevX EQU DWORD PTR prgDevs EQU DWORD PTR nDevs EQU DWORD PTR dExchTmp EQU DWORD PTR pDCBs. nDevices. For example.InitDevDr(dDevNum. This is a common requirement on many systems. The comments explain each of the ideas I've discussed here. sdInitdata):dError DeviceStat(dDevice. I provided a mechanism to prevent re-entrant use of the drivers if the a flag was set in the DCB when the driver initialized itself. nDevices MMURTL V1. “Systems Programming. [EBP+20] MOV prgDevs. Two simultaneous calls to the driver may not be possible (or desirable). [EBP+24] MOV dDevX. pStatRet. ndBlocks. I use system IPC messaging to block subsequent calls to a driver that indicates it is not re-entrant. pay attention to the comments instead of the details of the code. so I won't go over it here. Announcing the Driver to the OS This is the call that a driver makes after it's loaded to let the operating system know it's ready to work. fReplace) DeviceOp(dDevice. EAX MOV EAX. This is the layer in MMURTL that initializes device drivers and also redirects the three device driver calls to the proper entry points. InitDevDr(dDevNum. EBP+24 . This will give you a good place to start in your design. you will also find that some device drivers may not be re-entrant. when one user is doing serial communications. sdStatRetmax):dError In chapter 10. The following code example is from the file DevDrvr.0 . In a multitasking system. EBP+20 nDevices. [EBP+16] MOV nDevs. . The other three functions are the calls that the operating system redirects to the proper device driver as identified by the first parameter (the device number). and also that we allocate an exchange for messaging if the driver is not re-entrant. . pData):dError DeviceInit(dDevice. pDCBs. is called from the driver to provide the operating system with the information it needs to seamlessly blend it into the system.

pDCBs CMP BYTE PTR [EBX+fDevReent].Point EBX to rgpDCB . 64 INC dDevX JMP SHORT InitDev00 . and assign exchange from temp storage to each DCB MOV MOV LEA MOV SHL EAX. dExchTmp .EAX points to DCB CMP BYTE PTR [EAX+sbDevName]. 0 JNZ InitDev02 MOV EAX.Not valid DCB number InitDev01: . 2 ADD EBX.error exit .dDevNum MMURTL V1.JB InitDev01 MOV EAX.and whether it's to be replaced LEA EBX. dDevX SHL EAX.into temp storage CALL FWORD PTR _AllocExch CMP EAX.NONE left . . EAX CMP DWORD PTR [EBX]. ErcBadDevNum JMP InitDevEnd .device IS reentrant! LEA EAX. 0 JZ InitDev02 CMP DWORD PTR [EBP+12]. ErcBadDevName JMP InitDevEnd InitDev04: . now move the DCB pointer(s) into array .Set nDev to number of devices again . prgDevs . 2 . ErcDCBInUse JMP InitDevEnd InitDev02: . rgpDCBs EAX.pDCBx = 0 if not used yet . rgpDCBs MOV EAX. [EBP+20] . OK to use .Now see if there are more devices for this driver DEC nDevs JZ InitDev05 ADD prgDevs. we can check DCB items then move ptr MOV EAX.OK to replace existing driver? .No .Point EBX to OS rgpDCBs .nDevices .If we got here. 0 JNZ SHORT InitDevEnd InitDev06: . EAX EBX.Next devnum .12 .All error checking on DCB(s) should be done at this point InitDev05: .Next caller DCB .Check Device name size JA InitDev03 CMP BYTE PTR [EAX+sbDevName]. [EBP+16] nDevs. 0 .All went OK so far.Alloc Exch if driver in NOT reentrant MOV EBX.Empty. [EBP+24] EAX.0 -82- RICHARD A BURGESS .dDevNum .is Devname OK? JA InitDev04 InitDev03: MOV EAX.Yes .Now check to see if device is already installed .Allocate device Exchange PUSH EAX . 0 JNZ InitDev06 .Decrement nDevices .

ECX [EBX]. or reset it after a failure.ADD EBX. DeviceInit(dDevNum. This is the same exchange . . and . . The other two calls. [EBP+20] . 0 . are almost identical to DeviceInit(). EAX . 4 EAX. 0 JNZ InitDev06 .ECX has semaphore exchange InitDev07: .If the device driver was NOT reentrant . ErcBadDevNum .DCB (if more than 1) and set up OS pointer to it.pDCBs CMP BYTE PTR [EBX+fDevReent]. 64 nDevs InitDev07 EAX. rgpDCBs MOV EBX.ECX is still the exchange PUSH 0FFFFFFFEh .next DCB .Any more DCBs?? . You can see in the comments how we redirect this call to the proper code in the driver from the address provided in the DCB. pInitData.also place Exchange into DCB. EBX . SHL EBX. ADD EAX. nDevices . -83- RICHARD A BURGESS . DeviceOp() and DeviceStat().EBX now points to correct pointer . and are not presented here.ESP .EBP POP EBP RETF 16 . .Sorry no valid DCB JMP DevInitEnd DevInit01: LEA EAX.for all devices that one driver controls.next p in rgp of DCBs .Dummy message PUSH 0FFFFFFFEh CALL FWORD PTR _SendMsg .0 . MOV EBP. EAX and ECX are set up. [EBP+20] MOV ECX. 2 . MOV EBX. dExchTmp . [EAX] . PUBLIC __DeviceInit: .Set up for no error . PUSH EBP .EAX points to first DCB . [EBP+20] . CMP DWORD PTR [EBP+20]. loop through each .device IS reentrant! PUSH ECX . EAX EBX.Now that EBX.we send a semaphore message to the exchange for .now EBX points to DCB (maybe) CMP EBX.Valid Device number? JB DevInit01 MOV EAX. .Let erc in EAX fall through (Was ISend) InitDevEnd: MOV ESP. EAX MOV EAX. sdInitData). MOV EBX. EBP+20 EBP+16 EBP+12 Count = 12 .the first customer to use. MOV MOV ADD ADD DEC JNZ XOR [EAX+DevSemExch].Is there a pointer to a DCB? MMURTL V1. A Call to the driver This is the call that the user of the device driver makes to initially set up the device. .

0 JNE DevInitEnd DevInit03: PUSH EBX PUSH DWORD PUSH DWORD PUSH DWORD CALL DWORD POP EBX PUSH EAX . . ErcNoDevice DevInitEnd . ErcNoDriver DevInitEnd BYTE PTR [EBX+DevType].Serious kernel error! PTR PTR PTR PTR [EBP+20] [EBP+16] [EBP+12] [EBX+pDevInit] .call WAIT to get "semaphore" ticket. The next chapter begins the documentation of and for an operating system.Bogus Message . .save error (if any) CMP BYTE PTR [EBX+fDevReent]. 0 DevInit02 EAX.No.Ignore kernel error .dump params Your interface doesn’t have to be in assembly language like the preceding example.. This chapter (8) ends the basic theory for system design.Get ptr to DCB back into EBX .YES PUSH DWORD PTR [EBX+DevSemExch] PUSH 0FFFFFFFEh PUSH 0FFFFFFFEh CALL FWORD PTR _SendMsg DevInit04: POP EAX DevInitEnd: MOV ESP.Get DCB ptr back . but I’m sure you noticed that I added examples of the procedural interface in the comments.Push all params for call to DD .save ptr to DCB .Reentrant? JNZ DevInit04 ..Save ptr to DCB . If not we . CMP BYTE PTR [EBX+fDevReent]. .All looks good with device number .Ptr to message area . Send semaphore message to Exch . [EBX+DevSemMsg] PUSH EAX CALL FWORD PTR _WaitMsg POP EBX CMP EAX.Get device error back .JNZ MOV JMP DevInit1A: CMP JNZ MOV JMP DevInit1A EAX. It you use assembly language and provide code examples.Is there a physical device? DevInit02: .0 -84- RICHARD A BURGESS . 0 JNZ DevInit03 PUSH EBX PUSH DWORD PTR [EBX+DevSemExch] LEA EAX.Device IS reentrant .Push exchange number .Get semaphore ticket . adding the procedural call examples is a good idea.Yes .EBP POP EBP RETF 12 . 0 .so we check to see if driver is reentrant.NO driver! . MMURTL V1.

The following words and ideas are described to prevent confusion: Procedure .1 .This is also used interchangeably with function and procedure.Same as Parameter Understanding 32-BIT Software If you have been programming with a 16-bit operating system (e. WORD. Most MMURTL parameters to operating system calls are DWords (32-bit values).A Function in C or Pascal. If I were a perfectionist..Chapter 9.g. It may even be entertaining.a 16-bit value (signed or unsigned) DWord . or a Procedure in Assembler.a 32-bit value (signed or unsigned) Parameter . Before writing your first MMURTL program. as you discover the methods employed by this operating system.1 to visualize what you find in memory: Table 9. The Intel processors have often been called "backwords" because they store the least significant data bytes in lower memory addresses (hence. “Memory Management. Argument . One of the things that make processors different from each other is how data is stored and accessed in memory. Function . Some of the information provided in this section may actually not be needed to code a program for MMURTL.Same as Procedure. you should be familiar with the material covered in Chapter 4. but alas.0 0 1 2 3 Hex Value -85- RICHARD A BURGESS . Application Programming Introduction This section introduces the applications programmer to the MMURTL operating-system interface and provides guidance on programming techniques specific to the MMURTL environment. Terminology Throughout this section. Byte . or a Procedure in Pascal. “Interprocess Communications. I think you will find it useful. the byte values in a word appear reversed when viewed with a program that displays hexadecimal bytes).” These chapters provide discussions on the concept behind message based operating systems and paged memory management. usually passed on the stack. A Byte at address 0 is accessed at the same memory location as a word at address 0. and a dword is also accessed at the same address. If you are used to the 16-bit names for assembler variables.A value passed to a function or procedure. and most structures (records) have many 32-bit components.An eight bit value (signed or unsigned) Word . old habits are hard to break and one of my key design goals was simplicity for the programmer on Intel-based ISA systems. you will have to get used to the fact that all parameters to calls. MS-DOS). I would have called a 32-bit value a WORD. though. you will be comfortable with MMURTL because I have maintained the Intel and Microsoft conventions of BYTE. These are important concepts you should understand before you attempt to create or port your first application. All MMURTL operating system calls return values (called Error Codes). Look at the table 9. and DWORD. Call . This section should be used with the reference manual for the language you are using.” and Chapter 5.Memory Alignment Address Access As MMURTL V1. several words are used that may seem language-specific or that mean different things to different programmers. It provides information on basic video and keyboard usage as well as examples of multi-threading with MMURTL and information on memory-management techniques and requirements.

or simply convey information that may. Here is an example of a call to the operating system in assembler: PUSH 20 PUSH 15 CALL FWORD PTR SetXY In the preceding example. It is also very easy to use from assembly language. Most return a value that is a an error or status code. Operating System Calling Conventions All operating system calls are listed alphabetically in chapter 15. while the offset is ignored by the 386/486 processor. Pascal. or dError. A far call address to the operating system consists of a selector and an offset. not the real address of the SetXY function. All Public operating-system calls are accessible through call gates and can be reached from "outside" programs. Stack Usage Each of the MMURTL operating-system calls is defined as a function with parameters. This is because the processor enforces a four-byte aligned stack. You can even hard code the far address of the operating-system call because the system calls are at call gates. although it's usually transparent to the programmer using a high level language. You can push a dword on the stack and read it as a byte at the same location on the stack from a procedure. MMURTL V1. MMURTL always uses a DWord stack (values are always pushed and popped as dwords – 32-bit values. When running the Intel processors in 32-bit protected mode. The GDT entry for the call gate defines the actual address called and the number of dwords of stack parameters to be passed to the call.Byte Word Dword 01 01 01 02 02 03 04 01h 0201h 04030201h This alignment serves a very useful purpose. SetXY is actually a far pointer stored in memory that normally would contain the address of the SetXY function. Some do not. The CM32 compiler uses this convention. This also plays a useful role in languages such as C.) This is a hardware requirement of the 386/486 processors when 32-bit segments are used. These codes convey information to the caller about the status of the call. This means that an assembly-language programmer can make the far call to what appears to be a hard address (but is really a call gate). They are defined as far (this may be language-dependent) and require a 48-bit call address. The called operating-system function always cleans the stack. The selector is the call gate in the Global Descriptor Table (GDT). erc. and assembly language. this also makes a great deal of difference when values are passed on the stack. But the address is really a call gate. This will be done by the compiler. In some C compilers. The call gate will never change unless it becomes obsolete (in which case it will be left in for several major revisions of the operating system for compatibility purposes). or may not be an error depending on what you expect. It doesn't matter where the operating system code is in memory. All but a few of the operating-system calls use the stack to pass parameters to the operating system functions. “API Specification. and are expanded to 32-bit values. even the assembler or processor itself. It may indicate an error. All parameters are pushed from left to right. The descriptions in the API Specification (Chapter 15) show it as dError to indicate it is a 32-bit unsigned value. this is referred to as the Pascal Calling Convention. Conversions between types is automatic when read as a different type at the same address. The addresses of the calls are documented with the source code (header or Include files).” Most operating system calls in MMURTL return a dword (32-bit) error code which is often abbreviated as ERC. and in some cases. A list of all documented status or error codes can be found in header files included with the source code.0 -86- RICHARD A BURGESS .

00000000h Top of user address space Application base (1Gb) Top of OS Address space OS base address Even if six applications running at once. What does change your techniques is the fact that you are working with a flat address space all referenced to one single selector.... Your application sees the memory map shown in table 9. The library code for the high-level language will allocate pages and manage them for you. when an application allocates pages of memory they will begin somewhere above 1Gb (40000000h) MMURTL really doesn't provided memory management in the sense that compilers and language systems provides a heap or an area that is managed and cleaned up for the caller. This also means that a pointer to allocated memory will never start at 0. Memory-page allocation routines always return a pointer to the new memory.. There are cleanup routines in the operating system that will deallocate it for you if your program exits without doing it.. This would let you allocate 30 or 40 bytes at a time for dynamic structures such as linked lists (e. The address of the pointer is always a multiple of the size of a page (modulo 4096). you should deallocate it.g. 40000000h 3FFFFFFFh . Application programs all reside at the one-gigabyte memory location inside of their own address space. dnPages): dError QueryPages(pdnPagesRet): dError When your program needs a page of memory. MMURTL is kind of like your mother. “Interprocess Communications. Read up on Memory Management in Chapter 5 if you're really interested. Heap-management functions that allow you to allocate less than 4Kb at a time should be provided by your high-level language. you allocate it. The operating system provides three basic calls for application memory management. A MMURTL application considers itself as being the only thing running on the system aside from the operating system and device drivers. MMURTL is a Paged memory system. but she would much rather that you did it.2 . you simply allocate it. and returns them to the pool of free pages when they are turned in (deallocated). MMURTL V1. If you need a 2Mb array. There are no far data pointers.Basic Memory Map 7FFFFFFFh . In fact. They are listed below: AllocPage(nPages. When you are done with it. Paged virtual memory is implemented with the paging capabilities of the 386/486 processor.. Chapter 3.” and Chapter 4. The paging features of the 386/486 allow us to assign physical memory pages to any address ranges you like. they all see this same "virtual memory map" of the system. malloc() in C).0 -87- RICHARD A BURGESS . . MMURTL hands out (allocates) pages of memory as they are requested. The operating system and device drivers reside in low linear memory (0)..Memory Management MMURTL uses all of the memory in your system and handles it as it as one contiguous address span. She may pick up your dirty socks. while all applications and system services begin at the one-gigabyte mark.” describe this in great detail.. The operating system resides at address 0.2: Table 9. but it's bad manners not to cleanup after yourself. This doesn't change any programming techniques you normally use. Multiple applications all load and run at what seems to be the same address range which logically looks like they are all loaded in parallel to one another. ppMemRet): dError DeAllocPage(pMem.. “The Tasking Model. MMURTL has control of all Physical or Linear memory allocation and hands it out it as programs ask for it. .

This means that in a multitasking environment. when you write multithreaded applications (more than one task) you will probably need a way to synchronize their operation. so it will return a pointer to 4 pages just above the original 5 you allocated first (address 40105000h). while System Services and Applications run at user-level protection. pData2. dData2 ) : dError Before you can use messaging you will need an exchange where you can receive messages. The following calls are provided to acquire exchanges and to return them to the operating system when you no longer need them: AllocExch(pdExchRet): dError DeAllocExch(dExch): dError MMURTL V1. dRespExch. or you will need to know of one you send messages to. Of course. or "queue up" events of some kind between the threads (tasks). dMsg1. in which case you will use Request() and WaitMsg(). however. You are left with a hole in your addressable memory area. You may also require the use of a system service that there isn't a direct procedural interface for. Data.The memory allocation routines use a first fit algorithm in your memory space. This is not a problem. MMURTL does NOT. This is done with the SendMsg() and WaitMsg() calls. this doesn't mean you can allocate a gigabyte of memory. npSend. Operating System Protection MMURTL provides protection for the operating system and other jobs using the paging-hardware protection. The memory-allocation algorithm can't fit it into the 3 page hole you left. and Device Drivers run and can only be accessed at the system-protection level (level 0) . but I have to address the issues faced by those that write the library allocation routines for the languages. However. Now you have 6 pages beginning at 40103000h.0 -88- RICHARD A BURGESS . This is where non-specific intertask messaging will come into play. pRqHndlRet. It is up to you to manage your own addressable memory space. You now have 20K at the 40100000h address. wSvcCode. This will TERMINATE your application automatically (Ouch!). The operating system prevents ill-behaved programs from intentionally or accidentally accessing another program's data or code (from "a pointer in the weeds"). If you attempt to address this "unallocated" area a Page Fault will occur. The high-level language allocation routine may help you out with this. Application Messaging The most basic applications in MMURTL may not even use messaging directly. Now you allocate 4 pages. even if they know the where it's located (its physical or linear address). cbData1. a program that crashes and burns doesn't eat the rest of the machine in the process of croaking. The operating system Code. pMsgsRet) : dError WaitMsg(dExch. Then you deallocate 3 of these pages at address 40100000h (12K). Address 40100000h is returned to you. The following messaging calls are provided by the operating system for applications. dData0. Consider the following scenario: Your program takes exactly 1Mb before you allocate. You allocate 5 pages. It even prevents applications from executing code that doesn't belong to them. Your program can address up to one-gigabyte of space. dMsg2): dError CheckMsg(dExch. protect programmers from themselves. The operating system acts more like a referee than a mother. dData1. cbData2. but pointer arithmetic may get you into trouble if you allocate and deallocate often and lose track of what your active linear addresses are. so long as you know it can't be addressed. SendMsg (dExch.pqMsgRet):dError Request(pSvcName. You may not even have to call an operating-system primitive-message routine throughout the execution of your entire program. pData1.

Reentrancy considerations include modifications to variables in your data segment (two tasks sharing variables). printing. Job Control Block Applications may sometimes need to see the data in a Job Control Block. Messaging is very powerful. you need to allocate or provide memory from your data segment for the stack that the new task will use. Utilities to display information about all of the jobs running may also need to see this data. Memory-management protection provided by MMURTL will help some. /* lstring */ char *pJcbPD. and allocation or use of system resources such as memory. operating system messaging (sharing an exchange). /* Size of primary stack */ char sbUserName[30]. you need to know your job number. MMURTL is not a mind reader. /* Size of data segment */ char *pJcbStack.c provided with the operating system source code) for a good example of spawning new tasks. but can also be destructive (to the entire system). Prior to calling SpawnTask(). This eats all of your link blocks. Starting a New Thread Some programs need the capability to concurrently execute two or three things. /* Address of primary stack */ unsigned long sJcbStack. but you can easily eat up all of the system resources and cause the entire system to fail. You should be concerned about reentrancy if you have more than one task executing the same code. This new function will be scheduled for execution independently of your main program. The operating system provides the SpawnTask() call which gives you the ability to point to one of the functions in your program and turn it into a "new thread" of execution.0 -89- RICHARD A BURGESS . In order to use the GetpJCB() call. /* Size of code segment */ char *pJcbData. /* Address of data segment */ unsigned long sJcbData. They can even execute the same procedures and functions (share them). Look at the code for the Monitor (Monitor. Several tasks are spawned in the Monitor. The stack you allocate should be at least 512 bytes (128 dwords) plus whatever memory you require for your task.Normally. you basically have two programs running in the same memory space. struct JCBRec { long JobNum. The operating system is brought to its knees in a hurry. It may be simply keeping the time on the screen updated on a user interface no matter what else you are doing. In these cases you can "spawn" a new task to handle the secondary functions while your main program goes about its business. Attempts to write directly to the JCB will cause your application to be terminated with a protection violation. If you do. such as data communications. The pointer is read-only. and user interaction. Header files included with the source code have already defined the following structure for you.LString */ MMURTL V1. but you don't want that either. /* Address of code segment */ unsigned long sJcbCode. char sbJobName[14]. The GetpJCB() call will provide a pointer to allow you to read values from a JCB. accidentally sending messages in an endless loop to an exchange where no task is waiting to receive them will cause the system to fail. which the GetJobNum() call will provide. applications will allocate all of the exchanges they require somewhere early in the program execution and return them (DeAllocExch) before exiting. Other than that. For instance. only your program will be trashed. /* Linear add of Job's PD */ char *pJcbCode. You are responsible to ensure you don't overrun this stack. /* User Name .

0 -90- RICHARD A BURGESS . There are tables in Chapter 13 to show all of the possible values that can be returned. dcbPath): dError SetUserName(pUserName. the single procedural ReadKbd() operating-system call will suffice. All of the names and filenames (strings) are stored with the first byte containing the length the active size of the string. unsigned char ScrlCnt. dcbUserName): dError SetSysIn(pFileName. It provides access to all standard alphanumeric. char fCursType. The first of the two parameters to the ReadKbd() call asks you to point to where the 32-bit keycode value will be returned. pdcbPathRet): dError GetUserName(pUserNameRet. char JcbSysOut[50]. long VidMode. char *pVidMem. pPathRet. Only the active portions are shown in the preceding list. GetExitJob(pFileNameRet. with the second byte (offset 1) actually holding the first byte of data. A system service allows concurrent access to system-wide resources for applications.char sbPath[70]. For most applications. and special keys. This call returns a 32-bit value which contains the entire keyboard state (all shifted states such as ALT and CTRL are included). char *pVirtVid. char JcbCmdLine[80]. dcbJobName): dError SetExitJob(pFileName. with unused portions padded. long NormVid. dcbFileName): dError Some of the more commonly used information can be read from the JCB without defining the structure by using the following operating-system provided calls. long ExitError. pdcbUserNameRet): dError Basic Keyboard Internal support is provided for the standard IBM PC AT-compatible 101-key keyboard or equivalent. pcbFileNameRet): dError GetCmdLine(pCmdLineRet. The keyboard has a built-in device driver that the keyboard service accesses and controls. char JcbExitRF[80]. pdcbCmdLineRet): dError GetPath(dJobNum. long nCols. long CrntY. editing. “Keyboard Service. To change data in the JCB.” provides complete details on the keyboard implementation. while the second parameter is a flag asking if you want to wait for a keystroke if one isn't available. long nLines. char fCursOn. char JcbSysIn[50]. }. char fVidPause. dcbFileName): dError SetPath(pPath. 1 = Block */ Count since last pause */ Full screen pause */ OS Uses to allocate JCBs */ The JCB structure is a total of 512 bytes. They include: SetJobName(pJobName. long CrntX. you must use the calls provided by the operating system.LString */ Error Set by ExitJob */ Active video buffer */ Virtual Video Buffer */ Current cursor position */ /* Virtual Screen Size */ /* /* /* /* /* /* /* 0 = 80x25 VGA color text */ 7 = WhiteOnBlack */ 1 = Cursor is visible */ 0=UL. Chapter 13.LString */ std output . /* /* /* /* /* /* /* /* /* current path (prefix) */ Exit Run file (if any) */ Command Line . MMURTL V1. The keyboard interface is a system service.LString */ std input . long NextJCB.

These names are also defined in standard header files included with the operating-system code and sample programs. The colors for TTYOut(). Background colors describe the attributes. is the intensity bit. and 9. y is the position down the screen and referenced as 0 to 24.4 .3 .Foreground Colors (Low nibble) Normal Black Blue Green Cyan Red Magenta Brown White Binary 0000 0001 0010 0011 0100 0101 0110 0111 Hex 00h 01h 02h 03h 04h 05h 06h 07h Intensity Bit Set Gray Light Blue Light Green Light Cyan Light Red Light Magenta Yellow Bright White Binary 1000 1001 1010 1011 1100 1101 1110 1111 Hex 08h 09h 0Ah 0Bh 0Ch 0Dh 0Eh 0Fh Table 9. 8 background colors and 1 bit for blinking. PutChars() and PutAttrs() are made of 16 foreground colors. and cursor positioning. character placement. Background.4. Even though the attribute is passed in as a dword.3. Foreground colors. Positions on the screen are determined in x and y coordinates on a standard 80 X 25 matrix (80 across and 25 down). Table 9. x is the position across a row and referenced from 0 to 79. The video calls built into the operating system provide TTY (teletype) as well as direct screen access (via PutVidChars() and GetVidChar() calls) for all applications. #define BLACK #define BLUE MMURTL V1.Background Colors (High nibble) Normal Background Black Blue Green Cyan Red Magenta Brown Gray Binary 0000 0001 0010 0011 0100 0101 0110 0111 Hex Byte 00h 10h 20h 30h 40h 50h 60h 70h To specify an attribute to one of the video calls.0 0x00 1x07 -91- RICHARD A BURGESS . Look at the following coding example (Hex values are shown for the Attributes). This is 4000 bytes (even though an entire page is allocated . MMURTL has a basic character device driver built-in which provides an array of calls to support screen color. The high bit of each. the high nibble is the background. only the least significant byte is used. and Blink if desired to form a single value. The character set used is the standard IBM PC internal font loaded from ROM when the system is booted. In this byte. logically OR the Foreground.4096 bytes). and low nibble is the foreground. Tables 9.Basic Video Each application and system service in MMURTL is assigned a virtual video buffer which is the same size as the video memory in standard VGA-color character operation.

It is best to deallocate all system resources before calling ExitJob(). Terminating Your Application The call ExitJob() is provided to end the execution of your program. If no file is specified. MMURTL will cleanup after you if you don't. 17. This is done with the SetExitJob() call. such as the job-control block and virtual video memory. Remember. Some of the basic resources are saved for the new application. but an application knows what it allocated and it's more efficient to do it yourself from an overall system standpoint. MMURTL has to hunt for them. This terminates your application and loads another one that you specify. BGBLUE|WHITE). exchanges. The exit() function in a high-level language library will call this function. or system calls. all the resources are recovered from the job and it is terminated. MMURTL is more like a referee on the football field. Any task in your job may call ExitJob(). You will be terminated and sent to the "sidelines" if necessary. You can pass information to the incoming application by using the SetCmdLine() function. Another option to chaining is to tell the operating system what application to run when you exit. This includes any memory. Replacing Your Application Your application has the option of "chaining" to another application. "This is a test. and other things allocated while you were running.#define #define #define #define #define #define #define #define #define #define #define #define #define GREEN CYAN RED WHITE GRAY LTBLUE LTGREEN LTCYAN LTRED BGBLACK BGBLUE BGGREEN BGCYAN 0x02 0x03 0x04 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x00 1x70 0x20 0x30 dError = PutVidChars(0. You should also wait for responses to any requests you made. 0.". You can specify the name of a run file to execute when your job exits.0 -92- RICHARD A BURGESS . MMURTL V1. All tasks in your job will be shutdown at that time and the application will be terminated. This is done by calling the Chain() function.

It's not very useful. the service does the processing and Responds. Many people associate the term client/server only with a separate computer system that is the server for client workstations. Service names are casesensitive.0 -93- RICHARD A BURGESS .) Call RegisterSVC() with your name and Main Service Exchange. Use of the associated operating system calls is described in a generic format that can be understood by C. The file system and keyboard services are prime examples of message-based system services. Additional exchanges. then Respond. The service will get a pointer in the request block (pData1) that tells it where the user wants the number returned. if needed. but it's a good clean example showing the basic "guts" of a system service. They should have a basic understanding of multitasking and messaging and be able to concentrate on the application itself. Systems programmers require a better understanding of the operating system. It describes writing message based system services and device drivers. A Simple System Service Example Listing 10. service them. Pascal. MMURTL takes this down to a single processor level. as well as for other services. or provide services to application programs. Systems programming deals with writing programs that work directly with the operating system and hardware. Client programs make a Request. Anything else you need to prepare for business (Initialize variables etc. They contain theory and examples to help you understand how to access services. MMURTL V1. and Assembly language programmers. Systems Programming Introduction This section is aimed squarely at the MMURTL systems programmer. If you want (or need) to build device drivers or system services. you should make sure you understand the material covered in the architecture and Applications Programming chapters. MMURTL is a message-based operating system and is designed to support programs in a client/server environment. The service name is "NUMBERS " (note that the name is space-padded). A program that performs a specific function or group of functions can be shared with two or more programs is a system service.1 is the world's simplest system service for the MMURTL operating system. Main Service exchange (clients send requests here). It can be compared to Remote Procedure Calls with a twist. Initializing Your System Service The basic steps to initialize your service and start serving requests are listed below: Initialize or allocate any resources required. such as: Additional memory. Wait for messages. Writing Message-Based System Services System Services are installable programs or built-in operating system services that provide system-wide message based services for application programs. This is the basis behind the Request() and Respond() messaging primitives. and memory management operations. use messaging. Additional tasks if required. Its purpose it to hand out unique numbers to each request that comes in. Application programmers shouldn't have to worry about hardware and OS internal operations to accomplish their tasks.Chapter 10. if required.

long RqOwnerJob. /* First DWORD contains ptr to RqBlk */ if (pRqBlk. ErrorToUser): /* Respond to Request */ } } /* Loop while(1) */ } MMURTL V1. char npSend. char npRecv. /* get an exchange */ if (OSError) { /* look for a kernel error */ exit(OSError). else if (pRqBlk. Message). char *pData1. MainExch). OSError = AllocExch(&MainExch). while (1) { /* WHILE forever */ OSError = WaitMsg(MainExch. NextNumber = 0. long dData0. MainExch. int ServiceCode. long cbData1. long RQBRsvd1.ServiceCode == 0) /* Abort request from OS */ ErrorToUser = ErcOK. char *pData2. /* /* /* /* A pointer to a Request Block */ The number to return */ Where we wait for Requests */ The Message with the Request */ void main(void) { unsigned long OSError. long ServiceRoute. long RQBRsvd3. OSError = RegisterSvc("NUMBERS ". #define ErcOK 0 #define ErcBadSvcCode 32 struct pRqBlk unsigned long unsigned long unsigned long *RqBlk. /* Give them a number */ ErrorToUser = ErcOK.pData1 = NextNumber++.Listing 10. Message[2]. char *pRqHndlRet. long cbData2. /* Exch & pointer */ if (!OSError) { pRqBlk = Message[0]. ErrorToUser. long RespExch. /* Respond with No error */ } else ErrorToUser = ErcBadSvcCode. long dData2. A System Service /* Super Simple System Service */ struct RqBlk { /* 64 byte request block structure */ long ServiceExch. long RQBRsvd2. long dData1. /* Unknown Service code! */ OSError = Respond(pRqBlk.ServiceCode == 1) { /* User Asking for Number */ *pRqBlk.1. if (OSError) { /* look for a system error */ exit(OSError). }.0 -94- RICHARD A BURGESS .

The operating MMURTL V1. The memory address of the Request Block. This is called the service code. This gives the system service the opportunity to look at or use any parts of the Request Block it needs. This is the number 1 (one). Caller Information in a Request Three dwords are defined to allow a program to pass information to your service that you require to do your job. For larger data movement to the service. you should return the Error Bad Service Code which is defined in standard MMURTL header files. The first dword is the Request Block Handle. If you receive a service code you don't handle or haven't defined.The Request Block As you can see. As a system service writer. where callers pass in a file handle using these data items. when you receive a Request. and how much information a program must pass to you. Items In the Request Block The Request Block contains certain items that the service needs to do its job. In fact. They are 32-bit values. and whether or not they are signed or unsigned is up to you (the System Service writer). The operating system reserves service code 0 (Zero) for itself in all services. The service must never attempt to write to the request block. They may not contain pointers to the caller's memory area. The size of the area is defined by the accompanying 32-bit values. Wait returns and the two-dword message is filled in. As a system service. the system service interface using Request() and Respond() is not complicated. or any data movement back to the caller. They are one-way data values from the caller to you. in the File System. You may not use or define service code zero to your callers in your documentation. Programs and the operating system pass information to the service in various Request Block items. In the simple example above. and another RenameFile. you must describe in your documentation what Request information you expect from the caller. These items are dData0. and dData2. For example. and it is your responsibility to document this information for application programmers. For all other values of the service code. cbData1 and cbData2. and the aliased pointer to the user's memory is active and available only while the request block is being serviced. The Service Code A single word (a 16-bit value) is used for the caller to indicate exactly what service they are requesting from you. How many items you need to use depends on the complexity of the service you're writing. will be determined by how much information you need to carry out the request. This value is actually a pointer to the Request Block itself in unprotected operating system memory. another CloseFile. These two request block items are specifically designed as pointers to a data area in the caller's memory space. An installable service will operate at the user level of protection and access. How you handle this depends on how complex your service is.0 -95- RICHARD A BURGESS . and whether or not you hold requests from multiple callers before responding. and what service you provide. the operating system is telling you that a job (a user program or another service) has either exited or has been shut down by the operating system. installable services would find themselves terminated for the attempt. dData1. When a service receives a Request with service code 0. you define the data items you need. because the operating system does not alias them as such. The Request Block is in operating-system memory which is read-only to user-level jobs. A prime example of their use is in the File System service. one number represents OpenFile. The complexity of your service. the service only defines one service code that it handles. Several items in the Request Block are important to the service. the request block items pData1 and pData2 must be used.

When you write system services. cbData2. The first is the request block handle. and network frame handlers are all examples of devices (or pseudo devices) that require device drivers to control them. This type of service may even respond to the second request it receives before it responds to the first. If an error occurred or you need to pass some kind of status back to the caller. This means the keyboard service must place the global key code requests on another exchange while they handle normal requests.0 -96- RICHARD A BURGESS . You return a 0 (zero) if all went well. it may return five or six different values for various problems that can occur during the OpenFile() process. MMURTL is built in layers. before responding to any other requests you are holding. you may receive an Abort Service code from the operating system (ServiceCode 0). communications ports (serial and parallel). the file may not exist. callers can make a request to be notified of global key codes. While you are holding requests in an asynchronously. then respond before waiting for the next request. System Service Error Handling When the service Responds. The device drivers are the closest layer to the OS. tape drives. This type of service requires one or more additional exchanges to hold the outstanding requests. The abort request contains the Job number of the aborted program in dData0. which is a valuable system resource. You don't have to access a global in your program or make another call to determine what went wrong. In each of these cases. All operations are handled in a serial fashion. you must respond without accessing its memory or acting on its request. However. If so. Asynchronous Services Most services will be synchronous. Also be aware that these pointers are only valid from the time you receive the request. you would receive a non-zero number indicating the error. This is the easiest way to handle requests. you return a number that indicates what you want to tell them. Synchronous services wait. These key codes may not occur for long periods of time. A service may receive and hold more than one request before responding. but carries it one step farther. RAM disks. until you respond. The keyboard service is good example of an asynchronous service.system is “aware” of this. or maybe the File System couldn't allocate an operating system resource it needed to open your file. and many normal key strokes may occur in the meantime. Writing Device Drivers Device Drivers control or emulate hardware on the system. If so. In the keyboard service. The kernel primitive MoveRequest() takes a request you have received using Wait. You should not confuse device drivers with message-based system services. The file may already be opened by someone else in an incompatible mode. and moves it to another exchange. and will alias the pointers so the caller's memory space is accessible to the service. you must be aware of conventions used in error passing and handling. receive a request. Disk drives. As a service you must not attempt to access any data in the caller's space other than what is defined by pData1. If this happens. MMURTL has standardized entry points for all devices on the system. you need to ensure that you look at all the requests you are holding to see if this abort is for one of the requests. This is so the operating system can reclaim the request block. MMURTL uses this concept. You would look at each Request Block in the RqOwnerJob field to see if this request came from it. This type of service is defined as asynchronous. OpenFile() returns a 0 error code if everything went fine. Device drivers are accessed via call gates transparent to the applications programmer MMURTL V1. the error (or status) to the user is passed back as the second dword parameter in the respond call. You can then use WaitMsg() or CheckMsg() to get the requests from this second hold exchange when you know you can respond to them. you must respond to it immediately. cbData1 and pData2. An example of this is how the file system handles errors. Some operating systems simply give you a true or false style of response as to whether or not the function you requested was carried out without errors.

the file system is at user level (3). I have made it rather painless to build fast. basic video.or request-based system services and application code (both at User level 3) cannot access ports at all. Block Size. Device drivers have complete access to processor I/O ports for their devices without OS intervention. powerful. When a device driver initializes itself with the call InitDevDr(). or any OS structures. it will pass in a pointer to an array of DCBs. Only System code (protection level 0) is allowed processor port I/O access. device drivers and have even gone into substantial detail for beginners. or by simply "pushing" parameters on the stack in assembler and making a call.) Most byte-oriented devices tend to be sequential devices because they don't have a fixed size block and are not randomly accessed. The interface is non-specific. Other system calls may also have to be made to allocate or initialize other system resources it requires before it can handle its device. MMURTL takes care of synchronization of system-level hardware access so device driver programmers don't have to worry about it. Those who have written device drivers on other systems will appreciate this concept.which allow callers to easily and rapidly access code with a procedural interface from high-level languages (e. and sequential video access (ANSI drivers etc. RAM DISK). MMURTL provides several built-in device drivers including floppy disk. as well as where the data is on the device. the PICUs. Device drivers must not attempt to directly control the system DMA.. The DCB is shared by both the device driver and OS code.g. The device-driver builder must not access ports other than those required to control their hardware.. network link layer devices). Random-oriented devices are things like disk drives and RAM disks that require the caller to tell the driver how much data to read or write. it passes in a pointer to its DCB. while providing a fair amount of hardware independence. hard disk. C and Pascal). Network frame handlers may also be random because a link layer address is usually associated with the read and write functions. Things like Device name. MMURTL V1. All device drivers use a Device Control Block (DCB). to handling complex devices such as hard drives. although they may be sequential too (implementation dependent). MMURTL provides a complete array of calls to handle all of the hardware. multitasking. Each device driver must also maintain in itsdata area all hardware specific data (variables) needed to control and keep track of the state of the device.and sequential-oriented. or network frame handlers (i. keyboard. which ensures complete harmony in MMURTL's real-time. Some types of tape drives allow reading and writing to addresses (sectors) on the tape and therefore may also be random devices. keyboard. paged memory OS that uses two of the four possible protection levels of the 386/486 processors. The levels are 0 and 3 and are referred to as System (0) and User (3). If a device driver controls more than one device. you provided a pointer to your Device Control Blocks (DCB) after you have filled in the required information. With InitDevDr(). The device driver defines and keeps the DCB in its memory space. interrupt vectors. It does not directly access hardware.e. or is included in the OS code at build time. Device drivers must have access to the hardware they control. which actually becomes the OS memory space when you load it. it has to make a system call to set itself up as a device driver in the system. Sequential devices are things like communications ports. multi-threaded environment. The MMURTL standard Device Driver interface can handle both sequential and random devices with fixed or variable block and/orsector lengths. For example. Device drivers can be doing anything from simple disk device emulation (e. SCSI ports. or a processor protection violation will occur.g. and Device Type are examples of the fields in a DCB. Building Device Drivers When the device driver is installed by the loader. timer hardware. This means message. The call to setup as a device driver is InitDevDr().0 -97- RICHARD A BURGESS . and communication (serial and parallel) Devices in MMURTL are classified as two different basic types: random. Device Driver Theory MMURTL is a protected-mode.

In a multitasking environment it is possible that a deviceMMURTL V1. It has a main entry point which you specify when writing the program. It terminates your task and leaves your code resident to be called by the OS when one of the three device-driver calls are made. This is because the OS has a very small layer of code which provides common entry points to the three device driver calls for all drivers. Allocate exchanges. Check or set up the device’s. One very important function is blocking for non-re-entrant device drivers. it's the main program block. if needed. In C this would be main(). When you fill in the DCB you will fill in the addresses of these 3 entry points that the OS will call on behalf of the original caller. The generic steps are described below. if needed. if required. MMURTL does some housekeeping and then calls your specified function. you have three device driver routines that are ready to be called by the OS. This code will only execute once to allow you to initialize the driver. if needed for messaging from a second task or the ISRs (see the floppy device driver for an example of a device driver with a separate task to handle hardware interrupts). The OS defines the three following PUBLIC calls: DeviceInit() DeviceOp() DeviceStat() The parameter listings and function expectations are discussed in detail later in this chapter. In assembler this would be the START entry point specified for the program. Additional system resources available to device drivers are discussed later in this section. Set up interrupt service routines. You can name them anything you want in your program. or in Pascal. and is programmed. This code is immediately executed after loading the driver. Your functions may not have to do anything when called depending on the device you are controlling. Initialize or allocate any resources required: Allocate any additional memory. Enter the required data in the DCBs to pass to the InitDevDr() call. Your actual calls may vary depending on what system resources you need. It will be the OS that calls them and not the users of the devices directly. When programs call one of these PUBLIC calls. just like any other program. This layer of code performs several functions for you.0 -98- RICHARD A BURGESS . Device Driver Setup and Installation The initialization section of your device driver must make certain calls to set up and install as a driver. Your driver program must interface properly with the three public calls. This will generally be a very small section of code (maybe 30 lines). Call InitDevDr(). unless this is done in one of the procedures (such as DeviceInit()) as a normal function of your driver. InitDevDr() never returns to you. but they must have the same number and type of parameters (arguments) as the PUBLIC calls defined in the OS for all device drivers. At this point. But they must at least accept the calls and return a status code of 0 (for no error) if they ignore it. The device driver looks. How Callers Reach Your Driver All device drivers are accessed via one of 3 public calls defined in the OS. Do anything else you need before being called. You will have functions in your driver to handle each of these calls.Before calling InitDevDr you will most likely have to allocate other system resources and possibly even set up hardware interrupts that you will handle from your device. like any application in MMURTL. The main entry point will never be called again. Enable (UnMaskIRQ()) any hardware interrupts you are servicing.

This means all DMA transfers must be buffered into memory that was allocated using AllocDMAPage(). although it’s not a requirement. In other operating systems. One of the items you fill out in the DCB is a flag (fDevReent) to tell the OS if your driver is re-entrant. or how much was transferred.” Direct Memory Access Device (DMA) If your device uses DMA. the ISR usually sends the EOI sequence directly to the PICU. The following is a list with brief descriptions of the calls you may need to set up and use in your ISR: SetIRQVector() is how you tell the OS what function to call when your hardware interrupt occurs.”API Specification. Two calls provide these functions. and the addresses you use will probably never be equal to the physical address! Another thing to consider is that DMA on the ISA platforms is limited to a 16Mb physical address range. More detail about each of these calls can be found in the alphabetical listing of OS calls in Chapter 15. or have the ability to "time-out" if an expected action doesn't occur in a specific amount of time. you will have to use the DMASetUp() call each time before instructing your device to make the programmed DMA transfer. Your driver should use the AllocDMAPage() call to allocate memory that is guaranteed not to cross a physical segment boundary which also provides you with the physical address to pass to DMASetUp(). The Alarm() call will send a message to a specified exchange after a number of caller-specified 10-millisecond increments. MMURTL has a complete array of support calls for such drivers. MaskIRQ() and UnMaskIRQ() are used to turn on and off your hardware interrupt by masking and unmasking it directly with the PICU. Timer Facilities Quite often. device drivers must delay (sleep) while waiting for an action to occur. and size of a DMA move. EndOfIRQ() sends and "End of Interrupt" signal to the Programmable Interrupt Controller Units (PICU). Do not do this in MMURTL as other code may be required to execute in later versions of MMURTL which will render your device driver obsolete. This is accomplished with GetDMACount(). device drivers are very close to the hardware. DMASetUp() lets you set a DMA channel for the type. Many users of DMA also need to be able to query the DMA count register to see if the DMA transfer was complete. specifically DOS. direction. ISRs should be written in assembly language for speed and complete code control. See the section that deals specifically with ISRs for complete descriptions and examples of simple ISRs. The Sleep() call actually puts the calling process to sleep (makes it wait) for a specific number of 10-millisecond periods. The Alarm() call can be used asynchronously to allow the device code to continue doing something while the alarm is counting down. See the OS Public Call descriptions for more information on DMASetUp() and how to use AllocDMAPage(). Interrupts Several calls are provided to handle initializing and servicing hardware interrupt service routines (ISRs) in your code. If you use MMURTL V1. It is important that you understand that the memory addresses your program uses are linear addresses and do not equal physical addresses. and they must service interrupts from the devices they control. Most device drivers are NOT re-entrant so this will normally be set to FALSE (0). System Resources For Device Drivers Because they control the hardware.driver function could be called while someone else's code is already executing it. The floppy and harddisk device drivers also provide good ISR examples. especially if the driver controls two devices. They require special operating system resources to accomplish their missions. and should be called by you as the last thing your ISR does. Program code can and will most likely be loaded into memory above the 16Mb limit if you have that much memory in your machine. DMA also has some quirks you should be aware of such as its inability to cross-segment physical boundaries (64Kb). Many device drivers need to use DMA and timer facilities. This is often called an interrupt vector.0 -99- RICHARD A BURGESS . mode. MMURTL uses paged memory.

a structure in C. Message Facilities for Device Drivers Interrupt service routines (if you need them in your driver) sometimes require special messaging features that application programs don't need. A special call that may be required by some device drivers is IsendMsg(). DO NOT MODIFY ALLOCATE and 0.1 presents the information that needs to be included in the DCB. It doesn't force a task switch (even if the destination exchange has a higher priority process waiting there). If you must send a message that guarantees a task switch to a higher priority task. each of the three PUBLIC device calls. this is job ALLOCATE and 0. Detailed Device Interface Specification The following sections describe the device control block (DCB). so it may be called from an ISR while interrupts are disabled. IsendMsg() is a special form of SendMsg() that can be used inside an ISR (with interrupts disabled). You can look at some of the included device driver code with MMURTL to help you. Alarm(). You must ensure that there is no padding between the variables if you use a record in Pascal.0 Size 12 1 1 2 4 4 4 4 4 1 1 2 4 4 4 Offset 0 12 13 14 16 20 24 28 32 36 37 38 40 44 48 Description Device Name. IsendMsg() is used if an ISR must send more than one message from the interrupted condition. IsendMsg() also does not re-enable interrupts before returning to the ISR that called it. The size of a DEVICE CONTROL BLOCK is 64 bytes. 2 = SEQUENTIAL Bytes Per Block (1 to 65535 max. Device control block definition Name DevName sbDevName DevType NBPB dLastDevErc ndDevBlocks pDevOp pDevInit pDevStat fDevReent fSingleUser wJob OSUseONLY1 OSUseONLY2 OSuseONLY3 MMURTL V1. and how to implement them in your code. it must be the final call in your ISR after the EndOfIRQ() call has been made and just before the IRETD() (Return From Interrupt).Alarm(). or assembler to construct the DCB.) (0 for variable block size) Last error code from an operation Number of blocks in device (0 for sequential) pointer to device Oper. Device Control Block Setup and Use The DCB structure is shown in the following table with sizes and offsets specified. How the structure is implemented in each programming language is different. and KillAlarm() functions.1-. you will most likely need to use KillAlarm() which stops the alarm function if it hasn't already fired off a message to you. left justified Length of Device name in bytes 1 = RANDOM device. or you only need to send one message from the ISR. If your driver controls more than one device you will need to have a DCB for each one it controls. DO NOT MODIFY -100- RICHARD A BURGESS . Table 10. See the call descriptions for more information on the Sleep(). Handler pointer to device Init handler pointer to device Status handler Is device handler reentrant? Is device usable from 1 JOB only? If fSingleUser true. Table 10. DO NOT MODIFY ALLOCATE and 0. Multiple DCBs must be contiguous in memory. If you use SendMsg(). you can use SendMsg(). The length of each field in the DCB is shown in bytes and all values are unsigned. This will guarantee a task switch if the message is to a higher priority task. but easy to figure out.

floppy disk drives are usually named FD0 and FD1.Performs I/O operations in device DeviceInit() .0 -101- RICHARD A BURGESS . If it is a single-byte oriented device such as a communications port or sequential video then this would be 1. and pDevStat are pointers to the calls in your driver for the following PUBLIC device functions: DeviceOp() . DevType . This reduces disk arm movement which. not the next track.This is the number of addressable blocks in your device. head zero. MMURTL expects device drivers to organize access to each random device by 0 to n logical addresses. and WaitMsg() primitives. where several devices are controlled though one access point.When a driver encounters an error. then it's considered a random device and this byte should contain a 1.number of bytes per block. fDevReent . Each device name must be unique. or it's a disk driver that is initialized for 1024-byte sectors then this would reflect the true block size of the device. The return instruction you use should be a 32-bit near return. RETN is the proper instruction. non-zero true) that tells MMURTL if the device driver is re-entrant. DO NOT MODIFY The fields of the DCB must be filled in with the initial values for the device you control before calling InitDevDr(). on a floppy disk this would be the number of sectors x number of cylinders x number of heads on the drive. MMURTL will queue up tasks that call a device driver currently in use. In C-32 (MMURTL's standard C compiler).Each driver names the device it serves. defining the function with no additional attributes or modifiers defaults it to a 32-bit near return. For instance. usually an unexpected one. DO NOT MODIFY ALLOCATE and 0. The Hard disk driver operates the same way. If the device has variable length blocks then this should be 0. znBPB . it should set this variable with the last error code it received.this indicates whether the device is addressable. On very complicated devices such as a SCSI driver. and you read one sector past the end of that track. For devices that are not re-entrant. The queuing is accomplished with an exchange. In a true multitasking environment more than one task can call a device driver. It numbers all sectors on the disk from 0 to nTotalSectors (nTotalSectors is nHeads * nSecPerTrack * nTracks). In other words. but this is not much of a limitation. Each sector is considered a block. increases throughput. If the device is mountable or allows multiple media sizes this value should be set initially to allow access to any media for identification. sbDevName . sector zero.a single byte containing the number of bytes in the device name. Addressing with 32 bits limits address access to 4Gb on a single-byte device and 4Gb x sector size on disk devices. this is device dependent. Most device drivers have a single set of variables that keep track of the current state of the device and the current operation. If the device is sequential. pDevOp. Some names are standardized in MMURTL. if you are on track zero. This is usually dictated by your compiler or assembler and how you define the function or procedure. DO NOT MODIFY ALLOCATE and 0. ndDevBlocks . The driver does not need to reset this value as MMURTL resets it to zero on entry to each call to DeviceOP() for each device. but this doesn't prevent the driver from assigning a non-standard name. A portion of the reserved DCB is used for this purpose. pDevInit. the driver may have to tell MMURTL it's re-entrant. Most of the fields are explained satisfactorily in the descriptions next to the them.Initializes or resets the device DeviceStats() . If it is another device. the SendMsg(). MMURTL V1. but a driver for non-standard floppy devices can name them anything (up to 12 characters). The name is used so that callers don't need to know a device’s number in order to use it. but the following fields require detailed explanations: DevName . dLastDevErc . In assembler. we then move to the next head.This is a 1-byte flag (0 false. The head number takes precedence over track number when calculating the actual position on the disk. Disk drives are divided into sectors. For example. An example of how this is accomplished is with the floppy device driver. If data can be reached at a specific numbered address as specified in the DeviceOP() call parameter dLBA (Logical Block Address). MMURTL handles conflicts that occur when more than one task attempts to access a device. and the concept of an address for the data has no meaning (such as with a communications port).Returns device-specific status MMURTL uses indirect 32-bit near calls to your driver's code for these three calls. and handle device conflicts internally. then this should contain a 2.OSuseONLY4 OSuseONLY5 OSuseONLY6 4 4 4 52 56 60 ALLOCATE and 0. If the sectors are 512-byte sectors (MMURTL standard) then this would be 512.

Standard Device Call Definitions As described previously. dLBA.dLBA. Standard device errors should be used when they adequately describe the error or status you wish to convey to the caller. If fSingleUser is false this field is ignored.0 -102- RICHARD A BURGESS . They must be set to zero before the call to InitDevDr. where the parameters will be found on the stack after the function has set up its stack frame using Intel standard stack frame entry techniques. dOpNum. Note that your function should also remove these parameters from the stack.4. this flag should be true (non-zero). If this is zero. pInitData. This applies to devices like communications ports that are assigned and used for a session. no job is currently assigned the device. Your function implementations must do the same. OSUseONLY1. verify. dError = DeviceOp(dDevice. sdStatRetmax):dError Detailed information follows for each of these calls. ndBlocks. Device specific error codes should be included with the documentation for the driver. wJob .dnBlocks.5. write. The first 256 operations (dOp number) are pre-defined or reserved. The call is not device specific and allows any device to be interfaced through it.fSingleUser .dOpNum. all calls to device drivers are accessed through three pre-defined PUBLIC calls in MMURTL. this will be the job that is currently assigned to the device. The dOpNum parameter tells the driver which operation is to be performed. They are: DeviceOp (dDevice.These are reserved for OS use and device drivers should not alter the values in them after the call to InitDevDr.2.pData).6 . and format.If the fSingleUser is true. DeviceOp Function Implementation The DeviceOp() function is used by services and programs to carry out normal operations such as Read and Write on a device.3. pData):dError DeviceInit (dDevice. Returning zero indicates successful completion of the function.If a device can only be assigned and used by one Job (user) at a time. The call frame offsets for assembly language programmers are these: dDevice [EBP+24] dOpNum [EBP+20] dLBA [EBP+16] dnBlocks [EBP+12] pData [EBP+08] Parameter Descriptions: dDevice the device number dOpNum identifies which operation to perform 0 Null operation 1 Read (receive data from the device) 2 Write (send data to the device) 3 Verify (compare data on the device) 4 Format Block (tape or disk devices) MMURTL V1. The procedural interfaces are shown with additional information such as the call frame offset. All functions in MMURTL return errors via the EAX register. An almost unlimited number of operations can be specified for the Device Operation call (2^32). A non-zero value indicates and error or status that the caller should act on. They equate to standard device operations such as read. The rest of the dOp Numbers may be implemented for any devicespecific operations so long as they conform to the call parameters described here. pStatRet. sdInitdata):dError DeviceStat (dDevice.

An example of initialization would be a communications port for baud rate.pInitData.pdStatusRet).pStatRet.5 Format Track (disk devices only) 6 Seek Block (tape or disk devices only) 7 Seek Track (disk devices only) (Communications devices) 10 OpenDevice (communications devices) 11 CloseDevice (communications devices) (RS-232 devices with explicit modem control) 15 SetDTR 16 SetCTS Undefined operation number below 255 are reserved 256-n Driver Defined (driver specific) Dlba Logical Block Address for I/O operation. The size of the initializing data and its contents are device specific and should be defined with the documentation for the specific device driver.0 -103- RICHARD A BURGESS . The call frame offsets for assembly language programmers are: [EBP+20] [EBP+16] [EBP+12] [EBP+08] MMURTL V1.dInitData). Not all devices will return status on demand. In cases where the function doesn't or can't return status. and so on. The call frame offsets for assembly language programmers are: dDevice pStatRet dStatusMax pdStatusRet Parameter Descriptions: dDevice Device number to status pStatBuf Pointer to buffer where status will be returned dStatusMax caller sets this to tell you the max size of status to return in bytes pdStatusRet Pointer to dword where you return size of status returned in bytes DeviceInit Function Implementation Some devices may require a call to initialize them before use or to reset them after a catastrophe.dStatusMax. this will simply be the number of bytes. DError = DeviceStat(dDevice. For sequential devices this parameter will be ignored. dnBlocks Number of contiguous Blocks for the operation specified. you should return 0 to pdStatusRet and return the standard device error ErcNoStatus. pData Pointer to data (or buffer for reads) for specified operation DeviceStat Function Implementation The DeviceStat() function provides a way to for device-specific status to be returned to a caller if needed. DError = DeviceInit(dDevice. parity. For sequential devices.

If the driver controls more than one device. Please use the MMURTL API reference for a detailed description of their use. it should allocate all system resources it needs to operate and control its devices while providing service through the three standard entry points. MaskIRQ() Masks one interrupt (prevents it from interrupting) by programming the PICU. the driver will be substituted for the existing driver functions already in place. pDCBs This is a pointer to the DCB for the device. The definition and parameters to InitDevDr() are as follows: InitDevDr(dDevNum. such as a disk or SCSI controller. pDCBs. and so on. DMASetUp() Programs the DMA hardware channel specified to move data to or from your device. and a very good knowledge of the hardware. OS Functions for Device Drivers The following is a list of functions that were specifically designed for device drivers. After the Device driver has been loaded. If the driver is flagged as not re-entrant. They perform many of the tedious functions that would otherwise require assembly language. nDevices. fReplace If true. A 64-byte DCB must be filled out for each device the driver controls before this call is made. this is the first number of the devices. This means the devices are number consecutively. This does not mean that the existing driver will be replaced in memory. it only means the new driver will be called when the device is accessed. It also returns the physical address of the memory which is required for the DMASetUp call.DDevice [EBP+16] IInitData [EBP+12]\ dInitData [EBP+08] Parameter Descriptions: dDevice dword indicating Device number pInitData Pointer to device specific data for initialization be returned dInitData dword indicating maximum size of status to return in bytes Initializing Your Driver InitDevDr() is called from a device driver after it is first loaded to let the OS integrate it into the system. the second at pDCBs + 128. and the driver can handle two devices simultaneously the driver should be broken into two separate drivers. this is the pointer to the first in an array of DCBs for the devices. When a driver controls more than one device it must provide the Device Control Blocks for each device. The DBCs must be contiguous in memory. nDevices This is the number of devices that the driver controls. fReplace) dDevNum This is the device number that the driver is controlling. MMURTL V1. then all devices controlled by the driver will be locked out when the driver is busy. This is because one controller. and can't handle more than one active transfer at a time. UnMaskIRQ() Allows interrupts to occur from a specified channel on the PICU. AllocDMAMem() Allocates memory that will be compatible with DMA operations. If more than one device is controlled. If this is not the case. It must equal the number of contiguous DCBs that the driver has filled out before the InitDevDr call is made. usually handles multiple devices through a single set of hardware ports. and one DMA channel if applicable.0 -104- RICHARD A BURGESS . EndOfIRQ() Resets the programmable interrupt controller unit (PICU) at the end of the ISR sequence. A driver must specify at least as many devices as the original driver handled. This means the second DCB must be located at pDCBs + 64. SetIRQVector() Sets up a vector to your ISR.

0 -105- RICHARD A BURGESS . then use it. Your device drive will also return error codes for problems it has honoring the device call.Standard Device Error Codes The MMURTL device-driver interface code will return errors for certain conditions such as a device being called that's not installed. If an error code already defined in MMURTL's standard header file will adequately describe the error or status. MMURTL V1.

.

system boot in this file.1 shows a sample INITIAL. This file contains the file names of one or more run files you want to execute automatically on boot up.run C:\MMSYS\CLI. Drive:\DIR\NAME. .JOB . as well as the video display.after each entry. The monitor also serves as a context display controller by monitoring for global keys that indicate the user's desire to change. Any spaces. If it's displaying video data. When the user presses these global keys. No parameters are passed . Each line lists the full file specification of a RUN file. Even when a Job is not being displayed. or terminate. Active Job (Video & keyboard) When multiple jobs are running in MMURTL.You may list the jobs that you wanted executed upon .Job file . Many of the functions performed by the monitor are for testing the system.g.JOB file.Chapter 11. The Monitor Program Introduction The Monitor program is included as part of the operating system. . In future versions of MMURTL. the monitor attempts to open a text file in the system directory called INITIAL.RUN <---. (e. it is still running unless it's waiting for something such as keyboard input or some form of communication.1. only one job at a time can be displayed or accept keyboard input. Listing 11. A line that begins with a semicolon is ignored and considered a comment line. the keyboard. Initial Jobs After all internal device drivers and services are loaded. you will find a need for something similar to the monitor as an initial piece of code built into the operating system for testing. tabs or other characters are allowed before the RUN file name.INITIAL. If you intend to write an operating system. tabs or comments after .RUN) .0 -107- RICHARD A BURGESS . many of the functions now performed by the monitor will be moved to external programs. The current job may also be terminated by pressing CTRL-ALT-Delete. it continues to run because a job never actually knows if it is displaying data to the real screen or its own virtual screen. Listing 11. No spaces. the job he is currently viewing.to the run file.no spaces in front of the name and a proper end-of-line . The Monitor program enables you to select which job you are currently interacting with.file name including path.Comment lines begin with a semi-colon . The file name must contain the FULL . This is done by pressing the CTRLALT-PageDown keys to move forward through the active jobs until you see the one you want.the run file name are ignored. Sample Initial. a buffer in memory.. C:\MSamples\Service\Service.This is the initial jobs file. The name must start in the first column of a line.this will be loaded on bootup MMURTL V1. One run file name per line. .JOB. The format is very simple.Maximum line length is 80 characters total. is assigned to the new job or is terminated.

The first thing accomplished by the monitor is the initialization of all internal device drivers and system services.RUN. job display. It looks for Ctrl-Alt-PageDown and shifts the keyboard and video to the next job in numerical order. and access to the built-in debugger. Table 11. The initialization also includes starting two tasks inside the monitor code. The monitor echoes other keys to the screen to show you it is working properly. the monitor is reached with a JMP instruction. The device drivers that are initialized include the hard and floppy disk. The first additional task provides the context-switching capabilities by leaving a global key request with the keyboard service. MMURTL V1. the video and keyboard will be assigned to this job and taken away from the monitor. They are displayed as typed. The functions are provided with function keys which are labeled across the bottom of the display. RS-232 serial communications.This is the total number of 4K memory pages available for allocation by the memory-management code. A status code of Zero indicates successful initialization.1.1 describes the function of each assigned key.. These statistics include: Free 4K memory pages . priority task. During this process. Monitor Function Keys Key F1 F2 F3 F8 F10 Label LDCLI JOBS STATS BOOT DEBUG Function Loads a Command Line Interpreter List Jobs Show System Resource Status Reboots the system (hard reset) Enter the Debugger Monitor Program Theory After all internal static and dynamic structures are initialized. user interaction is provided via the functions keys discussed in table 11.0 -108- RICHARD A BURGESS . and the parallel port (LPT) driver.This is the total count of task switches since boot up. Performance Monitoring Performance monitoring is provided by sampling statistic-gathering public variables the operating system maintains and updates. This task also looks for the Ctrl-Alt-Delete key combination which terminates a job. The initialized services include the keyboard and the file system. The second task is used to recover resources when a job ends or is terminated for ill behavior (memory access or other protection violations).1.End of file If the executable job listed is CLI. any errors returned are displayed. Task switches total . Monitor Function Keys The Monitor also provides other functions such as system resource display. Once the initialization is complete. Table 11. The statistics provide a snap-shot of system resources every half second.

This is the number of free exchanges that can be allocated. Monitor Source Listing Text-formatting functions are provided with the xprintf() function defined in the monitor. that don't get picked up right away. There were no tasks ready to run. Tasks Ready to Run . but dozens of messages may be sent between tasks.This is the number of free job control blocks left. you will see the preemptive task switch count increase.h" "MData.h" "MJob. Free Exchanges . These structures are also in dynamically allocated memory.h" "MTimer. but the count is fixed at OS build time. the C library code can be used.h" "MFiles. Free Link Blocks . MMURTL V1. but have a fixed count at OS build time.h" "MKbd. Each new task uses one of these.These are task-management structures. The memory they occupy is dynamically allocated. This number does not directly relate the timer ticks because this can happen several times between the time interrupt interval.A small structure used by all messages sent and received. Monitor program source listing #define #define #define #define #define #define #include #include #include #include #include #include #include #include #include U32 unsigned long S32 long U16 unsigned int S16 int U8 unsigned char S8 char "MKernel. the request block is returned for re-use.2.h" "MMemory. including requests. even in the same job. See listing 11.0 MMURTL Monitor Tick:0 -109- RICHARD A BURGESS .This is the number of tasks currently queued to run.h" #define ok 0 #define ErcNoDevice 504 static char rgStatLine[] = "mm/dd/yy 00:00:00 ". These are static and the count is determined at OS build time.h" "MVid. Free Job Control Blocks . In future versions when a lot of the monitor functionality is moved out of the operating system.2. Free Task State Segments . Each job uses one. If this number is greater than zero.CPU idle ticks (no work) .This is the number of times the operating system had nothing to do. Listing 11. This number may seem a little high. I include no C library code in the operating system itself. Response is made by the service. They are also dynamically allocated with a fixed count at OS build time. These are also dynamically allocated with a fixed count at OS build time.h" "MDevDrv.

static char *CRLF = "\r\n\r\n". date. static char rgCPR1[] ="MMURTL (tm) . U32 BlocksMax. Burgess. /* Sectors per track */ U32 nBPS. /* Messaging for main */ /* Extra Messaging for main */ /* Structure for disk device driver status and setup */ static struct diskstattype { U32 erc. static char rgMonMenu2[] = " \xb3 \xb3 \xb3Reboot".0 -110- RICHARD A BURGESS . MngrHndl. /* total physical cylinders */ U32 nHead. 1990-1995. /* Messaging for stat task */ static unsigned long GPExch. static unsigned long GP1Exch. All Rights Reserved". LastRecalErc1. MUltitasking. LastErcByte1. Real-Time kerneL". static char rgMonMenu3[] = " \xb3Debug \xb3 \xb3 ". KillJobNum. gcode. fIntOnReset. LastSeekErc1. /* total heads on device */ U32 nSectors. second is ERROR */ /* Color test for xprintf */ KillMsg[2].static char rgMonMenu1[] = "LdCLI\xb3Jobs \xb3Stats \xb3 ". static long time. LastStatByte1. LastStatByte0. tick.A. U32 blocks_done. KillError. LastSeekErc0. static unsigned long GP1Msg[2]. U8 type_now. MngrMsg[2]. /* Number of bytes per sect */ U32 U32 U8 U8 U8 U8 U32 U32 U8 U8 LastRecalErc0. static unsigned long GPMsg[2]. /* current disk type for drive selected */ U8 resvd1[2]. MngrExch. static unsigned long GP1Hndl. LastErcByte0. static unsigned long Color = WHITE|BGBLACK. MMURTL V1. unsigned long KillExch.Message based. U8 fNewMedia. fKilled. static unsigned long GPHndl. /* padding for DWord align */ U32 nCyl. static char rgCPR2[] ="Copyright (c) R. /* Interrupt was received on HDC_RESET */ filler0. static static static static static static static static unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned long long long char long long long long /* Messaging for stat task KILL proc */ /* First DWORD = TSSExch.

nLBLeft. nReady. #asm MMURTL V1. /* 1024 byte stack for Stat task */ static long MngrStack[256]. U32 resvd1[2]. /* /* /* /* /* /* From From From From From From Keyboard.c */ Parallel. extern extern extern extern extern extern extern extern extern extern extern unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned long long long long long long long long long long long oMemMax. InitFS(void).c */ extern long GetExchOwner(long Exch. static unsigned long nMemPages. static long StatStack[256]. nEXCHLeft.U8 ResetStatByte. /* Status Byte immediately after RESET */ U8 filler1. lpt_setup(void). ***************************************************************/ #include <stdarg. /*=================== START OF CODE ====================*/ /************************************************************** Formatted output routines for monitor program xprintf.c */ HardIDE.0 -111- RICHARD A BURGESS . nHalts. fdisk_setup(void). /* out to 64 bytes */ }. nTSSLeft.asm */ Floppy. hdisk_setup(void). char *ppJCBRet). nSwitches. /* 1024 byte stack for Mngr task */ static unsigned char Buffer[512]. nJCBLeft. #define nMaxJCBs 34 /* 32 dynamic plus 2 static */ static struct JCBRec *pJCB. nSlices. coms_setup(void).c */ Fsys. extern long DeAllocJCB(long *pdJobNumRet.h> #define S_SIZE 100 /********************************************* * Determine if a character is a numeric digit **********************************************/ static long isdigit(long chr) { . nRQBLeft. xsprintf. BootDrive. char *pJCBRet). static struct diskstattype DiskStatus. /*============ protos (NEAR MMURTL support calls) =================*/ extern extern extern extern extern extern long long long long long long InitKBDService(void).c */ RS232.

while(chr = *fmt++) { if(chr == '%') { /* format code */ chr = *fmt++.0 JE _strlen1 INC ESI INC EAX JMP SHORT _strlen0 _strlen1: #endasm } /************************************************************* This does the actual parsing of the format and also moves to the next arg(s) in the list from the passed in arg pointer. unsigned long width. 30h . minus. width = value = i = 0. ptr = &numstk[32]. EAX MOV ESI. total = 0. value. i. } if(chr == '0') /* leading zeros */ zero = '0'. while(isdigit(chr)) { /* field width specifier */ MMURTL V1. The number of chars written is returned (not incl \0).[EBP+8] _strlen0: CMP BYTE PTR [ESI]. total. justify.No CMP AL. -1 isdigit2: #endasm } static long strlen(char *cs) { . chr. char *fmt.Yes isdigit0: XOR EAX. **************************************************************/ static long _ffmt(char *outptr. *ptr. zero.MOV EAX. zero = ' '. #asm XOR EAX.[EBP+8] CMP AL. chr = *fmt++. if(chr == '-') { /* left justify */ --justify.0 JL isdigit0 . *ptr = justify = minus = 0.0 -112- RICHARD A BURGESS . long *argptr) { char numstk[33].EAX . 39h .No JMP SHORT isdigit2 isdigit1: MOV EAX. JLE isdigit1 .

case 'c' : *--ptr = value. } value = *--argptr. /* output sign if any */ if(minus) { *outptr++ = '-'. generate the ASCII string */ if((chr = (value % i) + '0') > '9') chr += 7. case 'x' : case 'X' : i = 16. break. *--ptr = chr. break. ++argptr.width = (width * 10) + (chr . } /* pad with 'zero' value if right justify enabled if(width && !justify) MMURTL V1. if(width) --width. case 'o' : i = 8. } while(value /= i). chr = *fmt++. break. break. ++total. default: *--ptr = chr. case 'b' : i = 2. break.0 */ -113- RICHARD A BURGESS . case 's' : ptr = value.'0'). } case 'u' : i = 10. } if(i) do { /* get parameter value */ /* decimal number */ /* unsigned number */ /* hexadecimal number */ /* octal number */ /* binary number */ /* character data */ /* string */ /* value is ptr to string */ /* all others */ /* backup to last arg */ /* for all numbers. break. ++minus. switch(chr) { case 'd' : if(value & 0x80000000) { value = -value.

fmt. char buffer[S_SIZE]. ap).1.{ for(i = strlen(ptr). ++total. long total. TTYOut(buffer.) { va_list ap. Color). /* set up ap pointer */ total = _ffmt(buffer. ++i.0 */ -114- RICHARD A BURGESS . ++total. strlen(buffer).. } /* pad with 'zero' value if left justify enabled */ if(width && justify) { while(i < width) { *outptr++ = zero.. } } *outptr = 0. va_end(ap. return total. } /* move in data */ i = 0. . va_start(ap. } /************************************ MMURTL V1. } /************************************ Formatted print to screen *************************************/ long xprintf(char *fmt. value = width . ++total. ++i) *outptr++ = zero. } } } else { /* not format char. while((*ptr) && (i <= value)) { *outptr++ = *ptr++. fmt). ++total. ++i. return total. fmt). i < width. just move into string *outptr++ = chr.

1). PutVidChars(0. iLine. Msg[2].) { va_list ap.22).1.0. 80.. PutVidChars(54. . rgMonMenu1. rgMonMenu3. tick). long total. char *fmt. "%d". return total. 24. if (iLine >= 23) { ScrollVid(0. fmt. } /********************************************** Checks to ensure we don't scroll the function keys off the screen.23. *********************************************************/ static void StatTask(void) { unsigned long erc. Besides displaying the top status line for the monitor. 26. SetXY(0. 26.. &iLine). BLUE|BGWHITE). 24. Exch. SetXY(0. WHITE|BGBLUE). BLUE|BGWHITE). 24. MMURTL V1. va_end(ap. return. total = _ffmt(s. i. } /********************************************************* This is the status task for the Monitor.Formatted print to string s *************************************/ long xsprintf(char *s. xsprintf(&rgStatLine[70]. rgMonMenu2. **********************************************/ void CheckScreen() { long iCol. GetXY(&iCol. ap). When it gets one. } } /********************************************************* This is called to initialize the screen. va_start(ap.0 /* set up ap pointer */ -115- RICHARD A BURGESS . rgStatLine.1). fmt). 25. PutVidChars(27.80. fmt). BLUE|BGWHITE). it has the job of looking for messages from the job code that indicate a job has terminated. it recovers the last of the resources and then notifies the user on the screen. *********************************************************/ static void InitScreen(void) { ClrScr(). PutVidChars(0.

Tone(440.. xprintf("Job number %d terminated.5 second */ /* Now we check for tasks that are Jobs that are killing themselves (either due to fatal errors or no exitjob).) { GetCMOSTime(&time). /* sleep 0. /* seconds */ + + + + + + ((date ((date ((date ((date ((date ((date >> >> >> >> >> >> 20) 16) 12) 8) 28) 24) & & & & & & 0x0f). rgStatLine[10] = '0' rgStatLine[11] = '0' rgStatLine[13] = '0' rgStatLine[14] = '0' rgStatLine[16] = '0' rgStatLine[17] = '0' GetCMOSDate(&date). (time & 0x0f). Sleep(50). &pJCB).0 -116- RICHARD A BURGESS . GetTimerTick(&tick). xsprintf(&rgStatLine[70].*/ /* Get and save the error (KillMsg[0]) */ KillError = KillMsg[0]. /* Call GetExchOwner which gives us pJCB */ erc = GetExchOwner(KillMsg[1]. 0x0f). /* month */ 0x0f). ((time >> 4) & 0x0f). Error: %d\r\n". ((time >> 16) & 0x0f). KillError). if (!erc) { /* someone’s there wanting to terminate. "%d". ((time >> 12) & 0x0f).U8 *pPD. pPD = pJCB->pJcbPD. 80.. */ erc = CheckMsg(KillExch. rgStatLine.. "%d". KillJobNum. KillMsg). /* sleep 0.50). ((time >> 8) & 0x0f). CheckScreen(). rgStatLine[0] = '0' rgStatLine[1] = '0' rgStatLine[3] = '0' rgStatLine[4] = '0' rgStatLine[6] = '0' rgStatLine[7] = '0' + + + + + + ((time >> 20) & 0x0f). 0x0f). Sleep(50). PutVidChars(0. tick).0. for(. *pVid. The message has Error. /* Day */ 0x0f).0. WHITE|BGBLUE). rgStatLine. WHITE|BGBLUE). pVid = pJCB->pVirtVid. TSSExch in it.5 second */ GetTimerTick(&tick). xsprintf(&rgStatLine[70]. /* year */ 0x0f). PutVidChars(0. /* Must change video to monitor if this guy owned it */ MMURTL V1. if (!erc) { KillJobNum = pJCB->JobNum. tick). 80.

erc = WaitMsg(Exch. &i. 2).GetVidOwner(&i). DeAllocJCB(pJCB). char *pJCB. 4. /* Leave a Global Key Request outstanding with KBD service for the status task */ erc = Request("KEYBOARD". /* We're done (and so is he. It allows us to switch jobs with the CTRL-ALT-PageDown key. Msg). the PD and its 1st PDE (PT) were allocated as two pages next to each other in linear memory. } /* Now we deallocate the JCB and the TSSExch which will free the TSS automatically! */ DeAllocExch(KillMsg[1]). 0. we just reassign video and keyboard to the next active job (except the Debugger). 0. *********************************************************/ static void MngrTask(void) { long erc. /* No error returned */ /* When the JCB was created. 2. 0). j. DeAllocPage(pVid. 0. 0. */ DeAllocPage(pPD. &MngrHndl. Then we deallocate the single page for virtual video. i. 1). if (i == KillJobNum) { GetTSSExch(&Exch).) */ } } } /* for EVER */ } /********************************************************* This is the Manager task for the Monitor. MMURTL V1. &gcode.. So we can just deallocate both pages in one shot. /* Use our TSS exchange for Request */ SetVidOwner(1). if the debugger has the video.. 0. fKilled = 1. 0...0 -117- RICHARD A BURGESS . 1. Exch. Also. We don't actually switch jobs. fDone. MngrExch. 0. erc = Request("KEYBOARD". we don't do it at all! This also looks for CTRL-ALT-DELETE which kills the job that owns the keyboard/Video so long as it's not the Monitor or the Debugger.

0.4. } if (i != j) { SetVidOwner(i). for(. 0). &MngrHndl. 0). 0. if (i==2) i = 3. MngrExch. */ erc = GetVidOwner(&j). } } else if ((gcode & 0xff) == 0x7F) { erc = GetVidOwner(&j). else if (i>34) i = 1. 0. 0. &pJCB). &MngrHndl. 2. while (!fDone) { i++. #asm INT 03 #endasm MMURTL V1. MngrMsg). erc = WaitMsg(MngrExch. 0. 0. 0. 0. 0. i = j. erc = KillJob(j). 0. 0). MngrMsg). 0. *********************************************************/ static void GoDebug(void) { . 0. fDone = 0..0 -118- RICHARD A BURGESS . 0. 0. MngrExch. /* erc = 0 if valid JCB */ if ((!erc) || (i==j)) fDone = 1. } } /* for EVER */ } /********************************************************* This simply does a software interrupt 03 (Debugger). } /* CTRL-ALT-DEL (Kill)*/ /* leave another global key request */ erc = Request("KEYBOARD". 4. erc = GetpJCB(i.) { erc = WaitMsg(MngrExch. i. 0. erc = Request("KEYBOARD". &gcode. if (!erc) { if ((gcode & 0xff) == 0x0C) { /* Find next valid Job that is NOT the debugger and assign Vid and Keyboard to it. 4.

} /********************************************************* This strobes keyboard data port 60h with 00 after sending 0D1 command to Command port. cbrunfile. char ajobfile[50]. GetSystemDisk(&sdisk).Strobe bit 0 of keyboard crtlr output /********************************************************* This reads a file called Initial.first we clear interrupts . 1=B. sjobfile. sjobfile = strlen(ajobfile). 1. job = 0. We have to loop reading the status bit of the command port to make sure it's OK to send the command. */ ajobfile[0] = sdisk. fcli.0 -119- RICHARD A BURGESS . sdisk &= 0x7F. #asm CLI MOV ECX.Test The Input Buffer Full Bit . job. This resets the processor which then executes the boot ROM. j. fcli = 0. while (!fdone) { i = 0. 2=C etc.Job from the system directory and loads all jobs specified in the file.JOB\0". do MMURTL V1. 0.Read Status Byte into AL . *********************************************************/ static void Reboot(void) { . erc = OpenFile(ajobfile. fh. Video and keyboard are not assigned to any of these jobs unless it is cli.check port up to 64K times .AL STI #endasm return. /* 0=A. &ajobfile[1]. char arunfile[80]. 20).return. *********************************************************/ void LoadJobFile(void) { long erc. unsigned char sdisk.64h TEST AL. if (!erc) { fdone = 0. &fh). } . char fdone.run and the last job loaded. i. 0FFFFh Reboot0: IN AL.0FEh OUT 64h. CopyData(":\\MMSYS\\INITIAL. sjobfile.02h LOOPNZ Reboot0 MOV AL. sdisk += 0x41.

xprintf("Loading: %s. /* if the last successfully loaded job was a cli. else fcli = 0. erc = LoadNewJob(arunfile. } CloseFile(fh). if ((!erc) && (i > 1)) { if (arunfile[0] == '. 4... MMURTL V1.run". CheckScreen(). 0.{ erc = ReadBytes(fh. } } } else fdone = 1. /* a comment line */ cbrunfile = 0. /* null terminate for display */ if ((cbrunfile > 8) && (CompareNCS(&arunfile[cbrunfile-7]. Sleep(50). &arunfile[i++]. while ((arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile])) cbrunfile++. "cli. } while ((!erc) && (arunfile[i-1] != 0x0A) && (i < 80)). */ if ((job > 2) && (fcli)) { SetVidOwner(job). job = 0. &GP1Hndl. 7) == -1)) fcli = 1. } else { xprintf("ERROR %d Loading job\r\n". erc = 0. arunfile).') continue.0 -120- RICHARD A BURGESS . &j). CheckScreen(). if (!erc) { xprintf("Successfully loaded as job %d\r\n". GP1Exch. cbrunfile. assign the keyboard and video to it. &job). 1. job). CheckScreen(). erc).\r\n". 0x0A) 0x0D) 0x20) 0x09) && && && && if (cbrunfile > 2) { arunfile[cbrunfile] = 0. erc = Request("KEYBOARD". Sleep(50).

iLine. *********************************************************/ void Monitor(void) { long erc. 0. 0. &GP1Hndl. GP1Exch. GetSystemDisk(&sdisk)..JOB file not found in system directory. InitScreen(). 0. acli[40]. erc = LoadNewJob(acli. 0. 0). 0. unsigned char c. i. 0. 0. } return erc. if (!erc) { xprintf("New CLI Job Number is: %d\r\n". MMURTL V1. 2=C etc. ccode1.". } /********************************************************* This is the main procedure called from the OS after all OS structures and memory are initialized. /* 0=A. Sleep(50). if (!erc) erc = WaitMsg(GP1Exch. job. 16). j. xprintf("Loading: %s. 4. job. */ acli[0] = sdisk. } } /********************************************************* This Loads the MMURTL Command Line Interpreter and switches video and keyboard to give the user access to it.. *********************************************************/ static long LoadCLI(void) { long erc. &acli[1].0 -121- RICHARD A BURGESS . erc = Request("KEYBOARD". job. unsigned char sdisk. GP1Msg). if (!erc) erc = WaitMsg(GP1Exch. strlen(acli). job). GP1Msg). CheckScreen(). 0. SetVidOwner(job). &job). } } else { xprintf("INITIAL. 0.\r\n").RUN\0". iCol. CheckScreen(). 0). char text[70]. 1=B. sdisk += 0x41. acli). k. CopyData(":\\MMSYS\\CLI. sdisk &= 0x7F. unsigned long ccode.0. 0.

MUltitasking.0 -122- RICHARD A BURGESS . erc = AllocExch(&KillExch). c). Error: "). 24. xprintf("Total memory (Kb): %d\r\n". Real-Time kerneL\r\n").A. if (erc) xprintf("AllocExch (Mngr Exch) Error: %d\r\n". xprintf("Init floppy device driver. erc).. erc = SpawnTask( &StatTask. xprintf("%d\r\n". erc = hdisk_setup(). xprintf("BootDrive: %c\r\n". xprintf("Init hard disk device driver. /* 250 Hz for 150ms */ /* 250 Hz for 330ms */ /* Allocate an exchange for the Manager task global keycode */ erc = AllocExch(&MngrExch). i = (nMemPages*4096)/1024. xprintf("Init KBD Service Error: %d\r\n". xprintf("Copyright (c) R. if (erc) xprintf("SpawnTask (StatTask) Error: %d\r\n".Burgess. xprintf("Free memory (Kb): %d\r\n".. erc). if (erc) xprintf("AllocExch Error: %d\r\n". erc = QueryPages(&nMemPages). 1 ). 0. erc = fdisk_setup().15). erc). /* Allocate general purpose exchanges to use in the monitor */ erc = AllocExch(&GPExch). erc).33). 1991-1995 ALL RIGHTS RESERVED\r\n\r\n"). erc = AllocExch(&GP1Exch). Error: "). erc).Tone(250. if (erc) xprintf("AllocExch GP1 Error: %d\r\n". /* Task 4 */ MMURTL V1. i = (oMemMax+1)/1024. erc).Message based. xprintf("MMURTL (tm) . erc). i). Color = YELLOW|BGBLACK.. erc). c = ((BootDrive & 0x7f) + 0x41). erc). erc). &StatStack[255]. Color = WHITE|BGBLACK. xprintf("Init Parallel LPT Device Driver Error: %d\r\n". xprintf("%d\r\n". i). xprintf("Init Serial Comms Device Driver Error: %d\r\n". if (erc) xprintf("AllocExch (Kill Exch) Error: %d\r\n". erc = coms_setup(). Tone(1000. erc = lpt_setup(). erc = InitKBDService()..

c = ccode & 0xff. MMURTL V1.) { /* Loop forEVER looking for user desires */ /* Make a ReadKbd Key Request with KBD service. &pJCB). /* Col offset */ for (i=1. */ LoadJobFile(). /* wait for the keycode to come back */ erc = WaitMsg(GPExch. 0. /* Spawn manager task */ SpawnTask( &MngrTask. 0.0 -123- RICHARD A BURGESS .. 1 ). pJCB->JobNum).j). GPMsg).. text[pJCB->sbJobName[0]] = 0. 0. /* Line */ k = 0. Tell it to wait for a key. case 0x10: /* F2 Jobs */ InitScreen(). if (erc) xprintf("KERNEL Error from Wait msg: %d\r\n". if (erc) xprintf("Kbd Svc Request KERNEL ERROR: %d\r\n". /* erc = 0 if valid JCB */ if (!erc) { SetXY(k. erc).. 4. 0. i<nMaxJCBs. &GPHndl.. SetXY(k+10. /* lop off everything but the key value */ switch (c) { case 0x0F: /* F1 Run */ erc = LoadCLI().. xprintf("Job: %d\r\n". erc). erc). /* Call LoadJobFile to read job file from system directory and execute and jobs listed there. GPExch. xprintf("Name: %s\r\n". CopyData(&pJCB->sbJobName[1]. &ccode.\r\n").xprintf("Initializing file system. i++) { if (j > 20) k = 40. 1. text). for (. if (erc) xprintf("Error from LoadCLI: %d\r\n". erc). erc = GetpJCB(i. xprintf("File System.j). &MngrStack[255]. 10. text. 0). 1. erc = InitFS(). 0. j = 2. break. Error: %d\r\n". j++. 13). */ erc = Request("KEYBOARD".

case 0x00: /* No Key */ Sleep(3). PutVidChars(29. if ((ccode1 & 0xff) == 0x16) Reboot(). 1. GREEN|BGBLACK). " ". xprintf("CPU idle ticks (no work): %d\r\n". case 0x11: /* F3 Stats . 1. 1. 1. xprintf("Free Task State Segments: %d\r\n". Sleep(9).} } break. PutVidChars(29. 1. 1. 1. 0)) { /* ReadKbd no wait until no error */ SetXY(0. xprintf ("\r\n"). break. Sleep(9).1).Cancelled\r\n"). any other key to cancel"). nJCBLeft). GREEN|BGBLACK). } SetXY(0. PutVidChars(29. "/". erc = ReadKbd(&ccode1. "|". nReady). case 0x18: /* F10 Debug */ GoDebug(). Sleep(9).. nMemPages). xprintf("Preemptive task switches: %d\r\n". xprintf("Task switches total: %d\r\n". GREEN|BGBLACK). 1. 1. xprintf("Free Exchanges: %d\r\n". SetXY(0. "/". Sleep(9). while (erc = ReadKbd(&ccode1. Sleep(12). GREEN|BGBLACK). nLBLeft). xprintf("Free Request Blocks: %d\r\n".12). nHalts). 1. PutVidChars(29. PutVidChars(29.0 -124- RICHARD A BURGESS . GREEN|BGBLACK). GREEN|BGBLACK). nEXCHLeft). /* Sleep for 30 ms */ break. "-". case 0x16: /* F8 Reboot */ xprintf("\r\nF8 again to reboot. Sleep(9). "|". 1.future use */ case 0x13: /* F5 */ case 0x14: /* F6 */ case 0x15: /* F7 */ case 0x17: /* F9 */ case 0x19: /* F11 */ case 0x1A: /* F12 */ break. 1. xprintf("Tasks Ready to Run: %d\r\n". erc = QueryPages(&nMemPages).1). PutVidChars(29. break. case 0x12: /* F4 . 1. "\\". Sleep(12).. "\\". "-". GREEN|BGBLACK). 1. PutVidChars(29. 1. nRQBLeft). \r\n"). xprintf("Free Job Control Blocks: %d\r\n". 1. PutVidChars(29. xprintf("Free 4K memory pages: %d\r\n". xprintf("Any key to dismiss status. default: if (((c > 0x1F) && (c < 0x80)) || (c==0x0D) || (c==8)) { if (c==0x0D) MMURTL V1.. 1. Sleep(9). nSwitches). 1). nSlices). xprintf("Free Link Blocks: %d\r\n". GREEN|BGBLACK). 1. xprintf(". break. nTSSLeft). PutVidChars(29.loops displaying status till key is hit */ InitScreen().. GREEN|BGBLACK).

23. } } /* for EVER */ } MMURTL V1.TTYOut (CRLF.80. else TTYOut (&c. if (iLine >= 23) { ScrollVid(0.1).0 -125- RICHARD A BURGESS . WHITE|BGBLACK). WHITE|BGBLACK). &iLine). SetXY(0.1.22). 2. } } GetXY(&iCol. 1.

.

function keys are across the bottom. the debugger is an assembly language. Exiting the Debugger Pressing the Esc key will exit the debugger and begin execution at the next instruction.Chapter 12. The debugger provides the following functions: Display of instructions (disassembled) Dumping of linear memory as Bytes or dwords Display of Exchanges. Entering the Debugger The Debugger may be entered using the Debugger function key in the monitor.0 -127- RICHARD A BURGESS . the debugger is entered. non-symbolic debugger. Debugger Introduction The debugger is built into the operating system. Debugger Display The debugger display is divided into 3 sections. The registers can be examined to see where it happened. If the debugger was entered on a fault. The debugger will also exit using the Single Step command (F1 function key). See “Debugger Theory” section for more information on INT 03 instruction usage. and messages or tasks waiting Display of active tasks Set and Clear an instruction breakpoint Full register display Selection of Linear Address to display Display of important OS structure addresses Using the Debugger In its current incarnation. the Debug command in the command-line interpreter. and will be re-entered immediately following the execution of the next complete instruction. When this occurs. pressing Esc will restart the debugger at the offending address and you will re-enter the debugger again. such as dumping data. or by placing and INT 03 instruction anywhere in your application's assembly language file. Intimate knowledge of the Intel processor instruction set is required to properly use the debugger. Certain debugger display functions. The debugger may also start on its own if certain exceptions occur. The registers and function keys will be redisplayed when the function is finished. and a red banner is displayed. See “Debugger Theory” for more information. MMURTL V1. The complete general register set is displayed along the right side of the screen. The most common exceptions are the General Protection Fault (0D hex) and the Page Fault (0E hex). and the left side is the instruction and data display area. Some exceptions are designed to take you directly into the debugger and display an error code to indicate the problem. along with the exception number that caused it. will clear the screen for display. This usually provides enough information to correct the offending code. and why. It is not a separate run file.

The Debugger TSS is 512 bytes after this address.Link Blocks. F12 AddInfo .Set Breakpoint. This can be dumped to view all of the GDT entries. This returns to the application and executes one instruction.Dynamic TSSs. “Application Programming. low order). The names of the services and the exchanges they service are contained here.Job Control Blocks. This redisplays the current instruction pointer address. F6 Tasks . This the address of the array of Job Control Blocks.Dump Bytes. The JCB is discussed in detail in Chapter 9. This sets the breakpoint at the currently displayed instruction. This is the address of the Monitor TSS. The first two task state segments are static structures and are used for the operating system Monitor and debugger. which makes the first of the dynamic TSSs number 3. This allows you to set the code address the debugger is displaying (disassembling). F4 CS:EIP .Address Information. LBs . This requests a linear address. without executing them.Interrupt Descriptor Table. The GDT contains many items. The array of link blocks are allocated as a static array. This is an array of 512-byte structures. F3 ClrBP . The address is displayed on the left. The task number.Services Array. The request handle that you receive from the request primitive is actually a pointer. the address of the task's JCB. The abbreviations are given in the following list.” SVCs . GDT . F2 SetBP . The rest of the task state segments are in several pages of allocated memory and are initialized during system startup. These values are hard coded at system build. RdyQ . with the down arrow key. JCBs . . the address of the TSS. F8 CrntAdd .Debugger Function Keys Each of the debugger function key actions are described below: F1 SStep .First TSS. These are 16-byte structures used as links in chains of messages and tasks waiting at an exchange. a linear address. This displays all active tasks on the system. Each byte of a dword is displayed is in the proper order (High order. and all the data as well as the ASCII is show to the right. with the name of the structure and what use it may be during debugging.Clear Breakpoint. This may be useful if you have set up an interrupt and you want to ensure it was encoded properly and placed in the table.Dump Dwords. This is the address of the 32 queue structures where tasks that are ready to run are queued. F9 DumpB . next. and the priority of the task are displayed with each one. MMURTL V1.Request Block Array. RQBs . This lists names and linear addresses of important structures used during OS development. This clears the single breakpoint. You may move down through the instructions.0 -128- RICHARD A BURGESS . next.Global Descriptor Table.Single Step. This is the address of the first request block in the array of request blocks allocated during system initialization. the associated Job number. IDT . that will be somewhere in this array.Display Tasks. You may also use F8 CrntAdd (Current Address) to select a new address to display before setting the breakpoint. This becomes the active address.Display Exchanges. This is useful for dumping structures that are dword oriented. as most of them are. and all the data as well as the ASCII is shown to the right. This displays all exchanges and shows messages or tasks that may be waiting there. This can be dumped to view interrupt vector types and addresses. TSSs are numbered sequentially. Each TSS is 512 bytes. after which it immediately returns to the debugger where the next active code address and its associated instruction are displayed. The address is displayed on the left.Change Current Address. some of which include the TSS descriptor entries for you program. This does not change the address that is scheduled for execution. This requests a linear address. These structures can also be very useful during program debugging. and then dumps the bytes located at that address. Only exchanges that actually have a message or task will be displayed. F10 . F5 Exch . TSS1 .Goto CS:EIP.Ready Queue. TSS descriptors point to your task state segment and also indicate the privilege level of the task. TSS3 . and then dumps the dwords located at the that address. The F4 CS:EIP function key can be used to return to the next address scheduled for execution.This is the address of the array of currently registered services. The format is the same as the dump command in the CLI.

you can set breakpoints in several locations of your program by editing the . actually a trap. aTmr . just knowing how far your program made it is enough to solve the problem. or whatever). The paging hardware translates the linear address to a physical address for its own use. and also how local variables are accessed. PUSH EBP MOV EBP. and placing an INT 03 instruction there. like the older processors. You can start by searching for _main in your primary assembly language file generated by the C compiler. its down and dirty troubleshooting.0 -129- RICHARD A BURGESS . it is the index of that exchange in the array. If you set a breakpoint using the INT 03 instruction. which means you can have four active breakpoints in your program if you only use the registers. entering the debugger. When it occurs. This is the address of the array of exchanges in allocated memory. With near calls like you would make inside your program.Timer Blocks. Debugging Your Application If your application is "going south" on you (crashing. There is a symbolic debugger in MMURTL's near future. every task that executes it will produce the interrupt and enter the debugger. ESP . MMURTL V1. When you receive an exchange number form the AllocExch() function. it is an interrupt procedure that will switch to the debugger task after some fix-ups have been completed. The MMURTL debugger currently only uses one of these registers and allows only instruction breakpoints to be set. Each exchange is 16 bytes. or at the beginning of any instruction. or for . Remember that several tasks may be executing exactly the same piece of code. whenever any linear memory location is accessed. without having to fill in special registers. Lots of code here MOV ESP. such as [EBP+12]. but for right now. listed in your ATF file.START in an assembly language program. For applications. The following example shows typical entry and exit code found in a high-level procedure. The Sleep() and Alarm() functions each set up a timer block. There are four debug address registers in the processor.Exch . You would insert the INT 03 instruction wherever you feel would be useful. locking up. Stack parameters (arguments) are always referenced above the frame pointer.ASM files. EBP POP EBP RETN When local variables are accessed they are always referenced below the frame pointer in memory. the last parameter is always [EBP+8]. Debugger Theory The Intel 32-bit processors have very powerful debugging features.and displayed as an unsigned number. What this means is that you can set the processor to execute an interrupt. The INT 03 interrupt is just like any other on the system. In some cases. A single 4-byte integer is referenced as [EBP-4] or [EBP+FFFFFFFC]. This is the address of the array of timer blocks.Exchanges. The INT 03 instruction may also be placed in your code. In MMURTL. this really doesn't apply as much because they each have their own linear address space. Debugging in a multitasking operating system is complicated to say the least. entry 3 in the interrupt table is used to vector to a procedure or task. They have internal debug registers that allow you to set code and data breakpoints. The 386/486 processors makes it much easier because they let you determine if the breakpoint is local to a single task or global to all tasks. It may help you to be able to identify high-level entry and exit code sequences (begin and end of a C function).

it can mean hours of grueling debugging with very little indication of what's going on. a pointer to the currently running TSS. If you look through the 386/486 documentation. It doesn't have its own Page Directory or Page Tables like other jobs (programs). but not quite.. The debugger must be able to do this to get its job done. a page fault is fatal to a task. or faults. It can remove the currently running task and make itself run. No other task operates at a priority that high. It almost. including allocated memory. It means it tried to access memory that didn't belong to it. just like applications do.0 -130- RICHARD A BURGESS . called exceptions. MMURTL V1. usually a blank screen and nothing else. Table 12. If you have problems before the debugger is initialized. No 0* 1 3 4 5* 6* 7* 8* 9 10* 11* 12* 13* 14* 16 Type F T/F T T F F F A F F F F/T F F Description Divide by zero Debug Exception (debugger uses for single step) Breakpoint (set by debugger. is saved by the debugger. while others should not happen and are considered fatal for the task that caused them. This is so the debugger is using the tasks exact linear memory image. it has special privileges that allow to it to operate outside of the direct control of the kernel. or INT 03 in code) Overflow (INT0 instruction) Bounds Check (from Bound Instruction) Invalid Opcodes (reserved instructions) Coprocessor Not available (on ESC and wait) Double fault (real bad news. and the interrupt procedure does a task switch to the debugger's task. The debugger becomes the active job. Until MMURTL uses demand page virtual memory. When writing an operating system. becomes a task in the job it interrupted. that can occur. The debugger is the highest priority task on the system (level 1). you initially enter an interrupt procedure that places the running task in a hold status. This return address is used by the debugger entry code. The debugger cannot debug itself. the current pRunTSS. It has its own Job Control Block (JCB) and one task (its own TSS).Processor exceptions. A few housekeeping chores must be done before the task switch is made. Traps. The exceptions that cause entry into the debugger include Faults.. These include copying the interrupted task's Page Directory Entry into the debugger's JCB and the debugger's TSS. Even though the debugger has its own JCB and looks like another application. The debugger also has its own virtual screen.) Invalid TSS Segment Not Present Stack Fault General Protection Fault Page Fault Coprocessor error (*) The asterisk indicates that the return address points to faulting instruction. This effectively replaces the running task with the debugger task. Hold status simply means that it is not placed on the ready queue as if it were a normal task switch.1. you must be able to access all of its memory.1. Some of these are used by the OS to indicate something needs to be done. It's actually an orphan when it comes to being a program in its own right. MMURTL's debugger is also entered when other fatal processor exceptions occur.MMURTL's debugger is set up as a separate job. If you are going to debug a task. The INT 03 interrupt is not the only way to enter MMURTL's debugger. the debugger becomes one of the most important tools you can have. When the debug interrupt activates. and Aborts (processor fatal) and are shown in table 12. Instead. and do the reverse without the kernel even begin aware of what has happened. you will see that there are many interrupts.

it uses the values in the task's Task State Segment (TSS). along with any error that may have been removed from the stack on a fault. To do this. This also includes switching the video back to the rightful owner. The debugger removes himself as the running task and returns the interrupted task to the running state with the values in the TSS. which is exactly what you want. setting a breakpoint before the exception occurs. All of the values are just the way they were when the exception occurred except the CS and EIP.0 -131- RICHARD A BURGESS . then single step while watching the registers to see where the problem is. all of the registers from the task that was interrupted are displayed. To fix this so that you see the proper address of the code that was interrupted. The debugger uses this information to help you find the problem. it displays the registers from the interrupted task. These contain the address of the exception handler. The debugger exit code is just about the reverse of the code used to enter the debugger. MMURTL has a very small interrupt procedure for each of these exceptions. When the debugger is entered. you should kill the offending job and restart. This also has the effect of starting the task back at the instruction after it was interrupted which is the next instruction to execute. While you are in the debugger. If the debugger was entered because of a fault. MMURTL V1. These procedures get the information off the stack before switching to the debugger.Some of the exceptions even place an error code on the stack after the return address. we must get the return address from the stack and place it back into the TSS. This also allows you to restart the application after a breakpoint at the proper place. This makes the debugger transparent.

.

The exception to this rule is the Global Keyboard request to receive CTRL-ALT keystrokes. 1. The Service Name is KEYBOARD (uppercase as always). You can make a request then go do something else before you return to wait or to check the function to see if it's completed (yes boys and girls. Keyboard Service Introduction The keyboard is handled with a system service.not used (0) */ Not used (0) */ dData0 .” system services are accessed with the Request primitive. 0). Using the Request interface allows for asynchronous program operation. 0 0. Here is an example of a Keyboard Service request: erc = Request( "KEYBOARD". If you don't need asynchronous keyboard access. /* /* /* /* /* /* /* /* /* /* /* /* Service Name */ wSvcCode for ReadKbd */ dRespExch */ pRqHndlRet */ npSend (no Send Ptrs) */ pData1 */ cbData1 (size of KeyCode)*/ pData2 . &KeyCode 4. Each of the functions is identified by its Service Code number.fWait for Key */ dData1 . this is true multitasking). the public call ReadKbd() is provided which is a blocking call with only 2 parameters. MyExch. Only one program (job) will be assigned to receive keystrokes at any one time. As described in chapter 10. Several programs can have outstanding requests to the keyboard service at one time.1 shows the services provided by the service code. 0. I’ll describe it after I discuss the following services. It's easier to use. &MyRqhandle. 0. It is a service because it is a shared resource just like the file system. but not as powerful. Service Code 1 2 3 4 Function Read Keyboard Notify On Global Keys Cancel Notify on Global Keys Assign Keyboard MMURTL V1. “system programming.Chapter 13.Not used (0) */ All message-based services use the same request interface.Not used (0) */ dData2 . Available Services Table 13. 1.0 -133- RICHARD A BURGESS .

The first request pointer points to an unsigned dword where the key code will be returned. Read Keyboard The Read Keyboard function (1) allows a program to request keyboard input from the service. the error should be 0. you should send a Cancel Notify request (service code 3). A value of 0 means the request will be sent back to you immediately.Not used = 0 .1. Request Parameters for Read Keyboard: wSvcCode npSend pData1 dcbData1 pData2 dcbData2 dData0 = = = = = = = 1 0 Ptr where the KeyCode will be returned 4 .Wait for Key = 0 . In this case. Parameters for Notify On Global Keys function: wSvcCode npSend pData1 dcbData1 pData2 dcbData2 dData0 dData1 dData2 = = = = = = = = = 2 0 Ptr 4 0 0 0 0 0 - where the KeyCode will be returned Count of bytes in the code Not used Not used Not used Not used Not used MMURTL V1. The key code is undefined if this occurs (so you must check the error). This allows for "hot-key" operation.Not used 0 .Available Keyboard Services The four functions are described in the following section. The dData0 determines if the service will hold the request until a key is available. even if a key is not available. The error from the service is ErcNoKeyAvailable (700) if no key was available. Unlike regular keystrokes.0 -134- RICHARD A BURGESS .Count of bytes in the code 0 .Return with error if no key waiting 1 . and you no longer want to receive Global key notifications.Not used. A value of 1 in dData0 will cause the service to hold your request until a key is available. The key codes are described in detail in tables later in this chapter.Not used dData1 dData2 Notify On Global Keys The Notify On Global Keys function (2) allows a program to look for any keystroke that was entered with the CTRL and ALT keys depressed.Table 13. It also means that your application must have an outstanding Notify request with the keyboard service to receive these keys. The Global key codes returned are identical to the regular key codes. This means that users of the Read Keyboard function (1) will not see these keys. fWaitForKey (0 or 1) 0 . keys that are pressed when the CTRL-ALT keys are depressed are not buffered. If you have an outstanding Notify request. described in detail later.

and Scroll). Num. This request should only be used by services such as the Monitor or a program that manages multiple jobs running under the MMURTL OS. All ASCII text and punctuation is supported directly. The low-order byte is the actual key code.Cancel Notify On Global Keys The Cancel Notify On Global Keys function (3) cancels an outstanding Notify On Global keys request. Parameters for Notify On Global Keys function: wSvcCode npSend pData1 dcbData1 pData2 dcbData2 dData0 dData1 dData2 = = = = = = = = = 3 0 0 0 0 0 0 0 0 - Not Not Not Not Not Not Not used used used used used used used Assign Keyboard The Assign Keyboard Request assigns a new job to receive keys from the service. this cancels it. Locks. The Cancel Notify request will be returned also. as shown in table 13. This cancels all outstanding Notify requests from the same job number for your application. Alpha-Numeric Key Values The Key code returned by the keyboard service is a 32-bit (4 byte) value. The high-order byte is for the Numeric Pad indicator and is described later. The key will be properly shifted according to the state of the Shift and Lock keys.2. The upper 3 bytes provide the status of special keys (Shifts. Parameters for Assign Keyboard request: wSvcCode npSend pData1 dcbData1 pData2 dcbData2 dData0 dData1 dData2 = = = = = = = = = 3 0 0 0 0 0 x 0 0 - Not Not Not Not New Not Not used used used used Job Number (1 to nJobs) used used Key codes and Status MMURTL supports the standard AT 101-key advanced keyboard.4. This will leave you with the 8-bit code for the key.). The second byte provides the shift state which is six bits for Shift. The third byte is for lock states (3 bits for Caps. (D) Shift State Byte (Second Byte) MMURTL V1. as shown in table 13. If you have an outstanding Notify request. logically AND the key code by 0FFh (0xff in C). while providing complete keyboard status to allow further translation for all ASCII control codes. The Notify request will be sent back to the exchange with an error stating it was canceled. To eliminate all the key status from the 4-byte key code to get the keystroke value itself.3. shown in table 13. etc. Alt & Ctrl.0 -135- RICHARD A BURGESS .

This is needed if you use certain keys from the numeric pad differently than their equivalent keys on the MMURTL V1.3 shows how the bits are defined. CpLockMask NmLockMask ScLockMask EQU 00000100b EQU 00000010b EQU 00000001b Numeric Pad Indicator Only one bit is used in the high-order byte of the key code.0 -136- RICHARD A BURGESS . Table 13. Table 13. Shift or Alt keys were depressed for the key code being read: CtrlDownMask ShftDownMask AltDownMask EQU 00000011b EQU 00001100b EQU 00110000b Lock-State Byte (third byte) If you need to know the lock state of any of the lock capable keys (Caps. you can use the second byte in the 32-bit word returned.Shift State Bits Bit 0 1 2 3 4 5 6 7 Meaning When Set (1) Left CTRL key down Right CTRL key down Left Shift key down Right Shift key down Left Alt key down Right Alt key down Not used (0) Not used (0) The following masks in assembly language can be used to determine if Control. LSB) will be set if the keystroke came from the numeric key pad. Table 13. Num or Scroll).3 . Table 13. Alt or Shift).If you need to know the shift state of any of the shift keys (Ctrl. you can use the third byte in the 32-bit word returned. shows how the bits are defined..2.2 . This bit (Bit 0.Lock State Bits Bit 0 1 2 3 4 5 6 7 Meaning When Set (1) Scroll Lock On Num Lock On Caps Lock On Not used (0) Not used (0) Not used (0) Not used (0) Not used (0) The following masks in assembly language can be used to determine if one of the lock keys was active for the key code being read.

The Caps Lock key does not affect the keys shown with an asterisk (*) in the Shift Code column. Alt. Ctrl.1h to 7Fh). Zero and values above 127 will not be returned.Keys Values Base Key Esc 1 2 3 4 5 6 7 8 9 0 = BS HT A B C D E F G H I J K L M N O P Q R S T U V W X Desc.main keyboard. Escape KeyCode 1Bh 31h 32h 33h 34h 35h 36h 37h 38h 39h 30h 2Dh 3Dh 08h 09h 61h 62h 63h 64h 65h 66h 67h 68h 69h 6Ah 6Bh 6Ch 6Dh 6Eh 6Fh 70h 71h 72h 73h 74h 75h 76h 77h 78h Shifted Key ! @ # $ % ^ & * ( ) _ + A B C D E F G H I J K L M N O P Q R S T U V W X Shift Code 21h 40h 23h 24h 25h 5Fh 26h 2Ah 28h 29h 5Fh 2Bh 41h 42h 43h 44h 45h 46h 47h 48h 49h 4Ah 4Bh 4Ch 4Dh 4Eh 4Fh 50h 51h 52h 53h 54h 55h 56h 57h 58h Desc Exclamation At pound dollar percent carat ampersand asterisk open paren close paren underscore minus equal backspace tab MMURTL V1. The Shift Code column shows the value returned if the Shift key is active. Table 13. All codes are 7-bit values (1 to 127 . If a shift code is not shown.4 .0 -137- RICHARD A BURGESS .4 shows the hexadecimal value provided in the low-order byte of the key code (least significant) by the keyboard service for each key on a 101-key keyboard. A description is given only if required. or Locks). Key Codes Table 13. the same value is provided in all shifted states (Shift. the Enter key on the numeric keypad might do something differently in your program than the typewriter Enter key.. For example.

Table 13.Y Z [ \ ] ` CR . / Space accent Enter semicolon apostr. Table 13.5 shows the key code value returned. comma period slash 79h 7Ah 5Bh 5Ch 5Dh 60h ODh 3Bh 27h 2Ch 2Eh 2Fh 20h Y Z { | } ~ : " < > ? 59h 5Ah 7Bh 7Ch 7Dh 7Eh 3Ah 22h 3Ch 3Eh 3Fh open brace vert. .5 . bar close brace tilde colon quote less greater question Function Strip Key Codes Shift and Lock states do not affect the function key values. ' .Function Key Strip Codes Base Key F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 KeyCode 0Fh 10h 11h 12h 13h 14h 15h 16h 17h 18h 19h 1Ah MMURTL V1. Shift and Lock state bytes must be used to determine program functionality.0 -138- RICHARD A BURGESS .

and Special Pad Key Codes None of the keys in these additional key pads are affected by Shift or Lock states.6 . Caps lock does not affect these keys.7.Numeric Pad Key Codes The Shift values shown in Table 13. (Period) Cursor. 13.6 are returned when the shift keys or Num Lock are active.7.Numeric Pad Key Codes Base Key End Down Pg Dn Left Blank Key Right Home Up Pg Up Insert Delete * / + CR KeyCode 0Bh 02h 0Ch 03h 1Fh 04h 06h 01h 05h 0Eh 7Fh Dash Asterisk Slash Plus Enter Shift Code 31h 32h 33h 34h 35h 36h 37h 38h 39h 30h 2Eh 2Dh 2Ah 2Fh 2Bh 0Dh Shifted key 1 2 3 4 5 (Extra Code) 6 7 8 9 0 . Edit. The values returned as the key code are shown in table 13.0 -139- RICHARD A BURGESS . Table 13.Additional Key Codes Base Key Print Screen Pause Insert Delete Home End Pg Up Pg Dn Up Down Left Right KeyCode 1Ch 1Dh 0Eh 7Fh 06h 0Bh 05h 0Ch 01h 02h 03h 04h MMURTL V1.

Your Keyboard Implementation You may not want all of the functionality in your system that I provided. Remember.”Keyboard Code. or you may want more. The keyboard translation tables could be used to nationalize this system. You may also want the keyboard more deeply embedded in your operating system code. as I have tried to do in this chapter. I also recommend that you concentrate on the documentation for your keyboard implementation. when all else fails.0 -140- RICHARD A BURGESS . Pieces of it may be useful for your system. MMURTL V1.” contains the source code behind the system service described in this chapter. if you desire. such as international key translations. the programmer will read the documentation. Chapter 25.

hence the default system service name for a network routing service is network. and optionally. It certainly wasn't done because I liked the design or the filename length limitations. A full network file specification consists of three parts: The first part is the Network name enclosed in brackets [] The second part is the Node name on that network enclosed in braces { }. the network service receives the request from the file system.txt Network File Request Routing File system requests are routed based on the full file specification. and a node name is specified. For example: C:\Dir1\Dir2\Dir3\Filename. this is not necessary with Network names because the file system fixes them. MMURTL V1. The Service Name is "FILESYS ". This was done so MMURTL could be used without reformatting your hard disks or working from floppies. then the path – which is each directory separated by the backslash character . The filename across a network may not be limited to the MS-DOS file naming conventions described above.txt A full file specification for the FAT-compatible file system consist of a drive letter identifier followed by a colon. The internal management of a file system is no trivial matter. Because you are simply accessing one that was designed by someone else. A complete network filename looks like this: [network]{Node}C:\Dir1\Dir2\Filename. and limited the number of functions to what is necessary for decent operation. This depends on the type of file system on the node being accessed. This means that MMURTL can read and write MS-DOS disks (Hard and Floppy). I have tried to keep with the simplicity motto. The File System Service Introduction The file system included with MMURTL is compatible with the MS-DOS FAT (File Allocation Table) file system. File Specifications A filename consists of one to eight characters. The third part is the filename as described above. The network name matches the name of the network service when it was installed. A filename that is prefixed with a node name. Even though service names must be capitalized and space padded. then up to three more characters. but none as wide spread.Chapter 14. just how we access it. This also means that network names are limited to eight characters. There are better file systems (disk formats) in use. For example. there isn't anything new about the format. will be routed to the service with the same name.0 -141- RICHARD A BURGESS . This ties its hands so to speak. a period. filename. MS-DOS has the unenviable task of being compatible with its previous versions. When a network file is opened. a network name. the default name NETWORK is used.and finally the filename itself. The file handle that is returned will be unique on the system that originated the request.txt If the network name is omitted from the specification.

The file handle is used in a all subsequent file operations on that file.File Handles When a file is opened. This user is granted exclusive access to the file while it is open. No internal file buffers are allocated by the file system in Block mode. File Open Modes A file may be opened for reading and writing. The only restriction is that whole blocks must be read or written. Multiple users may open and access a Read-mode file. File Access Type A file may be opened in Block or Stream mode. Block Mode Block mode operation is the fastest file access method because no internal buffering is required. Only a single user may open a file in Modify mode. The data is moved in whole blocks in the fastest means determined by the device driver. Stream mode operation allows use of the following file system functions: CloseFile() ReadBytes() WriteBytes() SetFileLFA() GetFileLFA() GetFileSize() SetFileSize() DeleteFile() Errors will be returned from any function not compatible with the file-access type you specified when the file was opened.0 -142- RICHARD A BURGESS . This is a one page. in any mode or type. 4096 byte buffer. it may not be opened in Modify mode by any user. When a file is opened in Read mode. or just reading alone. the file system allocates an internal buffer to use for all read and write operations. Block mode operation allows use of the following file system functions: ReadBlock() WriteBlock() CloseFile() GetFileSize() SetFileSize() DeleteFile() Stream Mode In Stream mode. These two modes are called modify(Read and Write) and read(Read-only). MMURTL V1. a number called a file handle is returned to you. This number is how you refer to that file until closed. Make no assumptions about this number. The standard block size is 512 bytes for disk devices. A file handle is a 32-bit unsigned number (dword).

Listing 14.Size of a handle */ dData0 -.1 is an example of a File System request in the C programming language. The file size minus 1 is the last logical byte in the file. The current LFA for stream files is updated with each ReadBytes() and WriteBytes() function. Additional functions are provided to allow you to find and set the current LFA. 1. When a file is initially open for Stream access the file pointer is set to 0. The Logical File Address (LFA) is an unsigned dword (dLFA). /* /* /* /* /* /* /* /* /* /* /* /* ptr to name of service */ wSvcCode -. This means it can be accessed directly with the Request primitive. Using the Request interface allows for asynchronous program operation.size of name */ pData2 -. You can make a request. 0. you do not specify an LFA to read or write from. 1. MyExch.ModeRead */ dData1 -.returned handle */ cbData2 -. The small amount of time for message routing will not make any measurable difference in the speed of its operation because most of the time is spent accessing hardware. Block access files have no internal buffers and do not maintain a current LFA. Listing 14. no matter what Mode the file was opened in. Table 14.doc".1 .Logical File Address All files are logically stored as 1-n Bytes. This is the byte offset in the file to read or write from. &"AnyFile. All message-based services use the same request-based interface. &MyRqhandle. It meets all the requirements. Each of the functions is identified by its Service Code number.File System Service Codes Function OpenFile CloseFile MMURTL V1. The file system is an ideal candidate for a message-based service. File System Requests The file system is a message-based system service.1 Send ptr */ pData1 -. It also provides the shared access required for a true multitasking system. For Stream files. Openfile request in C dError = Request( "FILESYS ". &FileHandle 4. This is called a file pointer in some systems. Stream Access files have internal buffers and maintain your Current LFA. The procedural interface for Request has 12 parameters.respond here */ pRqHndlRet -.1 for OpenFile */ dRespExch -. Table 14.0 Service Code 1 2 -143- RICHARD A BURGESS . 1. The file system actually runs as a separate task.1.1 shows the service codes the file system supports. Block Access file system functions require to you specify a Logical File Address (LFA). 0). then go do something else before you come back to wait or check the function to see if it's completed (true multitasking). 11. LFA 0 is the beginning of the file. or file pointer.Block Type Access */ dData2 -.ptr to name */ cbData1 -.not used */ Unused Request parameters must be set to 0.may be needed */ nSendPtrs -.

Table 14.ReadBlock WriteBlock ReadBytes WriteBytes GetFileLFA SetFileLFA GetFileSize SetFileSize CreateFile RenameFile DeleteFile CreateDirectory DeleteDirectory GetDirectorySector 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Procedural Interfaces If you don't have a need for asynchronous disk access.2 lists the device names that are reserved and also the mode of stream access supported. but only through the procedural interfaces. KBD and VID devices only. MMURTL V1. Table 14. The request is transparent to the caller. VID and KBD devices may be accessed through the file system. This means there is a simple procedural interface call for each file system function. Device Access Through the File System Teletype fashion stream access to the NUL.0 -144- RICHARD A BURGESS . The procedural interface actually makes the request for you using your TSS Exchange. It is easier to use because it has less parameters. you can use a blocking procedural interface which is included in the file system itself.Device Stream Access Device NUL KBD VID LPT1 LPT2 COM1 COM2 COM3 COM4 FD0 FD1 HD0 HD1 Description NULL device Keyboard Video Printer 1 Printer 2 RS-232 1 RS-232 2 RS-232 3 RS-232 4 Floppy Disk Floppy Disk Hard disk Hard disk Access Write-Only read-only Write-Only None None None None None None None None None None Device access is currently implemented for NUL. All system device names are reserved and should not be used as filenames on the system.2 . High-level language libraries that implement device access (such as putchar() in C) must use the procedural interfaces for file access. The blocking procedural interface is described with each of the call descriptions that follow later in this chapter. The Request interface will not allow device access.

dcbName . Procedural parameters: dHandle .pointer to a dword where the handle to the file will be returned to you. dcbname. OpenFile Procedural interface: OpenFile (pName. Request Parameters for OpenFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 CloseFile Procedural interface: CloseFile (dHandle): dError CloseFile() closes a file that was previously opened. If a file is open in ModeRead. all buffers are flushed and deallocated. If stream access type was specified.pointer to the filename or full file specification. it can not be opened in ModeModify by any other users. Request Parameters for CloseFile: 1 1 pName dcbName pdHandleRet 4 (Size of a file handle) dOpenMode dAccessType Not used (0) MMURTL V1. Stream = 1. pdHandleRet . dOpenMode.a dword that was returned from OpenFile(). pdHandleRet): dError OpenFile() opens an existing file in the current path for Block or Stream operations. MODIFY = 1 dAccessType . Procedural parameters: pName . Opening in ModeModify excludes all others from opening the file.0 -145- RICHARD A BURGESS .Block = 0.DWord with length of the filename dOpenMode . dAccessType.READ = 0.File System Functions in Detail The following pages detail each of the file system functions that are available. Multiple users can open a file in ModeRead. A full file specification may be provided to override the current job path.

nBytes.Pointer to a buffer large enough to hold the count of blocks you specify to read.Logical File Address to read from. MMURTL V1. pDataRet.0 = = = = = = = = = 3 0 pDataRet nBytes (multiple of 512) pdnBytesRet 4 (size of dnBytesRet) dHandle 0 0 -146- RICHARD A BURGESS . pdnBytesRet . The file must be opened for Block access in Modify mode or an error occurs. This must be a multiple of the block size for the 512-byte disk.DWord with number of bytes to write. This must always be a multiple of 512. Procedural parameters: dHandle .DWord with number of bytes to read. dLFA. dLFA.Pointer to the data to write nBytes .pointer to a dword where the number of bytes successfully written will be returned. Procedural parameters: dhandle . nBytes . The file must be opened for Block access or an error occurs. Writing beyond the current file length is not allowed. dLFA . This will always be a multiple of the block size (n * 512). See the SetFilesize() section. dLFA .DWord with a valid file handle as returned from OpenFile.pointer to a dword where the count of bytes successfully read will be returned. pDataRet . This will always return a multiple of 512.Logical File Address to write to. This must be a multiple of the block size. This MUST be a multiple of the block size. pdnBlkRet .DWord with a valid file handle (as returned from OpenFile. nBytes. pdnBytesRet): dError This writes one or more blocks to a file. Request parameters for ReadBlock: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 WriteBlock Procedural interface: WriteBlock(dHandle. SetFileSize() must be used to extend the file length if you intend to write beyond the current file size. pdnBytesRet): dError This reads one or more blocks from a file.wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 ReadBlock = = = = = = 2 0 0 0 0 0 = dHandle = 0 = 0 Procedural interface: ReadBlock(dHandle. pData . One Block = 512 bytes. pData.

Dword with a valid file handle (as returned from OpenFile). Use SetFileLFA() to move the stream file pointer if you want to read from an LFA other than the current LFA. pData. pDataRet . nBytes. The bytes are written beginning at the current LFA. Parameters: dhandle . The file length is extended automatically if you write past the current End Of File. The file must be opened for Stream access or an error occurs. The file must be opened for stream access in Modify mode or an error occurs. The LFA is updated to the next byte address following the data you read.Dword with number of bytes to read. Use SetFileLFA() to move the stream file pointer if you want to read from an LFA other than the current LFA.0 -147- RICHARD A BURGESS . The LFA is updated to the next byte address following the data you wrote. The bytes are read from the current LFA. pDataRet. GetFileLFA may be used to find the current LFA. pdnBytesRet): dError This writes one or more bytes to a file. pdnBytesRet .Request parameters for ReadBlock: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 ReadBytes Procedural interface: ReadBytes(dHandle. Request Parameters for ReadBytes: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 WriteBytes Procedural interface: WriteBytes(dHandle. nBytes .pointer to a Dword where the number of bytes successfully read will be returned. = 5 = 0 = pDataRet = nBytes = pdnBytesRet = 4 (size of dnBytesRet) = dHandle = 0 = 0 = 4 = 1 = pData = nBytes (512 = 1 Block) = pdnBytesRet = 4 (size of dnBytesRet) = dHandle = 0 = 0 MMURTL V1. GetFileLFA() may be used to find the current LFA. pdnBytesRet): dError This reads one or more bytes from a file. nBytes.Pointer to a buffer large enough to hold the count of bytes you specify to read.

Pointer to the data to write. nBytes . dLFA): dError This sets the current LFA (file pointer) for Stream Access files. Request parameters for GetFileLFA: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 SetFileLFA Procedural interface: SetFileLFA(dHandle.Procedural parameters: dHandle .a pointer to a Dword where the current LFA will be returned. An error occurs if the file is opened for Block access.Dword with a valid file handle. Procedural parameters: dhandle . as returned from OpenFile pdLFARet . An error occurs if the file is opened for Block access. pdnBytesRet .Dword with number of bytes to write. Procedural parameters: dHandle . as returned from OpenFile() MMURTL V1. pData . The file will be set to EOF if you specify 0FFFFFFFF hex (-1 for a signed long value in C).pointer to a Dword where the number of bytes successfully written will be returned. pdLFARet): dError This gets the current LFA for files with stream mode access.Dword with a valid file handle. The file LFA can not be set past the End Of File (EOF).0 = 6 = 1 = pData = nBytes = pdnBytesRet = 4 (size of dnBytesRet) = dHandle = 0 = 0 = = = = = = = = = 7 0 pdLFARet 4 0 0 dHandle 0 0 -148- RICHARD A BURGESS . Request parameters for ReadBytes: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 GetFileLFA Procedural Interface: GetFileLFA(dHandle.Dword with a valid file handle (as returned from OpenFile).

Dword with a valid file handle.0 -149- RICHARD A BURGESS .dLFA . and allocated space on the disk. as returned from OpenFile() pdSizeRet . will be extended or truncated as necessary to accommodate the size you specify. Request parameters for GetFileLFA: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 SetFileSize Procedural interface: SetFileSize(dHandle. 0FFFFFFFF hex will set the current LFA to End Of File. Request parameters for SetFileLFA: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 GetFileSize Procedural interface: GetFileSize(dHandle.a pointer to a Dword where the current size of the file will be returned.a Dword with the LFA you want to set. dSize): dError This sets the current file size for Stream and Block Access files. Procedural parameters: dHandle . Request Parameters for SetFileSize: wSvcCode nSend = 10 = 0 = = = = = = = = = 9 0 pdSizeRet 4 0 0 dHandle 0 0 = 8 = 0 = 0 = 0 = 0 = 0 = dHandle = dLFA = 0 MMURTL V1. Procedural parameters: dhandle .Dword with a valid file handle. The file length.a Dword with the new size of the file. The current LFA is not affected for Stream files unless it is now past the new EOF in which case it is set to EOF automatically. pdSizeRet): dError This gets the current file size for files opened in any access mode. as returned from OpenFile() dSize .

0 -150- RICHARD A BURGESS . dcbNewName . dcbname. dcbName.Dword with length of the current filename pNewName . The file is closed after creation. Procedural parameters: pName . and ready to be opened and accessed.Dword with length of the new filename Request parameters for RenameFile: wSvcCode nSend pData1 cbData1 = = = = 12 1 pName dcbName = = = = = = = = = 11 1 pName dcbName 0 0 dAttributes 0 0 MMURTL V1.A Dword with one of the following values: 0 = Normal File 2 = Hidden File 4 = System File 6 = Hidden. System file Request parameters for CreateFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 RenameFile Procedural interface: RenameFile (pName. dAttributes): dError This creates a new. pNewName dcbNewName): dError This renames a file.pointer to the current filename or full file specification. The file must not be opened by anyone in any mode. dcbName .pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = 0 = 0 = 0 = 0 = dHandle = dSize = 0 CreateFile Procedural interface: CreateFile (pName.pointer to the new filename or full file specification.Dword with length of the filename dAttributes . Procedural parameters: pName . empty file in the path specified. dcbName .pointer to the filename or full file specification to create.

Dword with length of the path Request parameters for CreateDirectory: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 14 1 pPath dcbPath 0 0 0 0 0 MMURTL V1. This means all subdirectories above the last directory specified must already exist. dcbPath . the directory is created in your current path.Dword that was returned from OpenFile. The path must contain the new directory name as the last element.0 -151- RICHARD A BURGESS . No further access is possible. Request parameters for DeleteFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 13 0 0 0 0 0 dHandle 0 0 CreateDirectory Procedural interface: CreateDirectory (pPath. specified in the Job Control Block. dcbPath): dError This creates a new. and the filename is available for re-use.pData2 cbData2 dData0 dData1 dData2 DeleteFile = = = = = pNewname dcbNewname 0 0 0 Procedural interface: DeleteFile (dHandle): dError This deletes a file from the system. Procedural parameters: pPath . If only the new directory name is specified.pointer to the path (directory name) to create. empty directory for the path specified. The file must be opened in Modify mode (which grants exclusive access). Procedural parameters: dHandle . as opposed to a full path.

all subdirectories. Procedural parameters: pPath . If no path is specified.Dword with length of the path fAllFiles .0 -152- RICHARD A BURGESS . all files in this directory.If set to non-zero. or directory name. You specify which logical directory sector you want to read (from 0 to nTotalSectors-1).pointer to the path. the name is appended to the path from your job control block. dSectNum): dError Directories on the disk are one or more sectors in size. Each sector contain 16 directory entries. pSectRet. Request parameters for DeleteDirectory: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 15 1 pPath dcbPath 0 0 0 0 0 GetDirSector Procedural Interface: GetDirSector (pPath. The directory must exist in the path specified. dcbPath.Dword with length of the path MMURTL V1. fAllFiles): dError This deletes a directory for the path specified.pointer to the path. dcbPath . dcbPath. A single directory entry looks like this with 16 per sector: Name Ext Attribute Reserved Time Date StartClstr FileSize 8 Bytes 3 Bytes 1 Byte 10 Bytes 2 Bytes 2 Bytes 2 Bytes 4 Bytes The fields are read and used exactly as MS-DOS uses them. or directory name to delete. All files and sub-directories will be deleted if fAllFiles is non-zero. to read tj sector from dcbPath . the path in your JCB will be used. Procedural parameters: pPath . This call returns one sector from a directory. The path must contain the directory name as the last element. If only the existing directory name is specified. and files in subdirectories will be deleted. as opposed to a full path.DeleteDirectory Procedural interface: DeleteDirectory (pPath.

A more accurate name would be cluster allocation table because its actually allocating clusters of disk space to the files. Because it is a pointer. the location of every cluster of 1 or more sectors of a file is managed in a linked list of sorts. buffer pointers. The FCB actually contains a copy of the 32-byte directory entry from the disk. The FUBs contain the file pointer.0 -153- RICHARD A BURGESS . When it reaches 0. you allocate a one page buffer(4Kb) for reading and writing. it's not allocating files.pSectRet . Quite a few of them use this same name. and other things that are required to manage the user's link to the file.a Dword with the logical sector number to return Request parameters for GetDirSector: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 16 1 pPath dcbPath pSectRet 512 dSectNum 0 0 File System Theory Understanding a bit more about how the file system is managed may help you make better use of it. The maximum number of open files is currently limited to 128 files by this initial allocation. extra checking is done to ensure it is valid in every call. the FCB is deallocated for re-use. Internal File System Structures The file system has several internal structures it uses to manage the logical disks and open files. Some structures are dynamically allocated. File User Blocks Because this is a multitasking operating system. The file system starts with an initial 4Kb (one memory page) of FCBs. FAT Buffers In the FAT file system. This linked list is contained in a table called the File Allocation Table. FCB. while others are part of the operating system data segment. If the file is opened in Stream mode. the open mode. Each opened file is allocated an FCB. This is read into the FCB when the file is opened. The file handle is actually a pointer to the FUB. the FCB is not directly associated with the job that opened the file. There is one FUB for each instance of a user with an open file. A bad pointer could do some real damage here if you let it. Each FCB is 128 bytes.a pointer to a 512-byte block where the directory sector will be returned dSectNum . MMURTL V1. File Control Blocks All file system implementations have some structure that is used to manage an open file. The file system allocates file system structures dynamically when it is initialized. Another structure called the File User Block (FUB). The FCB keeps track of the number of FUBs for the file. is what links the user of a file to the FCB.

the initial filling of buffers for a word processor (and the final save usually). The first cluster of the file is listed in the directory entry. or three entries. source files for compilers. they use what is called a FAT16. The disk controller (IDE/MFM) can read 256 contiguous sectors in one read operation. A simple LRU (least recently used) algorithm ensures the most accessed FAT sectors will remain in memory. Usually. Run files being executed. the fifth points to the sixth. This is in itself is time-consuming. The fourth entry points to the fifth. This means each FAT entry is a 16-bit word. the user will strike next. and can only do this if 128Kb of the file is located in contiguous sectors. Locate the directory entry and read it into the FCB. read and close a file: Look through the FCBs to see if someone already has it open in ModeRead. you can usually do it quite rapidly. Load FAT buffer if required. From this example you can see that every file we access will require a minimum of two disk accesses to read the file. X = not your file. MMURTL's DOS FAT file system allocates a 1Kb FAT buffer based on file access patterns. and we'll assume the file size is 12Kb. On most hard disks. The hardware is limited to 128Kb reads in one shot. vector. If so. The file is open. However. Ever get lost clusters when you ran a Chkdsk in MSDOS? Who hasn't? It usually means it found some clusters pointing to other clusters that don't end. L = Last entry in your file. Directories are stored as files. This means that you have to go to the FAT to find where the whole directory is stored so you can look through it. and you read it into the FAT buffer if it is not already in one. This means it's important to attempt to keep as much FAT information in memory as you can and manage it wisely. FAT Position Entry in FAT 1 X 2 X 3 X 4 5 5 6 6 L 7 8 9 10 This means our file occupies the fourth. The beginning of the FAT represents the beginning of the usable data area on the logical disk. unless the piece of the FAT you need is already in memory. inside of a 400 Mb file. Let's look what the file system has to go through to open. Read What actually happens on a read depends on whether the file is opened in Block or Stream mode. Large database files are the major random users. That's 128Kb. and raster image files. you simply issue the read to the device driver to read the sectors into the caller's buffer (locating them using the FAT). with each cluster having an associated entry in the FAT.0 -154- RICHARD A BURGESS . A badly fragmented disk can force as many as four or five accesses to different parts of the FAT just to read one file. From this. you calculate which disk sector in the FAT has this entry. you'll assume that each FAT entry is worth 4Kb on disk. and the sixth is marked as the last.The disk is allocated in clusters. the actual contiguous read will usually be limited to the size of a cluster. Just 1Kb of FAT space represents 2Mb of data space on the disk if clusters are 4Kb. MMURTL V1. The list could go on and on. fifth and sixth cluster in the data area on the disk. Lets look at an example of how you use the FAT to locate all parts of file on the disk. A great number of files are read and written sequentially. File Operations Reading a file is a multipart process. In this example. That's a lot of FAT (or file). Assume the directory entry you read says the first FAT entry is number 4. If it's open in ModeWrite. allocate an FUB and attach it. return an error ErcFileInUse. the commands to the device driver will be broken up into several smaller reads as the file system finds each contiguous section listed in the FAT. If opened in Block mode. or it found a beginning cluster with no corresponding directory entry pointing to it. which is cluster size. and only a good level-4 mind reader knows where. because of the design of the FAT file system. If your disk isn't too badly fragmented and you are sequentially reading all of a file into memory (such as loading an executable file).

but much overhead is involved. you must find unallocated clusters. Both copies must be updated. Then we deallocate the FUB and FCB.Write Writing to a file is a little more complicated. On many disks there are also two copies of the FAT for integrity purposes. You flush buffers to disk if necessary for Write Mode. If opened in block mode. you simply write over the existing data while you follow the cluster chain. If the sectors have already been allocated in the FAT. attach them to the chain. then write the data. If the file is being extended. and depends on how the file was opened. It sounds simple.0 -155- RICHARD A BURGESS . Close This is the easy part. MMURTL V1. you issue the write command to the device driver to write out the sectors as indicated by the user.

.

2 .Chapter 15. Table 15. They are defined as far and require a 48-bit call address. while the offset is ignored by the 386/486 processor. Integer Byte (1 Byte signed) Integer Word (2 bytes signed) Integer DWord (4 bytes signed) Pointer (4 bytes . Table 15.32 bit near) Additional descriptive prefixes may be used to help describe the parameter. or poorly described function call can cause serious agony and hair loss in a programmer. These are known as compound descriptions.g. The call address consists of a selector and an offset.1 lists the prefixes. then an alphabetical list provides details for each call and possibly an example of its use. “File System Service”). I might add. API Specification Introduction This section describes all operating system public calls. If you are going to write your own operating system.0 -157- RICHARD A BURGESS .Size and Type Prefixes Prefix b w d ib iw id p Description Byte (1 byte unsigned) Word (2 bytes unsigned) Double Word (dword .. The offset should be 0.2 lists these additional prefix characters. you'll find the documentation to be almost 30 percent of the work – and it’s a very important part of the entire system. Public Calls Public Calls are those accessible through a call gate and can be reached from "outside" programs. They are listed by functional group first.Additional Prefixes Prefix a c s o n Description Array (of) Count (of) Size (of) Offset (into) Number (of) MMURTL V1. An improperly. The descriptor for the call gate defines the actual address called and the number of dwords of stack parameters to be passed to the operating system call. Table 15.1 . Function calls that are provided by system services (accessed with Request and Respond interface) are described in detail in the chapters of the book that apply to that service (e. The selector is the call gate entry in the Global Descriptor Table (GDT).4 bytes unsigned) d is the default when b or w is not present. The prefixes indicate the size and type of the data that is expected. Parameters to Calls (Args) All stack parameters are described by their names. Chapter 14. Table 15.

dStatRet): dError AllocExch(pdExchRet): dError DeAllocExch(dExch): dError SendMsg (dExch. or should be. pdDataRet . indicates a dword error or status code is returned from the function. dMsg2): dError WaitMsg(dExch.and optionally suffixes . or Assembler. dMsg1. Each compound set of prefixes . dMsg2): dError CheckMsg(dExch. The dError. be returned. It is processor (hardware) enforced. The caller need not worry about whether the upper part of a dword on the stack is cleared or set when a value is pushed because MMURTL will only use as much of it as is defined by the call. dcbDataRetMax .or 16-bit values) are pushed as dwords automatically.3 .Dword that contains the Count of Bytes of a filename.a Pointer to a Dword where data is returned. Pascal. Stack Conventions MMURTL keeps a dword (4 Byte) aligned stack. The following are examples of parameter names using prefixes and suffixes. Max means Maximum.Examples of compound prefixes are shown in table 15. pMsgsRet) : dError ISendMsg (dExch. The parameter (argument) names follow the naming convention described earlier. This information is provided for the assembly language programmers. MMURTL high-level languages follow this convention. Table1 5.Dword with the maximum count of bytes that can. Combine these prefixes with variable or parameter names and you have a complete description of the data. and finally. the most common returned value shown. dcbFileName . Messaging Respond(dRqHndl. grouped by type.Compound Prefixes Prefix pab pad pdcb pib pb cb n ppd Description Pointer to an array of bytes Pointer to an array of Double words Pointer to a DWord that is a count of bytes Pointer to a signed byte pointer to a byte DWord that contains a count of bytes (defaults to size d if not preceded by other) DWord than is a number of something Pointer to a Pointer that points to a Double word Descriptive suffixes may also be used to help describe some parameters. Many of these calls will not be used by the applications programmer. and Min means Minimum. The operating-system calls are described in a generic fashion that does not directly correspond to a particular programming language such as C. If a parameter is a byte.0 -158- RICHARD A BURGESS . a colon with the returned value. if a value is returned by the function. dMsg1. The function name is followed by the parameters.pqMsgRet):dError MMURTL V1.3. Ret means Returned. MMURTL procedures will only get the byte from the stack while the upper three bytes of the dword are ignored.accurately describes the provided or expected data types. Calls Grouped by Functionality The following is a list of all currently implemented operating system public calls. Bytes and words (8.

dEIP): dError SpawnTask(pEntry. dcbFileName. npSend. pDestination. pData2. dBytes) MMURTL V1. dData1. LinAdd. dcbFileName. ppMemRet. pS2. dOpNum. dStatusMax. dcbPath): dError GetPath(dJobNum. dnPages): dError AliasMem(pMem. wSvcCode. wCodeSeg. pdcbFileNameRet): dError SetSysOut(pFileName. dfDebug. dRespExch. pdJobNumRet):dError GetpJCB(dJobNum. pdcbCmdLineRet): dError SetPath(pPath.0 -159- RICHARD A BURGESS . dJobNum. pInitData. dESP. dcbAliasBytes. dcbMem. dDestExch) : dError Job Management Chain(pFileName. pDCBs. dData2 ) : dError MoveRequest(dRqBlkHndl.Request( pSvcName. fOSCode):dError SetPriority(bPriority): dError GetTSSExch(pdExchRet): dError System Service & Device Driver Management DeviceOp(dDevice. dLBA. dInitData): dError InitDevDr(dDevNum. pRqHndlRet. dSize): returned offset or -1 Compare(pS1. pData1. ppMemRet): dError AllocDMAPage(nPages. pStack. pStatRet. pPathRet. pJCBRet): dError GetJobNum(pdJobNumRet):dError SetJobName(pJobName. pPhyAddRet): dError QueryPages(pdnPagesRet): dError High Speed Data Movement FillData(pDest. nDevices. ppMemRet): dError DeAllocPage(pMem. pData): dError DeviceStat(dDevice. dcbJobName): dError SetExitJob(pFileName. pS2. dExch): dError UnRegisterSvc(pSvcName): dError Memory Management AllocPage(nPages. bFill) CompareNCS(pS1. pcbFileNameRet): dError SetCmdLine(pCmdLine. dExch. fReplace): dError RegisterSvc(pSvcName. dPriority. dData0. dcbCmdLine): dError GetCmdLine(pCmdLineRet. cBytes. JobNum): dError GetPhyAdd(dJobNum. ppAliasRet): dError DeAliasMem(pAliasMem. pdcbPathRet): dError SetUserName(pUserName. pdcbUserNameRet): dError SetSysIn(pFileName. dcbFileName): dError GetSysOut(pFileNameRet. dcbUserName): dError GetUserName(pUserNameRet. dSize): returned offset or -1 CopyData(pSource. dcbFileName): dError GetExitJob(pFileNameRet. pDestination. cbData2. pdPhyMemRet): dError AllocOSPage(nPages. cbData1. fDebug. dcbFileName): dError GetSysIn(pFileNameRet. pdcbFileNameRet): dError Task Management NewTask(dJobNum. dExitError):dError ExitJob(dExitError): dError LoadNewJob(pFileName. dPriority. dBytes) CopyDataR(pSource. dnBlocks. dStatusRet):dError DeviceInit(dDevice.

pbChars. pVectorRet): dError MaskIRQ (dIRQnum): dError UnMaskIQR (dIRQnum): dError EndOfIRQ (dIRQnum) Keyboard. dCrntLen. dfUp): dError ReadKbd (pdKeyCodeRet. dTicks): dError KillAlarm (dAlarmExch): dError Sleep(nTicks): dError MicroDelay (dn15us): dError GetCMOSTime(pdTimeRet): dError GetCMOSDate(pdDateRet): dError GetTimerTick(pdTickRet): dError ISA Hardware Specific I/O DmaSetUp(dPhyMem. pdYRet): dError SetXY(dNewX. pVector) GetIRQVector (dIRQnum. dChannel. pbExitChar. pDataOut. dPort) OutWord(Word. nLines. pbAttrRet): dError PutVidAttrs(dCol.0 -160- RICHARD A BURGESS . Speaker. sdMem. nChars. dEditAttr): dError MMURTL V1. dMode): dError GetDMACount(dChannel. & Video SetVidOwner(dJobNum): dError GetVidOwner(pdJobNumRet): dError GetNormVid(pdNormVidRet): dError SetNormVid(dNormVidAttr): dError Beep(): dError ClrScr(): dError GetXY(pdXRet.dPort) OutDWord(DWord. pbCharRet. nCols. pwCountRet): dError InByte(dPort): Byte InWord(dPort): Word InDWord(dPort): DWord InWords(dPort. dLine. dAttrib): dError EditLine( pStr. dAttrib): dError GetVidChar(dCol. pDataIn. dNewY): dError PutVidChars(dCol. dTicks10ms): dError TTYOut (pTextOut. dAttr): dError ScrollVid(dULCol. nChars. dLine. fdRead. dBytes) ReadCMOS(bAddress):Byte Interrupt and Call Gate Management AddCallGate: dError AddIDTGate: dError SetIRQVector(dIRQnum.dBytes) OutByte(Byte. dfWait): dError Tone(dHz. ddLine. dMaxLen. dTextOut. dPort) OutWords(dPort. pdLenRet.Timer and Timing Alarm(dExchRet. dULline.

Unlike most other operating system calls. AddCallGate() can only be executed by code running at supervisor level (0). but can be used by experienced systems programmers.0 -161- RICHARD A BURGESS . Parameters: AX . AddIDTGate() builds and adds an IDT entry for trap gates. AddIDTGate ()is primarily for operating system use. EAX Returns Error.Alphabetical Call Listing The remaining sections of this chapter describe each of the operating system calls available to programmers. They are passed in registers. else 0 if all went OK Alarm Alarm(dExchRet. AddIDTGate AddIDTGate: dError AddIDTGate() makes an entry in the Interrupt Descriptor Table for traps. the parameters are not stack-based.Selector of gate (08 or TSS selector for task gates) CX . This call doesn't check to see if the GDT descriptor for the call is already defined. AddCallGate AddCallGate: dError AddCallGate() makes an entry in the Global Descriptor Table (GDT) for a publicly available operating-system function. the parameters are not stack based. They are passed in registers.Word with Interrupt Number (00-FF) ESI . Unlike most other operating system calls.Word with Gate ID type as follows: Trap Gate with DPL of 3 8F00h Interrupt Gate with DPL of 3 8E00h Task Gate with DPL of 3 8500h BX . AddIDTGate() can only be executed by code running at supervisor level (0). or 0 if all went well. AddCallGate() is primarily for operating system use. and interrupts. Parameters: AX . and is the TSS descriptor number of the task for a task gate.Word with Call Gate ID type as follows: DPL entry of 3 EC0x (most likely) DPL entry of 2 CC0x DPL entry of 1 AC0x DPL entry of 0 8C0x (x = count of dword parameters 0-F) CX .Offset of entry point in operating system code to execute. It assumes you know what you are doing and overwrites one if already defined. It assumes you know what you are doing and overwrites the descriptor if it is already there. This must be zero (0) for task gates.Returns an error. but can be used by experienced systems programmers. exceptions. This call doesn't check to see if the IDT descriptor for the call is already defined.Offset of entry point in segment of code to execute EAX .Selector number for call gate in GDT (constants!) ESI . The selector number is checked to make sure you're in range (40h through maximum call gate number). exception gates and interrupt gates. The Selector of the call is Always 8 (operating-system code segment) for interrupt or trap. dnTicks): dError MMURTL V1.

No cleanup is done on the caller's memory space. This would be for the time-out function described above. It will always begin on a page boundary. This pointer is only valid when used with the DMA hardware. and don't get placed in a waiting condition. except it can be used asynchronously. ppAliasRet): dError AliasMem() provides an address conversion between application addresses.a pointer to 4-byte area where the pointer to the memory is returned. dJobNum.Dword (4 BYTES). dcbMem . The overhead included in entry.Alarm() is public routine that will send a message to an exchange after a specified period of 10-millisecond increments has elapsed. This procedure allocates an exchange (message port) for the job to use. Alarm() can be used for a number of things. AllocDMAPage AllocDMAPage(nPages. The message that is sent is two dwords. Parameters: pMem . Even addresses of identical numeric values are not the same addresses when shared between applications. You must not attempt to access more than this number of bytes from the other application's space. AllocExch AllocExch(pdExchRet): dError The kernel Allocate Exchange primitive. pdPhyMemRet): dError AllocDMAPage() allocates one or more pages of Memory and adds the entry(s) to the callers page tables. dcbMem. dJobNum . (The address the aliased memory pointer you will use). Parameters: nPages .). each containing 0FFFFFFFF hex (-1). ppAliasRet .0 -162- RICHARD A BURGESS . then see which one gets there first by simply waiting at the exchange. AliasMem AliasMem(pMem. do some other processing. In fact. One important use would be a time-out function for device drivers that wait for interrupts but may never get them due to hardware difficulties. Alarm() should not be called for extremely critical timing in applications. Parameters: dnTicks . This procedure is used by the operating system to perform address conversions when passing data using the Request() and Respond() primitives. MMURTL V1.The count of bytes pMem is pointing to. Alarm() is similar to Sleep(). pdPhyMemRet .Dword with the number of 10 millisecond periods to wait before sending a message the specified exchange. Alarm() is accomplished using the same timer blocks as Sleep(). This is the count of 4Kb pages to allocate. Using it as a normal pointer will surely cause horrible results. except you provide the exchange. you will cause a fault and your application will be terminated. exit and messaging code makes the alarm delay timing a little more than 10 milliseconds and is not precisely calculable. even though it's very close.A pointer to the pointer you want to use to access the other application's memory. the more precise the timing. Exchanges are required in order to use the operating system messaging system (SendMsg. Memory is guaranteed to be contiguous and will be within reach of the DMA hardware.a pointer to 4-byte area where the physical address of the memory is returned. If the caller continuously allocates pages and then deallocates them this could lead to fragmentation of the linear memory space. The larger dnTicks is. and then "CheckMsg()" on the exchange to see if it has gone off. you can set it to send a message to the same exchange you are expecting another message. The allocation routine uses a first-fit algorithm. ppMemRet. ppRet .A pointer to the memory address from the other application's space that needs to be aliased. Each application has its own linear address space. If you do. etc.Job number from the other application. This means you can set an alarm. Parameters: pdExchRet is a pointer to the dword where the exchange number is returned. WaitMsg.

AllocOSPage AllocOSPage(nPages, ppMemRet): dError Allocate Operating System Page allocates one or more pages of memory and adds the entry(s) to the operating system page tables. Memory is guaranteed to be contiguous and to always begin on a page boundary. No cleanup is done on the caller's memory space. If the caller continuously allocates pages and then deallocates them this could lead to fragmentation of the linear memory space. The allocation routine uses a first fit-algorithm. Parameters: nPages - a dword (4-bytes). This is the count of 4Kb pages to allocate. ppRet - a pointer to 4-byte area where the pointer to the memory is returned. AllocPage AllocPage(nPages, ppMemRet): dError Allocate Page allocates one or more pages of memory and adds the entry(s) to the callers page tables in upper linear memory. Memory is guaranteed to be contiguous and will always begin on a page boundary. No cleanup is done on the caller's memory space. If the caller continuously allocates pages and then deallocates them this could lead to fragmentation of the linear memory space. The allocation routine uses a first fit algorithm. Parameters: nPages - a dword (4-bytes). This is the count of 4Kb pages to allocate. ppRet - a pointer to 4-byte area where the pointer to the memory is returned. Beep Beep: dError A Public routine that sounds a 250-millisecond tone at 800Hz. Parameters: None Chain Chain(pFileName, dcbFileName, dExitError): dError Chain() is called by applications and services to replace themselves with another application (as specified by the filename). Chain()terminates all tasks belonging to the calling job and frees system resources that will not be used in the new job. If chain cannot open the new job or the new run file is corrupted, ErcBadRunFile, or similar errors, may be returned to the application and the chain operation will be canceled. If the chain is too far along, and running the new application fails, the ExitJob, if the JCB contained a valid one, will be loaded and run. If there is no exit job specified, the Job will be terminated as described in the ExitJob() call. Parameters: pFilename - this points to the name of a valid executable run file. dcbRunFilename - A Dword containing the length of the run file name. dExitError - A Dword containing an error code to place in the ErrorExit field of the JCB. The chained application may or may not use this error value. CheckMsg CheckMsg(dExch, pMsgsRet) : dError

MMURTL V1.0

-163-

RICHARD A BURGESS

The kernel CheckMsg() primitive allows a Task to receive information from another task without blocking the caller. In other words, if no message is available, Checkmsg() returns with an error to the caller. If a message is available, the message is returned to the caller immediately. The caller is never placed on an exchange and the Task Ready Queue is not evaluated. This call is used to check for non-specific messages and responses from services. The first dwords value (dMsgHi) determines if it's a non-specific message or a response from a service. If the value is less than 80000000h (a positive-signed long in C) it is a response from a service, otherwise it is a non-specific message that was sent to this exchange by SendMsg() or IsendMsg(). If it was a response from a service, the second dword is the status code from the service. IMPORTANT: The value of the first dword is an agreed-upon convention. If you allocate an exchange that is only used for messaging between two of your own tasks within the same job, then the two dwords of the message can represent any two values you like, including pointers to your own memory area. If you intend to use the exchange for both messages and responses to requests, you should use the numbering convention. The operating system follows this convention with the Alarm() function by sending 0FFFFFFFFh as the first and second dwords of the message. Parameters: dExch - is the exchange to check for a waiting message pMsgsRet - is a Pointer to two dwords in contiguous memory where the two dword messages should be returned if a message is waiting at this exchange. dError - returns 0 if a message was waiting, and has been placed in qMsg, else ErcNoMsg is returned.

ClrScr ClrScr() This clears the virtual screen for the current Job. It may or may not be the one you are viewing (active screen). Parameters: None Compare Compare(pS1, pS2, dSize) : returned offset or -1 This is a high-speed string compare function using the Intel string instructions. This version is ASCII case sensitive. It returns the offset of the first byte that doesn't match between the pS1 and pS2, or it returns -1 (0xffffffff) if all bytes match out to dSize. pS1 and pS2 - Pointers to the data strings to compare. DSize - Number of bytes to compare in pS1 and pS2. CompareNCS CompareNCS(pS1, pS2, dSize) : returned offset or -1 This is a high-speed string compare function using the Intel string instructions. This version is not case sensitive(NCS). It returns the offset of the first byte that doesn't match between the pS1 and pS2, or it returns -1 (0xffffffff) if all bytes match (ignoring case) out to dSize. pS1 and pS2 - Pointers to the data strings to compare. DSize – Number of bytes to compare in pS1 and pS2. CopyData CopyData(pSource, pDestination, dBytes)

MMURTL V1.0

-164-

RICHARD A BURGESS

This is a high-speed string move function using the Intel string move intrinsic instructions. Data is always moved as dwords if possible. Parameters: pSource - Pointer to the data you want moved. pDestination - Pointer to where you want the data moved. dBytes - Count of bytes to move. CopyDataR CopyDataR(pSource, pDestination, dBytes) This is a high-speed string move function using the Intel string move intrinsic instructions. This version should be called if the pSource and pDest address overlap. This moves the data from the highest address down so as not to overwrite pSource. Parameters: pSource - Pointer to the data you want moved. pDestination - Pointer to where you want the data moved. dBytes - Count of bytes to move. DeAliasMem DeAliasMem(pAliasMem, dcbAliasBytes, dJobNum): dError This invalidates the an aliased pointer that you created using the AliasMem() call. This frees up pages in your linear address space. You should use this call for each aliased pointer when you no longer need them. Parameters: pAliasMem - The linear address you want to dealias. This should be the same value that was returned to you in the AliasMem() call. dcbAliasBytes - Size of the memory area to dealias. This must be the same value you used when you made the AliasMem() call. dJobNum - This is the job number of the application who's memory you had aliased. This should be the same job number you provided for the AliasMem() call. DeAllocExch DeAllocExch(dExch): dError DeAllocate Exchange releases the exchange back to the operating system for reuse. If tasks or messages are waiting a the exchange they are released and returned as reusable system resources. Parameters: dExch - the Exchange number that is being released. DeAllocPage DeAllocPage(pMem, dnPages) : dError DeAllocate Page deletes the memory from the job's page table. This call works for any memory, OS and DMA included. Access to this memory after this call will cause a page fault and terminate the job. Parameters:
MMURTL V1.0

-165-

RICHARD A BURGESS

pMem - Pointer which should contain a valid memory address, rounded to a page boundary, for previously allocated pages of memory. dnPages - Dword with the count of pages to deallocate. If dnPages is greater than the existing span of pages allocated at this address an error is returned, but as many pages as can be, will be deallocated. If fewer pages are specified, only that number will be deallocated. DeviceInit DeviceInit(dDevice, pInitData, dInitData) : dError Some devices may require a call to initialize them before use, or to reset them after a catastrophe. An example of initialization would be a Comms port, for baud rate, parity, and so on. The size of the initializing data and its contents are device-specific and are defined with the documentation for the specific device driver. Parameters: dDevice - Dword indicating device number pInitData - Pointer to device-specific data for initialization. This is documented for each device driver. dInitData - Total number of bytes in the initialization data. DeviceOp DeviceOp(dDevice, dOpNum, dLBA, dnBlocks, pData): dError The DeviceOp() function is used by services and programs to carry out normal operations such as Read and Write on all installed devices. The dOpNum parameter tells the driver which operation is to be performed. The first 256 operation numbers, out of over 4 billion, are pre-defined or reserved. These reserved numbers correspond to standard device operations such as read, write, verify, and format. Each device driver documents all device operation numbers it supports. All drivers must support dOpNum 0 (Null operation). It is used to verify the driver is installed. Most drivers will implement dOpNums 2 and 3 (Read and Write). Disk devices (DASD - Direct Access Storage Devices) will usually implement the first eight or more. Parameters: dDevice - the device number dOpNum - identifies which operation to perform 0 Null operation 1 Read (receive data from the device) 2 Write (send data to the device) 3 Verify (compare data on the device) 4 Format Block 5 Format Track (disk devices only) 6 Seek Block 7 Seek Track (disk devices only) 8-255 RESERVED 256-n Driver Defined (driver specific) dLBA - Logical Block Address for I/O operation. For sequential devices this parameter will usually be ignored. See the specific device driver documentation. dnBlocks - Number of continguous blocks for the operation specified. For sequential devices this will probably be the number of bytes (e.g., COMMS). Block size is defined by the driver. Standard Block size for disk devices is 512 bytes. pData - Pointer to data, or return buffer for reads, for specified operation DeviceStat DeviceStat(dDevice, pStatRet, dStatusMax, dStatusRet):dError

MMURTL V1.0

-166-

RICHARD A BURGESS

The DeviceStat() function returns device-specific status to the caller if needed. Not all devices will return status on demand. In cases where the driver doesn't, or can't, return status, ErcNoStatus will be returned. The status information will be in record or structure format that is defined by the specific device driver documentation. Parameters: dDevice - Device number to status pStatBuf - Pointer to buffer where status will be returned dStatusMax - This is the maximum number of bytes of status you will accept. pdStatusRet - Pointer to dword where the number of bytes of status returned to is reported. DMASetUp DMASetUp(dPhyMem, sdMem, dChannel, fdRead, dMode): dError This is a routine used by device drivers to set up a DMA channel for a device read or write. Typical use would be for a disk or comms device in which the driver would setup DMA for the move, then setup the device, which controls DMA through the DREQ/DACK lines, to actually control the data move. Parameters: dPhyMem - is physical memory address. Device drivers can call the GetPhyAdd() routine to convert linear addresses to physical memory addresses. sdMem - number of bytes to move dChannel - legal values are 0, 1, 2, 3, 5, 6, and 7. Channels 5, 6 and 7 are for 16-bit devices, and though they move words, you must specify the length in bytes using sdMem. fdRead - any non zero value sets DMA to read (Read is a read from memory which sends data to a device). dMode - 0 is Demand Mode , 1 is Single Cycle mode (Disk I/O uses single cycle), 2 is Block mode, and 3 is Cascade mode, which is for operating system use only. EditLine EditLine( pStr, dCrntLen, dMaxLen, pdLenRet, pbExitChar, dEditAttr): dError This function displays a single line of text and allows it to be edited on screen. Display and keyboard input are handled inside of EditLine(). The line must be on a single row of the display with no character wrap across lines. The first character of the line is displayed at the current X and Y cursor coordinates for the caller's video screen. Parameters: pStr - a pointer to the character string to be edited. dCrntLen - the current length of the string (80 Max.). dMaxlen - the maximum length of the string (80 Max.). pdLenRet - a pointer to a dword where the length of the string after editing will be returned. PbExitCharRet - is a pointer to a Byte where the exit key from the edit operation is returned. dEditAttr - editing attribute. The display and keyboard are handled entirely inside EditLine(). The following keys are recognized and handled internally for editing features. Any other key causes EditLine() to exit, returning the contents and length of the string in its current condition 08 (Backspace) move cursor to left replacing char with 20h which is a destructive backspace Left-Arrow - Same as Backspace (destructive) 20-7E Hex places character in current position and advances position Special shift keys combination (CTRL and ALT) are not interpreted (CTRL-D = D, etc.). EndOfIRQ EndOfIRQ (dIRQnum)

MMURTL V1.0

-167-

RICHARD A BURGESS

This call is made by an interrupt service routine to signify it has completed servicing the interrupt. End of Interrupt commands are sent to the proper PICU(s) for the caller. Parameters: dIRQnum - the hardware interrupt request number for the IRQ that your ISR has finished serving. For a description of hardware IRQs, see the call description for SetIRQVector(). ExitJob ExitJob(dExitError): dError ExitJob() is called by applications and services to terminate all tasks belonging to the job, and to free system resources. This is the normal method to terminate your application program. A high-level language's exit function would call ExitJob(). After freeing certain resources for the job, ExitJob() attempts to load the ExitJob() Run File as specified in the SetExitJob() call. It is kept in the JCB. Applications can exit and replace themselves with another program of their choosing, by calling SetExitJob(), and specifying the Run filename, before calling ExitJob(). A Command Line Interpreter, or executive, which runs your program from a command line would normally call SetExitJob(), specifying itself so it will run again when your program is finished. If no ExitJob is specified, ExitJob() errors out with ErcNoExitJob and frees up all resources that were associated with that job, including, but not limited to, the JCB, TSSs, Exchanges, Link Blocks, and communications channels. If this happens while video and keyboard are assigned to this job, the video and keyboard are reassigned to the OS Monitor program. Parameters: dExitError - this is a dword that indicates the completion state of the application. The operating system does not interpret, or act on this value, in any way except to place it in the Job Control Block. FillData FillData(pDest, cBytes, bFill) This is used to fill a block of memory with a repetitive byte value such as zeroing a block of memory. This uses the Intel string intrinsic instructions. Parameters: PDest - is a pointer to the block of memory to fill. Cbytes - is the size of the block. BFill - is the byte value to fill it with. GetCmdLine GetCmdLine(pCmdLineRet, pdcbCmdLineRet): dError Each Job Control Block has a field to store the command Line that was set by the previous application or command line interpreter. This field is not directly used or interpreted by the operating system. This call retrieves the Command Line from the JCB. The complimentary call SetCmdLine() is provided to set the Command Line. The CmdLine is preserved across ExitJobs, and while Chaining to another application. High-level language libraries may use this call to retrieve the command line and separate or expand parameters (arguments) for the applications main function. Parameters: pabCmdLineRet - points to an array of bytes where the command line string will be returned. This array must be large enough to hold the largest CmdLine (79 characters). pdcbCmdLineRet - is a pointer to a dword where the length of the command line returned will be stored.

MMURTL V1.0

-168-

RICHARD A BURGESS

GetCMOSTime GetCMOSTime(pdTimeRet) : dError This retrieves the time from the on-board CMOS battery backed up clock. The time is returned from the CMOS clock as a dword as: Low order byte is the seconds (BCD), Next byte is the minutes (BCD), Next byte is the hours (BCD 24 hour), High order byte is 0. Parameters: pdTimeRet - A pointer to a dword where the time is returned in the previously described format. GetCMOSDate GetCMOSDate(pdDateRet): dError This retrieves the date from the on-board CMOS battery backed up clock. The date is returned from the CMOS clock as a dword defined as: Low order byte is the Day of Week (BCD 0-6 0=Sunday), Next byte is the Day (BCD 1-31), Next byte is the Month (BCD 1-12), High order byte is year (BCD 0-99). Parameters: pdTimeRet - A pointer to a dword where the date is returned in the previously described format. GetDMACount GetDMACount(dChannel, pwCountRet): dError A routine used by device drivers to get the count of bytes for 8-bit channels, or words for 16-bit DMA channels, left in the Count register for a specific DMA channel. Parameters: dChannel - legal values are 0, 1, 2, 3, 5, 6, and 7 (Note: Channels 5, 6 and 7 are for 16-bit devices.) pwCountRet - This is the value found in the DMA count register for the channel specified. Note that even though you specify bytes on 16-bit DMA channel to the DMASetUp call, the count returned is always the exact value found in the count register for that channel. The values in the DMA count registers are always programmed with one less than the count of the data to move. Zero (0) indicates one word or byte. 15 indicates 16 words or bytes. If you program a 16-bit DMA channel to move data from a device for 1024 words (2048 bytes) and you call GetDMACount() which returns 1 to you, this means all but two words were moved. GetExitJob GetExitJob(pabExitJobRet, pdcbExitJobRet): dError Each Job Control Block has a field to store the ExitJob filename. This field is used by the operating system when an application exits, to see if there is another application to load in the context of this job (JCB). Applications may use this call to see what the ExitJob filename. The complimentary call SetExitJob() is provided. The ExitJob remains set until it is set again. Calling SetExitJob() with a zero length value clears the exit job file name from the JCB.

MMURTL V1.0

-169-

RICHARD A BURGESS

Parameters: pabExitJobRet - points to an array of bytes where the ExitJob filename string will be returned. This array must be large enough to hold the largest ExitJob filename (79 characters). pdcbExitJobRet - is a pointer to a dword where the length of the ExitJob filename returned will be stored. GetIRQVector GetIRQVector (dIRQnum, pVectorRet): dError The Get Interrupt Vector call returns the 32-bit offset address in operating system address space for the ISR that is currently serving dIRQnum. Parameters: dIRQnum - the hardware interrupt request number for the vector you want returned. For a description of IRQs see the call description for SetIRQVector(). pVectorRet - A pointer where the address of the ISR will be returned. GetJobNum GetJobNum(pdJobNumRet):dError This returns the job number for the task that called it. All tasks belong to a job. Parameters: pbJobNumRet - a pointer to a dword where the job number is returned. GetNormVid GetNormVid(pdNormVidAttrRet): dError Each Job is assigned a video screen, virtual or real. The normal video attributes used to display characters on the screen can be different for each job. The default foreground color which is the color of the characters themselves, and background colors may be changed by an application. The normal video attributes are used with the ClrScr() call, and are also used by stream output from high level language libraries. This returns the normal video attribute for the screen of the caller. See SetNormVid() for more information. Parameters: pdNormVidAttrRet - this points to a dword where the value of the normal video attribute will be returned. The attribute value is always a byte but is returned as a dword for this, and some other operating system video calls. See Chapter 9, “Application Programming,” for a table containing values for video attributes. GetPath GetPath(dJobNum, pPathRet, pdcbPathRet): dError Each Job Control Block has a field to store and application’s current path. The path is interpreted and used by the File System. Refer to File System documentation. This call allows applications and the file system to retrieve the current path string. Parameters: dJobNum - this is the job number for the path string you want. pPathRet - this points to an array of bytes where the path will be returned. This string must be long enough to contain the longest path (69 characters). pdcbPathRet - this points to a dword where the length of the path name returned is stored.

MMURTL V1.0

-170-

RICHARD A BURGESS

GetPhyAdd GetPhyAdd(dJobNum, dLinAdd, pdPhyRet): dError This returns a physical address for given Linear address. This is used by device drivers for DMA operations on DSeg memory. Parameters: dJobNum - is the job number of owner of d LinAdd - is the Linear address you want the physical address for. This is the address itself, not a pointer it. pdPhyRet - points to the dword where the physical address is returned GetpJCB GetpJCB(dJobNum, ppJCBRet): dError This returns a pointer to the Job Control Block for the specified job number. Each job has a structure to control and track present job resources and attributes. The data in a JCB is read-only to applications and system services. Parameters: dJobNum - this is the job number for the Job Control Block you want a pointer to. ppJCBRet - this is a pointer that points to a pointer where the pJCB will be returned. GetSysIn GetSysIn(pFileNameRet, pdcbFileNameRet): dError This returns the name of the standard output file, or device, from the application's Job Control Block. Parameters: pFileNameRet - Pointer to an array where the name will be returned. This array must be at least 30 characters long. pdcbFileNameRet - Pointer to a dword where the length of the name will be returned. GetSysOut GetSysIn(pFileNameRet, pdcbFileNameRet): dError This returns the name of the standard output file or device from the application's Job Control Block. Parameters: pFileNameRet - Pointer to an array where the name will be returned. This array must be at least 30 characters long. pdcbFileNameRet - Pointer to a dword where the length of the name will be returned. GetTimerTick GetTimerTick( pdTickRet) : dError This returns the operating system tick counter value. When MMURTL is first booted it begins counting 10-millisecond intervals forever. This value will roll over every 16 months or so. It is an unsigned dword. Parameters: pdTickRet - A pointer to a dword where the current tick is returned.
MMURTL V1.0

-171-

RICHARD A BURGESS

GetTSSExch GetTSSExch(pdExchRet): dError This returns the exchange that belongs to the task as assigned by the operating system during SpawnTask() or NewTask(). This exchange is used by some direct calls while blocking a thread on entry to non-reentrant portions of the operating system. It can also be used by system services or library code as an exchange for the request primitive for that task so long as no timer functions will be used, such as Alarm() or Sleep(). Parameters: pdExchRet - a pointer to a dword where the TSS Exchange will be returned. GetUserName GetUserName(pUserNameRet, pdcbUserNameRet): dError The Job Control Block for an application has a field to store the current user's name of an application. This field is not directly used or interpreted by the operating system. This call retrieves the user name from the JCB. The complimentary call SetUserName() is provided to set the user name. The name is preserved across ExitJobs, and while Chaining to another application. Parameters: pabUsernameRet - points to an array of bytes where the user name will be returned. dcbUsername - is the length of the string to store. GetVidChar GetVidChar(dCol, ddLine, pbCharRet, pbAttrRet): dError This returns a single character and its attribute byte from the caller's video screen. Parameters: dCol - The column, or X position, of the character you want returned. dLine - The line location, or Y position, of the character you want returned. pbCharRet - a pointer to a byte where the character value will be returned. pbAttrRet - a pointer to a byte where the video attribute for the character is returned. GetVidOwner GetVidOwner(pdJobNumRet): dError This returns the job number for the owner of the real video screen, the one you are viewing. Parameters: pdJobNumRet - pointer to a dword where the current video owner will be returned. GetXY GetXY(pdXRet, pdYRet): dError This returns the current X and Y position of the cursor in your virtual video screen.
MMURTL V1.0

-172-

RICHARD A BURGESS

Parameters: PdXRet - is a pointer where you want the current horizontal cursor position returned. PdYRet - is a pointer where you want the current vertical cursor position returned. InByte InByte(dPort) : Byte This reads a single byte from a port address and returns it from the function. Parameters: DPort - The address of the port. InDWord InDWord(dPort) : DWord This reads a single dword from a port address, and returns it from the function. Parameters: DPort - The address of the port. InWord InWord(dPort) : Word This reads a single word from a port address, and returns it from the function. Parameters: DPort - The address of the port. InWords InWords(dPort, pDataIn, dBytes) InWords reads one or more words from a port using the Intel string read function. The data is read from dPort and returned to the address pDataIn. dBytes is the total count of bytes to read (WORDS * 2). This call is specifically designed for device drivers. Parameters: DPort - The address of the port. PDataIn - The address where the data string will be returned. DBytes - The total number of bytes to be read. This number is the number of words you want to read from the port times two. InitDevDr InitDevDr(dDevNum, pDCBs, nDevices, fReplace) : dError InitDevDr() is called from a device driver after it is first loaded to let the operating system integrate it into the system. After the Device driver has been loaded it should allocate all system resources it needs to operate and control its devices,
MMURTL V1.0

-173-

RICHARD A BURGESS

while providing the three standard entry points. A 64-byte DCB must be filled out for each device the driver controls before this call is made. When a driver controls more than one device it must provide the device control blocks for each device. The DBCs must be contiguous in memory. If the driver is flagged as not reentrant, then all devices controlled by the driver will be locked out when the driver is busy. This is because one controller, such as a disk or SCSI controller, usually handles multiple devices through a single set of hardware ports, and one DMA channel if applicable, and can't handle more than one active transfer at a time. If this is not the case, and the driver can handle two devices simultaneously at different hardware ports, then it may be advantageous to make it two separate drivers. See Chapter 10, “System Programming,” for more detailed information on writing device drivers. Parameters: dDevNum - This is the device number that the driver is controlling. If the driver controls more than one device, this is the first number of the devices. This means the devices are numbered consecutively. pDCBs - This is a pointer to the DCB for the device. If more than one device is controlled, this is the pointer to the first in an array of DCBs for the devices. This means the second DCB would be located at pDCBs + 64, the third at pDCBs + 128, etc. nDevices - This is the number of devices that the driver controls. It must equal the number of contiguous DCBs that the driver has filled out before the InitDevDr() call is made. fReplace - If true, the driver will be substituted for the existing driver functions already in place. This does not mean that the existing driver will be replaced in memory, it only means the new driver will be called when the device is accessed. A driver must specify and control at least as many devices as the original driver handled. ISendMsg ISendMsg (dExch, dMsgHi, dMsgLo): dError This is the Interrupt Send primitive. This procedure provides access to the operating system to allow an interrupt procedure to send information to another task via an exchange. This is the same as SendMsg() except no task switch is performed and interrupts remain cleared. If a task is waiting at the exchange, the message is associated with that task and it is moved to the Ready Queue. It will get a chance to run the next time the RdyQ is evaluated by the Kernel. Interrupt tasks can use ISendMsg ()to send single or multiple messages to exchanges during their execution. IMPORTANT: Interrupts are cleared on entry in ISendMsg() and will not be set on exit. It is the responsibility of the caller to set them if desired. This procedure should only be used by device drivers and interrupt service routine. The message is two double words that must be pushed onto the stack independently. When WaitMsg(), or CheckMsg() returns these to your intended receiver, it will be into an array of dwords, unsigned long msg[2] in C. dMsgLo will be in the lowest array index (msg[0]), and dMsgHi will be in the highest memory address (msg[1]). Parameters: dExch - a dword (4 bytes) containing the exchange to where the message should be sent. dMsgHi - the upper dword of the message. dMsgLo - the lower dword of the message. KillAlarm KillAlarm (dAlarmExch) : dError A Public routine that kills an alarm message that was set to be sent to an exchange by the Alarm() operating system function. All alarms set to fire off to the exchange you specify are killed. For instance, if you used the Alarm() function to send a message to you in three seconds so you could time-out on a piece of hardware, and you didn't need it anymore, you would call KillAlarm(). If the alarm is already queued through the kernel, nothing will stop it and you should expect the Alarm() message at the exchange.

MMURTL V1.0

-174-

RICHARD A BURGESS

Interrupt latency of the system is not affected for greater delay values. pdJobNumRet . DestExch) : dError The kernel primitive MoveRequest() allows a system service to move a request to another exchange it owns. This allocates all the system resources required for the new job including a Job Control Block.pointer to the filename to run. pdJobNumRet) : dError This loads and executes a run file. LoadNewJob LoadNewJob(pFileName.A dword containing the number of 15-microsecond intervals to delay your task. but application speed or appearance may be affected if the priority of your task is high enough.pointer to a dword where the new job number will be returned.Parameters: dAlarmExch . delays that may be required when interfacing with device hardware. and you call this often enough. Parameters: dIRQnum . MoveRequest MoveRequest(dRqBlkHndl. and the memory required to run code. dcbFileName .the hardware interrupt request number for the IRQ you want to mask. Parameters: pFilename . You should be aware that your task does not go into a wait state.0 -175- RICHARD A BURGESS . and data pages.is the exchange you specified in a previous Alarm() operating system call. MicroDelay guarantees the time to be no less than n-15us where n is the number of 15microseconds you specify. precise. The timing is tied directly to the 15us RAM refresh hardware timer. the CPU will not be interrupted by the particular piece of hardware even if interrupts are enabled. It will delay further execution of your task by the number of 15-microsecond intervals you specify. MaskIRQ MaskIRQ (dIRQnum) : dError This masks the hardware Interrupt Request specified by dIRQnum. initial Task State Segment. dcbFileName. When an IRQ is masked in hardware. stack. so the time could be much longer. The recommended maximum length is 20-milliseconds. It would move it to a second exchange until it can honor the request. Interrupts are not disabled. An example of use is when a system receives a request it can't answer immediately. although longer periods will work.length of the run filename. Virtual Video. MicroDelay MicroDelay(dn15us) : dError MicroDelay is used for very small. For a description of IRQs see the call description for SetIRQVector(). MMURTL V1. but a task switch will probably not occur during the delay even though this is possible. Page Descriptor. Parameters: dn15us . but instead is actually consuming CPU time for this interval.

dCodeSeg. dPort) This writes a single dword from a port address and returns it from the function. NewTask NewTask(dJobNum. dPort) This writes a single word from a port address and returns it from the function. If the priority of this new task is greater then the running task.Initial instruction pointer (offset in CSeg) OutByte OutByte(Byte.Which code segment.This cannot be used to forward a request to another service or Job because the data pointers in the outstanding request block have been aliased for the service that the request was destined for. OutDWord OutDWord(DWord.exchange where the Request is to be sent. OS=8 or User=18 Priority . OutWord OutWord(Word. Parameters: JobNum . ESP . Parameters: dRqBlkHndl .The byte value to write to the port.The address of the port.Non-zero if you want the enter the debugger immediately upon execution.0 -176- RICHARD A BURGESS .The dword value to write to the port.0-31 Primary Application tasks use 25 fDebug . then fills in the TSS from the parameters to this call and schedules it for execution. dESP. Paramters: Dword . The operating system uses this to create the initial task for a newly loaded job. Exch . dExch. DPort .Exchange for TSS. Most applications will use SpawnTask() instead of NewTask() because it's easier to use. dfDebug. Parameters: MMURTL V1.handle of the Request Block to forward DestExch .The address of the port.Stack pointer (offset in DSeg) EIP . it is made to run. Parameters: Byte .Job number the new task belongs to (JCB) CodeSeg . else it's placed on the Ready Queue in priority order. DPort . dPort) This writes a single byte to a port address. dEIP) : dError This allocates a new Task State Segment (TSS). No error is returned. dPriority.

if not displayed.pointer to the text to be displayed nChars .column to start on (0-79) dLine .number of characters pbChars is pointing to. dnChars. Parameters: DPort . The Basic Video section in Chapter 9. Parameters: pdnPagesRet . dLine. Each page of memory is 4096 byte. The “Basic Video” section in Chapter 9 describes all of the attribute values. This is done independently of the current video stream which means X and Y cursor coordinates are not affected. dAttr .The word value to write to the port. You specify the number of bytes total (Words * 2). dAttrib): dError This places characters with the specified attribute directly on the caller's video screen.line (0-24) dnChars .column to start on (0-79) dLine .” describes all of the attribute values. Parameters: dCol . This is the number of Words * 2. dLine.a pointer to a dword where the current count of free pages will be returned. It is independent of the current video stream which means X and Y cursor coordinates are not affected. pDataOut.The address of the port.0 -177- RICHARD A BURGESS .Word . or virtual screen. PutVidAttrs PutVidAttrs(dCol.line (0-24) pbChars . OutWords OutWords(dPort. Parameters: dCol . dBytes) This uses the processor string intrinsic function OUTSW to write the words pointed to by pDataOut to dPort.The address of the port. DBytes . dAttr): dError This applies the video attribute specified to the characters at the column and line specified on the video screen. nChars. MMURTL V1.A pointer to one or more dwords to send out the port.Dword with number of characters to apply the attribute to. PdataOut . “Application Programming.The count of bytes to write to the port address. PutVidChars PutVidChars(dCol.number of characters pbChars is pointing to dAtrib . QueryPages QueryPages(pdnPagesRet): dError This returns the number of free physical memory pages left in the entire operating system managed memory space.color/attribute to use during display. DPort . pbChars.

error ErcNoKeyAvailable will be returned.0 -178- RICHARD A BURGESS . dExch) : dError This procedure registers a message-based system service with the operating system.a pointer to an 8-byte string. if forwarded across a network. dData1. This will identify a system service name with a particular exchange. If fWait is true (non-zero). Service names are case sensitive. pRqHndlRet. pData1. fWait) : dError ReadKbd() is provided by the keyboard system service as the easy way for an application to read keystrokes. dData0. This call blocks your task until completion. The string must be left justified. or on what machine the request is being serviced. If fWait is false (0). cbData2. pData2. Request Request( pSvcName. Parameters: BAddress . the call will return the keycode to pdKeyCodeRet and return with no error. and padded with spaces (20h). RegisterSvc RegisterSvc(pSvcName. This information allows the operating system to direct requests for system services without the originator (Requester) having to know where the actual exchange is located. dRespExch.A pointer to a dword where the keycode will be returned. Examples: "KEYBOARD" and "FILESYS " dExch . cbData1.If true (non-zero).ReadCMOS ReadCMOS(bAddress):Byte This reads a single byte value at the address specified in the CMOS RAM and returns it from the function.The address of the byte you want from 0 to MaxCMOSSize ReadKbd ReadKbd (pdKeyCodeRet. If no key was available. Parameters: pSvcName . dData2 ): dError MMURTL V1. wSvcCode.the exchange to be associated with the service name. the call will wait until a key is available before returning. This is specific to the ISA/EISA PC architecture. fWait . Parameters: pdKeyCodeRet . npSend. Access to all other functions of the keyboard service are only available through the Request and Wait primitives. See the documentation on the Keyboard Service for a complete description of KeyCodes. the call will wait until a keycode (user keystroke) is available before returning.

1 and 2 are dwords used to pass data to a service. of the message sent back to the requester of the service. dStatRet . wSvcCode . With all kernel primitives. or received from the service. See Respond(). Do not use SendMsg or ISendMsg.a number (0. Function Codes 0. cbData2 . Zero will be returned if all was well. This allows a service to access a second memory area in your space if needed. the value returned from the Respond() call itself. are sending data to the service. dStatRet): dError Respond() is used by message-based system services to respond to the caller that made a request to it. dError. the value returned from the Request() call itself. It must be installed or included in the operating system at build time. It identifies a Request Block resource that was allocated by the operating system. and 65535 (0FFFFh) are always reserved by the operating system. space padded. dRespExch .the size of the data being sent to the service. MMURTL V1. cbData1 . This must be 0 if pData1 is not used. This may be data sent to the service. a dError indicating this problem would be returned (ErcNoSuchService). 65534 (0FFFEh). The npSend parameter is so network transport systems will know which way to move data on the request if it is routed over a network. npSend . Zero will be returned if the Request() primitive was properly routed to the service. Parameters: pSvcName . The descriptions for each service code within a service will tell you the value of npSend.This is the Request handle that was received at WaitMsg or CheckMsg() by the service. 1 and 2. Use of pData1 and pData2 are documented by the system service for each service code it handles. pData1 . or a memory area where data will be returned from the service.This is the status or error code that will be placed in the second dword. is an indication of whether or not the respond primitive functioned normally. dData2 . The use of dData0. The service actually knows which pointers it's reading or writing.The service code is a 16-bit unsigned word that identifies the action the service will perform for you.This is the operating system Request primitive. For example. This is the error/status that the service passes back to you to let you know it is handled your request properly. These are strictly for data and cannot contain pointers to data.A pointer to an 8-Byte left justified. dError. It allows a caller to send a "request for services" to a message-based system service. What each service expects is documented by the service for each service code. dData0. The reason for this is that the operating system does not provide alias memory addresses to the service for them. pData2 .same as pData1. dMsgLo. dData1. An error from the service is returned to your exchange (dRespExch) in the response message. 1 or 2) that indicates how many of the two pData pointers. This must be 0 if pData2 is not used. if you made a Request() to QUEUEMGR and no service named QUEUEMGR was installed. If pData1 and pData2 both point to data in your memory area that is going to the service. pRqHndlRet . Parameters: dRqHndl .dData0.0 -179- RICHARD A BURGESS . Respond Respond(dRqHndl.a pointer to a dword that will hold the request handle that the request call will fill in. Service name examples are 'QUEUEMGR' or 'FAXIN '. A service must respond to all requests using Respond(). is an indication of whether or not the Request() primitive functioned normally. The service must be registered with the operating system. otherwise an operating-system status code will be returned. With all kernel primitives. then npSend would be 2. otherwise an operating system status code will be returned. This is how you identify the fact that it is a request and not a non-specific message when you receive a message after waiting or checking at an exchange. 1 and 2 are defined by each service code handled by a system service. This error is not the error returned by the service. Always assume it will be network-routed. Values for the Service Code are documented by each service. service name.The exchange you want the response message to be sent to upon completion of the request.the size of the memory area in pData1 being sent to.a pointer to your memory area that the service can access.

Parameters: dExch .dULline.The upper left column to start on (0-79) DULLine . If dfUp is non-zero the scroll will be up. dMsgHi .Points to an array of bytes.The count of lines to be scrolled. dcbCmdLine .ScrollVid ScrollVid(dULCol.a polite term for crashed. The two-dword message is placed on the specified exchange.dnCols. DnLines . If you never make a request that indicates exchange X is the response exchange. The message is two double words that must be pushed onto the stack independently.The lower dword of the message. dcbCmdLine): dError Each Job Control Block has a field to store the command line that was set by the previous application or command line interpreter. SetCmdLine SetCmdLine(pCmdLine. and the bottom line would be blanked. and it is moved to the Ready queue.1).25. Longer values will cause ErcBadJobParam to be returned. dMsgLo): dError SendMsg() allows a task to send information to another task. and while Chaining to another application. Users of SendMsg() should be aware that they can receive responses from services and messages at the same exchange. in C this is unsigned long msg[2]. DfUp . The line(s) left blank is filled with character 20h and the normal attribute stored in the JCB.The upper left line (0-24) DnCols . When WaitMsg() or CheckMsg() returns these to your intended receiver.This non-zero to cause the scroll to be up instead of down. Parameters: DULCol . linked with that task. you will not receive a response at the X exchange unless the operating system has gone into an undefined state . the new command line string.The upper dword of the message. The complimentary call GetCmdLine() is provided to retrieve the Command Line.0 -180- RICHARD A BURGESS . which is the first dword pushed.dnLines. dfUp) This scrolls the described square area on the screen either up or down dnLines.80.A dword (4 BYTES) containing the exchange where the message should be sent. dMsgHi. the first dword would be less than 80000000h which is a positive 32-bit signed value. Parameters: pCmdLine . SendMsg SendMsg (dExch.0. This call stores the command line in the JCB. the parameters would be ScrollVid(0. msg[1]. If you want to scroll the entire screen up one line.The number of columns to be scrolled. it will be into an array of dwords. dMsgLo will be in the lowest array index. The command line field in the JCB is 79 characters maximum. This field is not directly used or interpreted by the operating system. If there was a task waiting at that exchange. the message is associated. msg[0]. The CmdLine is preserved across ExitJobs. MMURTL V1. dMsgLo . This is an agreed-upon convention. In this case the top line is lost. and dMsgHi will be in the highest memory address. The proper way to insure the receiver of the message doesn't assume it is a Response is to set the high order bit of dMsgHi. and is not enforced by the operating system. If it were a Response from a service.The length of the command line string to store. Command line interpreters may use this call to set the command line text in the JCB prior to ExitJob() or Chain() calls running an application.

pVector) The Set Interrupt Vector call places the address pVector into the interrupt descriptor table and marks it as an interrupt procedure. dcbExitJob . The filename must not longer than 79 characters.0 -181- RICHARD A BURGESS . When a job exits. This is typically used by a command line interpreter. MMURTL V1.A dword with the length of the string the pExitJob points to. The ExitJob remains set until it is set again. Calling SetExitJob with a zero-length value clears the exit job file name from the JCB. After the interrupt vector is set. pVector .If installed. Table 15. The complimentary call GetExitJob() is provided.SetExitJob SetExitJob(pabExitJobRet. Parameters: pExitJob . Table 15. SetIRQVector SetIRQVector(dIRQnum. pVector must be a valid address in the operating-system address space because the IDT descriptor makes a protection level transition to system level if the interrupt occurred in a user level task. Initially. the caller should then call UnMaskIRQ to allow the interrupts to occur.The hardware interrupt request number for the IRQ to be set. Built-in device drivers assume these values.Hardware IRQs IRQ 0 8254 Timer IRQ 1 Keyboard (8042) IRQ 2 Cascade from PICU2 (handled internally) IRQ 3 COMM 2 Serial port * IRQ 4 COMM 1 Serial port * IRQ 5 Line Printer 2 * IRQ 6 Floppy disk controller * IRQ 7 Line Printer 1 * IRQ 8 CMOS Clock IRQ 9 Not pre-defined IRQ 10 Not pre-defined IRQ 11 Not pre-defined IRQ 12 Not pre-defined IRQ 13 Math coprocessor * IRQ 14 Hard disk controller * IRQ 15 Not pre-defined * .The 32-bit address in operating system protected address space. these should follow ISA hardware conventions. all unassigned hardware interrupts are masked at the Priority Interrupt Controller Units (PICUs). Parameters: dIRQnum . This call sets the ExitJob string in the JCB. pdcbExitJobRet): dError Each Job Control Block has a field to store the ExitJob filename. and its ExitJob is zero length. which will happen most often. This field is used by the operating system when an application exits to see if there is another application to load in the context of this job (JCB).4 . This will be 0-7 for interrupts on 8259 #1 and 8-15 for interrupts on 8259 #2.Points to an array of bytes containing the new ExitJob filename.4 shows the predetermined IRQ uses. Applications may use this call to set the name of the next job they want to execute in this JCB. of the Interrupt Service Routine (ISR) that handles the hardware interrupt. the job is terminated and all resources are reclaimed by the operating system.

the color of the characters themselves. the entire screen will become Black characters on a White background.This is a dword containing the value of the video attribute to become the normal or default. See chapter 9 for a table containing values for video attributes. dcbPath . Parameters: pJobName . The last 13 characters of the complete path for the run file that is executing is used. This call allows applications to set their path string in the JCB. The normal video attributes are used with the ClrScr() call. The path is interpreted and used by the File System. you can ensure it is rescheduled at the new priority by making any operating-system call that causes this task to be suspended.Count of bytes in the pJobName parameter. The maximum length is 13 bytes. a text string. a command line interpreter would use this call to set the current path. Parameters: pPath . and also used by stream output from high level language libraries. The Sleep() function with a value of 1 would have this effect.SetJobName SetJobName(pJobName. The normal video attributes used to display characters on the screen can be different for each job. dcbJobName): dError This sets the job name in the Job Control Block. then call ClrScr(). Refer to the file System documentation). Longer values will be truncated. A zero length. SetPath SetPath(pPath. dcbPath): dError Each Job Control Block has a field to store the applications current path. dcbJobName . Because the task that is running is the one that wants its priority changed.This points to an array of bytes containing the new path string. The path is 79 characters maximum. It is used only for the display of job names and serves no other function. The default foreground color.Pointer to the job name you want to store in the job control block. Typically.This is the new priority (0-31).This is a dword containing the length of the new Path for the current Job. MMURTL V1. and background colors may be changed by an application. will clear the job name from the JCB. The attribute value is always a byte but passed in as a dword for this and some other operating system video calls. SetNormVid SetNormVid(dNormVidAttr): dError Each Job is assigned a video screen either virtual or real. If no job name is set by the application. Parameters: dNormVidAttr . This is a maximum of 13 characters. SetPriority SetPriority(bPriority): dError This changes the priority of the task that is currently executing.0 -182- RICHARD A BURGESS . If you SetNormVid() to Black On White. Parameters: bPriority .

dcbFileName .A maximum of 30 characters with the file or device name. If the SysIn name is set to KDB. Only ASCII data is returned from this call. If the SysOut name is VID. Parameters: DJobNum . This field is not used or interpreted by the operating system. The complimentary call GetUserName() is provided to retrieve it from the JCB. and only receiving the low order byte which is the character code. Only ASCII data is saved to the file. is VID. is KBD. dcbFileName): dError This sets the name of the standard input file stored in the job control block. dcbUserName): dError The job control block for an application has a field to store the current user's name of an application. WriteBytes().is the length of the string to store. if none is set by the application. dcbFileName . This is used by the MMURTL Monitor and can be used by another user written program manager.The count of characters pFileName is pointing to. SetSysOut SetSysOut(pFileName.The count of characters pFileName is pointing to. The default name. SetVidOwner SetVidOwner(dJobNum): dError This selects which job screen will be displayed on the screen. The keyboard system service bypasses this mechanism when you use the request/respond interface. SetUserName SetUserName(pUserName. you should read stream data from the file system using the procedural interface call ReadBytes(). SetXY SetXY(dNewX.SetSysIn SetSysIn(pFileName. which is the user's name. If you desire to read data from a file to drive your application. The direct access video calls bypass this mechanism.The job number for new owner of the active screen.points to a text string. The default name. Parameters: pUsername .0 -183- RICHARD A BURGESS . Parameters: pFileName . dNewY): dError MMURTL V1. and Chaining to another application.A maximum of 30 characters with the file or device name. dcbFileName): dError This sets the name of the standard output file stored in the job control block. which is the one to be displayed. The name is preserved across ExitJobs. this has the same effect as calling ReadKbd() with fWait set to true. dcbUsername . you should write stream data using the file system procedural interface call. Parameters: pFileName . If you desire to write data from your application to a file. writing to the file VID with WriteBytes() is the same as calling TTYOut(). if none is set.

should use priorities above 25. DNewY . The default exchange in the TSS is used so the caller doesn't need to provide a separate exchange.A non-zero value will cause the task to immediately enter the debugger upon execution. Sleep is accomplished internally by setting up a timer block with the countdown value. Parameters: pEntry . The instruction displayed in the debugger will be the first instruction that is to be executed for the task pStack . but testing will be required to ensure harmony.0 -184- RICHARD A BURGESS . 512 bytes is required for operating system operation.The new vertical cursor position row. which may consume valuable CPU time causing lower priority tasks to become starved. The overhead included in entry and exit code makes the timing a little more than 10ms and is not precisely calculable. Parameters: dnTicks . it is made to run. This allocates a new Task state segment (TSS). a busy-wait condition may cause the CPU to appear locked up. dPriority. or thread of execution with the instruction address pEntry. dPriority . lower numbers. or the real video screen if currently assigned.A dword with the number of 10-millisecond periods to delay execution of the task.This is a pointer to the function. An example of the use of Sleep() is inside a loop that performs a repetitive function and you do not want to create a busy-wait condition.This positions the cursor for the caller's screen to the X and Y position specified. procedure or code section that will become an independent thread of execution from the task that spawned it. Secondary tasks for device drivers will usually be lower than 12. fOSCode):dError This creates a new task. Sleep Sleep(nTicks): dError A Public routine that delays the calling process by putting it into a waiting state for the amount of time specified. Add the number of bytes your task will require to this value. System services will generally be in the 12-24 region. Print spoolers and other tasks that may consume a fair amount of CPU time. The timer interrupt sends the message. and clears the block when it reaches zero. pStack. Sleep sends -1 (0FFFFFFFF hex) for both dwords of the message. SpawnTask SpawnTask(pEntry. dTicks10ms): dError MMURTL V1. then fills in the TSS from the parameters you passed. and an exchange to send a message to when the countdown reaches zero. fOSCode . Application programs should use priority 25. Certain system services are higher priority. fDebug. 1 is accepted. it's placed on the Ready queue in priority order.If you are spawning a task from a device driver or within the operating system code segment you should specify a non-zero value. fDebug . when it's actually doing exactly what it's told to do. Sleep() should not be used for small critical timing in applications less than 20ms. and may cause applications to appear sluggish. This applies to the virtual screen. otherwise. If your task is a high enough priority. Parameters: DNewX . or make it sleep. such as the file system and keyboard.This is a number from 0 to 31. If the priority of this new task is greater then the running task.A memory area in the data segment that will become the stack for this task. Tone Tone(dHz. Many of the TSS fields will be inherited from the Task that called this function. Device drivers such as network frame handlers may even require priorities as high as 5.The new horizontal cursor position column.

UnMaskIRQ UnMaskIQR (dIRQnum) : dError This unmasks the hardware interrupt request specified by dIRQnum.The Cursor will be moved to column zero on the current line.The attribute or color you want. dTextOut. When an IRQ is masked in hardware. Parameters: PTextOut . and the bottom line will be blanked 0D Carriage Return . Parameters: pSvcName . will be moved down one line. Backspace will have no effect.0 -185- RICHARD A BURGESS . dAttrib) This places characters on the screen in a TTY (teletype) fashion at the current X & Y coordinates on the screen for the calling job.The number of 10-millisecond increments to sound the tone. DAttrib . DTextOut . "FILESYS " This name must exactly match the name supplied by the service when it was registered. For a description of IRQs see the call description for SetIRQVector(). it will be moved to the first column of the next line.pqMsgRet):dError MMURTL V1.A near Pointer to the text. If this line is below the bottom of the screen. If the cursor is in the last column on the last line the screen will be scrolled up one line and the bottom line will be blank. Parameters: dIRQnum .A Public routine that sounds a tone on the system hardware speaker of the specified frequency for the duration specified by dTicks. the CPU will not be interrupted by the particular piece of hardware even if interrupts are enabled. Service names are case sensitive.The cursor. next active character placement. Each dTick10ms is 10-milliseconds. The following characters in the stream are interpreted as follows: 0A Line Feed . 08 backspace .Dword that is the frequency in Hertz. If already at column 0. if the cursor is in the last column of a line. The string must left justified. WaitMsg WaitMsg(dExch. and padded with spaces (20h).The cursor will be moved one column to the left.A pointer to an 8-byte string. They will receive an error.The hardware interrupt request number for the IRQ you want to unmask. TTYOut TTYOut (pTextOut. the entire screen will be scrolled up one line. The cursor will then be placed in the first column on the last line. UnRegisterSvc UnRegisterSvc(pSvcName) : dError This procedure removes the service name from the operating system name registry. Parameters: dHz . no characters are changed. A value of 100 is one second. from 30 to 10000. Examples: "KEYBOARD" .The number of characters of text. For character placement. The backspace is non-destructive. dTicks10ms . The service with that name will no longer be available to callers.

it is possibly a pointer to a request block that was responded to. Execution of the process is suspended until a message is received at that exchange.Returns 0 unless a fatal kernel error occurs.This is the kernel WaitMsg() primitive. Parameters: DError . If no message is available. At that time. an array of two dwords. so this would be obvious to you.0 -186- RICHARD A BURGESS .A pointer to where the message will be returned. Of course. the caller's task will be placed on the Ready queue and executed in priority order with other ready tasks.The exchange to wait for a message pqMsg . you would have had to make the request in the first place. You've got to send mail to get mail. This is an agreed upon convention for the convenience of the programmer. If the first 4-byte value is a valid pointer. MMURTL V1. the task will be placed on the exchange specified. This procedure provides access to operating system messaging by allowing a task to receive information from another task. dExch . less than 8000000h.

Copy a file Dir . CLI version. The source code to the CLI is listed in this chapter at the end of this section. it does work and it's useful in its own right. It has been expanded. but it's a good start. The CLI screen is divided into two sections.Hex dump of a file Exit .Exit and terminate CLI (return to Monitor) Help . To see what has been included on the CDROM.Directory listing Debug . Internal Commands The CLI has several built-in commands and can also execute external commands (load and execute . MMURTL Sample Software Introduction While working with MMURTL.Type the contents of text file to the screen Each of these commands along with an example of its. The command prompt is a greater-than symbol (>) followed by a reverse video line.Delete a file Dump . The CLI contains some good examples using a wide variety of the job-management functions of the operating system. It provides information such as date.0 -187- RICHARD A BURGESS .g.Execute a run file (replacing this CLI) Type . is described in the following sections. and its real purpose is to show certain job management aspects of MMURTL.” Command Line Interpreter (CLI) The MMURTL Command-Line Interpreter (called CLI) is a program that accepts commands and executes them for you.Chapter 16.Clear Screen Copy . The top line is the status line. I wrote literally hundreds of little programs to test detailed aspects of MMURTL. D:\DIR\) RD .RUN files). This line is where you enter commands to be executed by the CLI. most of my efforts have been directed at the operating system itself.Remove directory Rename .Enter Debugger Del . It's still missing some important pieces (at least important to me). Some of these programs have their source code listed and are discussed in this chapter. use. Only in the last year or so have I made any real effort to produce even the simplest of applications or externally executable programs.Make Directory Path . However. One of the first was a simple command-line interpreter.Display list of internal commands Monitor . and your current path. The source code to the CLI is listed in this chapter and on the CD-ROM.Return to Monitor (leave this CLI running) MD . The rest of the screen is an interactive display area that will contain your command prompt. Other test programs that may be useful to you have been included on the CD-ROM and are discussed in this chapter.Set file access path (New path e. The internal commands are: Cls . job number for this CLI. “What's On the CD-ROM. time. see Appendix A.Rename a file (Current name New name) Run . MMURTL V1.

“The Debugger. a period is displayed instead. or the same name in a different directory. Debug (Enter Debugger) This enters MMURTL's built-in Debugger.0 -188- RICHARD A BURGESS .TXT <Enter> Dump (Hex dump of a file) This dumps the contents of a file to the screen in hexadecimal and ASCII format as follows (16 bytes per line): ADDRESS 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 xxxxxxxxxxxxxxx The address is the offset in the file.Cls (Clear Screen) The Cls command removes all the text from the CLI screen and places the command prompt at the top of the screen. Pressing Esc will stop the listing. Both names must be specified. Dump pauses after each screen full of text. >Copy THISFILE. A new line is displayed for each 16 bytes. For more information on the Debugger see chapter 12. To exit the Debugger press the Esc. If a file exists with the same name. Example: >Del C:\TEST\FILE.” Del (Delete a File) This deletes a single file from a disk device. you are prompted to confirm that you want to overwrite it. The listing is in multiple columns as: FILENAME SIZE DATE TIME TYPE FIRST-CLUSTER The first-cluster entry is a hexadecimal number used for troubleshooting the file system. >Dir C:\SOURCE\ <Enter> The listing fills a page and prompts you to press Enter for the next screen full. Dir (Directory listing) This lists ALL the files for you current path or a path that you specify. The current path will be properly used if a full file specification is not used. The TYPE is the attribute value taken directly from the MS-DOS directory structure. It means the file has been modified or newly created since the archive bit was last reset. Copy (Copy a file) This makes a copy of a file with a different name in the same directory. If the character is not ASCII text. The values are as follows: 00 or 20 is a normal file 01 or 21 a read-only file 02 or 22 is a hidden file 04 or 24 is a system file 10 is a sub-directory entry 08 is a volume name entry (only found in the root) File values greater than 20 indicate the archive attribute is set. MMURTL V1. The text from the line is displayed following the hexadecimal values.TXT THATFILE.TXT <Enter> Wildcards (pattern matching) and default names (leaving one parameter blank) are not supported in the first version of the CLI.

0 -189- RICHARD A BURGESS . Monitor (Return to Monitor) If this is the active CLI (the one displayed). MD (Make Directory) This creates an empty directory in the current path or the path specified on the command line. The path you set in the CLI will remain the path for any job that is run. a job's path is simply a prefix used by the file system to make complete file specifications from a filename. the tree will only be extend one level. The path may also be set programmatically (by a program or utility). listing. if you don't specify a full file specification (including the drive). With any file-system operation (opening. and .CLI (if it exists) and displays it on the screen..). Do not confuse the term path for the MS-DOS version of the path command.Exit (Exit the CLI) This exits this CLI and terminates this job. for example: >MD OLDSTUFF <Enter> >MD C:\SAMPLES\OLDSTUFF <Enter> The directory tree up to the point of the new directory must already exist. renaming. this will assign the keyboard and video back to the monitor. The current path is displayed on the status line in the CLI. The Keyboard and video will be assigned to the monitor if this was the active job. The only entries allowed are the two default entries (. Each job control block maintains a context for its particular job. This is an ASCII text file and you may edit it as needed.) MMURTL V1. etc. Examples: >Path D:\DIR\ <Enter> >PATH C:\ <Enter> >C:\MMSYS\ <Enter> RD (Remove Directory) This removes an empty directory in the current path or the path specified on the command line. or external command that is executed for this CLI. Help (List internal commands) This opens the text file called Help. The path must end with the Backslash. Don't be surprised if you return from a program you have executed to the CLI with a different path. no system wide concept of a "current drive" or "current directory" can be assumed. The file should located in the MMURTL system directory. Each CLI (Job) has its own path which is maintained by the operating system in the job control block. This CLI will still be executing. >RD OLDSTUFF <Enter> >RD C:\MMURTL\OLDSTUFF <Enter> The directory must be empty. In other words. Path (Set file access path) This sets the file-access path for this Job. Unlike a single tasking operating system. In MMURTL. the file system appends your specification to the current path for this job.

CLI tells the CLI what RUN file to execute for a command name. Here’s an example: >Type C:\MMSYS\Commands. These are RUN files (MMURTL executable files with the file suffix . Examples: >Run C:\NewEdit\NewEdit.0 -190- RICHARD A BURGESS . The file must not be open by any other job. There must also be at least one space or tab between the RUN-file name and the arguments (if they exist).RUN file) This executes the named RUN file in place of this CLI (same job number).Rename (Rename a file) This changes the name of a file.TXT Run (Execute a .CLI File The file COMMANDS.RUN).run . The File COMMANDS.run New.CLI Commands.CLI follows: . If the new filename exists you are prompted to either overwrite existing file or cancel the command.TXT <Enter> External Commands External commands are those not built into the CLI.run Print C:\MSamples\Print\Print.CLI <Enter> NEWNAME. Type (View a text file on the screen) This displays the contents of a text file to the screen one page at a time. An example of COMMANDS. It is a simple text file with one command per line.cli At least one space or tab must be between the command name and the RUN file. The first item on the line is the command name. The format of this ASCII text file is discussed below. No spaces or tabs are allowed before the command name. MMURTL V1.CLI must be located in the \MMSYS directory on your system disk.run <Enter> Any parameters specified after the RUN-file name are passed to the RUN file.Any line beginning with SEMI-COLON is a comment. The contents of the file COMMANDS. MMURTL looks for external commands in three places (in this order): Your current path (shown on the status line) The MMSYS directory on the system disk. Additional spaces and tabs are ignored. which is followed with the full file specification for the RUN file to execute. pausing after each page.run CM32 C:\CM32M\CM32. EDIT C:\MSAMPLES\EDITOR\Edit. The command format is as follows: >Rename OLDNAME. The Path remains the same as was set in the CLI.Txt <Enter> >Run D:\MMDEV\Test.End of Command.run DASM D:\DASMM\DASM.

h> #include <ctype.0 of the Command Line Interpreter. This key sequence is received and acted upon by the Monitor program.0 -191- RICHARD A BURGESS .Switches video and keyboard to next job or the monitor. The monitor and the Debugger will not terminate using this command. Global "Hot Keys" The system recognizes certain keys that are not passed to applications to perform system-wide functions.h" "\OSSOURCE\Status. CTRL-ALT-PAGE DOWN . Listing 16.h> #include <string. but other application might. CLI Source Listing The following is the source code for version 1. the parameters you specified on the command line are appended to those you have in the COMMANDS.Terminates the currently displayed job.1.Ver. 1.h" "\OSSOURCE\MMemory.h" "\OSSOURCE\MFiles.h" "\OSSOURCE\MTimer. The CLI doesn't recognize any Hot Keys.0 #define U32 unsigned long #define S32 long #define #define #define #define #define #define U16 unsigned int S16 int U8 unsigned char S8 char TRUE 1 FALSE 0 #include <stdio.h> /* Includes for OS public calls and structures */ #include #include #include #include #include #include #include #include #include "\OSSOURCE\MKernel. This key sequence is received and acted upon by the Monitor program.h" "\OSSOURCE\MData.CLI file. The Include files from the OS Source directory contain all the ANSI C prototype for the operating system functions.h" "\OSSOURCE\MVid. Global hot keys are those that are pressed while the CTRL and ALT keys are held down.h" "\OSSOURCE\MKbd. CTRL-ALT-DELETE .h" /******************** BEGIN DATA ********************/ /* Define the Foreground and Background colors */ MMURTL V1. In this case.CLI. CLI Source Code Listing .h" "\OSSOURCE\MJob.Any parameters you specify on the command line in the CLI are passed to the run file that is executed. You may also add parameters to the line in COMMANDS.

long cbCmd = 0. cmdpath[60]. /* Messaging for status task */ long StatStack[256]. clipath[60]. /* array of internal commands for parsing */ #define NCMDS 16 char paCmds[NCMDS+1][10] = "". /* "Del". /* /* /* /* holds system disk and system path */ Path to help file */ Path to CLI */ Path to command file */ CLI V1. /* "Dump". unsigned char bigBuf[4096]. char fUpdatePath. /* "Copy". char ExitChar. char aCmd[80]. /* "Monitor". hlppath[60]. /* "Cls".#define EDVID BLACK|BGWHITE #define NORMVID WHITE|BGBLACK #define STATVID BLACK|BGCYAN long iCol. /* 1024 byte stack for stat task */ long time. /* "Help". unsigned long GPHndl. /* Command line */ /* Messaging for Main */ unsigned long StatExch.not used */ 2 */ 3 */ 4 */ 5 */ 6 */ 7 */ 8 */ 9 */ 10 */ 11 */ 12 */ 13 */ 14 */ -192- RICHARD A BURGESS . date. /* "Exit". long cbPath. char text[70]. unsigned long GPMsg[2]. syspath[50]. /* "Ren". unsigned char Buffer[512]. /* "Debug". /* "Path". /* "Dir". /* MMURTL V1. /* "MD". iLine. char char char char sdisk. /* "RD".0 { 0 external */ 1 . unsigned long GPExch. long JobNum. /* "xxxxx". /* aStatLine is exactly 80 characters in length */ char aStatLine[] = "mm/dd/yy 00:00:00 char aPath[70].0 Job Path: ".

It runs as a separate task and is started each time the CLI is executed. 16 records 32 bytes each */ struct dirstruct { U8 Name[8]. /************************* BEGIN CODE ********************/ /********************************************************* This is the status task for the CLI. U8 Rsvd[10]. U8 Attr. *********************************************************/ void StatTask(void) { for(. U8 Ext[3]. U16 StartClstr. U16 Time. }. #define nParamsMax 13 #define ErcBadParams 80 /* Directory Entry Records. It updates the status line which has the time and path. } dirent[16]. U32 FileSize. #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define EXTCMD 0 RESVDCMD 1 CLSCMD 2 COPYCMD 3 DELCMD 4 DIRCMD 5 DBGCMD 6 DUMPCMD 7 EXITCMD 8 HELPCMD 9 MAKEDIR 10 MONCMD 11 PATHCMD 12 REMDIR 13 RENCMD 14 RUNCMD 15 TYPECMD 16 /* 15 */ /* 16 */ /* External Command */ long CmdNum = 0."Run".0 /* Param 0 is cmd name */ -193- RICHARD A BURGESS .) { if (fUpdatePath) { if (cbPath > 30) MMURTL V1. char *apParam[13].. long acbParam[13]. "Type". U16 Date.

0x0f). /* sleep 1 second */ } /* forEVER */ } /********************************************************* This displays error code text string if listed and NON zero. STATVID).cbPath = 30. 0x0f). break. break. FillData(st. cbPath). 80. case 228: pSt = "Root directory is full". + + + + + + ((time >> 20) & 0x0f). break. case 101: pSt = "Out of memory (need more for this)". MMURTL V1. case 230: pSt = "Disk is full (bummer)". *********************************************************/ void CheckErc (unsigned long erc) { char *pSt. break. case 205: pSt = "File is ReadOnly". case 208: pSt = "File in use". switch (erc) { case 0: return. (time & 0x0f). ' '). break. CopyData(aPath. break. Sleep(100). case 1: pSt = "End of file". case 226: pSt = "File Already Exists (duplicate name)". ((time >> 12) & 0x0f). case 200: pSt = "Invalid filename (not correct format)".0 -194- RICHARD A BURGESS . case 231: pSt = "Directory is full". break. ((time >> 4) & 0x0f). } GetCMOSDate(&date). ((time >> 8) & 0x0f). case 204: pSt = "Directory doesn't exist". /* month */ 0x0f). &aStatLine[47]. PutVidChars(0. break. case 202: pSt = "The name is not a file (it's a directory)". 40. /* year */ 0x0f). char st[40]. FillData(&aStatLine[47]. break. break. case 4: pSt = "User cancelled". case 203: pSt = "File doesn't exist". /* Day */ 0x0f). aStatLine. ((time >> 16) & 0x0f). case 222: pSt = "Can't rename across drives". case 223: pSt = "Can't rename across directories". break. break. aStatLine[0] = '0' + aStatLine[1] = '0' + aStatLine[3] = '0' + aStatLine[4] = '0' + aStatLine[6] = '0' + aStatLine[7] = '0' + GetCMOSTime(&time).0. break. break. 30. case 80: pSt = "Invalid parameters". break. aStatLine[10] = '0' aStatLine[11] = '0' aStatLine[13] = '0' aStatLine[14] = '0' aStatLine[16] = '0' aStatLine[17] = '0' ((date ((date ((date ((date ((date ((date >> >> >> >> >> >> 20) 16) 12) 8) 28) 24) & & & & & & 0x0f). case 201: pSt = "No such drive". break. break. 0). fUpdatePath = 0.

} SetXY(iCol. "Error %05d on last command". } /********************************************************* This is called to initialize the screen. unsigned char buff[17]. iLine = 2. pSt = st. If we are returning from and external command we do not reset the cursor position. } /********************************************* This does a hex dump to the screen of a memory area passed in by "pb" **********************************************/ U32 Dump(unsigned char *pb. erc = 0. &iLine). i. break. #asm INT 03 #endasm return. GetXY(&iCol.default: sprintf(st. } /********************************************************* This simply does a software interrupt 03 (Debugger). addr). aStatLine.0 -195- RICHARD A BURGESS . erc).0. while (cb) { printf("%08x ". GetXY(&iCol. ClrScr(). &iLine). return. MMURTL V1. pSt). *********************************************************/ void GoDebug(void) { . PutVidChars(0. j. long cb. STATVID). KeyCode. if (fNew) { iCol = 0. fNew is used to determine is we do or not. iLine). } printf("%s\r\n". unsigned long addr) { U32 erc. 80. *********************************************************/ void InitScreen(int fNew) { SetNormVid(NORMVID).

23). for (i=0. printf("ESC to cancel.23). } return erc. any other key to continue. 1). fh. iLine =1. if (!erc) { j=0.23.if (cb > 15) j=16. &fh). } /********************************************************* This sets up to call the dump routine for each page. if ((apParam[1]) && (acbParam[1])) { if (iLine >= 23) { ScrollVid(0. else j = cb.1). 512. } InitScreen(TRUE). MMURTL V1. SetXY(0.80. erc = ReadBytes (fh. &k). acbParam[1]. else cb=0. dret."). i++) { printf("%02x ". printf("%s\r\n". i<j. addr+=j. ReadKbd(&KeyCode. Buffer. l. } erc = OpenFile(apParam[1]. /* ReadKbd. ModeRead. SetXY(0. erc.*pb). &buff[0]). if (buff[i] > 0x7F) buff[i] = 0x2E.1. 0).. 1. if (buff[i] < 0x20) buff[i] = 0x2E. k.1). } buff[i+1] = 0. wait */ if ((KeyCode & 0xff) == 0x1b) { return 4. &dret). erc = 0. } if (cb > 15) cb-=16. ++iLine.0 -196- RICHARD A BURGESS . buff[i] = *pb++. if (iLine >= 22) { SetXY(0. This expects to find the filename in param[1]. while ((j<k) && (!erc)) { FillData(Buffer. 512.. *********************************************************/ long DoDump(void) { unsigned long j. /* file lfa */ GetFileSize(fh.

iLine).if (k-j > 512) l = 512.1. else l=k-j. 78. cbName. NORMVID). dret. KeyCode. erc = 0. 512. } CloseFile (fh).80. } erc = OpenFile(pName. if (!erc) { FillData(Buffer. j<=i. lfa. &lfa). } /********************************************************* This types a text file to the screen. dret = 1. long cbName) { long i. } if (dret) { PutVidChars(0. &fh). while ((erc<2) && (dret)) { GetFileLFA(fh. j. iLine++. if (erc < 2) erc = Dump(Buffer. return erc. erc. 0). *********************************************************/ long DoType(char *pName. lfa+i). SetXY(0. SetFileLFA(fh.1). if ((pName) && (cbName)) { if (iLine >= 23) { ScrollVid(0. &dret). 0.0 -197- RICHARD A BURGESS . } } else printf("Filename not given\r\n"). iLine. j++) { if ((Buffer[j] == 0x09) || (Buffer[j] == 0x0d) || (Buffer[j] == 0x0a)) Buffer[j] = 0x20. erc = ReadBytes (fh. 1. i. j).23. } for (j=0. i = 1. MMURTL V1. dret. j+=512. } SetXY(0. while ((Buffer[i-1] != 0x0A) && (i < dret)) { i++. Buffer. fh. Buffer.23).

char *pTimeRet. /* ReadKbd.mi. U32 cbTo) { U32 fhTo.mo. U16 date.yr).se. SetXY(0.da. wait */ if ((KeyCode & 0xff) == 0x1b) return(4). bytesLeft. any other key to continue. if (yr > 99) yr -=100. CloseFile (fh).if (iLine >= 22) { SetXY(0.0 -198- RICHARD A BURGESS . 1). fhFrom. hr = (time & 0xF800) >> 11. return erc. } } else printf("Filename not given\r\n"). U32 cbFrom. This uses a conservative 4K buffer.da. erc. ReadKbd(&KeyCode. sprintf(st. mo = (date & 0x01E0) >> 5. printf("ESC to cancel. } /******************************************************* This converts DOS FAT date & time and converts it into strings with the format MM/DD/YY HH/MM/SS.1900. da = date & 0x001F.. char *pDateRet) { U32 yr. CopyData(st. } /******************************************************* Copy a single file with overwrite checking. char *pTo. } } printf("\r\nError: %d\r\n".mi. THis could be made a good deal faster by allocating a large chunk of memory and using it instead of the 4K static buffer. MMURTL V1. bytesGot. yr = ((date & 0xFE00) >> 9) + 1980 . se = (time & 0x001F) * 2.se). char st[15]. iLine =1. sprintf(st. CopyData(st. InitScreen(TRUE). Use block mode for speed. pDateRet. "%02d-%02d-%02d".. pTimeRet."). bytesWant.23).mo. 8). erc). 8). mi = (time & 0x07E0) >> 5. This is used by the directory listing. "%02d:%02d:%02d". ********************************************************/ U32 CopyFile(char *pFrom.hr.1).hr. ********************************************************/ void CnvrtFATTime(U16 time.

cbTo. if (!erc) { /* both files are open in block mode */ erc = GetFileSize(fhFrom. &size).bytesOut. ModeModify. &bytesGot). 0). 0. &bytesOut). 1). 0. dLFA = 0. bigBuf. size). dLFA. if (!erc) erc = SetFileSize(fhTo. if (bytesGot) { erc = WriteBlock(fhTo. else bytesLeft-=bytesOut. } return(erc). } MMURTL V1. } CloseFile(fhTo). erc = ReadBlock(fhFrom. if (bytesLeft < bytesOut) bytesLeft = 0.0 -199- RICHARD A BURGESS . /* ReadKbd. erc = OpenFile(pFrom. } dLFA += bytesGot. ReadKbd(&KeyCode. while ((!erc) && (bytesLeft)) { if (bytesLeft >= 4096) bytesWant = 4096. if (bytesWant & 511) /* handle last sector */ bytesWant += 512. bigBuf. } } if (!erc) { erc = OpenFile(pTo. bytesLeft = size. bytesWant. &fhFrom). size. dLFA. cbTo. wait */ if (((KeyCode & 0xff)=='Y') || ((KeyCode & 0xff)=='y')) { erc = 0. bytesWant = (bytesWant/512) * 512. Overwrite? (Y/N)\r\n"). if ((!erc) || (erc==ErcDupName)) { if (erc == ErcDupName) { printf("File already exists. if (!erc) { /* Check to see if it exists already */ erc = CreateFile(pTo. ModeRead. bytesGot. KeyCode. &fhTo). dLFA. cbFrom. else bytesWant = bytesLeft. } } } CloseFile(fhFrom).

iLine++. KeyCode.1). &dirent[0].23). dirent[i]. wait */ if ((KeyCode & 0xff) == 0x1b) { erc = 4."). /* ReadKbd. erc. } fDone = 0.Ext.Name[0] != 0xE5)) { sprintf(st.80./********************************************************* Does a directory listing for param 1 or current path. st). fDone = 1. dirent[i]. printf("ESC to cancel. ReadKbd(&KeyCode. dirent[i]. if (!erc) { for (i=0. "%8s %3s %8d xx/xx/xx xx/xx/xx %2x %04x\r\n".1.Attr. SectNum). printf("%s". *********************************************************/ U32 DoDir(void) { U8 fDone. if (iLine >= 22) { SetXY(0. SectNum = 0.Name[0]) && (dirent[i].Name. } InitScreen(TRUE). acbParam[1]. &st[33]..FileSize. } if ((dirent[i]. CnvrtFATTime(dirent[i]. iLine =1. char st[78].0 -200- RICHARD A BURGESS . fDone = TRUE. 1). } } } MMURTL V1. 512.23. i<16.Name[0]) { erc = 1. SetXY(0. dirent[i]. dirent[i]. &st[24]). i++) { if (!dirent[i].. U32 SectNum.1). dirent[i].Time.StartClstr). if (iLine >= 23) { ScrollVid(0. SetXY(0. while (!fDone) { erc = GetDirSector(apParam[1]. any other key to continue. i.23).Date.

If it finds it.. j. } /********************************************************* This Fills in the ptrs to and sizes of each parameter found on the command line in the arrays apParams and acbParams. /* iCmd is index to aCmd */ iCmd = 0. The format for a line entry in Commands. /* default the param to empty */ apParam[iPrm] = 0. /* Null the ptr */ if (iCmd < cbCmd) { if (!isspace(aCmd[iCmd])) { apParam[iPrm] = &aCmd[iCmd++]. /* size of param */ } while ((iCmd < cbCmd) && (isspace(aCmd[iCmd]))) iCmd++. **************************************************/ long { long FILE char char char GetCLICommand(char *runfile) i. k. } acbParam[iPrm] = i. *********************************************************/ void ParseCmdLine(void) { long iCmd. then fixes up the aCmd command line just as if it had come from the user. } return(erc).CLI in the system directory and looks for the command name in apParam[0]. iPrm. fdone. it places the full filespec for the run file in "runfile" for the caller. iPrm<nParamsMax. for (iPrm=0. iCmd++. while ((iCmd < cbCmd) && (!isspace(aCmd[iCmd]))) { i++. SectNum++. *f.CLI is: name fullspec param param param.. } } } /************************************************* This opens and parses Commands. cmdline[90]. /* ptr to param */ i = 1.} else fDone = TRUE. MMURTL V1.0 /* used we build a pseudo command line */ /* used we build a pseudo command line */ -201- RICHARD A BURGESS . iPrm++) { acbParam[iPrm] = 0. i. rawline[90].

/* now move arguments if any. /* Index into rawline */ j = 0. /* Move cmd line we built to real cmd line */ strcpy(aCmd. return(0). /* null terminte */ /* skip whitespace in rawline */ while (isspace(rawline[i])) i++. } /************************************************** MMURTL V1. /* tell em we got it! */ } } else fdone = 1. cmdline[j] = 0.\r\n"). while (!fdone) { if (fgets(rawline. } } else printf("Commands. rawline.cmdline[0] = 0. if (f) { fdone = 0. /*Index into runfile name we rtn to caller*/ while (!(isspace(rawline[i]))) cmdline[j++] = rawline[i++]. "r"). f)) { if (rawline[0] == '. cbCmd = strlen(aCmd). /* skip whitespace in rawline */ while (isspace(rawline[i])) i++.') /* a comment line */ continue. /* Index into cmdline we are building */ k = 0. /* follwed by a space */ cmdline[j++] = ' '. runfile[k] = 0. acbParam[0]). 89.0 -202- RICHARD A BURGESS . cmdline). j = CompareNCS(apParam[0]. f = fopen(cmdpath. if (j == -1) { /* we found a match for the command */ /* Move the command name into the command line */ i = 0. /* now move runfile name */ while (!(isspace(rawline[i]))) runfile[k++] = rawline[i++]. and LF */ while (rawline[i]) cmdline[j++] = rawline[i++]. return(1).CLI not found.

ModeRead. SetCmdLine(aCmd. runfile[acbParam[0]+i] = 0. SetCmdLine(aCmd. If we don't find one.RUN"). if (!erc) { /* found a run file */ CloseFile(fh). we then go to COMMANDS. 0). &fh). strcat(runfile. strlen(runfile). cbCmd).0 -203- RICHARD A BURGESS . } else /* Try to find in System directory */ { strcpy(runfile. erc = OpenFile(runfile. long i. if (!erc) { /* found a run file */ CloseFile(fh). fh. SetCmdLine(aCmd. strlen(runfile).RUN file in the current path. strlen(runfile). we first look for a . acbParam[0]). &fh). SetExitJob(clipath. /* null terminate */ strcat(runfile. we go to the system directory. erc = OpenFile(runfile. strlen(runfile). if it's not there. acbParam[0]).When a command is specified to the CLI and it is not an internal command. strlen(runfile). CopyData(apParam[0]. erc. erc = Chain(runfile. i = strlen(runfile). 1. erc = Chain(runfile. 1.RUN"). } } } printf("Command not found\r\n"). If so. strlen(clipath)). } MMURTL V1. ***************************************************/ void FindRunFile(void) { char runfile[80]. ModeRead. strlen(runfile). SetExitJob(clipath. if (!erc) { /* found a run file */ CloseFile(fh). SetExitJob(clipath. ". syspath). cbCmd). /* Try to find in current path */ CopyData(apParam[0]. ". runfile. strlen(clipath)). strlen(clipath)). erc = Chain(runfile. } /* now we call GetCLICommand as a last resort */ else if (GetCLICommand(runfile)) { erc = OpenFile(runfile. 0).CLI and search it line by line to see if a run file is specified there. cbCmd). &fh). &runfile[i]. 1. 0). runfile[acbParam[0]] = 0. ModeRead. we set the command line with everything after the runfile specified and try to execute it.

strcpy(clipath. GetJobNum(&JobNum). syspath[2] = 0. */ GetSystemDisk(&sdisk)."HELP. "%02d". Note: No params to CLI. command file. syspath). } else { strcpy (aPath. &iLine). syspath[1] = ':'. if (iLine == 0) /* under status line */ iLine = 1. 8). 0).RUN"). 0. SpawnTask(&StatTask. erc = AllocExch(&GPExch). syspath). &StatStack[255]. We do not want to reset the screen completely so the use can see anything the external command left on the screen. syspath). sdisk &= 0x7F. MMURTL V1. */ cbPath = 0."COMMANDS. InitScreen(FALSE). syspath). "\\MMSYS\\"). and help file. strcat(hlppath. i.0". erc = AllocExch(&StatExch). strcat(clipath. j. 1=B. strcpy(hlppath. */ syspath[0] = sdisk. /* If a path was already set we assume that we are re-loading after an external command has been run. JobNum). sprintf(&aStatLine[37]. strcpy(cmdpath. fh. aPath. &cbPath). SetJobName("CLI V1. /* Get system disk and set up path names for cli. iLine). 2=C etc. strcat(cmdpath. /* 0=A."CLI.0 -204- RICHARD A BURGESS . 24. ***********************************************/ void main(void) { unsigned long erc. GetPath(JobNum. strcat(syspath.CLI")./********************************************** Main function entry point. sdisk += 0x41.CLI"). if (cbPath) { GetXY(&iCol. SetXY(0.

&fh). 79. aCmd[79] = 0. case CLSCMD: InitScreen(TRUE). break. if (!erc) MMURTL V1. acbParam[0] = 0. acbParam[2]). 1. strlen(clipath)). TTYOut ("\r\n". switch (CmdNum) { case EXTCMD: /* external command */ FindRunFile(). while (1) { FillData(aCmd. EditLine(aCmd. SetPath(syspath. NORMVID). EDVID). SetXY(0. strlen(syspath)). break. InitScreen(TRUE). if ((acbParam[0] == j) && (CompareNCS(apParam[0]. iLine). apParam[2]. acbParam[1]. 2. 0. case DELCMD: erc = OpenFile(apParam[1]. j) == -1)) { CmdNum = i. break. } fUpdatePath = 1. GetXY(&iCol. SetExitJob(clipath. } erc = 0. 1. else erc = ErcBadParams. paCmds[i]. &cbCmd. if (ExitChar == 0x0d) ParseCmdLine(). cbCmd.0 -205- RICHARD A BURGESS . cbCmd = 0. /* set up all params */ if ((acbParam[0]) && (apParam[0])) { /* It's a command! */ i = 1.cbPath = strlen(syspath). while (i <= NCMDS) { j = strlen(paCmds[i]). NORMVID). TTYOut (">". &iLine). ' '). } i++. break. acbParam[1]. case COPYCMD: if ((acbParam[1]) && (acbParam[2])) erc = CopyFile(apParam[1]. &ExitChar. CmdNum = 0. 79. apParam[0] = 0.

GPMsg). erc = WaitMsg(GPExch. &GPHndl. default: break. break. aPath.0 -206- RICHARD A BURGESS . fUpdatePath = 1. 0. acbParam[1]). case MONCMD: erc = Request("KEYBOARD". case MAKEDIR: erc = CreateDir(apParam[1]. 0. if (!erc) printf("Done. 0. case DUMPCMD: erc = DoDump(). break. case TYPECMD: erc = DoType(apParam[1]. while (aCmd[i] != ' ') i++. case RUNCMD: if (acbParam[1]) { i = 2. 0). break. acbParam[1]. case REMDIR: erc = DeleteDir(apParam[1]. apParam[2]. 0. strlen(clipath)). break. SetVidOwner(1). case HELPCMD: erc = DoType(hlppath. } break. 4. break. &cbPath). acbParam[1]). MMURTL V1. GPExch. 0). 0). case EXITCMD: SetExitJob("". break. case DBGCMD: GoDebug(). erc = Chain(apParam[1]. ExitJob(0). SetExitJob(clipath. break. acbParam[1]). break. break.\r\n"). case PATHCMD: erc = SetPath(apParam[1]. 1. break. break.erc = DeleteFile(fh). else erc = ErcBadParams. case RENCMD: if ((acbParam[1]) && (acbParam[2])) erc = RenameFile(apParam[1]. 0. strlen(hlppath)). cbCmd-i). acbParam[1]). 0. case DIRCMD: erc = DoDir(). break. if (!erc) erc = GetPath(JobNum. SetCmdLine(&aCmd[i]. acbParam[1]. acbParam[2]).

The top line provides continuous status information. if (iLine >= 23) { ScrollVid(0.80. The editor always word-wraps lines that extend beyond 79 columns.2. The source code is listed below and also included on the accompanying CD-ROM.} CheckErc(erc). } GetXY(&iCol. and typing mode (Insert or Overtype). Cursor and Screen Management Commands Keystroke Page Down Page Up Up Down Left MMURTL V1. The last line is used for data entry.1. &iLine). What's On the CD-ROM. See Appendix A. Editor Screen The editor screen is divided into three sections. The next 23 lines are the text editing area.23). Table 16.0 Action Move one screen down in the text file Move one screen up in the text file Cursor up one line (scroll down if needed) Cursor down one line (scroll up if needed) Cursor left one column (no wrap) -207- RICHARD A BURGESS . File Management and General Commands Keystroke ALT-S ALT-C ALT-O ALT-Q ALT-X Insert Esc ALT-V Action Saves changes to current file Closes & prompts to Save current file Open a new file Quits (Exits) the editor Same as ALT-Q (Exit) Toggles Insert & Overtype mode Exits filename entry mode for open-file command Make non-text chars Visible/Invisible (toggle) Table 16. SetXY(0. The editor contains a good example of extensive use of the keyboard service (translating KeyCodes). Editor Commands The following four tables describe all the editor commands. such as the position of the cursor in the file (column and line).23. The largest file that can be edited is 128Kb.1). status and error display.1. } } } A Simple Editor The editor included with MMURTL is a very simple in-memory editor. current filename.

File system calls are also used in place of equivalent C library functions.h> #include <ctype. Editor Source Code /* Edit.c A simple editor using MMURTL file system and keyboard services */ #define U32 unsigned long #define S32 long #define U16 unsigned int #define S16 int #define U8 unsigned char #define S8 char #define TRUE 1 #define FALSE 0 #include <stdio.3Block Selection and Editing Commands Keystroke F3 F4 F2 F9 F10 ALT Delete Action Begin Block (Mark) End Block (Bound) Unmark block (no block defined or highlighted) MOVE marked block to current cursor position COPY marked block to current cursor position Delete Marked Block Table 16. Listing 16.h" MMURTL V1.h> /* Includes for OS public calls and structures */ #include "\OSSOURCE\MKernel.Right ALT-B ALT-E ALT-UP Arrow ALT-Down Arrow ALT-Left Arrow ALT Right Arrow Home End SHIFT Right SHIFT Left Cursor right one column (no wrap) Go to Beginning of Text Go to End of Text Cursor to top of screen Cursor to bottom of screen Cursor to beginning of line Cursor to end of line Cursor to beginning of line Cursor to end of line Move cursor right 5 spaces Move cursor left 5 spaces 16.h" #include "\OSSOURCE\MMemory. Listing 16.Miscellaneous Editing Keys Keystroke Delete Backspace Tab Action Delete character at current cursor position Destructive in Insert Mode (INS) Non-destructive in Overtype mode (OVR) Pseudo tab every 4 columns (space filled) Editor Source Listing The editor source is a good example of a program that used a lot of functionality form the keyboard service.h" #include "\OSSOURCE\MData.h> #include <string.2.0 -208- RICHARD A BURGESS .4 .2 is the editor source code. which provides good examples of checking for file system errors.

*pBuf2. U8 fVisible. /* For copy and move */ U32 Line[NLINESMAX].h" "\OSSOURCE\MVid. fh. /* Param 0 is cmd name */ long acbParam[13]. /* cursor. U32 iRowMax. b1.. char aStat[80]. /* Get our command line */ long cbCmd = 0. /* offset of next char in */ U32 oBufLast.h" "\OSSOURCE\MJob. void clearbuf(void).sLine-1 */ U32 iLine. U32 sLine.h" "\OSSOURCE\MKbd. U32 iTabNorm. char *pBuf1. U32 oBufBound.cLines-1 */ U32 oBufInsert. struct EditRecType *pEdit. char *apParam[13].h" EDVID NORMVID MARKVID STATVID BRITEWHITE|BGBLUE WHITE|BGBLACK WHITE|BGRED BLACK|BGCYAN #define EMPTY 99999 #define NLINESMAX 26 struct EditRecType { U8 *pBuf. /* sBuf . char fModified. long cbFilename. unsigned char b. struct EditRecType EdRec. 0.1 */ U32 iColMin. U32 oBufLine0 /* oBufLine0 */ U32 iCol. U32 iAttrNorm. long erc.h" "\OSSOURCE\MFiles. #define nParamsMax 13 char Filename[60].0 -209- RICHARD A BURGESS . char aStat1[80]. /* prototype for forward usage */ /********************************************************* MMURTL V1. /* offset+1 of last char */ U32 oBufMark. U32 iColMax. unsigned char filler[100].#include #include #include #include #include #define #define #define #define "\OSSOURCE\MTimer. /* Screen coords */ U32 iRowMin. char aCmd[80]. U8 *pBufWork. }.. U8 bSpace. char fOvertype. /* cursor. /* Offset in buf for 1st char in line */ U32 iBufMax. 0. U32 iAttrMark.

} return (erc). "Error break. 24. 80. case 7: sprintf(st. } /************************************************************* Saves a file you are editing. i++) if (!st[i]) st[i] = ' '. "Error break. switch (call) { case 1: sprintf(st. %05d occured on ReadKbd". long i. STATVID). %05d occured on SetFileSize". NORMVID). 0). "Error break. the file will be closed MMURTL V1. case 2: sprintf(st. } /********************************************************* Clears the status line with 80 blank chars. 40. %05d occured on last command". 24. %05d occured on OpenFile". "Error break. erc). %05d occured on CreateFile". *********************************************************/ void ClearStatus(void) { char st[80]. erc). PutVidChars (40. default: sprintf(st. erc). } for (i=0. erc). *********************************************************/ long CheckErc(long call. %05d occured on WriteBytes". 80. "Error break. erc). Beep(). %05d occured on ReadBytes". FillData(st.Displays errors if they occur for certain file opeations. 0). case 4: sprintf(st.0 -210- RICHARD A BURGESS . erc). "Error break. case 6: sprintf(st. this will prompt you to save. long erc) { char st[40]. erc). case 3: sprintf(st. case 5: sprintf(st. "Error break. st. %05d occured on SetFileLFA". PutVidChars (0. erc). if (erc) { FillData(st. st. If fPrompt is true. 39. "Error break. If fClose. i<40.

SetFileLFA(fh. clearbuf(). pBuff = pEdit->pBuf. i <=pEdit->iBufMax. cbFilename = 0. fh = 0. if (((keycode & 0xff) == 'N') || ((keycode & 0xff) == 'n')) { fYes = 0. SetFileSize(fh. 24). fModified = 0.. unsigned char *pBuff. 24. ClearStatus(). STATVID). BLACK|BGCYAN). if (!erc) erc = CheckErc(5. fYes. TTYOut("This file has been modified. pEdit->fVisible = FALSE. if (fPrompt) { ClearStatus(). 1). WriteBytes (fh. Sleep(150). if (!erc) erc = CheckErc(3. SAVE IT? (Y/N)". } } if (fYes) { erc = CheckErc(6. "DONE. **************************************************************/ void SaveFile(int fPrompt. pEdit->oBufLast)). keycode. 10.0 -211- RICHARD A BURGESS . if ((fh) && (fModified)) { if (pEdit->fVisible) { /* fix visible characters */ for (i=0. } } MMURTL V1. &i)). ClearStatus(). SetXY(0. } fYes = 1. pBuf1. ClearStatus(). i++) if (pBuff[i] == 0x07) pBuff[i] = 0x20. ". 0)). int fClose) { U32 i. pEdit->oBufLast. 43. } } if (fh && fClose) { CloseFile(fh). ReadKbd(&keycode. PutVidChars (0..and the buffer will be closed.

cbFilename. BLACK|BGCYAN). keycode. pEdit->oBufLast = dret. else erc = 0. Create?? (Y/N)". BLACK|BGCYAN). pBuf1. If not. 1). if (((keycode & 0xff) == 'Y') || ((keycode & 0xff) == 'y')) { erc = CheckErc(4. } else { b1=0x0d. 1. &fh). 1. ReadKbd(&keycode. &dret). cbFilename = 0. BLACK|BGWHITE). ReadKbd(&keycode. if (!erc) erc = CheckErc(1. PutVidChars (0. if (filesize < 131000) /* Buf is 131071 */ { erc = ReadBytes (fh. 26. TTYOut("File is too large to edit. MMURTL V1.24). it will prompts to create. &cbFilename. &filesize).24). 0)). erc = 0. strncpy(Filename. OpenFile(Filename. cbFilename. cbFilename. &fh)). ModeModify. 24). 24). "Filename: ". TTYOut("Doesn't exist. filesize. SetXY(50. EditLine(Filename.0). dret. } } else if (erc == 203) { /* no such file */ Beep(). } if ((b1==0x0d) && (cbFilename)) { erc = OpenFile(Filename. 29. Beep().24. 0. 60.". name. erc). cbFilename = strlen(Filename). **************************************************************/ void OpenAFile(char *name) { U32 filesize. SetXY(0. /* offset+1 of last char */ pBuf1[pEdit->oBufLast] = 0x0F. fh = 0. SetXY(10. if (!name) { SetXY(0. BLACK|BGCYAN). ModeModify. 13). &b1. if (erc > 1) erc = CheckErc(2. CreateFile(Filename. if (!erc) { GetFileSize(fh. SetXY(50. 1). 10.0 -212- RICHARD A BURGESS . /* the SUN */ } else { CloseFile(fh)./************************************************************* This prompts for a filename to open and opens it if it exists.

unsigned char *pBuff. i. cbFilename = 0. } } else CheckErc(1. *************************************************************/ unsigned long findEol (unsigned long iBuf) { unsigned long unsigned char iEol.if (erc) { fh = 0. return(nEols). i = 0. erc). iEolMax. even though we word wrap always. *************************************************************/ unsigned long CountEols (void) { unsigned long nEols. ABSOLUTE means LFs were found. /* Calculate the most it could be */ MMURTL V1. } /************************************************************ This counts ABSOLUTE LINES from the begining of the buffer upto to point of oBufLine0 (which is the first char displayed in the window. *pBuff.0 -213- RICHARD A BURGESS . } if (!erc) ClearStatus(). while (i < pEdit->oBufLine0) /* count LFs */ if (pBuff[i++] == 0x0A) nEols++. pBuff = pEdit->pBuf. pBuff = pEdit->pBuf. ClearStatus(). } /************************************************************ This returns the index to the the last character in a line upto a maximum of sLine-1. iBuf points to the beginning point in the buffer to find the end of line for. } } else { cbFilename = 0. nEols = 0.

if (iEolMax < pEdit->oBufLast) { /* now work back to last space */ while ((pBuff[iEol] != pEdit->bSpace) && (iEol > iBuf)) iEol--.0 -214- RICHARD A BURGESS ..iEolMax = iBuf + pEdit->sLine-1. pBuff = pEdit->pBuf. /* Fix it if EOL is past end of data */ if (iEolMax > pEdit->oBufLast) iEolMax = pEdit->oBufLast.1. j. while ((pBuff[iEol] != 0x0A) && (iEol < iEolMax)) /* Find CR */ iEol++. } /************************************************************ This walks back through the buffer looking for the logical end of a line. } } return(iEol). while ((i) && (pBuff[i] != 0x0A)) i--. iEol = iBuf..allows */ if ((iEol > iBuf) && (pBuff[iEol] == pEdit->bSpace) && /* wrap-around w/ double space */ (iEol == iEolMax)) { if ((pBuff[iEol-1] == pEdit->bSpace) || ((iEol == iEolMax) && (pBuff[iEol+1] == pEdit->bSpace))) { while ((pBuff[iEol] == pEdit->bSpace) && (iEol > iBuf)) iEol--. /* now find first non-space . if (oBufStart) i = oBufStart . } } if ((iEol == iBuf) && (pBuff[iBuf] > 0) && (pBuff[iEolMax] > 0)) iEol = iEolMax. */ iEol = iEolMax. if (i > 0) MMURTL V1. *************************************************************/ /* handles "all-char" of full line */ unsigned long findPrevLine (unsigned long oBufStart) { unsigned long i. char *pBuff. i = 0. if ((iEol == iEolMax) && (pBuff[iEol] != 0x0A)) { /* if no CR. while ((pBuff[iEol] != pEdit->bSpace) && (iEol > iBuf)) iEol--.

} else /*buf col*/ PutVidAttrs (pEdit->iColMin. i = (findEol (j)) + 1. /* Get to known start of line */ do { j = i. while ((i > 0) && (pBuff[i] != 0x0A)) i--. *************************************************************/ void doMark (unsigned long iLn) { unsigned long iColStart. iBoundLoc = pEdit->oBufMark.pEdit->Line[iLn]. pEdit->iAttrNorm). iColStart-pEdit->iColMin. else iColFinish = pEdit->iColMin + pEdit->Line[iLn+1] . if (iBoundLoc < pEdit->Line[iLn+1]) iColFinish = pEdit->iColMin + iBoundLoc . MMURTL V1.1. if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufMark <= pEdit->oBufBound) { iMarkLoc = pEdit->oBufMark. } /************************************************************ This executes the BEGIN BLOCK (Mark) command. return(j). pEdit->iAttrNorm).pEdit->Line[iLn].0 -215- RICHARD A BURGESS .pEdit->Line[iLn] . } else { iMarkLoc = pEdit->oBufBound. } while (i < oBufStart). else iColStart = pEdit->iColMin. pEdit->iColMax . iLn. iMarkLoc. if (iColStart > pEdit->iColMin) PutVidAttrs (pEdit->iColMin. iLn.iColStart +1. iBoundLoc. PutVidAttrs (iColStart. pEdit->iAttrMark). iLn. iColFinish . } if ( ((iMarkLoc >= pEdit->Line[iLn]) && (iMarkLoc < pEdit->Line[iLn+1])) || ((iBoundLoc >= pEdit->Line[iLn]) && (iBoundLoc < pEdit->Line[iLn+1])) || ((iMarkLoc < pEdit->Line[iLn]) && (iBoundLoc >= pEdit->Line[iLn+1])) ) { if (iMarkLoc >= pEdit->Line[iLn]) iColStart = pEdit->iColMin + iMarkLoc . iBoundLoc = pEdit->oBufBound.i--. if (i) i++. iColFinish. if (iColFinish < pEdit->iColMax) PutVidAttrs (iColFinish+1.iColFinish.

*************************************************************/ MMURTL V1. } } /************************************************************ This inserts data into the main editing buffer. &pBuff[pEdit->oBufInsert+1]. } } } else { fOK = 0. char fOK. char fSpecInsert) { unsigned long cb. pEdit->oBufLast++.iLn.pEdit->oBufInsert + 1. } else { cb = pEdit->oBufLast . cb). Beep(). erc = 40400. pEdit->oBufInsert++. pBuff = pEdit->pBuf. } return (fOK). } /************************************************************ This executes the MOVE command which moves a marked block to the cursor's current location in the file. *************************************************************/ char putInBuf( unsigned char bPutIn. pEdit->iAttrNorm). if ((fOvertype) && (!fSpecInsert)) { pBuff[pEdit->oBufInsert] = bPutIn. fModified = 1. pBuff[pEdit->oBufInsert] = bPutIn. if (pEdit->oBufLast == pEdit->oBufInsert) pEdit->oBufLast++. *pBuff. if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufInsert-1 < pEdit->oBufMark) pEdit->oBufMark++. pEdit->sLine.0 -216- RICHARD A BURGESS . pEdit->pBufWork. if (pEdit->oBufInsert-1 <= pEdit->oBufBound) pEdit->oBufBound++. cb). CopyData (pEdit->pBufWork. pEdit->oBufInsert++. CopyData (&pBuff[pEdit->oBufInsert]. if ((pEdit->oBufInsert < pEdit->iBufMax) && ((pEdit->oBufLast < pEdit->iBufMax) || ((fOvertype) && (!fSpecInsert)))) { fOK = 1. char fOvertype.

0 -217- RICHARD A BURGESS . i <=iBd-iMk. if (pEdit->oBufMark < EMPTY) { fModified = 1. } if (pEdit->oBufInsert > iBd) { for (i=0. iMk.iBd + iMk .1. pEdit->oBufBound = iMk. i++) pBuff[pEdit->oBufInsert+i] = pBuffWork[iMk+i]. } else { pEdit->oBufMark = iBd.1.1. i<=iBd-iMk. i++) /* Shift overwritten ahead */ pBuff[pEdit->oBufInsert+iBd-iMk+1+i] = pBuffWork[pEdit->oBufInsert+i]. pEdit->oBufInsert = pEdit->oBufInsert . i++) pBuffWork[i] = pBuff[i]. i<=iMk . } /************************************************************ This executes the COPY command which copies a marked block to the cursor's current location in the file. if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark. if (pEdit->oBufBound > pEdit->oBufMark) { pEdit->oBufBound = iBd. i++) /* Move mk/bd */ pBuff[pEdit->oBufInsert+i] = pBuffWork[iMk+i]. *pBuffWork. for (i=0. *************************************************************/ void CopyIt (void) { unsigned long iMk.iBd .void moveData (void) { unsigned long i. iBd. pEdit->oBufMark = iMk.pEdit->oBufInsert . pBuffWork = pEdit->pBufWork. iBd = pEdit->oBufBound. for (i=0. pBuff = pEdit->pBuf. iMk = pEdit->oBufInsert. } else { iBd = pEdit->oBufMark. iBd. } } } else Beep(). i <= pEdit->oBufLast.iMk. } iBd = pEdit->oBufInsert + iBd . iMk = pEdit->oBufBound. MMURTL V1. if (pEdit->oBufInsert < iMk) { for (i=0. i++) pBuff[iMk+i] = pBuffWork[iBd+1+i]. char *pBuff. pEdit->oBufInsert . } if ((pEdit->oBufInsert < iMk) || (pEdit->oBufInsert > iBd)) { for (i=0.

if (pEdit->oBufBound > pEdit->oBufMark) { pEdit->oBufBound = iBd. *pBuffWork. pBuffWork = pEdit->pBufWork. i <= pEdit->iRowMax.iMk + 1. iBd = pEdit->oBufBound. CopyData(&pBuffWork[iMk]. if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark. } else { pEdit->oBufMark = iBd.iMk. pEdit->iAttrNorm). } } } else Beep(). pEdit->oBufLast = pEdit->oBufLast + iBd .iMk + 1. iBd = pEdit->oBufInsert + iBd . *************************************************************/ void normAttr (void) { unsigned long i. pBuff = pEdit->pBuf.pEdit->oBufInsert+1). (hides it).0 -218- RICHARD A BURGESS . } /************************************************************ This unmarks a selected block.char *pBuff. if (pEdit->oBufLast >= pEdit->oBufInsert) CopyData(&pBuffWork[pEdit->oBufInsert]. pEdit->sLine. if (pEdit->oBufMark < EMPTY) { fModified = 1. i. pBuffWork. pEdit->oBufInsert = pEdit->oBufInsert + iBd . i++) PutVidAttrs (pEdit->iColMin. MMURTL V1. &pBuff[pEdit->oBufInsert]. iMk = pEdit->oBufBound. *************************************************************/ void nullMarkBound (void) { pEdit->oBufMark = EMPTY. iMk = pEdit->oBufInsert. pEdit->oBufLast+1). pEdit->oBufBound = EMPTY. pEdit->oBufBound = iMk. for (i = pEdit->iRowMin. } else { iBd = pEdit->oBufMark. } if (pEdit->oBufLast+iBd-iMk+1 < pEdit->iBufMax) { CopyData(pBuff. pEdit->oBufLast . &pBuff[pEdit->oBufInsert+iBd-iMk+1]. iBd-iMk+1). } /************************************************************ This executes the COPY command which copies a marked block to the cursor's current location in the file. pEdit->oBufMark = iMk.

1. pBuff = pEdit->pBuf. *************************************************************/ void findCursor (void) { /* locates cursor based on oBufInsert . else fProb = FALSE.might be off screen . pEdit->oBufLast = pEdit->oBufLast . iBd. j. this will adjust screen */ unsigned long i. i = pEdit->iRowMin.normAttr (). &pBuff[iMk]. CopyData(&pBuff[iBd+1]. iBd = pEdit->oBufBound. } else { iBd = pEdit->oBufMark. if (fProb) { i = findPrevLine (pEdit->oBufInsert). this finds the proper location of the cursor in relationship to the portion of the file currently displayed. pEdit->oBufLast-iBd). *************************************************************/ void deleteData (void) { unsigned long i. pBuffWork = pEdit->pBufWork. if (pEdit->oBufInsert > iBd) pEdit->oBufInsert = pEdit->oBufInsert . } /************************************************************ This DELETES a selected block. while ((i <= pEdit->iRowMax) && (pEdit->oBufInsert >= pEdit->Line[i])) MMURTL V1. } if ((pEdit->oBufLine0 >= iMk) && (pEdit->oBufLine0 <= iBd)) fProb = TRUE.iBd + iMk. } } /************************************************************ After screen movement (such as scrolling). else if ((pEdit->oBufInsert > iMk) && (pEdit->oBufInsert <= iBd)) pEdit->oBufInsert = iMk. iMk = pEdit->oBufBound. if (pEdit->oBufInsert > pEdit->oBufLast) pEdit->oBufInsert = pEdit->oBufLast. iMk. pEdit->oBufLine0 = i.if it is. *pBuffWork. if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark. } nullMarkBound (). char *pBuff. if (pEdit->oBufMark < EMPTY) { fModified = 1.iBd + iMk . char fProb.0 -219- RICHARD A BURGESS .

find last good line */ while ((pEdit->Line[i] == EMPTY) && (i > pEdit->iRowMin)) { pEdit->iLine--. j = pEdit->iLine. If oBufInsert is not onscreen. if prev char <> CR. } } MMURTL V1.is. } i = pEdit->iLine. if ((pEdit->oBufInsert >= pEdit->oBufLine0) && (pEdit->oBufInsert < pEdit->Line[i])) { /* if bogus line. j = pEdit->iLine. if ((pEdit->Line[j+1] < EMPTY) && (pEdit->oBufInsert >= pEdit->Line[j+1])) pEdit->iLine = pEdit->iLine + 1. oBuf = pEdit->Line[i+1]. if (pEdit->iLine < pEdit->iRowMin) pEdit->iLine = pEdit->iRowMin.i++. this makes it so it is! *************************************************************/ void coordCursor_oBuf (void) { unsigned long oBuf. /* if bogus line. pEdit->oBufInsert = pEdit->Line[i] + pEdit->iCol . i = pEdit->iRowMax+1. if (pEdit->iLine > pEdit->iRowMax + 1) pEdit->iLine = pEdit->iRowMax. if (pEdit->oBufInsert > pEdit->oBufLast) pEdit->oBufInsert = pEdit->oBufLast. pEdit->iLine = i .0 -220- RICHARD A BURGESS . guarantee end of good */ i = pEdit->iLine. i = pEdit->iLine.1. if (pEdit->Line[i] == EMPTY) pEdit->iCol = pEdit->iColMax.pEdit->iColMin. pEdit->iCol = pEdit->oBufInsert + pEdit->iColMin pEdit->Line[i]. /* get to potential insert . i. } /************************************************************ This readjusts iCol & iLine to get them back in sync with oBufInsert if oBufInsert is on screen. pEdit->iCol = pEdit->oBufInsert . is not if prev = CR */ if (pEdit->oBufInsert == oBuf) /* if at EOL */ if (pEdit->pBuf[oBuf-1] == 0x0A) pEdit->oBufInsert--. if (pEdit->oBufInsert > oBuf) pEdit->oBufInsert = oBuf.pEdit->Line[j] + pEdit->iColMin.

i <= pEdit->iRowMax. oBuf. } } /************************************************************ Redisplay all data on the screen. This also sets all of the Line array values (Line[n]) *************************************************************/ void makeOnScreen (void) { unsigned long i. iLn. pEdit->oBufLine0 = pEdit->Line[j]. /* Set Line[iRowMin] to match oBufLine0 */ k = pEdit->iRowMin. j. *************************************************************/ void showScreen (char *pFiller) { unsigned long i. i++) { if (pEdit->Line[i] < EMPTY) { j = findEol (pEdit->Line[i]). else pEdit->Line[k+1] = EMPTY. pEdit->Line[k] = pEdit->oBufLine0. /* Set all subsequent Line[s] by calling findEol for each. k. k = pEdit->iRowMax. if (j < pEdit->oBufLast) /* j = offset of last char of line */ pEdit->Line[i+1] = j + 1. j++) pEdit->Line[j] = EMPTY. cb. } } /* If the InsertPoint (your cursor position) is past the last line then do this junk to fix it */ j = pEdit->iRowMin. j<NLINESMAX. /* If oBufInsert is not on screen (above current display) then find the previous line beginning and make that the new first line 1 until it is! */ while (pEdit->oBufInsert < pEdit->oBufLine0) pEdit->oBufLine0 = findPrevLine (pEdit->oBufLine0). MMURTL V1. */ for (i = k.0 /* EOL of iRowMax */ /* i = offset of last char of line */ -221- RICHARD A BURGESS . i = findEol (pEdit->Line[k]). i<=k. while (pEdit->oBufInsert >= pEdit->Line[k+1]) { for (i=j./************************************************************ Adjusts oBufLine0 to make sure that oBufInsert is on the screen. i++) pEdit->Line[i] = pEdit->Line[i+1]. else for (j=i+1. if (i < pEdit->oBufLast) pEdit->Line[k+1] = i + 1.

cb. i = pEdit->iRowMin. pEdit->iAttrNorm). else i = pEdit->oBufLast. i<NLINESMAX. pEdit->iLine = pEdit->iRowMin. pEdit->Line[i] = 0. pEdit->fVisible = FALSE. 0x20).oBuf + 1. cb = i . } } /************************************************************ Resets all variables for the editor and clears the buffers. Called before use and after closure. pBuff = pEdit->pBuf. FillData(filler. pFiller. not offset */ if ((!pEdit->fVisible) && (pBuff[i] == 0x0A) && (cb)) cb--. pBuff = pEdit->pBuf. for (i=0. *************************************************************/ void clearbuf (void) { unsigned long i. doMark (iLn). i++) pEdit->Line[i] = EMPTY.0 -222- RICHARD A BURGESS . makeOnScreen (). iLn++) { /* i = offset in buf of last char on line */ /* cb = nchars in line */ cb = 0. oBuf = pEdit->Line[iLn]. iLn <= pEdit->iRowMax. pEdit->iCol = pEdit->iColMin.Line correct */ for (iLn = pEdit->iRowMin. /* oBufInsert on screen . 80. char *pBuff. if (cb < pEdit->sLine) PutVidChars (pEdit->iColMin+cb. pEdit->iAttrNorm). iLn. iLn. pEdit->sLine-cb. if (oBuf < EMPTY) { if (pEdit->Line[iLn+1] < EMPTY) i = pEdit->Line[iLn+1] . MMURTL V1. } if ((cb) && (oBuf < EMPTY)) PutVidChars (pEdit->iColMin. pBuff+oBuf. /* Make size. pEdit->bSpace = 0x20.1.char *pBuff.

pEdit->iLine). *pBuffWork.0 /* the SUN */ -223- RICHARD A BURGESS . while (!fDone) { if (fScreen) { showScreen (filler). FillData(aStat. normAttr ()."C: %02d L: %05d nChars: %05d". long exch. if (cbFilename) MMURTL V1. for (i=0. /* TRUE = display entire screen */ char fDone. 0x20). char fSpecInsert. i <=pEdit->oBufLast. pEdit->oBufInsert = 0. pBuff[pEdit->oBufLast] = 0x0F. pEdit->iCol. fScreen = FALSE. pBuffWork = pEdit->pBufWork. i. It is a HUGE while loop which reads keystrokes and processes them. nullMarkBound().fModified = 0. normAttr (). k. } /* the SUN */ /************************************************************ This is the main editing function. if (pEdit->fVisible) { pEdit->bSpace = 0x07. /* we know oBufInsert on screen */ findCursor (). unsigned char b. sprintf(aStat. /* TRUE = insert no matter what fOvertype is */ char fScreen. i++) if (pBuff[i] == 0x20) pBuff[i] = pEdit->bSpace. pEdit->oBufLine0 = 0. pEdit->oBufLast). char *pBuff. pEdit->oBufLast = 0. fOvertype = FALSE. key. *************************************************************/ void Editor(char *pbExitRet) { unsigned long i. pBuff = pEdit->pBuf. fScreen = TRUE. j. fDone = FALSE. 80. } else pEdit->bSpace = 0x20. } SetXY (pEdit->iCol. i= CountEols() + pEdit->iLine. pBuff[pEdit->oBufLast] = 0x0F. erc = AllocExch(&exch).

break. 1)). pEdit->oBufInsert = 0. case 0x43: /* ALT-C -.Cursor to EOL */ i = pEdit->iLine. pEdit->iCol = pEdit->iColMin. case 0x04: /* ALT-Right Arrow . fScreen = TRUE. case 0x45: /* ALT-E -. coordCursor_oBuf (). while ((pEdit->Line[i+1] < EMPTY) && (i < pEdit->iRowMax)) { pEdit->iLine++. break.End of Text */ case 0x65: /* ALT-e */ pEdit->oBufInsert = pEdit->oBufLast. } pEdit->iCol = pEdit->iColMax. fSpecInsert = FALSE.Cursor to bottom of screen */ pEdit->iLine = pEdit->iRowMin. if (fOvertype) CopyData("OVR". i = pEdit->iLine. case 0x03: /* ALT-Left Arrow . break.Cursor to BOL */ pEdit->iCol = pEdit->iColMin.Cursor to top of screen */ pEdit->iLine = pEdit->iRowMin. 3).CloseFile */ case 0x63: /* ALT-c */ SaveFile(TRUE. break. 0.0 -224- RICHARD A BURGESS . aStat. STATVID). fScreen = TRUE. break. &aStat[77]. else CopyData("INS". b = key & 0xff. &aStat[40]. pEdit->iLine = pEdit->iRowMin. &aStat[77]. ReadKbd (&key. case 0x02: /* ALT Down Arrow . 80. i = pEdit->iLine. if (pEdit->Line[i+1] < EMPTY) { pEdit->iCol = pEdit->iColMin + MMURTL V1. /* True if char should be inserted even if fOver */ /* Wait for char */ CheckErc(7. cbFilename). if (key & 0x3000) { switch (b) { /* ALT key is down */ case 0x42: /* ALT-B -. coordCursor_oBuf (). PutVidChars (0. 3). pEdit->iCol = pEdit->iColMin.Beginning of Text */ case 0x62: /* ALT-b */ pEdit->oBufLine0 = 0. fScreen = TRUE.CopyData(Filename. case 0x01: /* ALT-UP Arrow . TRUE). break.

i = pEdit->iLine+1. case 0x51: /* ALT Q . case 0x4F: /* ALT-O OpenFile */ case 0x6F: /* ALT-o */ if (!fh) { clearbuf(). fScreen = TRUE. } fScreen = TRUE. OpenAFile(0).pEdit->Line[i]. FALSE). } else { for (i=0. break. pEdit->bSpace = 0x07. fScreen = TRUE. break. i++) if (pBuff[i] == 0x20) pBuff[i] = 0x07. } break.pEdit->Line[i+1] pEdit->Line[i]. case 0x7F: /* ALT Delete (Delete Marked Block) */ if (pEdit->oBufMark < EMPTY) { deleteData ().0 -225- RICHARD A BURGESS . } break. fDone = TRUE. case 0x56: /* ALT-V Make nontext chars Visible/Invisible */ case 0x76: /* ALT-v */ if (pEdit->fVisible) { for (i=0. } } else if (key & 0x0300) { /* CTRL key is down */ MMURTL V1.SaveFile */ case 0x73: /* ALT s */ SaveFile(FALSE. pEdit->bSpace = 0x20. TRUE). fScreen = TRUE. pEdit->fVisible = FALSE. i<=pEdit->oBufLast. break. pEdit->fVisible = TRUE. i <= pEdit->oBufLast. if ((pBuff[pEdit->Line[i]-1] == 0x0A) && (pEdit->iCol > pEdit->iColMin)) pEdit->iCol--.Quit */ case 0x71: SaveFile(TRUE. break. i++) if (pBuff[i] == 0x07) pBuff[i] = 0x20. default: break. case 0x53: /* ALT S . } else pEdit->iCol = pEdit->iColMin + pEdit->oBufLast .

pEdit->oBufLast+1).1. else if (pEdit->iCol > pEdit->iColMin) pEdit->iCol--. default: break. break. /* Don't overwrite CR */ if (pBuff[pEdit->oBufInsert] == 0x0A) fSpecInsert = TRUE. fScreen = TRUE. if (b == 0x0D) b = 0x0A. pBuffWork. if ((pEdit->oBufMark == pEdit->oBufBound) && (pEdit->oBufMark == pEdit->oBufInsert)) nullMarkBound (). CopyData(&pBuffWork[pEdit->oBufInsert+1]. if (!fOvertype) { CopyData(pBuff.5) pEdit->iCol += 5. } else if (key & 0x0C00) { /* SHIFT key is down & NOT letter keys */ switch (b) { case 0x04: /* SHIFT Right */ if (pEdit->iCol < pEdit->iColMax . case 0x03: /* SHIFT Left */ if (pEdit->iCol > pEdit->iColMin + 5) pEdit->iCol -= 5. if (!(putInBuf (b. fSpecInsert))) Beep().} /* Standard editing keys including LF */ else if (((b >= 0x20) && (b <= 0x7E)) || (b == 0x0D)) { coordCursor_oBuf (). break. pEdit->oBufLast = pEdit->oBufLast . &pBuff[pEdit->oBufInsert]. else if (pEdit->iCol < pEdit->iColMax) pEdit->iCol++. if (pEdit->oBufInsert) { pEdit->oBufInsert = pEdit->oBufInsert .0 -226- RICHARD A BURGESS . } } else { /* Unshifted editing keys */ switch (b) { case 0x08: /* Backspace */ if (pEdit->oBufLast) { coordCursor_oBuf (). MMURTL V1. fOvertype. pEdit->oBufLast-pEdit->oBufInsert). findCursor (). if (b == 0x20) b = pEdit->bSpace.1. pBuff[pEdit->oBufLast] = 0.

case 0x17: /* F9 . } break.if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufInsert <= pEdit->oBufMark) pEdit->oBufMark--. if (pEdit->oBufInsert <= pEdit->oBufBound) pEdit->oBufBound--. pEdit->oBufBound = pEdit->oBufMark. fScreen = TRUE. case 0x12: /* F4 -.Cursor to BOL */ pEdit->iCol = pEdit->iColMin. case 0x11: /* F3 -. i++) putInBuf (pEdit->bSpace. } break. if (pEdit->oBufMark == pEdit->oBufLast) pEdit->oBufMark = pEdit->oBufLast . break. i = pEdit->iLine. i <=j. } break. case 0x06: /* Home . case 0x10: /* F2 -.0 -227- RICHARD A BURGESS . if (pEdit->oBufBound == pEdit->oBufLast) pEdit->oBufBound = pEdit->oBufLast . } } } if (pEdit->oBufInsert < pEdit->oBufLine0) pEdit->oBufLine0 = findPrevLine (pEdit->oBufLine0).Begin Block */ if (pEdit->oBufLast > 0) { coordCursor_oBuf ().UNMARK BLOCK */ nullMarkBound (). FALSE). fScreen = TRUE. pEdit->oBufBound = pEdit->oBufInsert.End Block */ if (pEdit->oBufMark < EMPTY) { coordCursor_oBuf (). if (pEdit->oBufMark >= pEdit->Line[i+1]) pEdit->oBufMark = pEdit->oBufMark .1. if (pEdit->oBufInsert < pEdit->oBufLine0) pEdit->oBufLine0 = pEdit->oBufInsert. case 0x09: /* Tab */ if (pEdit->oBufLast + pEdit->iTabNorm < pEdit->iBufMax) { coordCursor_oBuf (). i = pEdit->iLine. pEdit->oBufMark = pEdit->oBufInsert. fScreen = TRUE.1.MOVE */ coordCursor_oBuf (). break. j = pEdit->iTabNorm . if (pEdit->oBufBound >= pEdit->Line[i+1]) pEdit->oBufBound--.(pEdit->iCol % pEdit->iTabNorm). moveData (). FALSE. for (i=1. } break.1. fModified = TRUE. MMURTL V1. fScreen = TRUE. fScreen = TRUE.

} } break. fScreen = TRUE.break. CopyIt (). k = pEdit->iLine. coordCursor_oBuf (). case 0x02: /* Down */ i = pEdit->iLine. case 0x0C: /* Page Down */ coordCursor_oBuf (). /*keep on screen*/ i = pEdit->iRowMax. /*fix for scrolling when iLine=iRowMax */ do { i = findPrevLine (i). if ((pEdit->Line[i+1] < EMPTY) && /*Down Arrow*/ (i < pEdit->iRowMax)) { MMURTL V1. if (pEdit->oBufInsert >= pEdit->Line[i+1]) pEdit->oBufInsert = pEdit->Line[i]. while ((pEdit->Line[i] == EMPTY) && (i > pEdit->iRowMin)) i--. i = pEdit->oBufLine0. /*fix for scroll when iLine=iRowMax*/ if (pEdit->iLine == pEdit->iRowMax) pEdit->oBufInsert = pEdit->Line[k]. case 0x01: /* Up */ if (pEdit->iLine > pEdit->iRowMin) { pEdit->iLine--. pEdit->oBufLine0 = i.pEdit->iRowMin.0 -228- RICHARD A BURGESS . /*always keep onScreen*/ if (pEdit->oBufInsert < pEdit->oBufLine0) pEdit->oBufInsert = pEdit->oBufLine0. pEdit->oBufLine0 = i. pEdit->oBufLine0 = pEdit->Line[i]. j = pEdit->iRowMax . fScreen = TRUE. } break. pEdit->iLine = pEdit->iRowMin. if (i > 0) { i = findPrevLine (i). j--. break. } else { /* scroll screen down if we can */ i = pEdit->oBufLine0. case 0x05: /* Page Up */ if (pEdit->oBufLine0) { coordCursor_oBuf (). break. pEdit->oBufInsert = i.COPY */ coordCursor_oBuf (). fScreen = TRUE. fScreen = TRUE. pEdit->iCol = pEdit->iColMin. i = pEdit->iRowMax. k--. } while ((j > 0) && (i > 0)). case 0x18: /* F10 .

pEdit->oBufLast--. pEdit->iLine = j. } break. else fOvertype = TRUE.0 -229- RICHARD A BURGESS . pEdit->iCol = i. } fScreen = TRUE. if (pEdit->oBufInsert < pEdit->oBufBound) pEdit->oBufBound--. case 0x04: /* Right */ if (pEdit->iCol < pEdit->iColMax) { pEdit->iCol++. &pBuff[pEdit->oBufInsert]. if ((pEdit->oBufLast) && (pEdit->oBufLast > pEdit->oBufInsert)) { CopyData(pBuff. if (pEdit->Line[i+1] < EMPTY) { pEdit->oBufInsert = pEdit->Line[i+1]. j = pEdit->iLine. case 0x0E: /* Insert */ if (fOvertype) fOvertype = FALSE. if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufInsert < pEdit->oBufMark) pEdit->oBufMark--.pEdit->iLine++. CopyData(&pBuffWork[pEdit->oBufInsert+1]. fScreen = TRUE. pBuffWork. if ((pEdit->oBufInsert == pEdit->oBufMark) && (pEdit->oBufMark == pEdit->oBufBound)) nullMarkBound (). } break. fModified = TRUE. case 0x7F: /* Delete */ coordCursor_oBuf (). pEdit->oBufLast-pEdit->oBufInsert). } else { /* ELSE scroll screen UP if we can */ i = pEdit->iRowMax. default: MMURTL V1. pEdit->oBufLast+1). break. } break. if (pEdit->oBufMark == pEdit->oBufLast) pEdit->oBufMark--. case 0x03: /* Left */ if (pEdit->iCol > pEdit->iColMin) { /*Left Arrow*/ pEdit->iCol--. if (pEdit->oBufBound == pEdit->oBufLast) pEdit->oBufBound--. } } break. pBuff[pEdit->oBufLast] = 0. i = pEdit->iCol. coordCursor_oBuf ().

/* REM buffer column */ if (fh) { if (pEdit->fVisible) /* fix visible characters */ for (i=0. pEdit = &EdRec. fh = 0. 0)). pBuf1. erc = AllocPage(32. if (fModified) { erc = CheckErc(6. &pBuf1). pEdit->pBuf pEdit->pBufWork pEdit->iBufMax pEdit->iColMin pEdit->iColMax pEdit->iRowMin pEdit->iRowMax pEdit->sLine pEdit->bSpace pEdit->fVisible MMURTL V1. pBuff[pEdit->oBufLast] = 0.break. 23. pEdit->oBufLast. erc = AllocPage(32. &pBuf2). 80. if (!erc) erc = CheckErc(3.0 /* 32 pages = 128K */ = = = = = = = = = = pBuf1. SetFileSize(fh. pEdit->sLine+1. if (!erc) erc = CheckErc(5.1 */ /*Screen coordinates*/ /* iColMax-iColMin+1 */ -230- RICHARD A BURGESS . fModified = 0. FALSE. *************************************************************/ void main(U32 argc. } /************************************************************ This is the main entry point for the editor. 0. } *pbExitRet = b. i <=pEdit->iBufMax. WriteBytes (fh. SetJobName("Editor".pEdit->oBufLast)). i++) PutVidAttrs (pEdit->iColMin. /*sBuf . pBuf2. 0x20. U8 *argv[]) { long i. 79. } CloseFile(fh). and then checks for a single parameter which should be the name of the file to edit. SetFileLFA(fh. It allocates two buffers of equal size (a main and a working buffer). i <=pEdit->iRowMax. } } } /* Not fDone */ for (i=pEdit->iRowMin. return. i. ClrScr(). 131071. 0). 6). i++) if (pBuff[i] == 0x07) pBuff[i] = 0x20. &i)). cbFilename = 0. 1.

ExitJob(0).h" #include "\OSSOURCE\RS232. DumbTerm source code #include <stdio. 0. 0.sLine-1 */ cursor. Listing 16. 0. EMPTY. 0x20). It's included here to demonstrate the interface to the RS-232 communications device driver. 0.h" #include "\OSSOURCE\MTimer. i. /*****************************************************/ void main(void) { int erc. EMPTY.3. fOvertype = FALSE.h" #include "\OSSOURCE\MKbd. MMURTL V1. for (i=0.pEdit->iAttrMark pEdit->iAttrNorm pEdit->iTabNorm pEdit->oBufLine0 pEdit->iCol pEdit->iLine pEdit->oBufInsert pEdit->oBufLast pEdit->oBufMark pEdit->oBufBound SetNormVid(NORMVID).h> #include <ctype. = = = = = = = = = = MARKVID. EDVID... /* Set Overtype OFF */ if (argc > 1) { OpenAFile(argv[1]). i = pEdit->iRowMin.h" #include "\OSSOURCE\MVid. 0. 4. } Editor(&b). struct statRecC com. pEdit->Line[i] = 0. 0.0 -231- RICHARD A BURGESS . 80. i<NLINESMAX. fModified = 0. /* /* /* /* /* /* /* /* Rev Vid*/ Rev Vid Half Bright Tabs every 4th column oBufLine0 */ cursor.h> #include "\OSSOURCE\MDevDrv.h" #define NORMVID BRITEWHITE|BGBLUE #define CLIVID WHITE|BGBLACK unsigned long key.h> #include <string. i++) pEdit->Line[i] = EMPTY.h" #include "\OSSOURCE\MJob.cLines-1 offset of next char in offset+1 of last char */ */ */ */ */ FillData(filler. 0. } DumbTerm DumbTerm is possibly the "dumbest" communications terminal program in existence.

printf("Error on Device Init: %d\r\n".stopbits = 9600. %d\r\n".0 -232- RICHARD A BURGESS . /* View other params which we could set.parity = com. %d\r\n". pData */ erc = DeviceOp(6. = 1. com. if (erc) { MMURTL V1. &com. ExitJob(erc).unsigned char b.XBufSize). &i). SetNormVid(NORMVID). dLBA. } /* set the params in the block */ com. dOpNum.IRQNum). if (erc) { SetNormVid(CLIVID). if (erc) { SetNormVid(CLIVID). %d\r\n". (MMURTL Comms Device Driver demo) \r\n"). com. ClrScr(). ClrScr(). com.RBufSize). The structure is defined in commdrv. but should already be defaulted with standard values when driver was initialized.RTimeOut). com. &i).XTimeOut).Baudrate com. 0. /* Get the 64 byte device status block which is specific to the RS-232 device driver. ClrScr(). lastb. 0.IOBase). /* Set the params we changed with a DeviceInit */ erc = DeviceInit(6.databits com. com. com. 64. &com. printf("Error on Device Stat: %d\r\n". ExitJob(erc). NO_PAR. erc). %d\r\n". dnBlocks. char fOK. printf(" printf(" Terminally DUMB. Dumb Terminal Program\r\n"). %d\r\n".h */ erc = DeviceStat(6. erc). } /* If device init went OK. 64). */ printf("IRQNum: printf("IOBase: printf("sXBuf: printf("sRBuf: printf("RTimeO: printf("XTimeO: %d\r\n". we open the comms port */ /* device. = 8. CmdOpenC.

CmdWriteB. 1. } } else { /* device. pData */ erc = DeviceOp(6.\r\n"). */ if ((lastb == 0x0D) && (b != 0x0A)) TTYOut ("\n". 0. if (!erc) { TTYOut (&b. erc). &b). printf("OpenCommC ERROR: %d \r\n". 0. dnBlocks. break. NORMVID).SetNormVid(CLIVID).. if (erc) printf("WriteByteCError: %d \r\n". ExitJob(erc). dLBA. &b). CmdCloseC. } printf("Communications Port Initialized. 1. NORMVID)... 0. dOpNum. dOpNum. 0. } } } } /* device. 0)) { b = key & 0x7f. SetNormVid(CLIVID). dnBlocks. erc). ClrScr(). &i). 0. 0. default: break. &b). pData */ erc = DeviceOp(6. dOpNum. dLBA. lastb = b.. /* This is it. fOK = 1. } } } MMURTL V1. else { if (b == 0x0D) { b = 0x0A. erc = DeviceOp(6. */ while (fOK) { if (!ReadKbd(&key. CmdReadB. /* no wait */ if (key & 0x3000) { /* ALT key is down */ switch (toupper(b)) { case 'Q' : /* device. /* add a LF if it's not there after a CR. dLBA.0 -233- RICHARD A BURGESS . CmdWriteB. 0. ClrScr(). 0. pData */ erc = DeviceOp(6. dnBlocks. ExitJob(erc).

char name[80].h> "\OSSource\MDevDrv.Suppress the Form Feed sent by the Print command \B .4.2. The differences handling different devices will be the values in the status record and also and commands that are specific to a driver. notice the device-driver interface is almost identical for each device. fDisplay = 0.h> <ctype.h" "\OSSource\MVid. The Print program formats a text file and sends it to the LPT device. Print source code /* A simple program that prints a single file directly using the Parallel Device Driver in MMURTL (Device No.4 or 8) \D .h" "\OSSource\MTimer. Listing 16.h> <stdlib. It recognizes the follow command-line switches: \n .h> <string.h" "\OSSource\Parallel.h> <stdio.h" "\OSSource\MJob.0 -234- RICHARD A BURGESS . Send with no translations. NoFF = 0.Print The print program is an example of using the device-driver interface for the parallel LPT device driver.Display the file while printing \F .h" "\OSSource\MKbd. In listing 16. col = 0. long long long long long tabstops = 4. /*****************************************************/ MMURTL V1.Expand tabs to n space columns (n = 1.Binary print.h" FF LF CR TAB 0x0C 0x0A 0x0D 0x09 unsigned long key. That's the intention of the design.4. (Compare DumbTerm and this program). FILE *f. 3 "LPT") */ #include #include #include #include #include #include #include #include #include #include #include #define #define #define #define <stdio. fBinary = 0. Print also converts single-line feeds to CR/LF. struct statRecL lpt.

printf("/F no FormFeed at end of file\r\n"). lastb.Tab stop translation value\r\n"). default: printf("Invalid switch"). no FF\r\n\n").0 -235- RICHARD A BURGESS . printf("Usage: Filename /1 /2 /4 /8 /F /D /B\r\n"). argv[i]. printf("/D Display file while printing\r\n"). char fdone. unsigned char b. ++i) /* start at arg 1 */ { ptr = argv[i]. NO translation. } if (!name[0]) { /* Input file not explicitly named errors out */ printf("Print File. *ptr. i. case 'B' : /* BINARY . 79).No translation at all! */ case 'b' : fBinary = 1. } } else if(!name[0]) strncpy (name. unsigned char *argv[]) { long erc. break. SetJobName("Printing".void main(long argc. printf("/B Binary print. break. printf("Error: Source filename required\r\n"). case 'D' : /* Display while printing */ case 'd' : fDisplay = 1. break. } MMURTL V1. printf("/1 /2 /4 /8 . name[0] = 0. break. i < argc.0\r\n"). exit(1). switch(*ptr) { case '1' : /* Tab Translation Width */ case '2' : case '4' : case '8' : tabstops = *ptr . erck. exit(1). cl. Version 1. 8). case 'F' : /* No FF at end of file */ case 'f' : NoFF = 1.0x30. break. for(i=1. if (*ptr == '/') { ptr++.

} else if (fBinary) { erc = DeviceOp(3. we open the printer port */ /* device.. pData */ erc = DeviceOp(3. b = (cl & 0xff). ExitJob(erc). lastb = b. &i). if (erc) { printf("Error getting LPT Device Status: %d\r\n". if (cl == EOF) { fdone = 1. } col = 0. ExitJob(erc). erc). b = 0. name). */ f = fopen(name. cl = fgetc(f). dLBA. ExitJob(erc). 0.0 1. } printf("Printing %s . fdone = 0. -236- RICHARD A BURGESS .h We do this just to see if it's a valid device. &lastb). &i). } else { MMURTL V1. CmdCloseLU. The structure is defined in parallel. dOpNum.\r\n".. erc). dnBlocks. if (!f) { /* device. dOpNum. 0. dnBlocks../* Get the 64 byte device status block which is specific to the parallel device driver. printf("Can't open: %s\r\n". pData */ erc = DeviceOp(3. while ((!fdone) && (!erc)) { i++. &lpt. */ erc = DeviceStat(3. } /* If device status went OK. 0. if (erc) { printf("OpenLPT ERROR: %d \r\n". CmdWriteB. 64. 0. name). &i).. dLBA. /* This is it. "r"). 0. i = 0. CmdOpenL.

break. -237- RICHARD A BURGESS . } } &b). dnBlocks. 0. &b). if (erc) MMURTL V1. dOpNum. lastb). &b). } } } } if ((!fBinary) && (!NoFF)) { erc = DeviceOp(3. 1. if (fDisplay) printf(" "). 1. 0. 1. /* reset */ break. %d\r\n". 0. 0. 0. CmdWriteB. "\f"). case LF: if (lastb != CR) { lastb = CR.switch (b) { /* print/translate the char */ case CR: erc = DeviceOp(3. &i). col++. 0). 1. erc = 4. 1. 0. dLBA. pData */ erc = DeviceOp(3. CmdWriteB. CmdWriteB. col++. &lastb). default: if (fDisplay) printf("%c". CmdWriteB. 0. /* device. erc = DeviceOp(3. if (fDisplay) printf("\r\n". 0. case TAB: do { erc = DeviceOp(3. } while (col % tabstops). } erc = DeviceOp(3. } fclose(f). erc = DeviceOp(3. b). if (i%100==0) /* every 100 chars see if they want to abort */ { erck = ReadKbd(&key. " "). break. CmdWriteB. erc). /* no wait */ if (!erck) { if (key & 0xff == 0x1b) { fdone = 1. CmdWriteB. col = 0.0 1. CmdCloseL. if (erc) printf("Error Writing Byte: break.

OSError = AllocExch(&MainExch). erc). ExitJob(erc). /* The number to return */ unsigned long MainExch.printf("Can't close LPT.C Listing Execute this from a command-line interpreter. /* A pointer to a Reqeust Block */ unsigned long NextNumber = 0. “Systems Programming.0 /* get an exchange */ -238- RICHARD A BURGESS . The following two listings show a simple system service and a client of that service. Pressing any key will terminate the program properly.h" ErcOK ErcOpCancel ErcNoSuchSvc ErcBadSvcCode 0 4 30 32 struct RqBlkType *pRqBlk. printf("Done\r\n"). The steps to deinstall are: 1) UnRegister the Service 2) Serve all remaining requests at service exchange 3) Deallocate all resources 4) Exit */ #include #include #include #include #define #define #define #define <stdio. Error: %d\r\n". /* Where we wait for Requests */ unsigned long Message[2]. ErrorToUser. and also allows deinstallation with proper termination and recovery of operating-system resources. The program is similar to the example shown in chapter 10.h> "\OSSource\MKernel. long *pDataRet.5 Simple Service Source Code /* Super Simple System Service. Listing 16. Service. It becomes a system service while keeping its virtual video screen.RUN program to exercise the service. /* Used for keyboard request */ void main(void) { unsigned long OSError. /* The Message with the Request */ long rqHndl. keycode. This is expanded from the sample in the System Programmer chapter to show how to properly deinstall a system service. } System Service Example Installable system services are the easiest way to coordinate resource usage and provide additional functionality in MMURTL.h" "\OSSource\MVid. MMURTL V1.” It has been expanded some.h" "\OSSource\MJob. While this program is running. run the TestSvc.

OSError).\r\n"). while (!CheckMsg(MainExch. SetNormVid(WHITE|BGBLACK). /* Unknown Service code! */ OSError = Respond(pRqBlk. Message)) { pRqBlk = Message[0]. ErcNoSuchSvc). ExitJob(ErcOpCancel). /* Give them a number */ ErrorToUser = ErcOK. NextNumber-1). OSError = Request("KEYBOARD". /* Respond with No error */ printf("NUMBERS Service gave out number: %d. } else ErrorToUser = ErcBadSvcCode. 1. /* Exch & pointer */ if (!OSError) { if (Message[0] == rqHndl) /* it was a keystroke and NOT a client */ { UnRegisterSvc("NUMBERS "). 4.. if (OSError) ExitJob(OSError). } DeAllocExch(MainExch). Message). /* 1 in dData0 = WAIT for key */ if (OSError) printf("Error on Keyboard Request:\r\n". 0.) */ /* look for a system error */ /* Now we wait for a client or for a keystroke to come back */ OSError = WaitMsg(MainExch. 0). ErrorToUser). 0.0 -239- RICHARD A BURGESS .\r\n". } pRqBlk = Message[0].. Respond(pRqBlk. MainExch). else if (pRqBlk->ServiceCode == 1) /* User Asking for Number */ { pDataRet = pRqBlk->pData1.if (OSError) ExitJob(OSError). while (1) { /* WHILE forever (almost.\r\n"). MainExch. ClrScr(). /* First DWORD contains ptr to RqBlk */ /* Abort request from OS */ if (pRqBlk->ServiceCode == 0) ErrorToUser = ErcOK. 0. *pDataRet = NextNumber++. &rqHndl. /* look for a kernel error */ OSError = RegisterSvc("NUMBERS ". 1. 0. /* Respond to Request */ MMURTL V1. printf("ANY valid keystroke will terminate the service. &keycode. printf("NUMBERS Service Installed.

Exch. 0. 4. Number). 0. if (Error) printf("Error %d from WaitMsg.h> #include "\OSSOurce\MKernel. } } DeAllocExch(Exch).h" unsigned long Number. 0. if (!Error) { Error = WaitMsg(Exch. else printf("NUMBERS Service gave out number: %d. 0. unsigned long Exch. &rqhndl.\r\n". if (Error) printf("Error %d from Request. Message[1]). Error = AllocExch(&Exch). Run him from another a CLI when Service is running */ #include <stdio. Error).0.6 exercises the sample system service listed previously. void main(void) { unsigned long Error.0). Error = Request("NUMBERS ".6.\r\n". Error).} } } /* Loop while(1) */ TestSvc. Listing 16.\r\n".C Listing The program in listing 16. } /* it's nice to do this for the OS */ MMURTL V1. Error). You should receive a proper error code indicating that the service is not available. 1. else { if (Message[1]) printf("Error %d from NUMBERS Service.\r\n". /* get an exchange */ /* The number to return */ /* Where hte service will respond */ /* The Message from the service */ if (Error) printf("Error %d allocating Exchange. TestSvc. Also try it when the service is not running. Execute this from a CLI while the service is running. unsigned long Message[2].\r\n".C /* Test client for the NUMBERS System Service. rqhndl. Message).0 -240- RICHARD A BURGESS . /* No Send ptrs */ &Number.

The position of this file in relationship to the others is critical because the offset to the IDT must be identified to the processor. use the stack just as a compiler would. MOSGDT. A brief description of each of the main source files follows. This is really just a data position holder in the data segment. MPublics. and used by the processor. The position of this file is also critical because it is identified to. MOSIDT. The caller cleans the stack in this convention. Introduction to the Source Code This chapter discusses each of the important source files. This ends up at address 800h physical and linear. In fact. It's not that I couldn't actually figure it out without them. You won’t have to go searching through hundreds of files to find a three-line function.This is the first assembler file that contains code. I hope I’ve met this goal. and structures together. but it is not used by the processor. which means its data will be at the lowest address. I can go back a year or two later and figure out what I did. The operating system entry point is located here (the first OS instruction executed). You may find some of the source files rather large.This is also a data definition file that sets up the operating system's Page Directory and first page table.This defines the basic Interrupt Descriptor Table (IDT).0 -241- RICHARD A BURGESS . There are also header files for the C source. with the called procedure cleaning the stack. Comments In the Code You'll find out that I make a lot of comments in my source code.ASM . but the number of variable arguments is passed in the EDI register. By including the comments. It helps me think.This is also a table. Calling Conventions I bring this topic up here because. This must be the second source file in the assembler template file. it ends up at physical and linear address 0. because the entries in the IDT are done dynamically. Organization The MMURTL source code is organized in a modular fashion. and INCLUDE files for the assembler that tie publics. This is also a required table for the Intel processors when they run in protected mode. This file should also remain in its current order because we have hard-coded its address in some source modules.ASM .ASM .). The GDT is 6K in size. Many of the lower-lever procedures use registers to pass data back and forth. Each logical section is contained in one or more files that are either C or assembler source code. I call it mangled because the arguments are still pushed left to right. This file should be left in the order I have it in. MAIN. Also some data is defined here. All source files are located in one directory. but most of the dependent functions will be contained in the file you are viewing or reading. The most prominent and the preferred method is pushing parameters left to right. the human becomes a compiler of sorts. Two conventions are used throughout the operating system. The ordering for MMURTL V1. I just expend the time up front instead of later. There are 29 main source files (excluding header and include files). you will see that much of the assembler looks almost like it was generated by a compiler. This defines the selector entries for all MMURTL public calls. however. common variables. Several selector entries are predefined so you can go directly into protected mode.ASM .This defines the Global Descriptor Table (GDT). and how to build the operating system using the tools included on the CD-ROM. some of the things that tie all of the source files together. The other is a mangled version of what you'd find in most C compilers for functions with an ellipse (. as you look through the code.. sometimes I write the comments describing a small block of code before I actually code it. One of my goals was to keep the source code organized in an easy-to-understand fashion. The IDT is a required table for the Intel processors. When a human writes this much assembler (and it's to be interfaced with assembler generated by a compiler).. MOSPDR.Chapter 17.ASM . Quite a few of them. This is the first file in the source. In fact.

when a device driver writers need DMA.This is an MS-DOS FAT-compatible file system.Floppy disk device drivers are much harder to write than IDE hard disk drivers.This file contains the bulk of the debugger logic. Keyboard. RQBCode.ASM .C . as well as port I/O support. even in the earliest stages of a project.along with all of the timer functions such as Sleep() .ASM . how much to move. undoubtedly. Floppy. On the other hand. DevDrvr.ASM . It seems to me that hardware manufacturers could be a little more compassionate towards software people. and the mode. System services in assembly language are not fun to write or maintain.Request block management code is in this file. It's not very pretty.The device-driver interface code and data are contained in this file. IntCode. is in this file. It provides stream access as well as block access to files. Debugging a debugger is even less fun. It drives two channels. MemCode.ASM .ASM was one of the first files written.C . It supports the 16550. FSys. Chapter 25 explains all the mysteries behind this table-driven piece of work.This file contains the RS-232 UART driver.C .The IDE hard disk device driver was pretty easy to write.This file is the debugger disassembler. This provides the re-entrancy protection and also the proper vectoring (indirect calling) of device-driver functions. MMURTL V1.ASM – Lower-level job functions and supporting code for functions in JobC. It think it still works with MFM drives. someday).ASM . DMACode.The interrupt service routine for the primary system timer . Parallel. This file has routines that handle DMA for you.0 -242- RICHARD A BURGESS . address aliasing and management of physical memory can be found in this file.ASM .C .ASM . they must handle it themselves.This is the file that collected all the little things that you need.ASM . and it works rather well.C . SVCCode. You will still see remnants of MFM drive commands in here because that's what I started with. The reasoning behind controlling the drive motors separately from the rest of the floppy electronics still has me baffled.The program loader and most of the job-related functions are handled in this file. This is one complicated driver. UASM. Keyboard. It is a table-driven disassembler used to display instructions in the debugger. TmrCode. you're on your own. but I don't have any more to test it. Chapter 26 goes into detail on this files contents.the remainder of the modules is not as important.Video code and data was also one of the first pieces I wrote. but can be modified for four.ASM . so I am reluctant to begin rewriting it (Although I will. You only need to know the channel. MiscCode.ASM . I was surprised. Highspeed string manipulation and comparison.C are contained in this file. If you have MFM drives.This is some code used internally that converts numbers to text and vice-versa.The code memory allocation. Video. I haven't tried playing "musical source files" either. JobC. Leaving them in their current order would probably be a good idea. NumCnvrt.In many systems.Interrupt Service Routine (ISR) handling code can be found here. but it's effective and accurate. but you never know where to put.ASM . if needed. Device driver writers will appreciate this.C .C .are in this file.ASM . HardIDE. Debugging is never fun when you have to do it at the assembler level.The simple parallel port driver is contained here. Debugger. You have to see what you're doing. JobCode.System service management code such as RegisterSvc() is in this file. but only to enable the input buffer (which helps). RS232.The keyboard source goes against my own rule of not combining a device driver with a system service. It has only the necessary calls to get the job done.

C . OS Page Directory & 1st Table (8k) . and the DASM assembler (also included).ASM .All of the kernel calls and most of the kernel support code is in this file. If you want a complete listing of all processor instructions and addresses. Most of them cause you to go directly into the Debugger.C After all the C source files are compiled.0 .ASM . All of the assembly language source files are then assembled with DASM. a C source file and must be compiled first with .The monitor program is contained in this file. 6K for GDT @ 00000800h Physical (1.5 minutes on a 486/33. Ordering for the remainder of the modules is not as important. specify /E on the command line after the name. DASM produces the operating system RUN file.INCLUDE Keyboard.” The only two MS-DOS commands you need are CM32 and DASM.INCLUDE Video.ASM .ASM . If you want an error file.ASM .ASM . One MS-DOS batch file (MakeALL.INCLUDE MOSIDT.DATA .5 Pages. This takes about 1.ASM . you use the DASM command as follows: C:\MMURTL> DASM MMURTL. Chapter 18 discusses the kernel and presents the most important pieces of its code along with discussions of the code.Exception handlers such as those required for processor faults are in this file. Monitor.INCLUDE MOSPDR.Kernel. This file is required to assemble complex programs with DASM. 6K) .ATF It's truly that simple. /L on the command line after the name.Assembler Template File for MMURTL V1. Main OS code and some data & START address .INCLUDE MAIN.1.0 -243- RICHARD A BURGESS . Except. Listing 17. but you don't need to read all the DASM documentation to build MMURTL. but it really isn't part of the operating system. An example of compiling one of the source files is: C:\MMURTL> CM32 Monitor. . Defines the Interrupt Descriptor Table . “DASM:A 32-Bit Intel-Based Assembler.INCLUDE MPublics.This file has all the helper routines that initialize most of the of the dynamic operating-system functions. The assembler is discussed in detail in chapter 28. Building MMURTL MMURTL requires the CM32 compiler (included on the CD-ROM) for the C source files. CM32 (no switches needed). Video code and data MMURTL V1. Each of the C source files is turned into an assembler file (which is what the CM32 compiler produces).ASM . .ASM . You could replace quite easily. The RUN file is in the standard MMURTL Run file format which is described completely in the Chapter 28. An asterisk (*) by the include file indicates it originates as .BAT) will build the entire operating system.ASM . The following is the Assembler Template File (ATF). . Assembler Template File . Keyboard ISR and Service . It provides a lot of the OS functionality. InitCode.These first 5 INCLUDES MUST be in this order!!!! . Indirect calling address table .INCLUDE MOSGDT.

This is because DASM doesn't support structures.INCLUDE .INCLUDE . .* .array.INCLUDE .ASM . . you can take the file system code (FSYS. but you would have no problem setting them up for TASM or MASM.ASM MiscCode. no doubt.INCLUDE .C) and it can be turned into a free-standing program with little effort.ASM DevDrvr.ASM Kernel.ASM JOBC. use another compiler for your project.0 -244- RICHARD A BURGESS .ASM JobCode.ASM Monitor. For instance.INCLUDE . The calling conventions (described earlier) may get you into trouble if you want to use some of the assembler routines from a high-level language. CM32 packs all variables with no additional padding. .I/O support) Kernel code Exception handlers Initialization support code The monitor code & data If you've read all of the chapters that precede this one.INCLUDE .INCLUDE . . . You will.END Debugger.ASM DMACode. . . . . .INCLUDE . .INCLUDE .INCLUDE .INCLUDE . Otherwise. You would also need to remove the numbers following the RET and RETF statements so the caller could clean the stack as is the C convention. This is one thing you should watch. As each piece of code is assembled.ASM MemCode.ASM FSys. MMURTL V1.ASM IntCode. you'll understand that all MMURTL programs are comprised of only two segments .* .ASM InitCode.* . You'll find that many items in MMURTL depend on the memory alignment of some of the structures.ASM UASM. you can calculate the correct offsets and fix the EQU statements for access where a C compiler would put them on the stack. If your C compiler allows it.. Using Pieces of MMURTL in Your OS You'll find that most of the source code is modular. Even though I use CM32 to compile the C source files.ASM Floppy.ASM NumCnvrt. The order of the assembler files in the ATF file determines the order of the data and code.ASM Except.INCLUDE . CM32 packs all structure fields completely. The same basic warning about the alignment of structures applies. the code can be compiled with any ANSI C compiler. The same is true for the data as it is encountered in the files. you can declare them as Pascal functions (some even use the old PLM type modifier).ASM HardIDE. I built each section of code in modular sections and tried to keep it that way.INCLUDE . In fact.* :* . The assembler INCLUDE files that define all the structures actually define offsets from a memory location.ASM Parallel. .* Code & Data for Debugger Code & Data for Debugger Disassembler Code and Data for Device Driver Interface Floppy Device Driver IDE Disk Device Driver Serial Comms Device Driver Parallel Comms Device Driver (RAB) File System Service Additional Job management Loader/Job handling Code & Data Timer Code ISR handling code Request Block Code DMA Handling Code Maybe Move to DLL later or can it Memory management code System Service management code Misc.INCLUDE .INCLUDE .INCLUDE .INCLUDE .INCLUDE . Using Pieces of MMURTL in Other Projects Some of you may find some of the code useful in other projects that have absolutely nothing to do with writing an operating system. .INCLUDE .INCLUDE .ASM TmrCode.code and data. code (string.ASM RQBCode. and they will work fine.* .* . it is placed into the code segment in that order.ASM SVCCode.ASM RS232.INCLUDE . .INCLUDE .

The bulk of them are in the file Main. The bulk of the other data items are things for statistics: the number of task switches (_nSwitches). the scheduler.DATA .1.1. and memory management tables. Kernel data segment source .INC RQB. and offsets into structures and tables. Variables named with a leading underscore are accessible from code generated with the C compiler.Used as temp in Service Abort function TimerTick DD SwitchTick DD dfHalted DD _nSwitches DD _nHalts DD _nReady DD MMURTL V1. The actual message handling functions.INC DD 0 . These all change with respect to the task that is running. and kernel publics functions.INC JOB. I think it will make more sense to you. For public functions that are reached through call gates from applications. For the most part these are statistic-gathering variables. Kernel Data The source file for the kernel doesn't really contain any data variable declarations. See listing 18.INCLUDE dJunk EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN MOSEDF. If you read through the descriptions of these little "helper" routines before you look at the code for the kernel primitives. the number of times the CPU found itself with nothing to do (_nHalts). job control blocks. Being an old "structured" programmer. The INCLUDE files define things like error codes.INCLUDE . The code in the kernel is divided into four main sections: data.Chapter 18. the name of the routine is unmodified (no underscores used). so I could get to them with the monitor program. internal public functions.INC TSS.0 -245- RICHARD A BURGESS . I have made most of the "called" functions reside above those that call them. a single underscore will be prepended to the name.INCLUDE . and associated kernel helper procedures turn out to be less than 3K of executable code. Naming Conventions For helper routines that are only called from other assembly language routines. Listing 18. The data that the kernel primitives deal with are usually used in context to the task that is running. two underscores will be prepended to the name. local helper functions.ASM.INCLUDE . The Kernel Introduction The kernel code is very small. and the number of tasks ready to run (_nReady). For functions that must be reached by high-level languages (such as C). These are things such as the task state segments.

from an exchange but not yet linked to a TSS and placed on the ready queue). and if a task is waiting there. REGISTERS : EAX. INPUT : ESI. In MMURTL. for example.MsgHead = NIL MMURTL V1. which means interrupts must be cleared. pExch => EAX. pointed to by the ESI register. link blocks. This is important. OUTPUT : NONE . The message can be a pointer to a Request or an 8-byte generic message. if . Interrupts will already be disabled when this routine is called. the task is immediately associated with the message and placed on the ready queue in priority order. . such as enQueueMsg and deQueueMsg. For this reason we share the HEAD and TAIL link pointers of an exchange for tasks and messages. is coded in-line.0 -246- RICHARD A BURGESS . A flag tells you whether it’s a task on a message. The following lines begin the code segment for the kernel: . JZ eqMsgDone .. You may notice that there is a deQueueTSS but no enQueueTSS.CODE EXTRN LinToPhy NEAR enQueueMsg The enQueueMsg places a link block containing a message on an exchange.EAX . This routine will place the link block pointed to by EAX onto the exchange . and therefore. I also had to really keep good track of which registers I used. MOV DWORD PTR [EAX+NextLB]. This is because the enQueueTSS functionality is only needed in two places. we must ensure that interrupts are disabled prior to the allocation or deallocation of all kernel data segment resources.EAX . There can never be tasks and messages at an exchange at the same time (unless the kernel is broken!). The concept itself should be very important to you if you intend to write your own multitasking operating system.EDX. MODIFIES : EDX . an exchange is a place where either messages or tasks wait (link blocks that contain the message actually wait there). If EAX is NIL then the routine returns. if pLBin = NIL THEN Return. and exchanges.FLAGS . 0 . pLBin^. 0 . along with enQueueRdy and deQueueRdy.2. pLBin => ESI CMP DWORD PTR [EAX+EHead]. OR EAX.2. it's that they manipulate common data and therefor are not reentrant.EAX . It's not that the routines are called so often. See listing 18. . Listing 18. This is strictly a speed issue. Because certain kernel functions may be called from ISRs. . Queueing for messages at an exchange enQueueMsg: .Next <= NIL. The functions described have a certain symmetry that you'll notice.ESI. XCHG ESI. The interface to these calls are exclusively through registers. Most of these functions manipulate all the linked lists that maintain things like the ready queue. This especially applies when a message is "in transit" (taken. When a message is sent to an exchange. and because portions of other kernel functions may be interrupted by a task change that happens because of an action that an ISR takes.Local Kernel Helper Functions This section has all of the little functions that help the kernel primitives and scheduler code.

exchanges can hold messages or tasks.[EAX+ETail] . . If not. MOV [EAX+ETail]. Flag it as a Msg (vice a task) XCHG EAX. No! (return 0) MOV EAX... MOV DWORD PTR [EAX+fEMsg]. Flag it as a Msg (vice a task) XCHG EAX. .[EAX+NextLB] . JZ deMsgDone . Get Msg Flag OR EAX. As stated before. it returns NIL (0) in the EAX register. 1 ..3. then MOV [EAX+EHead]. else eqMNotNIL: MOV EDX. MMURTL V1. .Next. you return NIL (0).ESI .JNE eqMNotNIL . If not.ESI.ESI . deMsgDone: RETN . pLBout <= . De-queueing a message from an exchange deQueueMsg: . EAX ..ESI . Is it a Msg? JZ deMsgDone . Put pExch Back in ESI RETN . 1 .head and EBX . This routine will dequeue a link block on the exchange pointed to by the .ESI . MODIFIES : *prgExch[ESI]. See listing 18. Put pExch Back in ESI eqMsgDone: RETN .EBX. Listing 18.EBX . OUTPUT : EAX . . MOV [ESI+EHead]. if pLBout = NIL then Return.FLAGS .[ESI+fEMsg] .ESI . MOV EBX.MsgHead <= pLBin. If it's a message you remove it from the linked list and return it. MOV DWORD PTR [EAX+fEMsg].[ESI+EHead] . ESI register and place the pointer to the link block dequeued into EAX.MsgHead. REGISTERS : EAX. deQueueTSS The deQueueTSS removes a pointer to a TSS from an exchange if one exists there. EAX .ESI . MOV [EDX+NextLB]. If not. INPUT : ESI .msg.MsgTail^. You check the flag in the exchange structure to see which is there.0 -247- RICHARD A BURGESS . ...MsgTail <= pLBin. OR EAX.MsgHead^. . See listing 18.MsgHead <= . . .. it returns NIL (0) in the EAX register. deQueueMsg The deQueueMsg removes a link block from an exchange if one exists there.NextLB <= pLBin.4. MOV [EAX+ETail]. but not both. MOV EAX.MsgTail <= pLBin.3.

. This routine will dequeue a TSS on the exchange pointed to by the ESI .TSSHead^..EDX . REGISTERS : EAX. MODIFIES : EAX. EAX pts to proper Rdy Queue CMP DWORD PTR [EAX+Head].EDX.5.Listing 18.5.EBX . EAX . enQueueRdy The enQueueRdy places a task (actually a TSS) on the ready queue. Add offset of RdyQ => EAX ADD EAX. EBX JNZ deTSSDone . See listing 18. Adding a task to the ready queue PUBLIC enQueueRdy: .FLAGS . .. 0 .0 -248- RICHARD A BURGESS .EBX . 0 . De-queueing a task from an exchange deQueueTSS: .Next. The Rdy Queue is an array of QUEUES (2 pointers. XOR EBX. head & tail per QUEUE). INPUT : ESI .EBX. This routine will place a TSS pointed to by EAX onto the ReadyQueue. deTSSDone: RETN .EBX. INPUT : EAX . .4.[ESI+fEMsg] . MOV EBX. OR EAX. Set up to return nothing MOV EBX. one for each of the possible task priorities in MMURTL. XOR EAX.[EAX+Priority] . MOV DWORD PTR [EAX+NextTSS]. JZ deTSSDone . .EAX .EAX . Listing 18. Times 8 (size of QUEUE) LEA EDX. This . if Head = NIL MMURTL V1. JZ eqRdyDone . algorithm chooses the proper priority queue based on the TSS priority.. if pTSSout = NIL then Return. OUTPUT : EAX . Priority => EAX. if pTSS = NIL then return. This links the TSS to rgQueue[nPRI]. . . OUTPUT : NONE .[ESI+EHead] . Msg flag (is it a Msg) OR EBX.ESI. pTSSin => EBX SHL EAX. INC _nReady .TSSHead. MODIFIES : EAX. pTSSin^. in EBX XCHG EAX. EAX return NIL if no TSS is waiting at Exch ESI .FLAGS . register and place the pointer to the TSS dequeued into EAX. MOV [ESI+EHead].[EAX+NextTSS] .TSSHead <= . . get the priority MOV BL. OR EAX.Next <= NIL. .EBX . It's a Msg (return leaving EAX 0) MOV EAX. REGISTERS : EAX.EBX .RdyQ . pTSSout <= . . 3 .EDX . The ready queue is a structure of 32 linked lists.EBX. You dereference the TSS priority and place the TSS on the proper linked list.

EBX eqRdyDone: RETN . If there was no task queued.6. EAX is returned as NIL. . . Set up the number of times to loop LEA EBX. The pointer to this TSS is also removed from the list.6. MODIFIES : RdyQ . Listing 18. This routine will return a pointer in EAX to the highest priority task .ECX. . ...[EBX] OR EAX. . . . this returns NIL (0). deQueue the process And return with the pointer in EAX .[EAX+Tail] MOV [EDX+NextTSS].EBX MOV [EAX+Tail].ECX deRdyDone: RETN .Tail <= pTSSin. deQueueRdy The deQueueRdy finds the highest-priority ready queue (out of 32) that has a task waiting there and returns a pointer to the first TSS in that list. If none is found.EBX. De-queueing the highest priority task PUBLIC deQueueRdy: .[EAX+NextTSS] MOV [EBX].EBX MOV [EAX+Tail]. OUTPUT : EAX . .NextTSS <= pTSSin.JNE eqRNotNIL MOV [EAX+Head]. Keep in mind. . that 0 is the highest priority. . .0 -249- RICHARD A BURGESS . .Head <= pTSSin. RETURN Otherwise. . EAX JZ deRdyDone DEC _nReady MOV ECX. then . Get pTSSout in EAX IF pTSSout is NIL Then go and check the next priority. .Tail^. else . Then the routine will "pop" the TSS from the RdyQ. Get base address of RdyQ in EBX deRdyLoop: MOV EAX. . IF pTSSout is NIL Then there are No TSSs on the RdyQ. and 31 is the lowest.RdyQ . Point to the next Priority Queue DEC ECX and LOOP IF NOT ZERO .EBX RETN eqRNotNIL: MOV EDX. See listing 18. MMURTL V1. EAX JNZ deRdyFound ADD EBX. . .Tail <= pTSSin. INPUT : NONE .sQUEUE LOOP deRdyLoop deRdyFound: OR EAX. MOV ECX.. . . queued on the RdyQ.. REGISTERS : EAX. .nPRI .FLAGS . .

ECX.ChkRdyQ MMURTL's preemptive nature requires that you have the ability to check the ready queues to see what the highest priority task is that could be executed. . . Get base address of RdyQ in EBX ChkRdyLoop: MOV EAX. . Listing 18. Finding the highest priority task PUBLIC ChkRdyQ: .0 -250- RICHARD A BURGESS . without actually removing it from the queue. RemoveRdyJob (NEAR) This routine searchs all ready queue priorities for tasks belonging to pJCB.FLAGS .ASM because they work closely with kernel structures. They provide things like resource garbage collection and exchange owner manipulation. This why it's specified as PUBLIC. See listing 18. Removing a terminated task from the ready queue . It WILL NOT remove it from the Queue. check the next priority. INPUT : NONE . This is called when we are killing MMURTL V1.7. RemoveRdyJob When a job terminates.nPRI ." See Listing 18. This routine will return a pointer to the highest priority TSS that . OUTPUT : EAX . the RemoveRdyJob recovers the task state segments (TSSs) that belonged to that job. .ASM) that provides preemptive task switching.[EBX] OR EAX.8. Set up the number of times to loop LEA EBX. is queued to run. Get pTSSout in EAX . EAX JNZ ChkRdyDone ADD EBX. Point to the next Priority Queue . If there was no task queued. . REGISTERS : EAX.7. . EAX is returned as NIL. MODIFIES : RdyQ .8. .RdyQ . IF pTSSout is NIL Then go and . either by its own choosing or is killed off due to some unforgivable protection violation. They are located in Kernel. This routine provides that functionality for the piece of code in the timer interrupt routine (in TimerCode. DEC ECX and LOOP IF NOT ZERO . When one is found it is removed from the queue and the TSS is freed up. Listing 18.sQUEUE LOOP ChkRdyLoop ChkRdyDone: RETN . . MOV ECX. "It's a nasty job. .EBX. Internal Public Helper Functions The routines described in the following sections are "public" to the rest of the operating system but not accessible to outside callers. but someone's got to it.

MOV [EAX+NextTSS]. PUBLIC _RemoveRdyJob: . EDI OR EAX. Make TSS invalid MOV pFreeTSS.Free up the TSS (add it to the free list) MOV EBX. OUTPUT : NONE . MOV EBP. Fix link in Queue list . Yes. MOV DWORD PTR [EAX+TSS_pJCB]. POP EBX MOV EAX.sQUEUE LOOP RemRdyLoop XOR EAX. EAX OR EAX.. . No MOV EDI.[EBX+Head] MOV EDI. a job. Get pTSS in EAX EDI points to last TSS by default (or NIL) Is pTSS 0 (none left queued here) Valid pTSS! .EAX JNZ RemRdy0 RemRdyLoop1: MOV [EBX+Tail]. No error . Is this from the JCB we want? JNE RemRdy2 . . INC _nTSSLeft . REGISTERS : All general registers are trashed . pFreeTSS <= pTSSin.RdyQ .ESP . pJCB is a pointer to the JCB that the tasks to kill belong to. . RemoveRdyJob(char *pJCB):ercType . Point to the next Priority Queue . . pJCB EQU DWORD PTR [EBP+8] . MOV ECX.Next <= pFreeTSS. PUSH EBP . . Get base address of RdyQ in EBX . [EAX+NextTSS] MOV [EBX+Head].EAX . 0 . [EAX+TSS_pJCB] .0 . EAX JZ RemRdyLoop1 MMURTL V1. . deQueue the TSS .Go here to dequeue a TSS at head of list RemRdy0: CMP EDX. Save ptr to RdyQue (crnt priority) . Next Queue please -251- RICHARD A BURGESS . EDI PUSH EBX . pTSSin^. EDI ADD EBX. . .pFreeTSS . All done (clean stack) . Is it Zero? . MODIFIES : RdyQ . . INPUT : (pJCB on stack) .EBX points to begining RemRdyLoop: MOV EAX. . Procedureal Interface : .nPRI . Set up the number of times to loop LEA EBX. DEC ECX and LOOP IF NOT ZERO . .EBX . EDI always points to last TSS or NIL . EAX POP EBP RETN 4 of next Priority Queue . Make EAX point to new head TSS .

EAX JMP RemRdy2 RemRdy3: . . char *pJCBRet): dErrror MMURTL V1.Jump the removed link . A pointer to the JCB of the owner is returned. See listing 18.Save ptr to RdyQue (crnt priority) . [EAX+TSS_pJCB] JE RemRdy3 MOV EDI. Yes. [EAX+NextTSS] . . . POP . MOV [EAX+NextTSS]. This function is used with the next one.Free up the TSS (add it to the free list) MOV EBX.Make ESI point to NextTSS MOV ESI.EBX .EDI points to prev TSS . EAX JZ RemRdyLoop1 CMP EDX. Next TSS . . . This call identifies who owns an exchange by returning a pointer to the job control block. Get next link in list Valid pTSS? No. .9. Make TSS invalid MOV pFreeTSS. SetExchOwner().Now we fix the list (Make Prev point to Next) .0 -252- RICHARD A BURGESS .Go here to dequeue a TSS in middle or end of list RemRdy2: MOV EAX. Next Queue please . pFreeTSS <= pTSSin. . Listing 18.This extracts EAX from the list MOV [EDI+NextTSS].Next <= pFreeTSS. Trash it. MOV DWORD PTR [EAX+TSS_pJCB]. GetExchOwner (NEAR) EBX ESI. INC _nTSSLeft .EAX .Is EDI the new Tail? (ESI = 0) . Finding the owner of an exchange .JMP RemRdy0 . . OR JZ JMP GetExchOwner The code that loads new jobs must have the capability to allocate default exchanges for the new program. pTSSin^. [EDI+NextTSS] OR EAX. These are NEAR functions and not available to outside callers (via call gates). . The Exchange allocation routines assume that the caller will own this exchange. deQueue the TSS . No.Yes. . ErcOutofRange is returned is the exchange number is invalid (too high) Procedureal Interface : GetExchOwner(long Exch. Next Queue please Is this from JCB we want? Yes. This is not so if it's for a new job. . 0 . ESI RemRdyLoop1 RemRdy2 . ErcNotAlloc is returned if the exchange isn't allocated. .back to check next TSS This routine returns the owner of the exchange specified. .9. . ESI PUSH EBX . back to check next at head of list .pFreeTSS .EAX points to crnt TSS .

[EBP+12] CMP EAX. . MMURTL V1. is a pointer to the JCB that the tasks to kill belong to. . SetExchOwner This is the complimentary call to GetExchOwner(). . .ErcOutOfRange JMP GEOEnd GEO01: MOV EDX. Get Resp Exchange in EDX Is the exchange out of range? No. . This is used by the Job code to set the owner of a TSS exchange to a new JCB (even though the exchange was allocated by the OS). EAX XOR EAX. Exch is . This call sets the exchange owner to the owner of the job control block pointed by the second parameter of the call. .ESP MOV EAX. SetExchOwner (NEAR) This routine sets the owner of the exchange specified to the pJCB specified. . EQU DWORD PTR [EBP+12] EQU DWORD PTR [EBP+8] . Changing the owner of an exchange . Exch . EAX JNZ GEO02 MOV EAX. [EBP+8] MOV [ESI]. . .Where to return pJCB of Exchange .sEXCH MUL EDX MOV EDX. .prgExch ADD EDX. . No. . . not allocated . Compute offset of Exch in rgExch sExch * Exch number Add offset of rgExch => EAX EDX -> Exch . . char *pNewJCB): dErrror Exch is the exchange number. pJCBRet . . . pJCBRet the exchange number. .0 -253- RICHARD A BURGESS . . . . Valid Exch (Allocated) .EAX MOV EAX.nExch JB GEO01 MOV EAX. Listing 18. . . . [EDX+Owner] OR EAX. . . mentioned previously.10. No error checking is done as the job code does it upfront! Procedureal Interface : SetExchOwner(long Exch.EBP POP EBP RETN 8 . . ErcNotAlloc JMP SHORT GEOEnd GEO02: MOV ESI. EAX GEOEnd: MOV ESP.10. continue Yes.. See listing 18. Error in EAX register PUBLIC _GetExchOwner: PUSH EBP MOV EBP.

ESP MOV ESI. .ESP MOV EAX. . . . We ignore the kernel errors. Get the number of Service Descriptors PUBLIC _SendAbort: PUSH EBP MOV EBP.11. The error it returns is of no consequence. .EDX MOV EBX. . Procedureal Interface : SendAbort(long JobNum. . . EBX XOR EAX. . .. pNewJCB EQU DWORD PTR [EBP+8] PUBLIC _SetExchOwner: PUSH EBP MOV EBP. EAX POP EBP RETN 8 SendAbort System services are programs that respond to requests from applications. The kernel knows it's already dead and will reclaim the request block and exchanges that were used. . Exchange Number Compute offset of Exch in rgExch sExch * Exch number Add offset of rgExch => EAX EAX -> oExch + prgExch . System services may hold requests from many jobs at a time until they can service the requests (known as asynchronous servicing). telling them that a particular job has died. . . . . other services. . . SendAbort (NEAR) This routine sends one abort message to each valid service with the jobnum of the aborting job. [EBP+12] MOV EDX. Listing 18. the SendAbort function is called to send a message to all active services.0 -254- RICHARD A BURGESS . .OFFSET rgSVC MOV ECX. pNewJCB is a pointer to the JCB of the new owner. or the operating system itself. If a service is holding a request from the job that died. . Exch EQU DWORD PTR [EBP+12] . [EBP+8] MOV [EAX+Owner]. it should respond to it as soon as it gets the abort message. . Get the address of rgSVC .prgExch ADD EAX. ValidExch): dErrror JobNum is the job that is aborting ValidExch is any valid exchange so the request will go through JobNum EQU DWORD PTR [EBP+12] ValidExch EQU DWORD PTR [EBP+8] .11. Notifying services of a job’s demise . It should not process any data for the program (because it's dead). . . If we receive a kernel error on Request it may be becuase it is a service that is aborting itself.nSVC SAB01: MMURTL V1. . If a program sent a request. . then exited or was killed off because of nasty behavior. .sEXCH MUL EDX MOV EDX. . . See listing 18. . .

They're in this file because they work closely with the kernel.Push all the params to make the request PUSH ESI . See listing 18. Listing 18.dData1 PUSH 0 . Public Kernel Functions The remainder of the code defines the kernel public functions that are called through call gates.cbData1 MOV EAX. . 0 JE SAB05 .0 -255- RICHARD A BURGESS . [EBP+8] . except this function requires several more parameters. NO.Abort Service Code MOV EAX.pData1 PUSH 0 . next service PUSH ESI .dData2 CALL FWORD PTR _Request .dData0 PUSH 0 .12.pData0 PUSH 0 . Request( pSvcName [EBP+56] . Request kernel primitive code . The procedural interface to Request looks like this: .CMP DWORD PTR [ESI]. and code for it is in the file SVCCode.Exchange PUSH EAX PUSH OFFSET dJunk .JobNum PUSH EAX . A request block is the basic structure used for client/server communications.EBP POP EBP RETN 8 . These are functions such as GetPriority(). Request() The kernel Request primitive sends a message like the Send() primitive.Next Service name .Save count and pointer to SVC name PUSH ECX .asm. Valid name? . Some of the functions are auxiliary functions for outside callers and not actually part of the kernel code that defines the tasking model.pHandleRet PUSH 0 . EAX MOV ESP. The function that searches the array is GetExchange(). These are called with 48-bit (FAR) pointers that include the selector of the call gate. The exchange where a request should be queued is determined by searching the system-service array for a matching request service name specified in the request block.npSend PUSH 0 .12. . .Get count and ptr to SVC names back from stack POP ECX POP ESI SAB05: ADD ESI. wSvcCode [EBP+52] MMURTL V1. sSVC LOOP SAB01 XOR EAX.pName PUSH 0 .cbData0 PUSH 0 . [EBP+12] . A system structure called a request block is allocated and some of these parameters are placed in it. The offsets to the stack parameters are defined in the comments for each of calls.

[EBP+56] . [EBP+36] MOV [EBX+pData1].0 ..ESI still has the exchange for the service . dRespExch pRqHndlRet dnpSend pData1 dcbData1 pData2 dcbData2 dData0 dData1 dData2 [EBP+48] [EBP+44] [EBP+40] [EBP+36] [EBP+32] [EBP+28] [EBP+24] [EBP+20] [EBP+16] [EBP+12] ) : dError PUBLIC __Request: PUSH EBP MOV EBP. [EBP+52] MOV [EBX+ServiceCode]. continue Yes.Leaves Service Exch in ESI if no errors OR EAX. .No.EAX has ptr to new RqBlk (or 0 if none) STI OR EAX. .Get them a request block CLI CALL NewRQB . EAX MOV EAX. AX MOV [EBX+RespExch]. .Validate exchange MOV EDX.put now pts to RqBlk Svc Exch into RqBlk Svc Code Svc Code into RqBlk Resp Exch into RqBlk crnt JCB (Job Num of owner) in RqBlk dData0 in RqBlk dData1 in RqBlk dData2 in RqBlk pData1 in RqBlk RICHARD A BURGESS -256- . EAX MOV EAX.put . ESI MOV EAX. .Get .put .Put . JMP ReqEnd . . [EBP+20] MOV [EBX+dData0].nExch JB Req03 MOV EAX.Put . EAX ptr to new RqBlk MOV EAX.EDX still has the response exchange .Put . . . EAX MOV EAX. EAX MOV [EBX+ServiceExch].put .put .Yes.Get . Req04: .Did we get one? (NIL (0) means we didn't) JNZ Req04 . . .Yes.ESP . . Get Resp Exchange in EDX Is the exchange out of range? No. .Get . EAX MMURTL V1.Get .EAX has pRqBlk (Handle) MOV EBX. return error Req02: . Error in EAX register . Save the Previous FramePtr ..EBX . . .Get .EAX .No JMP ReqEnd .Get . [EBP+48] CMP EDX.pServiceName CALL GetExchange . EDX CALL GetCrntJobNum MOV [EBX+RqOwnerJob].Validate service name from registry and get exchange MOV EAX.. EAX MOV EAX.Any errors? JZ SHORT Req02 .ercOutOfRange JMP ReqEnd Req03: . Sorry. ErcNoMoreRqBlks . [EBP+16] MOV [EBX+dData1]. [EBP+20] MOV [EBX+dData2]. Set up New FramePtr . . EAX .

it as one anyway (so no problem) MOV EDI.put in RqBlk . . EAX MOV EAX.Get cbData2 .Next <= NIL.The ptr to the exch is required for deQueueTSS so we get it.EDX ESI.Now we allocate a Link block to use MOV EAX. . EAX MOV EAX. free up RqBlk Move error in the EAX register Go home with bad news MOV DWORD PTR [EAX+LBType]. 0 . . 3 JB Req06 MOV EAX.EDX . [EBP+32] MOV [EBX+cbData1]. . [EBP+28] MOV [EBX+pData2]. AL [EBX+npRecv]. .Give it to them .ESI now points to the exchange CALL deQueueTSS MMURTL V1. MOV [EAX+DataLo].[EAX+NextLB] MOV pFreeLB. .prgExch EAX.Now we will return the RqBlkHandle to the user. .Leave in CL . 2 Req06: MOV MOV SUB MOV [EBX+npSend].Ptr to return handle to .MOV EAX. pLB^. [EBP+44] MOV [EDI]. MOV MOV MUL MOV ADD MOV EAX.put in RqBlk . . No interruptions from here on .Must be 2 or less . AL CL.EAX . .Caculate nRecv (2-nSend) .EBX DEC _nLBLeft . . RqHandle into Lower 1/2 of Msg MOV DWORD PTR [EAX+DataHi].Save RqBlk in EDX CLI . EAX MOV EAX. . .ESI EDX. . This is a Request Link Block MOV DWORD PTR [EAX+NextLB]. .pFreeLB OR EAX.At this point the RqBlk is all filled in. 2 CL.ESI still has the exchange Number for the service. EAX <= pFreeLB.Data PUSH EAX . [EBP+24] MOV [EBX+cbData2]. sEXCH EDX EDX. DeQueue a TSS on that Exch -257- RICHARD A BURGESS . pFreeLB <= pFreeLB^.Number of Send PbCbs .EAX JNZ Req08 CALL DisposeRQB MOV EAX.Put nSend PbCbs into RqBlk . Is pFreeLB NIL? (out of LBs) NO.Get pData2 .ercNoMoreLBs JMP ReqEnd Req08: MOV EBX. EBX .Put npRecv in RqBlk .Get cbData1 . 0 .The handle is actually a ptr to the RqBlk but they can't use .0 . Store zero in upper half of pLB^. EBX MOV EDX.put in RqBlk . [EBP+40] CMP EAX.Next .. CL .REQLB . Save pLB on the stack . . Exch => EAX Compute offset of Exch in rgExch Add offset of rgExch => EAX MAKE ESI <= pExch . ..

OR EAX,EAX JNZ Req10 POP EAX CALL enQueueMsg XOR EAX,EAX JMP SHORT ReqEnd Req10: POP EBX MOV [EAX+pLBRet],EBX CALL enQueueRdy MOV EAX,pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX,pRunTSS JNE Req12 XOR EAX,EAX JMP SHORT ReqEnd Req12: MOV MOV MOV INC MOV MOV JMP XOR ReqEnd: STI MOV ESP,EBP POP EBP RETF 48 Respond() pRunTSS,EAX BX,[EAX+Tid] TSS_Sel,BX _nSwitches EAX, TimerTick SwitchTick, EAX FWORD PTR [TSS] EAX,EAX

; ; ; ; ; ;

Did we get one? Yes, give up the message No, Get the pLB just saved EnQueue the Message on Exch No Error And get out!

; ; ; ; ; ; ; ; ;

Get the pLB just saved into EBX and put it in the TSS EnQueue the TSS on the RdyQ Get the Ptr To the Running TSS and put him on the RdyQ Get high priority TSS off the RdyQ If the high priority TSS is the same as the Running TSS then return Return to Caller with erc ok.

; Make the TSS in EAX the Running TSS ; Get the task Id (TR) ; Put it in the JumpAddr for Task Swtich ; Keep track of how many swtiches for stats ;Save time of this switch for scheduler ; ; JMP TSS (This is the task swtich) ; Return to Caller with erc ok.

; ; ; ; Rtn to Caller & Remove Params from stack

The Respond primitive is used by system services to respond to a Request() received at their service exchange. The request block handle must be supplied along with the error/status code to be returned to the caller. This is very similar to Send() except it de-aliases addresses in the request block and then deallocates it. The exchange to respond to is located inside the request block. See listing 18.13. Listing 18.13. Respond kernel primitive code ; ; Respond(dRqHndl, dStatRet): dError ; ; dRqHndl EQU DWORD PTR [EBP+16] dStatRet EQU DWORD PTR [EBP+12] PUBLIC __Respond: PUSH EBP MOV EBP,ESP MOV EAX, dRqHndl MOV ESI, [EAX+RespExch] CMP ESI,nExch JNAE Resp02
MMURTL V1.0

; ; ; ; ; ; ;

Save Callers Frame Setup Local Frame pRqBlk into EAX Response Exchange into ESI Is the exchange out of range? No, continue

-258-

RICHARD A BURGESS

MOV EAX,ercOutOfRange JMP RespEnd Resp02: MOV MOV MUL MOV ADD MOV CMP JNE MOV JMP Resp04:

; Error into the EAX register. ; Get out

EAX,ESI ; Exch => EAX EDX,sEXCH ; Compute offset of Exch in rgExch EDX ; EDX,prgExch ; Add offset of rgExch => EAX EAX,EDX ; ESI,EAX ; MAKE ESI <= pExch DWORD PTR [EAX+Owner], 0 ; If the exchange is not allocated Resp04 ; return to the caller with error EAX,ercNotAlloc ; in the EAX register. RespEnd ;

MOV EAX, dRqHndl ; Get Request handle into EBX (pRqBlk) MOV EBX, [EAX+RqOwnerJob] CALL GetCrntJobNum CMP EAX, EBX JE Resp06 ;Same job - no DeAlias needed MOV EAX, dRqHndl MOV EBX, [EAX+cbData1] OR EBX, EBX JZ Resp05 MOV EDX, [EAX+pData1] OR EDX, EDX JZ Resp05 ; Get Request handle into EBX (pRqBlk) ; ;No need to dealias (zero bytes)

;Null pointer!

PUSH ESI ;Save pExch across call PUSH EDX ;pMem PUSH EBX ;cbMem CALL GetCrntJobNum PUSH EAX CALL FWORD PTR _DeAliasMem ;DO it and ignore errors POP ESI ;get pExch back Resp05: MOV EAX, dRqHndl ; Get Request handle into EBX (pRqBlk) MOV EBX, [EAX+cbData2] ; OR EBX, EBX JZ Resp06 ;No need to dealias (zero bytes) MOV EDX, [EAX+pData2] OR EDX, EDX JZ Resp06 ;Null pointer! PUSH ESI ;Save pExch across call PUSH EDX ;pMem PUSH EBX ;cbMem CALL GetCrntJobNum ; PUSH EAX CALL FWORD PTR _DeAliasMem ;DO it and ignore errors POP ESI ;get pExch back Resp06: MOV EAX, dRqHndl CLI CALL DisposeRQB ; Allocate a link block
MMURTL V1.0

; Get Request handle into EBX (pRqBlk) ; No interruptions ; Return Rqb to pool. Not needed anymore

-259-

RICHARD A BURGESS

MOV EAX,pFreeLB OR EAX,EAX JNZ Resp07 MOV EAX,ercNoMoreLBs JMP RespEnd Resp07:

; NewLB <= pFreeLB; ; IF pFreeLB=NIL THEN No LBs; ; ; caller with error in the EAX register

MOV EBX,[EAX+NextLB] ; pFreeLB <= pFreeLB^.Next MOV pFreeLB,EBX ; DEC _nLBLeft ; Update stats MOV DWORD PTR [EAX+LBType], RESPLB ; This is a Response Link Block MOV DWORD PTR [EAX+NextLB], 0 ; pLB^.Next <= NIL; MOV EBX, dRqHndl ; Get Request handle into EBX MOV [EAX+DataLo],EBX ; Store in lower half of pLB^.Data MOV EBX, dStatRet ; Get Status/Error into EBX MOV [EAX+DataHi],EBX ; Store in upper half of pLB^.Data PUSH EAX ; Save pLB on the stack CALL deQueueTSS ; DeQueue a TSS on that Exch OR EAX,EAX ; Did we get one? JNZ Resp08 ; Yes, give up the message POP EAX ; Get the pLB just saved CALL enQueueMsg ; EnQueue the Message on Exch XOR EAX, EAX ; No Error JMP SHORT RespEnd ; And get out! Resp08: POP EBX MOV [EAX+pLBRet],EBX CALL enQueueRdy MOV EAX,pRunTSS CALL enQueueRdy CALL deQueueRdy CMP JNE XOR JMP Resp10: MOV MOV MOV INC MOV MOV JMP XOR RespEnd: STI MOV ESP,EBP POP EBP RETF 8 MoveRequest() This is the kernel MoveRequest primitive. This allows a service to move a request to another exchange it owns. This cannot be used to forward a request to another service or job, because the pointers in the request block are not properly
MMURTL V1.0

; ; ; ; ; ;

Get the pLB just saved into EBX and put it in the TSS EnQueue the TSS on the RdyQ Get the Ptr To the Running TSS and put him on the RdyQ Get high priority TSS off the RdyQ

EAX,pRunTSS Resp10 EAX,EAX SHORT RespEnd

; If the high priority TSS is the ; same as the Running TSS then return ; Return to Caller with erc ok. ;

pRunTSS,EAX BX,[EAX+Tid] TSS_Sel,BX _nSwitches EAX, TimerTick SwitchTick, EAX FWORD PTR [TSS] EAX,EAX

; Make the TSS in EAX the Running TSS ; Get the task Id (TR) ; Put it in the JumpAddr ;Save time of this switch for scheduler ; ; JMP TSS ; Return to Caller with erc ok.

; ; ; Rtn to Caller & Remove Params

-260-

RICHARD A BURGESS

aliased. It is very similar to SendMsg() except it checks to ensure the destination exchange is owned by the sender. See listing 18.14. Listing 18.14. MoveRequest kernel primitive code

; Procedural Interface : ; ; MoveRequest(dRqBlkHndl, DestExch):ercType ; ; dqMsg is the handle of the RqBlk to forward. ; DestExch the exchange to where the Request should be sent. ; ; ;dRqBlkHndl EQU [EBP+16] ;DestExch EQU [EBP+12] PUBLIC __MoveRequest: PUSH EBP MOV EBP,ESP MOV ESI, [EBP+12] CMP ESI,nExch JNAE MReq02 MOV EAX,ercOutOfRange JMP MReqEnd MReq02: MOV EAX,ESI MOV EDX,sEXCH MUL EDX MOV EDX,prgExch ADD EAX,EDX MOV ESI,EAX MOV EDX, [EAX+Owner] CALL GetpCrntJCB CMP EDX, EAX JE MReq04 MOV EAX, ErcNotOwner JMP MReqEnd MReq04: CLI ; Allocate a link block MOV EAX,pFreeLB OR EAX,EAX JNZ MReq08 MOV EAX,ercNoMoreLBs JMP MReqEnd MReq08: MOV EBX,[EAX+NextLB] MOV pFreeLB,EBX DEC _nLBLeft MOV MOV MOV MOV ; pFreeLB <= pFreeLB^.Next ; ; Update stats ; No interruptions from here on ; ; ; ; ; NewLB <= pFreeLB; IF pFreeLB=NIL THEN No LBs; caller with error in the EAX register Go home with bad news ; ; ; ; ; ; ; ; ; ; ; ; Exch => EAX Compute offset of Exch in rgExch Add offset of rgExch => EAX MAKE ESI <= pExch Put exch owner into EDX (pJCB) Leaves it in EAX (uses only EAX) If the exchange is not owned by sender return to the caller with error in the EAX register. Get out ; ; ; ; ; ; ; ;

Get Exchange Parameter in ESI Is the exchange is out of range No, continue in the EAX register. Get out

DWORD PTR [EAX+LBType], REQLB ; This is a Request Link Block DWORD PTR [EAX+NextLB], 0 ; pLB^.Next <= NIL; EBX, [EBP+16] ; RqHandle [EAX+DataLo],EBX ; RqHandle into Lower 1/2 of Msg

MMURTL V1.0

-261-

RICHARD A BURGESS

MOV DWORD PTR [EAX+DataHi], PUSH EAX CALL deQueueTSS OR EAX,EAX JNZ MReq10 POP EAX CALL enQueueMsg JMP SHORT MReqEnd MReq10: POP EBX MOV [EAX+pLBRet],EBX CALL enQueueRdy MOV EAX,pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX,pRunTSS JNE MReq12 XOR EAX,EAX JMP SHORT MReqEnd MReq12: MOV MOV MOV INC MOV MOV JMP XOR MReqEnd: STI MOV ESP,EBP POP EBP RETF 8 SendMsg() ; ; ; ; pRunTSS,EAX BX,[EAX+Tid] TSS_Sel,BX _nSwitches EAX, TimerTick SwitchTick, EAX FWORD PTR [TSS] EAX,EAX

0 ; ; ; ; ; ; ;

; Store zero in upper half of pLB^.Data Save pLB on the stack DeQueue a TSS on that Exch Did we get one? Yes, give up the message Get the pLB just saved EnQueue the Message on Exch And get out!

; ; ; ; ; ; ; ; ;

Get the pLB just saved into EBX and put it in the TSS EnQueue the TSS on the RdyQ Get the Ptr To the Running TSS and put him on the RdyQ Get high priority TSS off the RdyQ If the high priority TSS is the same as the Running TSS then return Return to Caller with erc ok.

; Make the TSS in EAX the Running TSS ; Get the task Id (TR) ; Put it in the JumpAddr for Task Swtich ; Keep track of how many swtiches for stats ;Save time of this switch for scheduler ; ; JMP TSS (This is the task swtich) ; Return to Caller with erc ok.

This is the kernel SendMsg primitive. This sends a non-specific message from a running task to an exchange. This may cause a task switch, if a task is waiting at the exchange and it is of equal or higher priority than the task which sent the message. See listing 18.15. Listing 18.15. SendMsg kernel primitive code

; ; Procedural Interface : ; ; SendMsg(exch, dMsg1, dMsg2):ercType ; ; ; ; ; ; ;

exch is a DWORD (4 BYTES) containing the exchange to where the message should be sent. dMsg1 & dMsg2 are DWord values defined and understood only by the sending and receiving tasks.

MMURTL V1.0

-262-

RICHARD A BURGESS

SendExchange MessageHi MessageLo

EQU [EBP+14h] EQU DWORD PTR [EBP+10h] EQU DWORD PTR [EBP+0Ch] ; ; ; ; ; ; ;

PUBLIC __SendMsg: PUSH EBP MOV EBP,ESP MOV ESI,SendExchange CMP ESI,nExch JNAE Send00 MOV EAX,ercOutOfRange JMP SendEnd Send00: MOV MOV MUL MOV ADD MOV CMP JNE MOV JMP Send01: CLI ; Allocate a link block MOV EAX,pFreeLB OR EAX,EAX JNZ SHORT Send02 MOV EAX,ercNoMoreLBs JMP SHORT MReqEnd Send02: MOV EBX,[EAX+NextLB] MOV pFreeLB,EBX DEC _nLBLeft MOV MOV MOV MOV MOV MOV

Get Exchange Parameter in ESI If the exchange is out of range the return to caller with error in the EAX register.

EAX,ESI ; Exch => EAX EDX,sEXCH ; Compute offset of Exch in rgExch EDX ; EDX,prgExch ; Add offset of rgExch => EAX EAX,EDX ; ESI,EAX ; MAKE ESI <= pExch DWORD PTR [EAX+Owner], 0 ; If the exchange is not allocated Send01 ; return to the caller with error EAX,ercNotAlloc ; in the EAX register. SendEnd ;

; No interrupts ; ; ; ; ; NewLB <= pFreeLB; IF pFreeLB=NIL THEN No LBs; caller with error in the EAX register Go home with bad news

; pFreeLB <= pFreeLB^.Next ; ; Update stats

DWORD PTR [EAX+LBType], DATALB ; This is a Data Link Block DWORD PTR [EAX+NextLB], 0 ; pLB^.Next <= NIL; EBX,MessageLo ; Get lower half of Msg in EBX [EAX+DataLo],EBX ; Store in lower half of pLB^.Data EBX,MessageHi ; Get upper half of Msg in EBX [EAX+DataHi],EBX ; Store in upper half of pLB^.Data ; Save pLB on the stack ; No interrupts ; DeQueue a TSS on that Exch ; ; ; ; ; ; Did we get one? Yes, give up the message Get the pLB just saved No interrupts EnQueue the Message on Exch And get out (Erc 0)!

PUSH EAX CLI CALL deQueueTSS STI OR EAX,EAX JNZ Send25 POP EAX CLI CALL enQueueMsg JMP Send04 Send25: POP EBX
MMURTL V1.0

; Get the pLB just saved into EBX

-263-

RICHARD A BURGESS

CLI MOV [EAX+pLBRet],EBX CALL enQueueRdy MOV EAX,pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX,pRunTSS JNE Send03 JMP SHORT Send04 Send03: MOV MOV MOV INC MOV MOV JMP Send04: XOR EAX,EAX SendEnd: STI MOV ESP,EBP POP EBP RETF 12 pRunTSS,EAX BX,[EAX+Tid] TSS_Sel,BX _nSwitches EAX, TimerTick SwitchTick, EAX FWORD PTR [TSS]

; ; ; ; ; ; ; ; ;

No interrupts and put it in the TSS EnQueue the TSS on the RdyQ Get the Ptr To the Running TSS and put him on the RdyQ Get high priority TSS off the RdyQ If the high priority TSS is the same as the Running TSS then return Return with ErcOk

; Make the TSS in EAX the Running TSS ; Get the task Id (TR) ; Put it in the JumpAddr ;Save time of this switch for scheduler ; ; JMP TSS

; Return to Caller with erc ok.

; ; ; ;

ISendMsg() This is the kernel ISendMsg primitive (Interrupt Send). This procedure allows an ISR to send a message to an exchange. This is the same as SendMsg() except no task switch is performed. If a task is waiting at the exchange, the message is associated (linked) with the task and then moved to the RdyQ. It will get a chance to run the next time the RdyQ is evaluated by the kernel, which will probably be caused by the timer interrupt slicer. Interrupt tasks can use ISendMsg() to send single or multiple messages to exchanges during their execution. Interrupts are cleared on entry and will not be set on exit! It is the responsibility of the caller to set them, if desired. ISendMsg is intended only to be used by ISRs in device drivers. You may notice that jump instructions are avoided and code is placed in-line, where possible, for speed. See listing 18.16. Listing 18.16. IsendMsg kernel primitive code

; Procedural Interface : ; ; ISendMsg(exch, dMsg1, dMsg2):ercType ; ; exch is a DWORD (4 BYTES) containing the exchange to where the ; message should be sent. ; ; dMsg1 and dMsg2 are DWORD messages. ; ; Parameters on stack are the same as _SendMsg. PUBLIC __ISendMsg: CLI PUSH EBP
MMURTL V1.0

; ;INTS ALWAYS CLEARED AND LEFT THAT WAY! ;

-264-

RICHARD A BURGESS

MOV EBP,ESP MOV ESI,SendExchange CMP ESI,nExch JNAE ISend00 MOV EAX,ercOutOfRange MOV ESP,EBP POP EBP RETF 12 ISend00:

; ; ; ; ; ; ; ;

Get Exchange Parameter in ESI If the exchange is out of range then return to caller with error in the EAX register.

MOV EAX,ESI ; Exch => EAX MOV EDX,sEXCH ; Compute offset of Exch in rgExch MUL EDX ; MOV EDX,prgExch ; Add offset of rgExch => EAX ADD EAX,EDX ; MOV ESI,EAX ; MAKE ESI <= pExch CMP DWORD PTR [EAX+Owner], 0 ; If the exchange is not allocated JNE ISend01 ; return to the caller with error MOV EAX,ercNotAlloc ; in the EAX register. MOV ESP,EBP ; POP EBP ; RETF 12 ; ISend01: ; Allocate a link block MOV EAX,pFreeLB OR EAX,EAX JNZ SHORT ISend02 MOV EAX,ercNoMoreLBs MOV ESP,EBP POP EBP RETF 12 ISend02: MOV EBX,[EAX+NextLB] MOV pFreeLB,EBX DEC _nLBLeft ; pFreeLB <= pFreeLB^.Next ; ; ; NewLB <= pFreeLB; ; IF pFreeLB=NIL THEN No LBs; ; ; caller with error in the EAX register ; ; ;

MOV DWORD PTR [EAX+LBType],DATALB ; This is a Data Link Block MOV DWORD PTR [EAX+NextLB],0 ; pLB^.Next <= NIL; MOV EBX,MessageLo ; Get lower half of Msg in EBX MOV [EAX+DataLo],EBX ; Store in lower half of pLB^.Data MOV EBX,MessageHi ; Get upper half of Msg in EBX MOV [EAX+DataHi],EBX ; Store in upper half of pLB^.Data PUSH EAX ; Save pLB on the stack CALL deQueueTSS ; DeQueue a TSS on that Exch OR EAX,EAX ; Did we get one? JNZ ISend03 ; Yes, give up the message POP EAX ; No, Get the pLB just saved CALL enQueueMsg ; EnQueue the Message on Exch JMP ISend04 ; And get out! ISend03: POP EBX MOV [EAX+pLBRet],EBX CALL enQueueRdy ISend04: XOR EAX,EAX MOV ESP,EBP
MMURTL V1.0

; Get the pLB just saved into EBX ; and put it in the TSS ; EnQueue the TSS on the RdyQ

; Return to Caller with erc ok. ;

-265-

RICHARD A BURGESS

POP EBP RETF 12

; ;

WaitMsg() This is the kernel WaitMsg primitive. This procedure allows a task to receive information from another task via an exchange. If no message is at the exchange, the task is placed on the exchange, and the ready queue is reevaluated to make the next highest-priority task run. This is a very key piece to task scheduling. You will notice that if there is no task ready to run, I simply halt the processor with interrupts enabled. A single flag named fHalted is set to let the interrupt slicer know that it doesn't have to check for a higher-priority task. If you halted, there wasn't one to begin with. The WaitMsg() and CheckMsg() primitives also have the job of aliasing memory addresses inside of request blocks for system services. This is because they may be operating in completely different memory contexts, which means they have different page directories. See listing 18.17. Listing 18.17. WaitMsg kernel primitive code ; ; A result code is returned in the EAX register. ; ; Procedural Interface : ; ; Wait(dExch,pdqMsgRet):ercType ; ; dExch is a DWORD (4 BYTES) containing the exchange to ; where the message should be sent. ; ; pMessage is a pointer to an 8 byte area where the ; message is stored. ; WaitExchange EQU [EBP+10h] pMessage EQU [EBP+0Ch] ; ; PUBLIC __WaitMsg: ; PUSH EBP ; MOV EBP,ESP ; MOV ESI,WaitExchange ; Get Exchange Parameter in ESI CMP ESI,nExch ; If the exchange is out of range JNAE Wait00 ; the return to caller with error MOV EAX,ercOutOfRange ; in the EAX register. MOV ESP,EBP ; POP EBP ; RETF 8 ; Wait00: MOV MOV MUL MOV ADD MOV CMP JNE MOV MOV EAX,ESI ; ExchId => EAX EBX,sEXCH ; Compute offset of ExchId in rgExch EBX ; EDX,prgExch ; Add offset of rgExch => EAX EAX,EDX ; ESI,EAX ; Put Exch in to ESI DWORD PTR [EAX+Owner], 0 ; If the exchange is not allocated Wait01 ; return to the caller with error EAX,ercNotAlloc ; in the EAX register. ESP,EBP ;

MMURTL V1.0

-266-

RICHARD A BURGESS

POP EBP RETF 8 Wait01: CLI CALL deQueueMsg OR EAX,EAX JZ Wait02 JMP Wait05 Wait02: MOV EAX,pRunTSS

; ;

; ; ; ; ;

EAX <= pLB from pExch (ESI) If no message (pLB = NIL) Then Wait for Message Else Get Message and Return

; Get pRunTSS in EAX to Wait

;This next section of code Queues up the TSS pointed to ;by EAX on the exchange pointed to by ESI ; (i.e., we make the current task "wait") MOV DWORD PTR [EAX+NextTSS], 0 ; pTSSin^.Next <= NIL; XCHG ESI,EAX ; pExch => EAX, pTSSin => ESI CMP DWORD PTR [EAX+EHead], 0 ; if ..TSSHead = NIL JNE Wait025 ; then MOV [EAX+EHead],ESI ; ..TSSHead <= pTSSin; MOV [EAX+ETail],ESI ; ..TSSTail <= pTSSin; MOV DWORD PTR [EAX+fEMsg], 0 ; Flag it as a TSS (vice a Msg) XCHG ESI,EAX ; Make ESI <= pExch Again JMP SHORT Wait03 ; else Wait025: MOV EDX,[EAX+ETail] ; ..TSSTail^.NextTSS <= pTSSin; MOV [EDX+NextTSS],ESI ; MOV [EAX+ETail],ESI ; ..TSSTail <= pTSSin; MOV DWORD PTR [EAX+fEMsg], 0 ; Flag it as a TSS (vice a Msg) XCHG ESI,EAX ; Make ESI <= pExch Again ;We just placed the current TSS on an exchange, ;now we get the next TSS to run (if there is one) Wait03: CALL deQueueRdy OR EAX, EAX JNZ Wait035 MOV MOV INC STI HLT CLI XOR MOV JMP Wait035: CMP EAX,pRunTSS JE Wait04 ; Same one as before??? ; You bet! NO SWITCH!!! EDI, 1 dfHalted, EDI _nHalts ; No, then HLT CPU until ready ; Halt CPU and wait for interrupt ; An interrupt has occured. Clear Interrupts EDI,EDI dfHalted, EDI Wait03 ; Get highest priority TSS off the RdyQ ; Anyone ready to run? ; Yes (jump to check pTSS)

; Check for a task to switch to

;Now we switch tasks by placing the address of the ;new TSS in pRunTSS and jumping to it. This forces ;a hardware task switch.

MMURTL V1.0

-267-

RICHARD A BURGESS

MOV MOV MOV INC MOV MOV JMP ; ; ; ; ; Wait04:

pRunTSS,EAX BX,[EAX+Tid] TSS_Sel,BX _nSwitches EAX, TimerTick SwitchTick, EAX FWORD PTR [TSS]

; Make high priority TSS Run. ; ; ;Save time of this switch for scheduler ; ; JUMP TSS (Switch Tasks)

A task has just finished "Waiting" We are now in the new task with its memory space (or the same task if he was high pri & had a msg) If this is a system service it may need RqBlk address aliases If it is an OS service we alias in OS memory!

MOV EDX,pRunTSS MOV EAX,[EDX+pLBRet] Wait05: ; ; ; ; ; ; ; ;

; Put the TSS in EAX into EDX ; Get the pLB in EAX

If we got here, we have either switched tasks and we are delivering a message (or Req) to the new task, or the there was a message waiting at the exch of the first caller and we are delivering it. Either way, the message is already deQueued from the exch and the critical part of WaitMsg is over. We can start interrupts again except when we have to return the Link Block to the pool (free it up) ; WE CAN RESTART INTERRUPTS HERE ; Is the link block a Req Link Block? ; No, Treat it as a data link block

STI CMP DWORD PTR [EAX+LBType],REQLB JNE Wait06 ;pLB.DataLo is RqHandle (pRqBlk) PUSH EAX MOV EBX,[EAX+DataLo]

; Save ptr to Link Block ; Get pRqBlk into EBX

;Now we set up to alias the memory for the service ; (Alias the 2 Pointers in the RqBlk) ;_AliasMem(pMem, dcbMem, dJobNum, ppAliasRet): dError MOV ECX, [EBX+cbData1] OR ECX, ECX JZ Wait051 MOV EAX, [EBX+pData1] OR EAX, EAX JZ Wait051 ; ;is cbData1 0? ;Yes ; ;is pData1 NULL? ;Yes ;Set up params for AliasMem ;pMem ;cbMem ;dJobNum ;Offset to pData1 in RqBlk ;Linear Address of pData1 ;Error?? ;No, continue ;Make stack right

PUSH EAX PUSH ECX MOV EAX, [EBX+RqOwnerJob] PUSH EAX ADD EBX, pData1 PUSH EBX CALL FWORD PTR _AliasMem OR EAX, EAX JZ Wait051 POP EBX
MMURTL V1.0

-268-

RICHARD A BURGESS

MOV ESP,EBP POP EBP RETF 8 Wait051: POP EAX PUSH EAX MOV EBX,[EAX+DataLo] MOV ECX, [EBX+cbData2] OR ECX, ECX JZ Wait052 MOV EAX, [EBX+pData2] OR EAX, EAX JZ Wait052 PUSH EAX PUSH ECX MOV EAX, [EBX+RqOwnerJob] PUSH EAX ADD EBX, pData2 PUSH EBX CALL FWORD PTR _AliasMem OR EAX, EAX JZ Wait052 POP EBX MOV ESP,EBP POP EBP RETF 8 Wait052: POP EAX Wait06: MOV MOV MOV MOV MOV EBX,[EAX+DataLo] ECX,[EAX+DataHi] EDX,pMessage [EDX],EBX [EDX+4],ECX ; ; ; ; ;

;Return Error... ; ; ;Second Pointer (pData2) ;Restore ptr to Link Block ;Save again ; Get pRqBlk into EBX ; ;is cbData2 0? ;Yes ; ;is pData2 NULL? ;Yes ;Set up params for AliasMem ;pMem ;cbMem ;dJobNum ;Offset to pData2 in RqBlk ;Linear Address of PData1 ;Error?? ;No, continue ;Make stack right ;Return Error... ; ;

;Restore ptr to Link Block

Get pLB^.Data into ECX:EBX Get Storage Addr in EDX Put pLB^.Data in specified memory space (EDX)

;Return the LB to the pool CLI MOV EBX,pFreeLB ; pLBin^.Next <= pFreeLB; MOV [EAX+NextLB],EBX ; MOV pFreeLB,EAX ; pFreeLB <= pLBin; INC _nLBLeft ; STI ; XOR EAX,EAX ; ErcOK! (0) MOV ESP,EBP ; POP EBP ; RETF 8 ;

CheckMsg() This is the kernel CheckMsg primitive. This procedure allows a task to receive information from another task without blocking. In other words, if no message is available CheckMsg() returns to the caller. If a message is available it is returned to the caller immediately.
MMURTL V1.0

-269-

RICHARD A BURGESS

return to the caller with error MOV EAX. Compute offset of Exch in rgExch MUL EBX . . MOV EDX. CheckMsg kernel primitive code .EBP POP EBP RETF 8 Chk02: CLI CALL deQueueMsg OR EAX. .pdqMsg):ercType . Exch => EAX MOV EBX. .EBP POP EBP RETF 8 Chk03: STI . Chk01: MOV EAX. . Listing 18. Add offset of rgExch => EAX ADD EAX. MOV ESI. .nExch .REQLB MMURTL V1. return with erc no msg . . Procedureal Interface : .EAX .ercNoMsg MOV ESP. . RETF 8 . POP EBP . . 0 .ChkExchange .18. MOV EBP.EDX . Put pExch in to ESI CMP DWORD PTR [EAX+Owner].EAX JNZ Chk03 STI MOV EAX. . MOV ESP. in the EAX register. .The caller is never placed on an exchange and the ready queue is not evaluated.ercOutOfRange . . . Can't be interrupted EAX <= pLB from pExch (ESI) If pLB = NIL Then Go to get msg and return . in the EAX register. address aliasing must also be accomplished here for system services if they have different page directories. ChkExchange EQU [EBP+10h] pCkMessage EQU [EBP+0Ch] PUBLIC __CheckMsg: . PUSH EBP . . CMP DWORD PTR [EAX+LBType].0 -270- . . A result code is returned in the EAX register. As with WaitMsg().18.prgExch . . MOV ESI.ercNotAlloc MOV ESP. exch is a DWORD (4 BYTES) containing the exchange to where the . If the exchange is not allocated JNE Chk02 . CheckMsg(exch. the return to caller with error MOV EAX. pdqMsg is a pointer to an 8 byte area where the message is stored. message should be sent.ESI .We can be interrupted again . Get Exchange Parameter in ESI CMP ESI. If the exchange is out of range JNAE Chk01 . . See listing 18.EBP .ESP .sEXCH . . Is the link block a Req Link Block? RICHARD A BURGESS .

EAX JZ Chk031 PUSH EAX PUSH ECX MOV EAX.Save again .Error?? .cbMem ._AliasMem(pMem.pLB. Get pRqBlk into EBX .Return Error. ECX JZ Chk031 MOV EAX.Yes .Make stack right . Treat it as a data link block .Make stack right . dJobNum.[EAX+DataLo] MOV ECX. (Alias the 2 Pointers in the RqBlk) .No.DataLo is RqHandle (pRqBlk) PUSH EAX MOV EBX.Yes .Set up params for AliasMem .dJobNum . continue ..cbMem . .No..is cbData2 0? . [EBX+cbData2] OR ECX. [EBX+RqOwnerJob] PUSH EAX ADD EBX. . . MMURTL V1.EBP POP EBP RETF 8 . [EBX+RqOwnerJob] PUSH EAX ADD EBX. [EBX+cbData1] OR ECX. . . continue . ECX JZ Chk032 MOV EAX. Save ptr to Link Block .0 -271- RICHARD A BURGESS .pMem . ppAliasRet): dError MOV ECX. No. EAX JZ Chk031 POP EBX MOV ESP. ..[EAX+DataLo] . [EBX+pData2] OR EAX.Offset to pData1 in RqBlk . .dJobNum .Second Pointer (pData2) . pData2 PUSH EBX CALL FWORD PTR _AliasMem OR EAX. pData1 PUSH EBX CALL FWORD PTR _AliasMem OR EAX.Yes .is pData2 NULL? . Get pRqBlk into EBX . .Offset to pData2 in RqBlk ..Now we set up to alias the memory for the service .Linear Address of PData1 . dcbMem.is cbData1 0? . EAX JZ Chk032 POP EBX MOV ESP.Error?? .is pData1 NULL? .Yes . .Restore ptr to Link Block . EAX JZ Chk032 PUSH EAX PUSH ECX MOV EAX.Linear Address of pData1 .pMem .EBP POP EBP RETF 8 Chk031: POP EAX PUSH EAX MOV EBX.Set up params for AliasMem .Return Error.JNE Chk04 . [EBX+pData1] OR EAX.

EBP POP EBP RETF 8 . NTS_Job EQU [EBP+36] . For MMURTL version 1. The change will be transparent to applications when it occurs. STI . . .x. pFreeLB <= pLBin.19. Priority. EIP): dErcRet . and any other access violations caused with result in a general protection violation. See listing 18. fDebug.ESP MOV EDX.Data in specified memory space (EDX) .19.Job Num for this task NTS_CS EQU [EBP+32] . .EAX .Exchange for TSS NTS_ESP EQU [EBP+16] .EBX . or page fault.Restore Ptr to Link Block . Listing 18. This is used primarily to create a task for a job other than the one you are in. . CodeSeg. . NewTask kernel primitive code . NTS_Pri CMP EDX. ESP. INC _nLBLeft .pCkMessage [EDX]. . nPRI-1 MMURTL V1.pFreeLB .EAX MOV ESP.ECX .Return the LB to the pool CLI MOV EBX.[EAX+DataLo] ECX.EBX [EDX+4]. . Get pLB^. MOV [EAX+NextLB].Data into ECX:EBX Get Storage Addr in EDX Put pLB^. NewTask() This is the kernel NewTask() primitive.Chk032: POP EAX Chk04: MOV MOV MOV MOV MOV EBX. . NewTask() will allocate operating system stack space separately from the stack memory the caller provides as a parameter. stack protection is not provided for transitions through the call gates to the OS code.TRUE for DEBUGing NTS_Exch EQU [EBP+20] .8 for OS.Next <= pFreeLB. This creates a new task and schedules it for execution. . In future versions. pLBin^. The protect in this version of MMURTL is limited to page protection. . XOR EAX.[EAX+DataHi] EDX. Exch. .Initial stack pointer NTS_EIP EQU [EBP+12] .Task start address PUBLIC __NewTask: PUSH EBP MOV EBP. MOV pFreeLB. . Procedural interface: . .Priority of this task NTS_fDbg EQU [EBP+24] .0 . NewTask(JobNum. 18h for user task NTS_Pri EQU [EBP+28] .ErcOK! (0) . The operating system job-management code uses this to create the initial task for a newly loaded job. .Priority OK? -272- RICHARD A BURGESS .

Restore pNewTSS to EAX . . . IF pFreeTSS=NIL THEN Return. NTS_Exch ECX. . .. . NTS_Job CALL GetpJCB MOV ECX.get the PD from the JCB MOV EAX.0 .EAX now has pJCB .Set up to call LinToPhy .Put new Exch in TSS (ECX is free) EBX.Set up to call GetpJCB . NTS_EIP .No. 0 MMURTL V1. . [ECX+JcbPD] PUSH EAX MOV EAX.ercNoMoreTSSs JMP NTEnd NT0002: MOV EBX.ercBadPriority JMP NTEnd NT0000: MOV CMP JBE MOV JMP NT0001: CLI MOV EAX. ECX .pFreeTSS OR EAX.EBX EBX. NewTSS <= pFreeTSS. pFreeTSS <= pFreeTSS^.put Priority into TSS DWORD PTR [EAX+TSS_EFlags].ercOutOfRange NTEnd .Debug on entry? -273- RICHARD A BURGESS .Put pJCB into TSS .Save pNewTSS again . ECX.CX .EBX [EAX+TSS_ESP0]. NTS_Job CALL LinToPhy MOV EBX.EBX DEC _nTSSLeft STI . EAX POP EAX MOV [EAX+TSS_CR3].ECX now has pJCB .Put Physical Add for PD into TSS_CR3 .mov CS into TSS [EAX+TSS_CS]..Exch in range? PUSH EAX .EAX now has pNewTSS MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV [EAX+Priority]. nExch NT0001 EAX.EBX CMP DWORD PTR NTS_fDbg.EAX JNZ NT0002 MOV EAX.Save pNewTSS .EBX . EAX POP EAX MOV [EAX+TSS_pJCB].Next .JBE NT0000 MOV EAX.DL . ECX.mov ESP into TSS [EAX+TSS_ESP].0202h .Load the Flags Register [EAX+TSS_Exch].Now we get pJCB from JobNum they passed in so we can . NTS_ESP .[EAX+NextTSS] MOV pFreeTSS.Get Physical Address for PD into EAX . NTS_CS .mov EIP into TSS (Start Address) [EAX+TSS_EIP].we can't be interrupted .pNewTSS into EAX .ECX MOV EBX.

Just put new guy on the ReadyQue (EAX) XOR EAX. SpawnTask kernel primitive code . . 1 NT0004: MOV EBX.New TSS -> EAX .Move new TSS into pRunTSS . EAX JMP FWORD PTR [TSS] XOR EAX.EDX PUSH EDX CALL enQueueRdy POP EAX MOV pRunTSS. .20.Put Selector/Offset in "TSS" . the newly created task is placed on the ready queue if it is not a higher priority than the task that created it. See listing 18.20.ErcOk MMURTL V1. . It's the easy way to create additional tasks for existing jobs.Save time of this switch for scheduler . . fDebug.No MOV WORD PTR [EAX+TSS_TrapBit].EAX MOV BX.0 -274- RICHARD A BURGESS . New TSS -> EDX .EBP POP EBP RETF 28 SpawnTask() SpawnTask is the kernel primitive used to create another task (a thread) in the context of the caller's job control block and memory space.Yes .BX INC _nSwitches MOV EAX.ercOk JMP NTEnd . .BL . . SpawnTask(pEntry. pStack. Procedural Interface: .Get who's running CMP BYTE PTR [EDX+Priority]. NTS_Pri . . . .Who got the highest Pri? JA NT0005 .EAX . Listing 18.JE NT0004 .We can't be interrupted MOV EDX.pRunTSS .Get priority of new task CLI . dPriority. As with NewTask().New guy does (lowest num) CALL enQueueRdy .Save New TSS . fOSCode).EAX NTEnd: STI MOV ESP.CrntTSS -> EAX.Jump to new TSS .[EAX+Tid] MOV TSS_Sel. pEntryST EQU DWORD PTR [EBP+28] dPriST EQU DWORD PTR [EBP+24] fDebugST EQU DWORD PTR [EBP+20] pStackST EQU DWORD PTR [EBP+16] fOSCodeST EQU DWORD PTR [EBP+12] NewExchST NewTSSST EQU DWORD PTR [EBP-4] EQU DWORD PTR [EBP-8] .Return to caller NT0005: XCHG EAX. . TimerTick MOV SwitchTick.

EBX MOV EBX.Next .EDX MOV EDX. . 0 . 8 CMP dPriST. NewTSS <= pFreeTSS.pEntryST . [EBX+TSS_CR3] . [EBX+TSS_pJCB] . .PUBLIC __SpawnTask: PUSH EBP MOV EBP.EBX WORD PTR [EAX+TSS_CS].[EAX+NextTSS] MOV pFreeTSS. .Load the Flags Register CMP fDebugST.Defaults to OS code selector fOSCodeST.. move into new TSS MOV DWORD PTR [EAX+TSS_EFlags]. 0 ST0003 WORD PTR [EAX+TSS_CS].Yes ST0004: MMURTL V1.mov ESP into TSS MOV [EAX+TSS_ESP]. pRunTSS MOV EDX. pStackST . NewExchST PUSH EAX CALL FWORD PTR _AllocExch OR EAX. .Priority OK? . OSCodeSel . nPRI-1 JBE ST0001 MOV EAX. EAX JNZ STEnd .0202h .ESP SUB ESP.0 ..ercBadPriority JMP STEnd ST0001: LEA EAX.Dealloc Exch if we didn't get a TSS PUSH NewExchST CALL FWORD PTR _DeAllocExch MOV EAX. 1 .Debug on entry? JE ST0004 .No MOV WORD PTR [EAX+TSS_TrapBit]. IF pFreeTSS=NIL THEN Return.mov EIP into TSS MOV [EAX+TSS_EIP].Yup.Get pJCB from Crnt Task MOV [EAX+TSS_pJCB].Get CR3 from crnt task MOV [EAX+TSS_CR3]. EAX .we can't be interrupted . JobCodeSel .EBX MOV EBX.Allocate a new TSS CLI MOV EAX. . .pFreeTSS OR EAX.EDX .Make OS code selector -275- RICHARD A BURGESS . NewTSSST.ercNoMoreTSSs .EAX JNZ ST0002 STI .EBX MOV [EAX+TSS_ESP0]. bad news .see if we got an error . pFreeTSS <= pFreeTSS^. .two local DWORD vars .mov exch into TSS [EAX+TSS_Exch].Save new TSS EBX. NewExchST .Allocate exchange .No. JMP NTEnd ST0002: MOV EBX.EBX DEC _nTSSLeft STI MOV MOV MOV MOV CMP JNE MOV ST0003: MOV EBX.

PUBLIC __AllocExch: PUSH EBP MOV EBP. New TSS -> EDX .Put Selector/Offset in "TSS" . Zero the Exch Index . just put new guy on Q.EAX STEnd: STI MOV ESP.21.21.pRunTSS CMP [EDX+Priority].New TSS -> Stack .we can't be interrupted .Save time of this switch for scheduler . Allocate exchange code .Move new TSS into pRunTSS .ercOk . .mov priority into BL . . .CrntTSS -> EAX.Old guy does. New guy does (lowest num) . . Get number of exchanges in ECX .prgExch MOV ECX.ESI MOV EBX.Who got the highest Pri? . . AllocExch(pExchRet):dError .ESP XOR ESI.EAX MOV BX. pExchRet is a pointer to where you want the Exchange Handle . This is an auxiliary function to allocate an exchange. TimerTick MOV SwitchTick. 0 MMURTL V1. . . .EBP POP EBP RETF 20 AllocExch() . .EDX PUSH EDX CALL enQueueRdy POP EAX MOV pRunTSS. . dPriST MOV [EAX+Priority].BL CLI MOV EDX. Listing 18.[EAX+Tid] MOV TSS_Sel.BX INC _nSwitches MOV EAX. EBX <= ADR rgExch . .EAX JMP STEnd ST0005: XCHG EAX. returned. The exchange is a "message port" where you send and receive messages.BL JA ST0005 CALL enQueueRdy XOR EAX. EAX JMP FWORD PTR [TSS] XOR EAX. Is this exchange free to use RICHARD A BURGESS -276- .If crnt >.MOV EBX. It is property of a Job (not a task).New TSS -> EAX . Procedural Interface : . . See listing 18.ErcOk .put in TSS . .0 . The Exchange Handle is a DWORD (4 BYTES).nExch AE000: CLI CMP DWORD PTR [EBX+Owner].Get who's running . .Place crnt TSS on Q .Jump to new TSS .Return to caller .

. Exch is the Exchange Handle the process is asking to be released.EBP . TSSs. Stats XOR EAX. .ercOK (0) MOV ESP. .22.[EAX+Owner] EBX.prgExch EAX. .ESI EDX. Those link blocks may also contain requests. Get pRunTSS in EDX MOV EAX. .JE AE001 ADD EBX. If we found a Free Exch. .[EDX+TSS_pJCB] .pRunTSS .[EDX+TSS_pJCB] EDX. . MMURTL V1. .[EBP+0CH] . .EAX . . PUBLIC __DeAllocExch: PUSH EBP MOV EBP. POP EBP . and request blocks are freed up as necessary. . Load the Exchange Index in ESI Get the Exchange Index in EAX Compute offset of Exch in rgExch Add offset of rgExch => EAX Make a copy in ECX (ECX = pExch) Get the pRunTSS in EDX Get pJCB in EBX Get the Exchange Owner in EDX If the CurrProc owns the Exchange. RETF 4 . . Messages are deQueued. . Procedural Interface : . Get the pExchRet in EDX MOV [EDX]. See listing 18.0 . JUMP Point to the next Exchange Increment the Exchange Index Keep looping until we are done There are no instances of the MOV EDX. Make the Exch owner the Job STI . however. This function has a lot more to do. . Make the msg/TSS queue NIL MOV DWORD PTR [EBX+ETail].EAX EDX.EAX .ESP MOV MOV MOV MUL MOV ADD MOV MOV MOV MOV CMP ESI. DeAllocExch() This is the compliment of AllocExch(). Deallocate exchange code . DEC _nEXCHLeft . This means that DeAllocExch() must also be a garbage collector. . When an exchange is returned to the free pool of exchanges.0 .sEXCH INC ESI LOOP AE000 STI MOV EAX.pRunTSS EBX. and link blocks. MOV DWORD PTR [EBX+EHead].sEXCH EDX EDX. . . Get the pJCB in EAX MOV [EBX+Owner].EBP POP EBP RETF 4 AE001: . .[EBP+0CH] EAX. Listing 18. DeAllocExch(Exch):ercType . . .EDX ECX.ESI . Put Index of Exch at pExchRet MOV EDX. . .22.0 -277- RICHARD A BURGESS .EDX . . . it may have left over link blocks hanging on it.ercNoMoreExch MOV ESP.

EBP . yes .ercNotOwner MOV ESP.Next <= pFreeLB.0 -278- RICHARD A BURGESS . . is this the OS??? . pLBin^. Free up the exchange.instructions or a service crashes! . This is primarily provided for system services that provide direct access blocking calls for customers. Get the message off of the Exchange OR EAX. JUMP MOV ESI.pFreeTSS . INC _nTSSLeft . 0 .Return the LB to the pool MOV EBX. No.pFreeLB . Stats STI . ECX . CLI .EAX . if not owner. MOV DWORD PTR [ECX+fEMsg].only happen if a system service writer doesn't follow . pTSSin^. JMP DE001 . OFFSET MonJCB JE DE000 MOV EAX.ercOK (0) MOV ESP. EAX JZ DE002 . . Go And Check for more. .0 . Check to See if TSS is queued JE DE002 . GetTSSExch() This is an auxiliary function that returns the exchange of the current TSS to the caller. See if a message may be queued JE DE001 .EBX . Reset msg Flag. MOV pFreeLB. MOV DWORD PTR [EAX+TSS_pJCB]. If we find an RqBlk on the exchange we must respond . 0 . These services use the default exchange in the TSS to make a request for the caller of a procedural interface. See listing 18. Yes. Make TSS invalid MOV pFreeTSS. INC _nLBLeft . Go And Check for more.Next <= pFreeTSS.JE DE000 CMP EBX.with ErcInvalidExch before we continue! This will .Free up the TSS (add it to the free list) MOV EBX.EAX . . pFreeTSS <= pTSSin. 0 . pFreeLB <= pLBin. NIL = Empty. MOV [EAX+NextLB].EAX . Nothing there. RETF 4 . MOV [EAX+NextTSS]. Go check for Task (TSS) MOV ESI. JMP DE000 . DE001: CMP DWORD PTR [ECX+EHead]. DE002: MOV DWORD PTR [ECX+Owner]. MMURTL V1. 0 . ESI must point to Exch for deQueue CALL deQueueMsg . . yes .EBX . POP EBP .23. INC _nEXCHLeft . Get the TSS off of the Exchange . ESI must point to Exch for deQueue CALL deQueueTSS . XOR EAX. Go free the Exch. CMP DWORD PTR [ECX+fEMsg].EBP POP EBP RETF 4 DE000: . ECX .

Get the Ptr To the Running TSS MOV EBX. SetPriority . Procedural Interface : . [EAX+TSS_Exch] .24.24 Listing 18. MOV EBP. ErcOK POP EBP . RETF 4 SetPriority() This is an auxiliary function that sets the priority of the caller's task. Put Index of Exch at pExchRet XOR EAX. to the priority specified in the single parameter.[EBP+0CH] . returned.pRunTSS . GetTSS exchange code .No error. BL . . EAX . Get the new pri into EBX AND EBX. PUBLIC __SetPriority . . MOV EBP. 01Fh .pRunTSS . MOV EAX. POP EBP . RETF 4 . MOV EAX. This doesn't really affect the kernel. . GetTSSExch(pExchRet):dError . PUSH EBP . . Get Exch in EBX MOV [ESI].ESP . .ESP .23. If this is used to set a temporary increase in priority. bPriority is a byte with the new priority. EAX . PUBLIC __GetTSSExch: . Procedural Interface : . .0 -279- RICHARD A BURGESS . .This sets the priority of the task that called it . Nothing higher than 31! MOV BYTE PTR [EAX+Priority]. . the complimentary call GetPriority() should be used so the task can be returned to its original priority.Listing 18. ErcOK . SetPriority(bPriority):dError . See listing 18. Set priority code . Get the pExchRet in EDX MOV EBX. PUSH EBP . The Exchange is a DWORD (4 BYTES). pExchRet is a pointer to where you want the Exchange Handle .EBX .[EBP+0CH] . Get the Ptr To the Running TSS MOV ESI.Put it in the TSS XOR EAX. . MMURTL V1. . because the new priority isn't used until the task is rescheduled for execution. .

25. Get priority code . Procedural Interface : .ESP . MMURTL V1. RETF 4 . . . GetPriority .This gets the priority of . ErcOK POP EBP . DL . . XOR EAX. Get the MOV DL. and passes it to bPriorityRet.GetPriority() This is an auxiliary function that returns the current priority of the running task. . Callers should use GetPriority() before using the SetPriority() call so they can return to their original priority when their "emergency" is over. Get the MOV EBX. EAX . priority returned.0 -280- RICHARD A BURGESS .[EBP+0CH] . MOV EAX. bPriorityret is a pointer to a .25.pRunTSS . See listing 18. MOV EBP. . Listing 18. PUBLIC __GetPriority . the task that called it byte where you want the Ptr To the Running TSS return pointer into EBX No error. SetPriority(bPriorityRet):dError . . BYTE PTR [EAX+Priority] MOV BYTE PTR [EBX]. PUSH EBP .

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

Listing 19.100000h ADD _nPagesFree. OFFSET pTbl1 . which means we start at the top of 2Mb (1FFFFCh). and the Video RAM and Boot ROM.Another megs worth of pages .1FFFFCh XOR EBX.ECX MOV EBX.Are we above 64 megs .Make Page Table Entry AND DWORD PTR [EDX]. oMemMax must be last byte .06D72746CH MEMLoop: MOV DWORD PTR [EAX]. 0001h . EAX .EBX MOV ECX.Marks page in PAM ADD EDX. EAX CALL MarkPage . 4 . You also calculate the number of pages of physical memory this is and store it in the GLOBAL variable nPagesFree. Note that linear addresses match physical addresses for the initial OS data and code.'mrtl' test string value for memory .EAX SUB EAX.3 MOV _oMemMax.OUT: .EBX JMP MemLoop MemLoopEnd: .3. Memory management initialization code .3 ADD EAX. See listing 19.EDX points to OS Page Table 1 XOR EAX.Make it the last DWord again .0 -282- RICHARD A BURGESS . It sets nPagesFree after finding out just how much you have.Zero out for next meg test The Page Allocation Map is now sized and Zeroed.1 Mb of pages = 256 . Present MOV EBX.Set oMemMax . 0FFFFF000h .Point to 1st physical/linear page (0) IMM001: MOV [EDX]. .0h MOV DWORD PTR [EAX].ECX JNE MemLoopEnd ADD EAX.Increase PAM by another meg . Now you must fill in bits used by OS. 256 MOV EAX.IN: . and makes PTEs. . This first part MARKS the OS code and data pages as used .DWORD PTR [EAX] CMP EBX.3FFFFFCh JAE MemLoopEnd XOR EBX.Yes.USED: . MOV EDX. in megabytes. 32 CMP EAX. Continuation of memory management init. We assume 1MB to start.Supervisor. you have. Its the law! Listing 19.Move in test string . 256 ADD sPAM. InitMemMgmt: MOV _nPagesFree.InitMemMgmt InitMemMgmt finds out how much memory.3 also fills out each of the page table entries (PTEs) for the initial OS code and data.Yes! .Set it to zero intially .Next table entry MMURTL V1. which was just loaded. .top of 2 megs (for DWORD) . by writing to the highest dword in each megabyte until it fails a read-back test.Next Meg .NO! . PUBLIC Nothing Nothing (except that you can use memory management routines now!) ALL REGISTERS ARE USED.See if we got it back OK . EAX .Read test string into EBX .2.Leave upper 20 Bits OR DWORD PTR [EDX]. neither of which you consider free. It places the highest addressable offset in global oMemMax. The code in listing 19.2.

Store in Control Reg 3 EAX. 100000h . This is done by loading CR3 with the physical address of the page directory. After the MOV CR0 you must issue a JMP instruction to clear the prefetch queue of any bogus physical addresses.0 -283- RICHARD A BURGESS .Reserve 192K for OS (for now) . Listing 19. go back for more . EAX .Go for more Now you fill in PAM and PTEs for Video and ROM slots.Mark it used in the PAM ADD EDX.Mark it "User" "ReadOnly" & "Present" MOV EBX. See listing 19. and then writing it again. IMM002: MOV MOV SHR MOV ADD IMM003: MOV [EDX]. C0000h –FFFFFh. EAX . 0101b . allow you to set ROM areas as usable RAM. Right now you just mark everything from A0000 to FFFFF as used. EBX.Points to 128K Video & 256K ROM area .Make Page Table Entry AND DWORD PTR [EDX].Get Control Reg 0 EAX. EDX.ADD CMP JAE JMP EAX. then reading CR0. Now you allocate an exchange that the OS uses as a semaphore use to prevent reentrant use of the any of the critical memory management functions. EAX .Store Control Reg 0 IM0005 . The initial page directory and the page table are static.5. 4096 EAX.Make it index (SHR 12. See listing 19.5. finding the inaccessible ones and marking them as allocated in the PAM. such as the 82C30 C&T. Continuation of memory management init. which is the upper 384K of the first megabyte.6.Clear prefetch queue MMURTL V1. CR0 . EDX. This covers A0000 through 0FFFFFh. EAX.Physical address of OS page directory CR3. 30000h SHORT IMM002 SHORT IMM001 . SHL 2) . The routine in listing 19. Listing 19.Next page please CMP EAX. 80000000h .4. 4 . ANDing it with 8000000h.Leave upper 20 Bits OR DWORD PTR [EDX].No. but I can't be sure everyone can do it. . 0FFFFF000h .Next PTE entry ADD EAX. Several chip sets on the market. OFFSET PDir1 . Now we can go into paged memory mode. EBX.4 could be expanded to search through the ROM pages of ISA memory. EAX . nor can I provide instructions to everyone.1Mb yet? JAE IMM004 . Turning on paged memory management EAX.EDX pts to Page Table IMM004: MOV MOV MOV OR MOV JMP IM0005: .Set paging bit ON CR0.Yes JMP SHORT IMM003 . 4096 . 0A0000h EAX 10 OFFSET pTbl1 EBX .Setup for MarkPage call CALL MarkPage .

8.Done initializing memory managment .6.USED: Nothing EBX is the physical address of the new page.7.Page Allocation Map . Of course. Allocation of memory management exchange .Alloc Semaphore Exch for Memory calls PUSH EAX CALL FWORD PTR _AllocExch PUSH PUSH PUSH CALL MemExch . This reduces nPagesFree by one. FindHiPage FindHiPage finds the first unused physical page in memory from the top down and returns the physical address of it to the caller. See listing 19. sPAM DEC EAX . OFFSET pNextPT .no memory left. Get 'em! RETN . Flags PUSH EAX PUSH ECX PUSH EDX MOV ECX.Send a dummy message to pick up 0FFFFFFF1h 0FFFFFFF1h FWORD PTR _SendMsg You must allocate a page table to be used when one must be added to a user PD or OS PD. LEA EAX. 1 page for Next Page Table MOV EAX. MMURTL V1. OFFSET rgPAM MOV EAX. 0 . I'm afraid.. Listing 19.Are we at Bottom of PAM? FHPn . . or 0 if error EBX. Listing 19. It also marks the page as used.Where we are in PAM . PUSH EAX CALL FWORD PTR _AllocOSPage .8.Listing 19. MemExch .IN : . This must be done in advance of finding out we need one because we may not have a linear address to access it if the current PTs are all used up! It a little complicated. this means if you call FindHiPage and don't use it you must call UnMarkPage to release it..No EAX.0FFh . Finishing memory management init. Find highest physical page code . PUSH 1 .0 -284- RICHARD A BURGESS .OUT : .EAX+ECX will be offset into PAM FHP1: CMP JNE CMP JE BYTE PTR [ECX+EAX]. assuming that you will allocate it.7.All 8 pages used? FHP2 . See listing 19.

Back for next byte . It also marks the page as used. Once again. BYTE PTR [ECX+EAX] FHP3: BT JNC CMP JE DEC JMP FHPf: BTS EDX.Another Byte lower .FOUND ONE! (goto found) .Page Allocation Map . .At the bottom of the Byte? .Error (BAD CPU???) . EBX FHPf EBX.No .9. assuming that you will allocate it.Another Byte lower . EDX MOV DL.Add page number in byte .0FFh FLP2 EAX EAX. or 0 if error EBX.\ Listing 19.Set page in use . 7 XOR EDX. EBX MOV BYTE PTR [ECX+EAX]. Find lowest physical page code .no memory left..Multiply time 8 (page number base in byte) . Flags PUSH EAX PUSH ECX PUSH EDX MOV ECX. EAX SHL EBX.Set to zero for error FindLoPage finds the first unused physical page in memory from the bottom up and returns the physical address of it to the caller.Set the bit indexed by EBX .Back for next byte FLP1: CMP JNE INC CMP JAE JMP FLP2: MMURTL V1.All 8 pages used? . if we call FindLoPage and don't use it.Test bits . EBX POP EDX POP ECX POP EAX RETN FindLoPage EDX.9.Get the byte with a whole in it.Start at first byte in PAM . we must call UnMarkPage to release it. sPAM FLPn SHORT FLP1 -285- RICHARD A BURGESS . See listing 19.OUT : . .Now EBX = Physical Page Addr (EBX*4096) . EAX . 3 ADD EBX.0 BYTE PTR [ECX+EAX].USED: Nothing EBX is the physical address of the new page.IN : .. OFFSET rgPAM XOR EAX. This reduces nPagesFree by one. 12 DEC _nPagesFree POP EDX POP ECX POP EAX RETN FHPn: XOR EBX. .One less available . ..DEC EAX JMP SHORT FHP1 FHP2: MOV EBX.Are we past at TOP of PAM? .Next bit . 0 FHPn EBX FHP3 .. DL SHL EAX.

Code to mark a physical page in use .Page Allocation Map AND EBX. See listing 19. EBX MOV BYTE PTR [ECX+EAX]..Get the byte with a whole in it.10.End of the Byte? .EBX is now bit offset into byte of PAM MOV DL.Error (BAD CPU???) .Get Bit offset into PAM AND EBX. EDX MOV DL. .Now EBX = Physical Page Addr (EBX*4096) .Set to zero for error Given a physical memory address. EBX .10.Set the bit indexed by EBX . 07h . This function is used with the routines that initialize all memory management function. Listing 19. BYTE PTR [ECX+EAX] FLP3: BT JNC INC CMP JAE JMP FLPf: BTS EDX. 0FFFFF000h . . OFFSET rgPAM . 12 DEC _nPagesFree POP EDX POP ECX POP EAX RETN FLPn: XOR EBX. This reduces nPagesFree by one.OUT : .Get the byte into DL BTS EDX. EBX SHR ECX.One less available .IN : . . DL SHL EAX. 12 .Save the new PAM byte DEC _nPagesFree .FOUND ONE! (goto found) .Set page in use .. 15 .XOR EBX. 8 FLPn FLP3 .One less available POP EDX MMURTL V1.Round down to page modulo 4096 MOV ECX. EBX FLPf EBX EBX. Flags PUSH EAX PUSH ECX PUSH EDX MOV EAX. EBX POP EDX POP ECX POP EAX RETN MarkPage EDX. [EAX+ECX] .ECX is now byte offset into PAM SHR EBX. EAX SHL EBX. 3 ADD EBX. MarkPage finds the bit in the PAM associated with it and sets it to show the physical page in use.BitSet nstruction with Bit Offset MOV [EAX+ECX].USED: EBX is the physical address of the page to mark Nothing EBX.Add page number in byte .0 -286- RICHARD A BURGESS .Multiply time 8 (page number base in byte) .Next bit .Test bits . DL . EBX XOR EDX.

0 -287- RICHARD A BURGESS . USED: EAX. etc. EFlags .Linear Address of PTE for this linear address . EBX SHR ECX.BitReset instruction . DL INC _nPagesFree POP EDX POP ECX POP EAX RETN .Page Allocation Map .Physical Address . 07h ADD EAX. OFFSET rgPAM AND EBX. PUBLIC LinToPhy: MMURTL V1.12.Job Number that owns memory we are aliasing . This function supports the similarly named public routine.12. The JCB is used to identify whose page tables you are translating. 15 SHR EBX.USED: EBX is the physical address of the page to UNmark Nothing EBX.11.IN : . This also leave the linear address of the PTE itself in ESI for callers that need it.Round down to page modulo .One more available LinToPhy LinToPhy looks up the physical address of a 32-bit linear address passed in. The linear address is used to look up the page table entry which is used to get the physical address. [EAX] BTR EDX.POP ECX POP EAX RETN UnMarkPage Given a physical memory address. OUTPUT: EAX -. EBX MOV [EAX]. . INPUT: EAX -. ESI. 0FFFFF000h MOV ECX. 12 AND EBX.ECX is now byte offset into PAM . This call is used for things like aliasing for messages. See listing 19. Code to convert linear to physical addresses .11.Linear address . ECX MOV DL. . Listing 19. Code to free a physical page for reuse . This increases nPagesFree by one. ESI -. .EBX is now bit offset into byte of PAM . EBX. Flags PUSH EAX PUSH ECX PUSH EDX MOV EAX. . . DMA operations. See listing 19.OUT : . EBX -. Listing 19. UnMarkPage finds the bit in the PAM associated with it and resets it to show the physical page available again.

2 ADD EBX.get rid of lower 12 to make it an index .Shift out lower 22 bits leaving 10 bit offset . The EAX register will be set to 0 if you are looking for OS memory.Index into PD for PT we are working on .EAX now has REAL PHYSICAL ADDRESS! FindRun finds a contiguous run of free linear memory in one of the user or operating-system page tables. [EBX] AND EAX. Save in EBX . 2048 FR0: MMURTL V1. OUT: EAX . [EAX+JcbPD] ADD EAX.Get original linear back in EBX .Save Linear . 0FFFFF000h POP EBX AND EBX. 12 SHL EBX.mask off lower 12 .Leaves pJCB in EAX .13. Listing 19. . in which case we return 0.Holds count of pages (saved for caller) . 256 for user) Number of Pages for run Linear address or 0 if no run is large enough still has count of pages EBX.Move to shadow addresses MOV ECX. EBX .Physical base of page is in EAX .PUSH EBX CALL GetpJCB MOV EAX. . See listing 19.Save it again . 00000FFFh OR EAX. IN : EAX .Save this address for caller . 2048 SHR EBX.*4 to make it a byte offset into PD shadow . EAX CALL GetpCrntJCB MOV ESI.ESI now has ptr to PD .Keeps count of how many free found so far . This is an interesting routine because 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.13.Cut off upper 22 bits of linear . EBX MOV EAX.*4 makes it byte offset in PT .Move to shadow addresses in PD . [EAX+JcbPD] ADD ESI. . USED: EAX. The linear run may span page tables if additional tables already exist.Leaves pCrntJCB in EAX .0 -288- RICHARD A BURGESS . the caller sets it to 256 if you are looking for user memory. 2 ADD EBX. Code to find a free run of linear memory .EBX now points to Page Table entry! . . This is either at address base 0 for the operating system. EBX .Index into shadow addresses from EAX . 22 SHL EBX. The linear address of the run is returned in EAX unless no run that large exists. . EAX MOV ESI.Get original linear .EAX now has Linear of Page Table .Address of PD saved here . FindRun: PUSH PUSH PUSH PUSH PUSH PD Shadow Base Offset for memory (0 for OS. EAX MOV EAX. EFlags (all other registers saved & restored) EBX ECX EDX ESI EDI . EBX MOV EDX. [EBX] POP EBX PUSH EBX AND EBX.Copy number of pages to ECX. EBX RETN FindRun .EBX/EAX now points to shadow .EAX now has ptr to PD! .Get rid of upper 10 bits . 003FFFFFh SHR EBX. or the 1Gb address mark for the user.

This is the linear address we ended at . The address determines the protection level of the PTE's you add. Next PTE Please. Many functions that pass data in registers are designed to compliment each other's register usage for speed.EDX was index into PD.we have a linear run large enough to satisy the request. .The starting linear address is equal to number of the last .One less page (0 offset) .PTE we found minus ((npages -1) * 4096) . 12 OR EAX.empty (available) . .0 -289- RICHARD A BURGESS . . . EAX FR2: CMP JB INC JMP FR3: CMP DWORD PTR [EDI+EAX*4].Times size of page (* 4096) . . . 1024 FR3 EDX SHORT FR0 . If it is less than 1GB it means operating-system memory space that you will set to system. .go INC MOV JMP FROK: . next PTE please ECX. FR1: XOR EAX.EDX is 10 MSBs of Linear Address . .Not empty. EAX .Return 0 cause we didn't find it! JMP SHORT FREXIT . 22 SHL EAX.EAX indexes into PT (to compare PTEs) MMURTL V1. and the count of pages should be in EBX. while EAX was index into PT.Not done yet.Are we past last PTE of this PT? . go for it XOR EAX. keep testing .In use . This is the way FindRun left them. 0 JNE DEC JZ INC JMP FR4: . EAX. Above 1Gb is user which we will set to user level protection. The linear address of the run should be in EAX. EBX . EDX DEC EBX SHL EBX.Zero means it's . 12 SUB EAX.Is the address NON-ZERO (valid)? JNZ FR1 .Yes.MOV EDI.No.We found enough entries goto OK . .We kept original count in EBX FR2 FR4 ECX FROK EAX SHORT FR2 . EBX FREXIT: POP EDI POP ESI POP EDX POP ECX POP EBX RETN AddRun AddRun adds one or more page table entries (PTEs) to a page table. if the run spans two or more tables. or tables. See listing 19. . EDI .One more empty one! .Lin address of next page table into EDI OR EDI.From current linear address in tables we got here we must reset ECX for full count and back and start looking again EAX .EAX is next 10 bits of LA .If . SHL EDX.If we got here it means that ECX has made it to zero and . [ESI+EDX*4] .EBX still has count of pages.Next PT! .14.

Linear address of next page table into EDI PUSH EAX CALL GetpCrntJCB MOV ESI. .Get index into PD for first PT . User ReadOnly .0 .Save EAX thru GetpCrntJCB call . EBX EAX 22 2 . . 003FF000h SHR EDX. EAX AND EDX. [ESI] . OUT: . EDX.and OR in the appropriate control bits.Copy number of pages to ECX (EBX free to use). [EAX+JcbPD] POP EAX ADD ESI.Make it index to DWORDS . 10 AR0: MOV EDI.get rid of upper 10 bits & lower 12 .LinAdd into EDX again . EDX.Start at the entry 0 of next PT JMP SHORT AR0 .then check the original linear address to see if SYSTEM or USER .At this point.Restore linear address .(save for caller) .EDX is index to exact entry DEC ECX . 2048 ADD ESI.EBX has Phys Pg (only EBX affected) . . EDX MOV EDX. MEMSYS CMP EAX. ARDone: MMURTL V1. .ESI now has ptr to PD! .EDX .Are we done?? JZ ARDone ADD EDX. EDI is pointing the next PT.LinAdd to EDX . next PDE (to get next PT) XOR EDX.Yes.Index into PD for PT (10 vice 12 -> DWORDS) .ESI now points to initial PT (EDX now free) .SO EDI+EDX will point to the next PTE to do. 4 .Now we must call FindPage to get a physical address into EBX. .No.Sets User/Writable bits of PTE -290- RICHARD A BURGESS . IN : . EFlags PUSH PUSH PUSH PUSH PUSH MOV MOV SHR SHL EBX ECX EDX ESI EDI ECX.See if it's a user page . EDX. . Adding a run of linear memory .Leaves pCrntJCB in EAX . AddRun: EAX Linear address of first page EBX Number of Pages to add Nothing EAX. 4 . . . go do next PTE ADD ESI.Offset to shadow address of PD . CMP EDX. MEMUSERD AR2: MOV DWORD PTR [EDI+EDX]. USED: .Set PTE to present.14. 40000000h JB AR2 OR EBX. EBX .Are we past last PTE of this PT? JB AR1 . . THEN store it in PT. 4096 .Listing 19. . AR1: CALL FindHiPage OR EBX.Next PTE please.

Leaves pCrntJCB in EAX . .15. USED: EAX. . . EDX. Once again. .Get index into PD for first PT . and the count of pages should be in EBX. .(save for caller) . . . .ESP SUB ESP. .we have to move the other guy's physical pages into MOV MOV SHR SHL ECX. .or tables. Aliased runs are always at user protection levels even if they are in the operating-system address span. OUT: Nothing . AliasLin EQU DWORD PTR [EBP-4] AliasJob EQU DWORD PTR [EBP-8] AddAliasRun: PUSH EBP MOV EBP. The new linear address of the run should be in EAX. if the run spans two or more tables .ESI now has linear address of PD PUSH EAX CALL GetpCrntJCB MOV ESI. . Listing 19. EDX.Save EAX thru GetpCrntJCB call . The ESI register has the linear address you are aliasing and the EDX register has the job number.adding PTEs from another job's PTs marking them as alias entries. [EAX+JcbPD] MMURTL V1. 8 MOV AliasLin. Adding an aliased run of linear memory . EDX. AddAliasRun adds one or more PTEs to a page table .15. .Copy number of pages to ECX (EBX free to use). this is the way FindRun left them.LinAdd to EDX . (from find run) .Make it index to DWORDS . IN : EAX Linear address of first page of new alias entries . ESI Linear Address of pages to Alias (from other job) . EBX Number of Pages to alias . EBX EAX 22 2 . EDX Job Number of Job we are aliasing . EDX PUSH PUSH PUSH PUSH PUSH EBX ECX EDX ESI EDI . ESI MOV AliasJob.POP EDI POP ESI POP EDX POP ECX POP EBX RETN AddAliasRun . EFlags . .This first section sets to make [ESI] point to first PT that . See listing 19.0 -291- RICHARD A BURGESS .

Index into PD for PT (10 vice 12 -> DWORDS) . EAX AND EDX.Restore ESI (LinToPhy used it) . 10 ALR0: MOV EDI.Start at the entry 0 of next PT .0 -292- RICHARD A BURGESS .Address we are aliasing . go do next PTE . AliasLin ADD AliasLin.This is the Physical address to store in the new PTE. 4 CMP EDX. EDX MOV EDX. 003FF000h SHR EDX.Are we past last PTE of this PT? . EDI is pointing to the PT we are in. .cut off system bits of PTE . .Job we are aliasing .Offset to shadow addresses in PD .Set system bits as ALIAS . 0FFFFF000h OR EAX.SO then EDI+EDX will point to the next PTE to do. 4 XOR EDX.POP EAX ADD ESI. .No. . 2048 ADD ESI.mark it MEMALIAS before adding it to PT.Now we must call LinToPhy with Linear Add & JobNum .Now store it in new PTE MOV DWORD PTR [EDI+EDX]. MEMALIAS POP ESI .Save for next loop (used by LinToPhy) .Next PTE please. .Set up for next loop (post increment) .Are we done?? . AliasJob MOV EBX.EDX JMP SHORT ALR0 ALRDone: POP POP POP POP POP EDI ESI EDX ECX EBX . . next PDE (to get next PT) . .Yes. 4096 JB ALR1 ADD ESI. . ALR1: PUSH ESI MOV EAX. .Linear address of crnt page table into EDI . MOV ESP.get rid of upper 10 bits & lower 12 .At this point.EBP POP EBP RETN MMURTL V1. 4096 CALL LinToPhy We must . to get a physical address into EAX. EAX DEC ECX JZ ALRDone ADD EDX. . AND EAX.EAX now has physical address for this page .Restore linear address . . [ESI] .ESI now points to first PT of interest . .LinAdd into EDX again . .EDX is index to exact entry .

because there is no need to update anyone else's PDs. JcbPD ADD EDI. . This sets the protection on the PT to user-Read-and-Write. This is easier than AddOSPT. _nPagesFree OR EAX. 2048 EBX.Offset to PcbPD in JCB .Put in Linear address of PT (upper half) OR MOV ADD MOV MOV MMURTL V1. ErcNoMem JMP AUPTDone AUPT01: CALL GetCrntJobNum MOV EBX. MEMUSERD [ESI].no free phy pages!) . 511 AUPT02: ADD ESI.AddUserPT AddUserPT creates a new user page table. No! (Try again) .Linear add back into EBX . Individual PTEs will be set read-only for code. EAX MOV ESI.EDI points to UserPD Address .Number of entries (at least 1 is already gone) 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 EAX.Sorry. EBX . .Physical address in lower half . . See listing 19. .ESI now points to PD . pNextPT CALL LinToPhy . [EDI] ADD ESI.ESI now points to upper 1 GB in PD .Move to shadow . Adding a page table for user memory . and sticks it in the user's page directory. initializes it. Next possible empty entry . USED: EAX. pNextPT [ESI]. . .Pre allocated Page (Linear Address) . Listing 19. PUSH EDX . EAX ESI. [ESI] OR EBX.16. This will be in user address space above 1GB. Is it empty? . Put it in the User PD (and linear in shadow). OUT: 0 if OK or Error (ErcNoMem .EAX will have Physical address . MOV EAX. AddUserPT: PUSH EBX . PUSH EDI .Leaves job num in EAX (for LinToPhy) . Find first empty slot CALL GetpCrntJCB MOV EDI. EFlags .16.(save for caller) PUSH ECX . 2048 MOV ECX. PUSH ESI . EAX JNZ AUPT01 MOV EAX.0 -293- RICHARD A BURGESS . EBX LOOPNZ AUPT02 . out of physical mem . IN : Nothing .pJCB in EAX .Set user bits (Read/Write) . as shown in the following. 4 MOV EBX.See if have enuf physical memory .

ErcNoMem JMP AOPTDone AOPT01: MOV EAX. EAX CALL FindRun OR EAX. ErcNoMem JMP SHORT AUPTDone AUPT05: MOV pNextPT.0 . it won't happen except once or twice while the operating system is running.next time. _nPagesFree OR EAX.See if have enuf physical memory . 511 MMURTL V1. Listing 19. Find first empty slot MOV ESI.Now we now need another PreAllocated Page Table for . pNextPT CALL LinToPhy . Count of PDEs to check -294- RICHARD A BURGESS . You must do this to ensure the operating-system code can reach its memory no matter what job or task it's is running in. USED: EAX.PD shadow offset needed by FindRun (0) . OUT: 0 if OK or Error (ErcNoMem . in many cases. Put it in the OS PD (and linear in shadow). MOV EAX. PUSH ESI .Sorry. OFFSET PDir1 MOV ECX. EAX CALL AddRun AUPTDone: POP EDI POP ESI POP EDX POP ECX POP EBX RETN AddOSPT AddOSPT is more complicated than AddUserPT because a reference to each new operating-system page table must be placed in all user page directories. AddOSPT: PUSH EBX .no free phy pages!) . . .AddRun will return NON-ZERO on error . Adding an operating system page table .was there an error (0 means no mem) . IN : Nothing .size of request . 1 XOR EAX.17. out of physical mem . EAX JNZ AOPT01 MOV EAX. PUSH EDX .(save for caller) PUSH ECX . Get a run of 1 for next new page table MOV EBX. . See listing 19. Adding an operating-system page table doesn't happen often. . EAX JNZ AUPT05 MOV EAX.OS Job Number (Monitor) . .Pre allocated Page (Linear Address) . PUSH EDI . .. .save pNextPT (the linear address) . .17. ESI points to OS Pdir .EAX will have Physical address . 1 MOV EBX. EFlags .

EDX CALL GetpJCB MOV ECX. 2048 . .Move to shadow . .EAX now has pointer to a job's PD .See if PD id zero (Inactive JCB) . No! (Try again) now points to empty PDir Slot still has Physical Address of new table Physical Address back into EBX put them into PDir . JcbPD MOV EBX.Lower half of PD (Physical Adds) FWORD PTR _CopyData EBX EAX . 2048 MOV EBX.Move to shadow PUSH EBX ADD EAX. Update ALL PDs from PDir1 !! This doesn't happen often if it happens at all. ECX JZ AOPT04 ADD EAX. Next possible empty entry . pNextPT MOV [ESI].Next JCB . ESI EAX Get and . . . # of dynamic JCBs .AOPT02: ADD ESI. [EAX+JcbPD] OR ECX.Get values from stack ADD EBX. . .Save nJCB we are on . 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) .Source of Copy .Move to shadow PUSH EAX PUSH 1024 .Put in Linear address of PT (upper half) OR EAX. 2 JA AOPT03 MMURTL V1.Source EAX . nJCBs AOPT03: MOV EAX.EAX NOW points to PD of JCB . [ESI] OR EBX. 4 MOV EBX.Save values on stack MOV EDX.Jobs 1 & 2 use PDir1 (Mon & Debugger) -295- RICHARD A BURGESS . Not a valid JCB (unused) . PRSNTBIT MOV [ESI].Destination 1024 .Is it a valid Job? (0 if not) .Upper half of PD (Linear Adds) CALL FWORD PTR _CopyData POP EDX AOPT04: DEC EDX CMP EDX.Physical address in lower half . Get back JCB number . EBX LOOPNZ AOPT02 .0 . .Linear add back into EBX . EAX ADD ESI. OFFSET PDir1 PUSH EDX PUSH EAX PUSH EBX PUSH PUSH PUSH CALL POP POP EBX . 2048 .No. Is it empty? .Set present bit . EBX .

ErcNoMem JMP SHORT AOPTDone AOPT05: MOV pNextPT. . EAX CALL AddRun XOR EAX.Word with Call Gate ID type as follows: . EAX JNZ AOPT05 MOV EAX. OUT: EAX Returns Errors. . . .next time. AddGDTCallGate AddGDTCallGate builds and adds a GDT entry for a call gate.18. ESI. IN: AX . ESI Offset of entry point in segment of code to execute .0 -296- RICHARD A BURGESS .We now need another PreAllocated Page Table for .At this point the new table is valid to ALL jobs! . The calling conventions all follow the Pascal-style definition.. Listing 19.18. DPL entry of 2 CC0x (Not used in MMURTL) . This call doesn't check to see if the GDT descriptor for the call is already defined. .AddRun . CX Selector number for call gate in GDT (constants!) . (x = count of DWord params 0-F) . EBX. Public Memory Management Calls You now begin the public call definitions for memory management. It assumes you know what you are doing and overwrites one if already defined. DPL entry of 0 8C0x (OS call ONLY) .PD shadow offset needed by FindRun (0) .size of request . DPL entry of 1 AC0x (Not used in MMURTL) . . See listing 19. The selector number is checked to make sure you're in range. .was there an error (0 means no mem) . . allowing access to OS procedures. DPL entry of 3 EC0x (most likely) . ECX. . EAX CALL FindRun OR EAX. Not all of the calls are present here because some are very similar. USES: EAX. Adding a call gate to the GDT . Get a run of 1 for next new page table MOV EBX. EAX AOPTDone: POP EDI POP ESI POP EDX POP ECX POP EBX RETN .save pNextPT (the linear address) . else 0 if all's well . This is 40h through the highest allowed call gate number. .Set ErcOK (0) . EFLAGS MMURTL V1. 1 XOR EAX.

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

. n4KPages EQU [EBP+10h] . .These equates are also used by AllocPage ppMemRet EQU [EBP+0Ch] .Yes! Serious problem.More than 0? .Can't be zero! . A result code is returned in the EAX register. . .Yes .No Error! EAX. Allocate each physical page. TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX. This allows runs to span table entries.20. 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.n4KPages EAX. EAX JNZ SHORT ALOSP02 .PD shadow offset needed by FindRun (0) . .0h JNE SHORT ALOSPExit MOV OR JNZ MOV JMP ALOSP00: CMP JBE MOV JMP ALOSP01: MOV EBX.n4KPages XOR EAX. If the current PT doesn't have enough contiguous entries.Put Msg in callers TSS Message Area .20. The steps involved depend on the internal routines described above. Allocating a page of linear memory .ppMemRet): dError . . .See if have enuf physical memory .RETF . The steps are: Ensure you have physical memory by checking nPagesFree. but not close enough to easily combine their code. pRunTSS ADD EAX.Kernel Error?? . ErcNoMem SHORT ALOSPExit .0 -298- RICHARD A BURGESS . AllocOSPage This 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.ESP PUSH MemExch MOV EAX.Wait at the MemExch for Msg .(0 = No Runs big enuf) . Listing 19.EAX ALOSP00 EAX.size of request . _nPagesFree ALOSP01 EAX. EAX CALL FindRun OR EAX.ercBadMemReq ALOSPExit . AllocOSPage(dn4KPages. PUBLIC __AllocOSPage: PUSH EBP MOV EBP.If we didn't find a run big enuf we add a page table MMURTL V1.size of request . we add another page table to the operating-system page directory. Procedureal Interface : .Yes . See listing 19.Sorry boss. Find a contiguous run of linear pages to allocate. AllocPage and AllocDMAPage are not shown here because they are almost identical to AllocOSPage. we're maxed out EAX.

CALL AddOSPT OR EAX. Make PTE entries and return alias address to caller. if it crosses page boundaries.See if it's 0 (0 = NO Error) . .Add a new page table (we need it!) . The step involved in the algorithm are: See if the current PD equals specified Job PD. . AliasMem AliasMem creates alias pages in the current job's PD/PTs if the current PD is different than the PD for the job specified. Listing 19. EAX JZ SHORT ALOSP01 JMP SHORT ALOSPExit ALOSP02: .Send a Semaphore msg (so next guy can get in) PUSH 0FFFFFFF1h .Give em new LinAdd XOR EAX. . you need two pages. pMem is the address to alias.0 -299- RICHARD A BURGESS . ppAliasRet is the address to return the alias address to. ppMemRet . No alias is needed. Procedureal Interface : .EBP .Does not return error .No error ALOSPExit: .pMem EQU [EBP+24] . Even if the address is only two bytes.Get original error back (ignore kernel erc) MOV ESP. Exit. CALL FWORD PTR _SendMsg . JobNum is the job number that pMem belongs to.Get address of caller's pointer MOV [EBX]. . dJobNum. POP EBP .ppAliasRet EQU [EBP+12] . EAX . . . If so. The pages are created at user protection level even if they are in operating memory space. however . . dcbMem is the number of bytes needed for alias access.21.EAX now has linear address .dcbMem EQU [EBP+20] .ERROR!! . . ppAliasRet): dError . Code to alias memory . . PUSH EAX .Save last error PUSH MemExch . This is for the benefit of system services installed in operating-system memory. PUSH 0FFFFFFF1h . Calculate how many entries (pages) will be needed.Go back & try again . . This allows system services to access a caller memory for messaging without having to move data around.EBX still has count of pages CALL AddRun .JobNum EQU [EBP+16] . POP EAX . dcbMem. . MMURTL V1. This wastes no physical memory.EAX still has new linear address MOV EBX. See if they are available. .Only an entry in a table. AliasMem(pMem. RETF 8 . EAX .

dcbMem EBX . EAX ALSP01: MOV EBX.0h JNE ALSPDone . .OS Job? .Now we find out whos memory we are in to make alias .Save nPages in ECX . pRunTSS ADD EAX. EAX. ECX CALL FindRun . EBX.Put Msg in callers TSS Message Area . User memory .Puts Current Job Num in EAX . EAX JNZ ALSP04 CALL GetCrntJobNum CMP EAX. EAX.No.0 .EAX is now nPages we need! EAX . .No! Add a new USER page table -300- RICHARD A BURGESS .Wait at the MemExch for Msg .Set up for OS memory space . . .Yes .Yes . [EBP+16] CMP EAX. .EBX is number of pages for run CALL GetCrntJobNum CMP EAX.Kernel Error?? . PUSH MemExch MOV EAX.EAX is 256 for user space.pMem into EAX 0FFFh .EAX is nPages-1 .Now wait our turn with memory management .Are they the same Page Directory?? . 1 JE SHORT ALSP011 MOV EAX. . .Was there enough PTEs? . [EBP+24] .See if it is OS based service .ESP CALL GetCrntJobNum MOV EBX. 256 JMP SHORT ALSP01 ALSP011: XOR EAX.OS Job? .MODULO 4096 (remainder) [EBP+20] . alias it . EAX JMP ALSPDone .See if it is OS based service .PUBLIC __AliasMem PUSH EBP MOV EBP.EAX has 0 or 256 .Number of pages we need into EBX .Yes! Serious problem. EAX. TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX. EBX JNE ALSPBegin XOR EAX.Exit.Yes . .EAX is now linear address or 0 if no run is large enough . . EAX.Add the remainder of address DWORD PTR [EBP+20] . we're done ALSPBegin: . EAX ECX.Add to dcbMem 12 .EBX still has count of pages OR EAX.No.Yes.Get Job number for pMem . 0 for OS . . We're IN! ALSP00: MOV AND MOV ADD ADD SHR INC MOV EBX. 1 JE SHORT ALSP03 CALL AddUserPT JMP SHORT ALSP03 MMURTL V1. No Error .

.EBP POP EBP RETF 16 .original pMem . . JobNum):ercType . dcbAliasBytes is the size of the original memory aliased MMURTL V1.We are done ALSPExit: .No! Add a new OS page table MOV EDI.pAliasRet . 0FFFh EDI. DeAliasMem(pAliasMem. [EBP+12] [ESI]. . EAX MOV ESI.Set . This would not interfere with any of the memory allocation routines.Get original error back (ignore kernel erc) .22. .Job number . Procedureal Interface : . PUSH MemExch . EDI EAX. See listing 19.Save last error . pAliasMem is the address to "DeAlias" . dcbAliasBytes. [EBP+24] MOV EDX. . Code to remove alias PTEs . POP EAX ALSPDone: MOV ESP.Now. . [EBP+24] EAX. PUSH EAX . Listing 19. EAX JZ SHORT ALSP00 JMP SHORT ALSPExit ALSP04: . EAX ESI.Address to alias .Returned address to caller! .0 -301- RICHARD A BURGESS .ALSP02: CALL AddOSPT ALSP03: OR EAX. CALL FWORD PTR _SendMsg .EBX .0 = NO Error .Save alias page address base . . PUSH 0FFFFFFF1h .Go back & try again . EAX . take new alias mem and add trailing bits to address . [EBP+16] CALL AddAliasRun .and return to caller so he knows address (EDI is lin add) MOV AND ADD MOV MOV XOR EAX. . DeAliasMem DeAliasMem zeros 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 PTE's one at a time.Send a Semaphore msg (so next guy can get in) . .ERROR!! .Get remaining bits . PUSH 0FFFFFFF1h .Set to 0 (no error) .EAX .Set has linear address (from find run) Sve in EDI still has number of pages to alias ESI to linear address of pages to alias (from other job) EDX job number of job we are aliasing .22.

Get PTE into EBX . PUBLIC __DeAliasMem PUSH EBP MOV EBP.Number of pages into EDI & ECX EDI.Calculate the number of pages to DeAlias MOV AND MOV ADD ADD SHR INC MOV MOV MOV DALM01: MOV EBX.and pointer to PTE in ESI (We NEEDED THIS).but less than you asked for! ECX. ErcBadLinAdd SHORT DALMExit .EAX is nPages-1 EAX .pAliasMem . [EBP+20] .Is page an ALIAS? .DO not zero it! . 4096 LOOP DALM01 DALMExit: MOV ESP.Add to dcbMem EAX. [EBP+20] .dcbMem EAX.0 .so we zero out the page. 0FFFh .Yes.Call this to get address of PTE into ESI EBX.dcbAliasBytes .pMem into EAX EBX. . .We did some. 12 .If we fall out EAX = ErcOK already .NO . PRSNTBIT JNZ DALM02 CMP JNE MOV JMP DALM011: MOV EAX.We dealiased what we could. EBX . EDI DALM011 EAX.ESP .If we got here the page is presnt and IS an alias . DWORD PTR [EBP+20] .Now we have Physical Address in EAX (we don't really need it) . ALIASBIT JZ DALM03 . -302- RICHARD A BURGESS . it's page is present . EAX MOV [ESI]. [EBP+16] .EAX is now nPages we need! ECX.EBP MMURTL V1.. . . [EBP+12] CALL LinToPhy . EDX MOV EAX.Linear Mem to DeAlias .Add the remainder of address EAX.DO NOT deallocate the physical page MOV EBX.Save also in EDI (for compare) EDX.NO! (did we do any at all) .MODULO 4096 (remainder) EAX.Is page present (valid)??? . .None at all! .Address of next page to deallocate . XOR EAX.Job num into EAX for LinToPhy .See if PTE is an alias. . if so just ZERO PTE. EAX . .AliasJobNum EQU [EBP+20] EQU [EBP+16] EQU [EBP+12] .ZERO PTE entry .Next linear page .ECX . EAX DALM03: ADD EDX. ErcBadAlias JMP SHORT DALMExit DALM02: TEST EBX. [ESI] TEST EBX. .

. .else deallocate physical page THEN zero PTE MOV EBX. pOrigMem AND EDX.and pointer to PTE in ESI.Leave Job# in EAX for LinToPhy . .Error?? . Listing 19. The lower 12 bits of the pointer is actually ignored . DeAllocPage(pOrigMem. PUBLIC __DeAllocPage: PUSH EBP MOV EBP. TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX. n4KPages):ercType . . . pOrigMem EQU [EBP+10h] n4KPagesD EQU [EBP+0Ch] . Procedureal Interface : . DeAllocPage This frees up linear memory and also physical memory that was acquired with any of the allocation calls. pOrigMem is a POINTER which should be point to memory page(s) to be . n4KPagesD DAP01: MOV EBX.Is page present (valid)??? . because we deallocate 4K pages.ESP PUSH MemExch MOV EAX.0h JNE DAMExit MOV EDX. its page is present -303- RICHARD A BURGESS .Yes! . It will always free linear memory.Address of next page to deallocate . .See if PTE is an alias.Now we have Physical Address in EAX . pRunTSS ADD EAX. This will only free physical pages if the page is not marked as an alias. Deallocating linear memory . . 0FFFFF000h MOV ECX.23.0 .Put Msg in callers TSS Message Area . . .23. See listing 19. deallocate.POP EBP RETF 12 . providing it is valid.Wait at the MemExch for Msg . this will deallocate or deAlias as much as it can before reaching an invalid page. PRSNTBIT JNZ DAP02 MMURTL V1. EDX CALL GetCrntJobNum CALL LinToPhy . . n4KPages is the number of 4K pages to deallocate . Even if you specify more pages than are valid.Number of pages to deallocate . . .Yes.Linear Mem to deallocate . if so just ZERO PTE.Get PTE into EBX . [ESI] TEST EBX.Drop odd bits from address (MOD 4096) .

Is page an ALIAS? ..None at all! MOV EAX. ErcShortMem JMP SHORT DAMExit DAP02: TEST EBX.but less than you asked for! . pdnPagesRet is a pointer where you want the count of pages MMURTL V1. . ALIASBIT JNZ DAP03 . .save Memory error .If we got here the page is presnt and NOT an alias .get Memory error back . . . Code to find number of free pages . . . AND EBX.If we fall out EAX = ErcOK already . .0 -304- RICHARD A BURGESS .Next linear page .NO! (did we do any at all) . 0FFFFF000h CALL UnMarkPage DAP03: XOR EAX. . over memory error .so we must unmark (release) the physical page. Listing 19. n4KPagesD DAP011 EAX.CMP JNE MOV JMP DAP011: ECX. 4096 LOOP DAP01 DAMExit: PUSH EAX PUSH MemExch PUSH 0FFFFFFF1h PUSH 0FFFFFFF1h CALL FWORD PTR _SendMsg CMP EAX.Send a dummy message to pick up .We did some. it's an Alias .ZERO PTE entry . EAX ADD EDX. ErcBadLinAdd SHORT DAMExit . You can do this because you can give them any physical page in the system.EBP POP EBP RETF 8 QueryMemPages This gives callers the number of physical pages left that can be allocated. Their 1Gb linear space is sure to hold what we have left. 0 JNE DAMExit1 POP EAX DAMExit1: MOV ESP.Yes. QueryMemPages(pdnPagesRet):ercType .24.get rid of OS bits .We deallocated what we could. Procedureal Interface : .24. See listing 19.Kernel error has priority . . EAX MOV [ESI]. . so next guy can get in .

.No Error . LinAdd is the Linear address you want the physical address for . PUBLIC __GetPhyAdd: PUSH EBP MOV EBP. EAX XOR EAX. . .EBP POP EBP RETF 12 .EBP POP EBP RETF 4 . See listing 19.pPhyRet EQU [EBP+12] .25. Public for linear to physical conversion . pPhyRet):ercType . . GetPhyAdd(JobNum. .25. .LinAdd EQU [EBP+16] .JobNum EQU [EBP+20] . Linear Address . EAX . EAX EAX. left available returned .ESP MOV MOV MOV XOR ESI. No Error . be returned . Job Number . . [EBP+12] MOV [ESI]. GetPhyAdd This returns the physical address for a linear address. Procedureal Interface : . 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. EAX MOV ESP. pMemleft EQU [EBP+0Ch] PUBLIC __QueryPages: PUSH EBP MOV EBP. MOV ESP. . . pPhyRet points to the unsigned long where the physical address will .0 -305- RICHARD A BURGESS . .END OF CODE MMURTL V1. pMemLeft EAX. . 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. Listing 19.ESP MOV EBX. pPhyRet . . . LinAdd. . _nPagesFree [ESI]. [EBP+16] MOV EAX.. [EBP+20] CALL LinToPhy MOV ESI.

.

I added up all the clock cycles in the ISR for a maximum time-consuming interrupt.200 BPS will interrupt every 520us. The numbers before each instruction are the count of clocks to execute the instruction on a 20MHz-386. was the effect on interrupt latency.2 percent. memory wait-states. a slow machine. See listing 20. The most time-critical interrupts are nonbuffered serial communications. my worries were unfounded. divided by the total clocks for the same period. along with a piece of the kernel.0 -307- RICHARD A BURGESS .000 clocks on a 20-MHz machine. -1 PUSH EAX PUSH EAX CALL FWORD PTR _ISendMsg MMURTL V1. preemptive multitasking. In a 10ms period we have 200.1.Chapter 20. and it also wasn't fast enough to provide smooth. Somewhere between these two was the average. I was consuming less than 0.1. Listing 20. It was 0. certain problems with memory access such as non-cached un-decoded instructions.. A single communications channel running at 19.FALSE JE IntTmr03 CMP DWORD PTR [EAX+CountDown]. and anything less than 20-MHz all but disappearing. This file contains all of the code that uses or deals with timing functions in MMURTL. As you well know. I experimented with several different intervals. as well as the fastest one possible. The timer interrupt service routine (ISR) is also documented here. 20-MHz machines were "screamers" and I was even worried about consuming bandwidth on machines that were slower than that.6 percent for the worst case. Loop in timer ISR 6 3 6 3 2 2 5 2 2 2 40 IntTmr01: CMP DWORD PTR [EAX+fInUse]. however. Even more important than total bandwidth. actually the scheduler.0h JNE IntTmr02 PUSH EAX PUSH ECX PUSH DWORD PTR [EAX+TmrRespExch] MOV EAX. Your bandwidth is the percentage of clocks executed for a given period. It's at the end of the timer ISR. This doesn't take into account nonaligned memory operands. Choosing a Standard Interval Most operating systems have a timer interrupt function that is called at a fixed interval to update a continuously running counter. or 3000 clocks. The following are the instructions in the timer interrupt service routine that could be "looped" on for the maximum number of times in the worst case. which is very conservative. Two of them will come in at 260us. But even with these calculations. Back when I started this project. MMURTL is no exception.2 percent of available bandwidth. etc. with 100-MHz machines around. My calculated worst case was approximately 150us. The average really depends on program loading. The average is was far less. testing is the only real way to be sure it will function properly. I did some time-consuming calculations to figure what percentage of CPU bandwidth I was using with my timer ISR. I began with 50ms but determined I couldn't get the resolution required for timing certain events. so I simply added 10 percent for the overhead. especially synchronous. Timer Management Source Code Introduction The timer code is the heart of all timing functions for the operating system. because the timer interrupt sends messages for the Sleep() and Alarm() functions. which works out to about 1. I ended up with 10ms.

Timer Data The following section is the data and constants defined for the timer interrupt and associated functions.DATA . The Timer Block is a structure that contains . Data and constants for timer code . and 250 on a 486 or a Pentium.INCLUDE TSS.sTmrBlk LOOP IntTmr01 This works out to 125 clocks for each loop. This I could live with.INC . The real worst case would probably be more like 150us.Number of timer blocks in use PUBLIC rgTmrBlks DB (sTmrBlk * nTmrBlks) DUP (0) MMURTL V1. PUBLIC nTmrBlksUsed DD 0 . TIMER INTERRUPT COUNTER and TimerBlocks .2. Each TimerBlock is 12 Bytes long .0 -308- RICHARD A BURGESS . that would be 860us just for this worst-case interrupt. EXTRN SwitchTick DD EXTRN dfHalted DD PUBLIC TimerTick DD 0 . See listing 20. hold on a minute! That only happens if all of the tasks on the system want an alarm or must wake up from sleep exactly at the same time. and finally with test programs. The odds of this are truly astronomical.INCLUDE MOSEDF. But.INC . the exchange number of a task that is sleeping.FALSE DEC nTmrBlksUsed JMP IntTmr03 IntTmr02: DEC DWORD PTR [EAX+CountDown] IntTmr03: ADD EAX.320 clocks. That's a bunch! On a 20-MHz machine. The only additional time consumer is if we tack on a task switch at the end if we must preempt someone.Incremented every 10ms (0 on bootup). 400 clocks on a 20-MHz CPU is 50 nanoseconds times 400.2 2 6 5 7 6 2 11 POP ECX POP EAX MOV DWORD PTR [EAX+fInUse]. This doesn't cause a problem at all.ALIGN DWORD . Multiply the total (255) times the maximum number of timer blocks (64) and it totals 16.DD 00000000h CountDown EQU 8 . including the 10 percent overhead. and I tried a hundred calculations with a spread sheet.DD 00000000h TmrRespExch EQU 4 . Listing 20. or one that has requested an alarm. sTmrBlk EQU 12 fInUse EQU 0 .2. A task switch made by jumping to a TSS takes approximately 400 clocks on a 386. or 20us.DD 00000000h . SwitchTick and dfHalted are flags for the task-scheduling portion of the timer interrupt. These are the offsets into the structure . . . plus 130 clocks for the execution of ISendMsg() in each loop.

Anyone sleeping or have an alarm set? . The Sleep() and Alarm() functions set these blocks as callers require.rgTmrBlks MOV ECX.Yes .Move forward thru the blocks IntTmr01: CMP DWORD PTR [EAX+fInUse]. It would require manipulating items in the TSS to make it look as if they weren't nested. You check to see if the same task has been running for 30ms or more. you switch to it. See listing 20. Listing 20.save ptr to rgTmpBlk PUSH ECX . allow the timer interrupt to access kernel helper functions from Kernel.Yes .. The three external near calls. It's really the only part of task scheduling that isn't cooperative.ASM. the first things defined in this code segment.goto decrement it PUSH EAX .. See listing 20.No . The timer interrupt service routine .3. and they may be of an equal. which is the one you interrupted.FFFFFFFFh MMURTL V1.save current count PUSH DWORD PTR [EAX+TmrRespExch] . yet very important part.is count at zero? JNE IntTmr02 . It is a small. If it is the same or higher.INTS are disabled automatically .0h .ISend Message MOV EAX. priority than the task that is now running. . The timer interrupt code also performs the important function of keeping tabs on CPU hogs.3.Yes! . The timer interrupt fires off every 10 milliseconds. If so. You couldn't reschedule tasks this way if you were using interrupt tasks because this would nest hardware task switches. 0 JE SHORT TaskCheck IntTmr00: LEA EAX. Other tasks maybe waiting at the ready queue to run. Keep this in mind if you use interrupt tasks instead of interrupt procedures like I do.FALSE . You have now interrupted that task. The timer interrupt is part of the scheduling mechanism.EAX has addr of Timer Blocks . External functions for timer code . INT 20 .Timer Code The timer interrupt and timing related function are all defined in the file TmrCode.4.No.No . They may have been placed there by other ISRs. The task you interrupted is placed back on the ready queue in exactly the state you interrupted it. At all times on the system.nTmrBlks CLD .Timer Block found? JE IntTmr03 .ECX has count of Timer Blocks .0 -309- RICHARD A BURGESS . Listing 20. only one task is actually executing.ASM. The Timer Interrupt The timer interrupt checks the timer blocks for values to decrement.CODE EXTRN ChkRdyQ NEAR EXTRN enQueueRdy NEAR EXTRN deQueueRdy NEAR . -1 . we call ChkRdyQ and check the priority of that task.goto next block CMP DWORD PTR [EAX+CountDown].4. PUBLIC IntTimer: PUSHAD INC TimerTick CMP nTmrBlksUsed.Timer Tick. or higher.

next block please! . Get the task ID MOV TSS_Sel.DL is pri of current .Free up the timer block DEC nTmrBlksUsed .Hasn't been 30ms yet for this guy! CALL ChkRdyQ OR EAX. dfHalted OR EAX. and save in EDI ..FALSE .. [ESI+Priority] CMP DL. [EAX+Priority] JC TaskCheckDone CALL deQueueRdy MOV EDI.get ptr back MOV DWORD PTR [EAX+fInUse].switch him out if someone with an equal or higher pri .unless were done .Is this from a HALTed state? .Get next highest Pri in EAX (Leave Queued) .is on the RdyQ TaskCheck: MOV EAX.or Equal.Must do this before we switch! CALL FWORD PTR _EndOfIRQ . we will . Get high priority TSS off the RdyQ . If so. PUSH 0 .empty blk IntTmr02: DEC DWORD PTR [EAX+CountDown] IntTmr03: ADD EAX.10ms more gone. JL SHORT TimerTick SwitchTick EBX 3 TaskCheckDone .Correct count of used blocks JMP IntTmr03 .[EDI+Tid] . MOV ESI.bogus msg CALL FWORD PTR _ISendMsg . EAX JNZ TaskCheckDone MOV EAX. MMURTL V1.number was higher and we DON'T .if we got a CARRY on the subtract.current pri.If current is higher(lower num). . ESI CALL enQueueRdy . TimerTick . Put it in the JumpAddr for Task Swtich INC _nSwitches . That means . MOV EBX. CMP EAX.tell him his times up! POP ECX .Change this to change the "time slice" .. Save time of this switch for scheduler MOV SwitchTick. Put current one in EAX ..EDI .No.0 -310- RICHARD A BURGESS .. Keep track of how many switches for stats INC _nSlices . If the Queued Pri is LOWER . EAX JZ TaskCheckDone .BX .keep going .skip decrement . SUB EAX. we want to Switch.for more then 30ms (3 ticks).The CMP subtracts Pri of Queued from .Compare current pri to highest queued . EAX . no need to check tasks . Keep track of # of preemptive switches MOV EAX.get count back POP EAX .bogus msg PUSH EAX . pRunTSS MOV DL.YES. his .Is there one at all?? .sTmrBlk LOOP IntTmr01 . and on the RdyQ MOV pRunTSS. EAX MOV EAX.We will now check to see if this guy has been running . Make the TSS in EDI the Running TSS MOV BX.PUSH EAX .

DelayCnt . MOV DWORD PTR [EAX+fInUse]. The sleep routine delays the calling task by setting up a timer block with a countdown value and an exchange to send a message to when the countdown reaches zero.Empty block? JNE Delay02 .ESP .pRunTSS . . RETF 4 .Get TSS_Exch for our use MOV EBX.goto next block MOV EBX.Pass exchange (for WaitMsg) ADD ECX.nTmrBlks . See listing 20.Up the blocksInUse count MOV ECX.Use the Timer Block INC nTmrBlksUsed .EBX . TaskCheckDone: PUSH 0 CALL FWORD PTR _EndOfIRQ POPAD .FALSE .EAX points to timer blocks MOV ECX.clear direction flag Delay01: CLI .EBX . Sleep() .unless were done MOV EAX.5.TRUE .ErcOk .Count of timer blocks CLD .[ECX+TSS_Exch] .No .Sorry. Listing 20. IRETD . JMP TSS (This is the task swtich) . MOV EBP. MOV [EAX+TmrRespExch].0 -311- RICHARD A BURGESS . Code for the Sleep function DelayCnt EQU [EBP+0Ch] . out of timer blocks Delay03: MOV ESP.Must do this before we switch! . POP EBP . The timer interrupt sends the message and clears the block when it reaches zero.can't let others interfere CMP DWORD PTR [EAX+fInUse].Offset of msg area PUSH ECX CALL FWORD PTR _WaitMsg . DelayCnt CMP EAX.EBP . PUBLIC __Sleep: PUSH EBP .TSS_Msg .Get delay count MOV [EAX+CountDown]. MOV EAX. This requires an exchange.all is well JMP Delay03 Delay02: STI ADD EAX.JMP FWORD PTR [TSS] POPAD IRETD .rgTmrBlks .ErcNoMoreTBs . 0 .5.put it in timer block! STI PUSH EBX .See if there's no delay JE Delay03 LEA EAX. The exchange used is the TSS_Exch in the TSS for the current task.and Wait for it to come back MOV EAX.sTmrBlk LOOP Delay01 .FAR return from publics MMURTL V1.

can't let others interfere DWORD PTR [EAX+fInUse]. If the alarm is already queued through the kernel.put it in timer block! .clear direction flag Alarm01: CLI CMP JNE MOV MOV MOV INC MOV MOV STI MOV JMP Alarm02: STI ADD EAX.rgTmrBlks . 0 . which means the message has already been sent. AlarmExch EQU [EBP+10h] AlarmCnt EQU [EBP+0Ch] .It's OK to interrupt now EAX.Use the Timer Block nTmrBlksUsed . It must be set up each time.6. AlarmCnt CMP EAX.0 .Sorry. Listing 20. Procedural Interface: .EBX .nTmrBlks .ESP . MOV EAX. Alarm(nAlarmExch. nothing will stop it.See if there's no delay JE Alarm03 LEA EAX.Count of timer blocks CLD .sTmrBlk LOOP Alarm01 MOV EAX. . MOV EBP. All alarms set to fire off to that exchange are killed.FALSE . Code for the Alarm function . See listing 20.7. The message will always be two dwords with 0FFFFFFFFh (-1) in each.AlarmCnt . MMURTL V1.unless were done .6.It's OK to interrupt now .ErcNoMoreTBs Alarm03: MOV ESP. KillAlarm() KillAlarm searches the timer blocks looking for any block that is destined for the specified Alarm exchange. AlarmExch [EAX+TmrRespExch]. AlarmCnt):dErc . PUBLIC __Alarm: PUSH EBP .Get delay count [EAX+CountDown].goto next block EBX.TRUE .EBX .Up the blocksInUse count EBX. See listing 20. .Empty block? Alarm02 . The message is not repeatable.all is well Alarm03 . DWORD PTR [EAX+fInUse].No .EBP POP EBP RETF 8 .ErcOk . . out of timer blocks . -312- RICHARD A BURGESS .Alarm() Alarm() sets up a timer block with a fixed message that will be sent when the countdown reaches zero.EAX points to timer blocks MOV ECX.

Block in use? KAlarm02 . . The refresh bit is based on the system's quartz crystal oscillator.ErcOk . .Listing 20.unless were done . . Listing 20.No blocks in use . Procedural Interface: .EBP POP EBP RETF 4 .can't let others interfere DWORD PTR [EAX+fInUse]. Code for the MicrDelay function . This forms an instruction loop checking the toggled value of an I/O port. MicroDelay() Microdelay() provides small-value timing delays for applications and device drivers. The timing for this delay is based on the toggle of the refresh bit from the system status port.TRUE . .FALSE . PUBLIC __KillAlarm: PUSH EBP MOV EBP.Make Empty nTmrBlksUsed .EBX .ESP CMP nTmrBlksUsed.EAX points to timer blocks . See listing 20. Code for the KillAlarm function .EAX MOV ESP.No . KillAlarm(nAlarmExch):dErc . .sTmrBlk LOOP KAlarm01 KAlarm03: XOR EAX. The count is in 15us increments.KAlarmExch LEA EAX. .8. 0 JE KAlarm03 MOV EBX. The task is actually not suspended at all.nTmrBlks CLD KAlarm01: CLI CMP JNE CMP JNE MOV DEC .Does this match the Exchange? KAlarm02 DWORD PTR [EAX+fInUse]. KAlarmExch EQU [EBP+0Ch] .Get exchange for killing alarms to .It's OK to interrupt now . This call will not be very accurate for values less than 3 or 4 (45 to 60 microseconds).ALl done -. The call can also be inaccurate due to interrupts if they are not disabled. which drives the processor clock.goto next block [EAX+TmrRespExch]. Procedural Interface MicroDelay(dDelay):derror PUBLIC __MicroDelay: PUSH EBP MMURTL V1.Count of timer blocks . so we get out! .Make blocksInUse correct KAlarm02: STI ADD EAX. But it's still very much needed.rgTmrBlks MOV ECX.7.0 .8. -313- RICHARD A BURGESS .clear direction flag .

check refrest bit .71h MOV BL. 10h CMP AH. pCMOSTimeRet MOV [ESI].AL IN AL. The time is returned from the CMOS clock as a dword. .Seconds . See listing 20. EAX MOV ESP. .04h OUT 70h. . 61h AND AL. EBX MOV EAX.Check toggle of bit .9.0 .EBP POP EBP RETF 4 .Get system status port . MMURTL doesn't keep the time internally. EBX MMURTL V1. . 0 JE MDL01 MDL00: IN AL. Code to read the CMOS time .Get delay count . Procedural Interface: GetCMOSTime(pdTimeRet):derror pCMOSTimeRet EQU [EBP+12] PUBLIC __GetCMOSTime: PUSH EBP MOV EBP. . AL SHL EBX.9. AL JE MDL00 MOV AH. The low order byte is the seconds in Binary Coded Decimal (BCD). [EBP+0Ch] CMP ECX.Hours .AL IN AL. .71h MOV BL.AL IN AL.ESP MOV ECX.00h OUT 70h.Minutes .Give 'em the time -314- RICHARD A BURGESS . AL LOOP MDL00 MDL01: XOR EAX. 8 MOV EAX. and the high order byte is 0.02h OUT 70h. the next byte is the minutes in BCD.MOV EBP. 8 MOV EAX.get out if they came in with 0! .ESP XOR EBX.Clear time return .Toggle! Move to AH for next compare .71h MOV BL. Listing 20.AL SHL EBX. the next byte is the Hours in BCD. GetCMOSTime() This reads the time from the CMOS clock on the PC-ISA machines.No toggle yet .AL MOV ESI.

AL SHL EBX.71h MOV BL. pCMOSDateRet MOV [ESI]. XOR EBX.Day of month .09h OUT 70h.Month . and the high order byte is year (BCD 0-99). MOV EBP.71h MOV BL.EBP POP EBP RETF 4 . .07h OUT 70h.06h OUT 70h. the next byte is the month (BCD 1-12). No Error .ESP . 8 MOV EAX.XOR EAX.71h MOV BL.AL IN AL. 0-6 0=Sunday). AL SHL EBX. . . . .AL MOV ESI.10.EBP POP EBP RETF 4 . GetCMOSDate(pdTimeRet):derror . Procedural Interface: .AL IN AL.0 -315- RICHARD A BURGESS . EAX MOV ESP. No Error . See listing 20. EAX MOV ESP. GetCMOSDate() The Date is returned from the CMOS clock as a dword. MMURTL V1. 8 MOV EAX. 8 MOV EAX. pCMOSDateRet EQU [EBP+12] PUBLIC __GetCMOSDate: PUSH EBP .Day of week .Clear date return MOV EAX. EBX . . The low-order byte is the day of the week (BCD. .71h MOV BL.AL SHL EBX.Give 'em the time . the next byte is the day (BCD 1-31). Listing 20.Year .10.08h OUT 70h. EBX XOR EAX.AL IN AL. Code to read the CMOS date .AL IN AL.

pTickRet MOV EAX. . Because it is a dword. 61h MMURTL V1.1. AL MOV AL. . divide 1. is used to generate tones. GetTimerTick(pdTickRet):derror . .193182Mhz .000 ticks per month. The length of time the tone is on is controlled by the Sleep() function which is driven by the system tick counter.ESP MOV ESI. Hardware timer number 2. .Timer 2. AL IN AL.Send quotient (left in AX) -316- RICHARD A BURGESS .193182 Mhz . Code to return the timer tick .800. The Current Timer Tick is returned (it's a DWord). With a 10ms interval.DIVISOR is in EBX (Freq) . Listing 20. Listing 20. MSB. AL XOR EDX. The system clock drives the timer so the formula to find the proper frequency is a function of the clocks frequency. EDX MOV EAX. which is connected to the system internal speaker.0 . pTickRet EQU [EBP+12] PUBLIC __GetTimerTick: PUSH EBP MOV EBP.12. .No Error .12. Helper function for Beep() and Tone() .11. .GetTimerTick() This returns the ever-increasing timer tick to the caller. See listing 20. Binary . LSB.11. . EAX XOR EAX.EBX needs the desired tone frequency in HERTZ .To find the divisor of the clock.193182Mhz by Desired Freq.This does all work for BEEP and TONE .ECX needs length of tone ON-TIME in 10ms increments BEEP_Work: MOV AL. . EAX POP EBP RETF 4 Beep_Work() Beep_Work() is an internal function (a helper) used by the public calls Tone() and Beep(). AH NOP NOP NOP NOP OUT 42h. 10110110b OUT 43h. there are over 4 billion ticks before rollover occurs. TimerTick MOV [ESI]. See listing 20. The procedural interface: . This means the system tick counter will roll-over approximately every 16 months. this amounts to 262. 1193182 DIV EBX OUT 42h.The clock freq to Timer 2 is 1. MMURTL maintains a double word counter that begins at zero and is incremented until the machine is reset or powered down.

. . AL RETN Beep() . 11111100b OUT 61h.Freq .ESP MOV EBX. Procedural Interface: Tone(dFreq. . 35 CALL Beep_Work POP EBP RETF Tone() . Code to produce a beep . (Sorry. Listing 20. Tone allows the caller to specify a frequency and duration of a tone to be generated by the system's internal speaker. dTickseRet):derror dFreq is a DWord with the FREQUENCY in HERTZ dTicks is a DWord with the duration of the tone in 10ms increments EQU [EBP+10h] EQU [EBP+0Ch] ToneFreq ToneTime . 800 MOV ECX. Listing 20. .OR AL. . . I couldn't resist. It uses the helper function Beep_Work described earlier. .14. . This call uses the helper function Beep_Work described earlier. 00000011b PUSH EAX POP EAX OUT 61h.13.350ms . Procedural Interface: Beep() PUBLIC __Beep: PUSH EBP MOV EBP. See listing 20. Code to produce a tone .13. This function has nothing to with the Road Runner. MMURTL V1.ECX is TIME ON in 50ms incs. AL PUSH ECX . CALL FWORD PTR _Sleep IN AL.) This provides a fixed tone from the system's internal speaker as a public call for applications to make noise at the user. 61h NOP NOP NOP NOP AND AL.0 -317- RICHARD A BURGESS . . .14. if it had. I would have called it Beep_Beep(). See listing 20.

PUBLIC __Tone: PUSH EBP MOV EBP. MMURTL V1.0 -318- RICHARD A BURGESS . ToneFreq MOV ECX. . ToneTime CALL Beep_Work POP EBP RETF 8 . .ESP MOV EBX.

Initial OS Page Directory (PD). In chapter 7.INCLUDE JOB. Following these tables is the data segment portion from Main. Even though the Intel processors can operate on data that is not aligned by its size. It is the entry point in the operating system. Initial OS Page Table (PT).ASM. the first instruction executed after boot up.INCLUDE TSS. The order of the first 5 files included in the assembler template file for MMURTL are critical. a workhorse for Main. The code contains comments noting which of these items are temporary. Periodically you will see the . The tables prior to this portion of the data segment are: Interrupt Descriptor Table (IDT).DATA .INC . I generally try to align data that is accessed often.” I covered the basics of a generic sequence that would be required for initialization of an operating system.INC for structure details.INC .ASM which is presented later in this chapter.INC . and Public Call Gate table.ALIGN command. Initialization Code Introduction This chapter contains code from two separate files.======================================================= PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC MonTSS DbgTSS pFreeTSS pDynTSSs _nTSSLeft DB DB DD DD DD sTSS dup (0) . Global Descriptor table (GDT). Kernel Structures . The assembler does no alignment on its own. . Main Operating System Data . The second file is InitCode. and I don't worry about the rest of it.0 -319- RICHARD A BURGESS .1. Initial TSS for Debugger NIL .See MOSEDF. OS Global Data Listing 21. This is because many things have to be started before you can allocate memory where the dynamic allocation of resources can begin. It defines the selectors for public call gates so the rest of the OS code can call into the OS.Chapter 21. Certain data structures and variables are allocated that may only be used once during the initialization process.ASM.ALIGN DWORD command issued to the assembler. Everything is packed unless you tell it otherwise with the . Listing 21. Pointer to Free List of Task State Segs 0 .ASM. “OS Initialization.INCLUDE MOSEDF. For stats (less static TSSs) MMURTL V1. The public call gate table is not a processor-defined table as the others are. Initial TSS for OS/Monitor sTSS dup (0) . ptr to alloced mem for dynamic TSSs nTSS-2 .1 begins the data segment after the static tables that were in previous include files.======================================================= . The first file is Main. access to the data is faster if it is.ALIGN DWORD .

;-----------------PUBLIC rgLBs DB (nLB*sLINKBLOCK) dup (0) ; pool of LBs PUBLIC pFreeLB DD NIL ; Ptr to Free List of Link Block PUBLIC _nLBLeft DD nLB ; For Monitor stats ;-----------------; The RUN Queue for "Ready to Run" tasks PUBLIC RdyQ DB (sQUEUE*nPRI) dup (0) ; Priority Based Ready Queue

;-----------------; Two static Job Control Blocks (JCBs) to get us kick-started. ; The rest of them are in allocated memory PUBLIC MonJCB PUBLIC DbgJCB ;-----------------PUBLIC PUBLIC PUBLIC PUBLIC GDTLimit GDTBase IDTLimit IDTBase DW DD DW DD 0000h 00000000h 0000h 00000000h ;Global Descriptor Table Limit ;base ;Interrupt Descriptor Table Limit ;base DB sJCB dup (0) DB sJCB dup (0) ; Monitor JCB ; Debugger JCB

;-----------------PUBLIC rgSVC DB (sSVC*nSVC) dup (0) Descriptors

; Setup an array of Service

;-----------------;Exchanges take up a fair amount of memory. In order to use certain kernel ;primitives, we need exchanges before they are allocated dynamically. ;We have an array of 3 exchanges set aside for this purpose. After ;the dynamic array is allocated an initialized, we copy these into ;the first three dynamic exchanges. These static exchanges are used ;by the Monitor TSS, Debugger TSS, and memory Management. PUBLIC nExch rgExchTmp DD 3 ; This will be changed after allocation ; of dynamic exchanges. 3) dup (0) ; Setup three static temporary Exchanges ; for Monitor and Debugger TSSs ; These are moved to a dynamic Exch Array ; as soon as it's allocated. OFFSET rgExchTmp ; Pointer to current array of Exchanges. 0 ; Pointer to dynamic array Exchanges. nDynEXCH ; For Monitor stats

DB (sEXCH *

PUBLIC PUBLIC PUBLIC

prgExch

DD

pExchTmp DD _nEXCHLeft DD

;------------------;Scheduling management variables PUBLIC TSS PUBLIC TSS_Sel .ALIGN DWORD PUBLIC pRunTSS PUBLIC SwitchTick PUBLIC dfHalted
MMURTL V1.0

DD 00000000h DW 0000h

; Used for jumping to next task ; " "

DD NIL DD 0 DD 0

; Pointer to the Running TSS ; Tick of last task switch ; nonzero if processor was halted

-320-

RICHARD A BURGESS

PUBLIC PUBLIC PUBLIC PUBLIC

_nSwitches _nSlices _nReady _nHalts

DD DD DD DD

0 0 0 0

; ; ; ;

# # # #

of of of of

switches for statistics sliced switches for stats task Ready to Run for stats times CPU halted for stats

;THESE ARE THE INITIAL STACKS FOR THE OS Monitor AND Debugger OSStack DD 0FFh DUP (00000000h) ;1K OS Monitor Stack OSStackTop DD 00000000h OSStackSize EQU OSStackTop-OSStack Stack1 Stack1Top Stack1Size DD 0FFh DUP (00000000h) ;1K Debugger Stack DD 00000000h EQU Stack1Top-Stack1

;----------------rgNewJob cbNewJob rgOSJob cbOSJob rgDbgJob cbDbgJob _BootDrive DB 'New Job' EQU 7 DB 'MMOS Monitor' EQU 12 DB 'Debugger DD 12 DD 00 ' ;Placed in New JCBs

;Placed in first JCB

;Name for Job ;Size of Job Name ;Source drive of the boot

Operating System Entry Point
The first instruction in Main.ASM is the entry point to the operating system. This is the first instruction executed after the operating system is booted. Look for the .START assembler command. The address of this instruction is known in advance because the loader(boot) code moves the data and code segments of the operating system to a specific hard-coded address. The .VIRTUAL command allows you to tell the assembler where this code will execute, and all subsequent address calculations by the assembler are offset by this value. It is similar to the MSDOS assembler ORIGIN command, with one major difference: DASM doesn't try to fill the segment prior to the virtual address with dead space. The .VIRTUAL command can only be used once in an entire program and it must be at the very beginning of the segment it's used in. For MMURTL, the start address is 10000h, the first byte of the second 64K in physical memory. You'll see many warnings about the order of certain initialization routines. I had to make many warnings to myself just to keep from "housekeeping" the code into a nonworking state. I felt it was important to leave all of these comments in for you. See listing 21.2. Listing 21.2. OS Initialization Code and Entry Point

; This begins the OS Code Segment .CODE ;

MMURTL V1.0

-321-

RICHARD A BURGESS

.VIRTUAL 10000h ; ; ; ; ; ; ; ; ; ; ;

;64K boundry. This lets the assembler know ;that this is the address where we execute

BEGIN OS INITIALIZATION CODE This code is used to initialize the permanent OS structures and calls procedures that initialize dynamic structures too. "Will Robinson, WARNING, WARNING!! Dr. Smith is approaching!!!" BEWARE ON INITIALIZATION. The kernel structures and their initialization routines are so interdependent, you must pay close attention before you change the order of ANYTHING. (Anything before we jump to the monitor code that is)

EXTRN InitCallGates NEAR EXTRN InitOSPublics NEAR EXTRN _Monitor NEAR EXTRN InitIDT NEAR EXTRN InitFreeLB NEAR EXTRN InitDMA NEAR EXTRN Set8259 NEAR EXTRN InitKBD NEAR EXTRN AddTSSDesc NEAR EXTRN InitMemMgmt NEAR EXTRN InitNewJCB NEAR EXTRN InitVideo NEAR EXTRN InitFreeTSS NEAR EXTRN InitDynamicJCBs NEAR EXTRN InitDynamicRQBs NEAR EXTRN DbgTask NEAR ;========================================================================== ; Set up the initial Stack Pointer (SS = DS already). ; This is the first code we execute after the loader ; code throws us into protected mode. It's a FAR jump ; from that code... ;========================================================================== .START PUBLIC OSInitBegin: LEA EAX,OSStackTop MOV ESP,EAX MOV _BootDrive, EDX

; Setup initial OS Stack ; FIRST THING IN OS CODE. ; Left there from BootSector Code

;========================================================================== ; Set up OS Common Public for IDT and GDT Base and Limits ; THE SECOND THING IN OS ;========================================================================== SGDT FWORD PTR GDTLimit SIDT FWORD PTR IDTLimit ;A formality - we know where they are! ;

;========================================================================== ; Setup Operating System Structures and motherboard hardware ; THIS IS RIGHT AFTER WE GET A STACK. ; YOU CAN'T ALLOCATE ANY OS RESOURCES UNTIL THIS CODE EXECUTES!!! ;========================================================================== CALL InitCallGates ; Sets up all call gates as DUMMYs ; except AddCallGate which must be made valid first!

MMURTL V1.0

-322-

RICHARD A BURGESS

CALL InitOSPublics CALL InitIDT

; Sets up OS PUBLIC call gates ;Sets up default Interrupt table ;NOTE: This uses CallGates! ; count of Link Blocks ; EDX is size of a Link Block ; Init the array of Link Blocks ; Sets up DMA with defaults ; Set up 8259s for ints (before KBD) ; Initialize the Kbd hardware ; Highest IRQ number (all IRQs) ; Tell em to work

MOV ECX,nLB MOV EDX,sLinkBlock CALL InitFreeLB CALL InitDMA CALL Set8259 CALL InitKBD PUSH 0 CALL FWORD PTR _EndOfIRQ

;Set time counter divisor to 11938 - 10ms ticks ;Freq in is 1.193182 Mhz/11932 = 100 per second ;or 1 every 10 ms. (2E9Ch = 11932 decimal) MOV OUT MOV OUT STI AL,9Ch 40h,AL AL,02Eh 40h,AL ; ; ; ; Makes the timer Tick (lo) 10 ms apart by setting clock divisior to (hi byte) 11,932

; We are ready to GO (for now)

;========================================================================== ; The following code finishes the initialization procedures BEFORE the ; OS goes into paged memory mode. ; We set up an initial Task by filling a static TSS, creating and loading ; a descriptor entry for it in the GDT and we do the same for the debugger. ;========================================================================== ; ; ; ; ; ; ; ; Make the default TSS for the CPU to switch from a valid one. This TSS is a valid TSS after the first task switch. IMPORTANT - Allocate Exch and InitMemMgmt calls depend on pRunTSS being valid. They can not be called before this next block of code!!! Note that this TSS does NOT get placed in the linked list with the rest of the TSSs. It will never be free. We also have to manaully make it's entry in the GDT.

;The following code section builds a descriptor entry for ;the initial TSS (for Montitor program) and places it into the GDT MOV EAX, sTSS MOV EBX, 0089h MOV EDX, OFFSET MonTSS MOV EDI, OFFSET rgTSSDesc CALL AddTSSDesc ; ; ; ; Limit of TSS (TSS + SOFTSTATE) G(0),AV(0),LIM(0),P(1),DPL(0),B(0) Address of TSS Address of GDT entry to fill

;Now that we have valid Descriptor, we set up the TSS itself ;and Load Task Register with the descriptor (selector) ;Note that none of the TSS register values need to be filled in ;because they will be filled by the processor on the first ;task switch.
MMURTL V1.0

-323-

RICHARD A BURGESS

MOV MOV MOV SUB MOV MOV LTR MOV

EBX, OFFSET MonTSS ;Get ptr to initial TSS in EBX pRunTSS,EBX ;this IS our task now!!! EAX, OFFSET rgTSSDesc ;ptr to initial TSS descriptor EAX, OFFSET GDT ;Sub offset of GDT Base to get Sel of TSS WORD PTR [EBX+TSS_IOBitBase], 0FFh; I/O Permission [EBX+Tid],AX ; Store TSS Selector in TSS (Task ID) WORD PTR [EBX+Tid] ; Setup the Task Register BYTE PTR [EBX+Priority], 25 ;Priority 25 (monitor is another APP) MOV DWORD PTR [EBX+TSS_CR3], OFFSET PDir1 ;Phys address of PDir1 MOV WORD PTR [EBX+TSSNum], 1 ;Number of first TSS (Duh) ;Set up Job Control Block for Monitor (always Job 1) ;JOB 0 is not allowed. First JCB IS job 1! MOV EAX, OFFSET MonJCB ; MOV DWORD PTR [EAX+JobNum], 1 ;Number the JCB MOV EBX, OFFSET MonTSS ;Must put ptr to JCB in TSS MOV [EBX+TSS_pJCB], EAX ;pJCB into MonTSS MOV EBX, OFFSET PDir1 ;Page Directory MOV ESI, OFFSET rgOSJob ;Job Name MOV ECX, cbOSJob ;Size of Name XOR EDX, EDX ;NO pVirtVid yet (set up later in init) CALL InitNewJCB

; IMPORTANT - You can't call AllocExch before pRunTSS is VALID! ; THIS IS THE FIRST POINT AllocExh is valid MOV EAX, pRunTSS ADD EAX, TSS_Exch PUSH EAX CALL FWORD PTR _AllocExch

;Alloc exch for initial (first) TSS

;============================================================================= ; ; Set up DEBUGGER Task and Job ; The debugger is set up as another job with its one task. ; The debugger must not be called (Int03) until this code executes, ; AND the video is initialized. ; We can't use NewTask because the debugger operates independent of ; the kernel until it is called (INT 03 or Exception) ; ;The following code section builds a descriptor entry for ;the initial TSS (for Debugger) and places it into the GDT MOV EAX, sTSS MOV EBX, 0089h MOV EDX, OFFSET DbgTSS MOV EDI, OFFSET rgTSSDesc+8 CALL AddTSSDesc ; ; ; ; Limit of TSS (TSS + SOFTSTATE) G(0),AV(0),LIM(0),P(1),DPL(0),B(0) Address of Debugger TSS Address of GDT entry to fill in

;Now that we have valid Descriptor, we set up the TSS itself MOV EAX, OFFSET rgTSSDesc+8 ; ptr to second TSS descriptor SUB EAX, OFFSET GDT ; Sub offset of GDT Base to get Sel of TSS
MMURTL V1.0

-324-

RICHARD A BURGESS

MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV

EBX, OFFSET DbgTSS ; Get ptr to initial TSS in EBX WORD PTR [EBX+TSS_IOBitBase], 0FFh; I/O Permission [EBX+Tid],AX ; Store TSS Selector in TSS (Task ID) BYTE PTR [EBX+Priority], 1 ; Debugger is HIGH Priority DWORD PTR [EBX+TSS_CR3], OFFSET PDir1 ;Physical address of PDir1 EDX, OFFSET DbgTask [EBX+TSS_EIP],EDX WORD PTR [EBX+TSS_CS],OSCodeSel ; Put OSCodeSel in the TSS WORD PTR [EBX+TSS_DS],DataSel ; Put DataSel in the TSS WORD PTR [EBX+TSS_ES],DataSel ; WORD PTR [EBX+TSS_FS],DataSel ; WORD PTR [EBX+TSS_GS],DataSel ; WORD PTR [EBX+TSS_SS],DataSel ; WORD PTR [EBX+TSS_SS0],DataSel ; EAX, OFFSET Stack1Top DWORD PTR [EBX+TSS_ESP],EAX ; A 1K Stack in the Dbg TSS DWORD PTR [EBX+TSS_ESP0],EAX ; DWORD PTR [EBX+TSS_EFlags],00000202h ; Load the Flags Register WORD PTR [EBX+TSSNum], 2 ; Number of Dubegger TSS

;Set up Job Control Block for Debugger ;JOB 0 is not allowed. First JCB IS job 1, debugger is always 2 MOV EAX, OFFSET DbgJCB MOV DWORD PTR [EAX+JobNum], MOV [EBX+TSS_pJCB], EAX MOV EBX, OFFSET PDir1 MOV ESI, OFFSET rgDbgJob MOV ECX, cbDbgJob MOV EDX, 1 CALL InitNewJCB

2 ;Number the JCB ;EBX still points to DbgTSS ;Page Directory (OS PD to start) ;Name ;size of name ;Debugger gets video 1

;Now allocate the default exchange for Debugger MOV EAX, OFFSET DbgTSS ADD EAX, TSS_Exch PUSH EAX CALL FWORD PTR _AllocExch

;Alloc exch for Debugger TSS

At this point, all of the static data that needs to be initialized is done. You had to set up all the necessary items for kernel messaging so we could initialize memory management. This was necessary because memory management uses messaging. There are many "chickens and eggs" here. Move one incorrectly and whamo – the code doesn't work. Not even the debugger. This really leaves a bad taste in your mouth after about 30 hours of debugging with no debugger. The memory-management initialization routines were presented in chapter 19, “Memory Management Code.” See listing 21.3. Listing 21.3. Continuation of OS initialization

;================================================================ ; ALSO NOTE: The InitMemMgmt call enables PAGING! Physical addresses ; will not necessarily match Linear addresses beyond this point. ; Pay attention! ; CALL InitMemMgmt
MMURTL V1.0

;InitMemMgmt allocates an exch so

-325-

RICHARD A BURGESS

;this the first point it can be called. ;It also calls SEND! Once we have the memory allocation calls working, we can allocate space for all of the dynamic arrays and structures such as task state segments, exchanges, and video buffers. See listing 21.4. Listing 21.4. Continuation of OS initialization

;================================================================ ; FIRST POINT memory management calls are valid ;================================================================ ;Now we will allocate two virtual video screens (1 Page each) ;for the Monitor and Debugger and place them in the JCBs ;Also we set pVidMem for Monitor to VGATextBase address ;cause it has the active video by default PUSH 1 ; 1 page MOV EAX, OFFSET MonJCB ; Ptr to Monitor JCB ADD EAX, pVirtVid ; Offset in JCB to pVirtVid PUSH EAX CALL FWORD PTR _AllocOSPage ; Get 'em! MOV EAX, OFFSET MonJCB ; Make VirtVid Active for Monitor MOV DWORD PTR [EAX+pVidMem], VGATextBase

PUSH 1 MOV EAX, OFFSET DbgJCB ADD EAX, pVirtVid PUSH EAX CALL FWORD PTR _AllocOSPage MOV EAX, OFFSET DbgJCB MOV EBX, [EAX+pVirtVid] MOV [EAX+pVidMem], EBX CALL InitVideo

; 1 page ;

; Get 'em! ; ; Video NOT active for Debugger ;Set Text Screen 0

; ;

MOV EAX,30423042h ;BB on CYAN - So we know we are here! MOV DS:VGATextBase+00h,EAX

;=============================================================== ; FIRST POINT video calls are valid ; FIRST POINT Debugger is working (only because it needs the video) ; At this point we can allocate pages for dynamic structures and ; initialize them. ;=============================================================== ; Allocate 8 pages (32768 bytes) for 64 Task State Segments (structures). ; Then call InitFreeTSS (which fills them all in with default values) PUSH 8 ; 8 pages for 64 TSSs (32768 bytes) MOV EAX, OFFSET pDynTSSs ; PUSH EAX CALL FWORD PTR _AllocOSPage ; Get 'em! XOR EAX, EAX
MMURTL V1.0

; Clear allocated memory for TSSs

-326-

RICHARD A BURGESS

MOV ECX, 8192 MOV EDI, pDynTSSs REP STOSD MOV EAX, pDynTSSs MOV ECX, nTSS-2 CALL InitFreeTSS CALL InitDynamicJCBs CALL InitDynamicRQBs

; (512 * 64 = 32768 = 8192 * 4) ; where to store 0s ; Do it

; count of dynamic TSSs (- OS & Dbgr TSS) ; Init the array of Process Control Blocks

;================================================================ ; Allocate 1 page (4096 bytes) for 256 Exchanges (16*256=4096). ; Exchanges are 16 bytes each. Then zero the memory which has ; the effect of initializing them because all fields in an Exch ; are zero if not allocated. PUSH 1 MOV EAX, OFFSET pExchTmp PUSH EAX CALL FWORD PTR _AllocOSPage XOR MOV MOV REP EAX, EAX ECX, 1024 EDI, pExchTmp STOSD ; 1 pages for 256 Exchs (4096 bytes) ; Returns ptr to allocated mem in pJCBs ; ; Get it! ; ; ; ; Clear allocated memory (4*1024=4096) where to store 0s Store EAX in 1024 locations

;Now we move the contents of the 3 static exchanges ;into the dynamic array. This is 60 bytes for 3 ;exchanges. MOV MOV MOV REP MOV MOV MOV ESI, prgExch EDI, pExchTmp ECX, 12 MOVSD EAX, pExchTmp prgExch, EAX nExch, nDynEXCH ; ; ; ; ; ; ; Source (static ones) Destination (dynamic ones) 12 DWords (3 Exchanges) Move 'em! The new ones prgExch now points to new ones 256 to use (-3 already in use)

All of the basic resource managers are working at this point. You actually have a working operating system. From here, you go to code that will finish off the higher-level initialization functions such as device drivers, loader tasks. See listing 21.5. Listing 21.5. End of Initialization Code

CALL _Monitor ;

;Head for the Monitor!!

;The Monitor call never comes back (it better not...) ; HLT ; ;Well, you never know (I AM human)

Initialization Helpers
The file InitCode.ASM provides much of the support "grunt work" that is required to get the operating system up and running. The procedure InitOSPublics() sets up all the default values for call gates, and interrupt vectors, and it initializes some of the free dynamically allocated structures such as TSS's and link blocks.

MMURTL V1.0

-327-

RICHARD A BURGESS

Near the end of this file are the little assembly routines to fill in each of the 100-plus call gate entries in the GDT. I have not included them all here in print because they are so repetitive. This is noted in the comments. See listing 21.6. Listing 21.6. Initialization Subroutines

.CODE ; EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN

IntQ NEAR IntDivBy0 NEAR IntDbgSS NEAR IntDebug NEAR IntOverFlow NEAR INTOpCode NEAR IntDblExc NEAR INTInvTss NEAR INTNoSeg NEAR INTStkOvr NEAR IntGP NEAR INTPgFlt NEAR IntPICU2 NEAR IntTimer NEAR IntKeyBrd NEAR

;=========================================================================== ;The following code initializes structures just after bootup ;=========================================================================== ; ; INPUT : ECX,EDX ; OUTPUT : NONE ; REGISTERS : EAX,EBX,ECX,FLAGS ; MODIFIES : pFreeLB,rgLBs ; ; This routine will initialize a free pool of link blocks. ; The data used in this algorithm are an array of ECX link blocks (rgLBs), ; each EDX bytes long and pointer to a list of free link blocks (pFreeLB). ; ; The pFreeLB pointer is set to address the first element in rgLBs. Each ; element of rgLBs is set to point to the next element of rgLBs. The ; last element of rgLBs is set to point to nothing (NIL). ; PUBLIC InitFreeLB: LEA EAX,rgLBs ; pFreeLB <= ^rgLBs; MOV pFreeLB,EAX ; LB_Loop: MOV EBX,EAX ; for I = 0 TO ECX ADD EAX,EDX ; rgLBs[I].Next <= MOV [EBX+NextLB],EAX ; ^rgLBs[I+1]; LOOP LB_Loop ; MOV DWORD PTR [EBX+NextLB], 0 ; rgFree[1023].Next <= NIL; RETN ; ;======================================================================= ; AddTSSDesc ; Builds a descriptor for a task and places it in the GDT. If you ; check the intel documentation the bits of data that hold this ; information are scattered through the descriptor entry, so ; we have to do some shifting, moving, anding and oring to get
MMURTL V1.0

-328-

RICHARD A BURGESS

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

the descriptor the way the processor expects it. See the Intel docs for a complete description of the placement of the bits. Note: The granularity bit represents the TSS itself, not the code that will run under it!

IN: EAX - Size of TSS EBX - Decriptor type (default for OS TSS is 0089h) (0089h - G(0),AV(0),LIM(0000),P(1),DPL(00),(010),B(0),(1)) EDX - Address of TSS EDI - Address of Desc in GDT OUT: GDT is updated with descriptor USED: EFlags (all other registers are saved)

PUBLIC AddTSSDesc: ;The following code section builds a descriptor entry for ;the TSS and places it into the GDT PUSH EAX PUSH EBX PUSH EDX PUSH EDI DEC EAX ; (Limit is size of TSS-1) SHL EBX,16 ; Chinese puzzle rotate ROL EDX,16 ; Exchange hi & lo words of Base Addr MOV BL,DH ; Base 31 .. 24 MOV BH,DL ; Base 23 .. 16 ROR EBX,8 ; Rotate to Final Alignment MOV DX,AX ; Limit 15 .. 0 with Base 15 .. 0 AND EAX,000F0000h ; Mask Limit 19 .. 16 OR EBX,EAX ; OR into high order word MOV [EDI],EDX ; Store lo double word MOV [EDI+4],EBX ; Store hi double word POP EDI POP EDX POP EBX POP EAX RETN ;========================================================================= ; InitFreeTSS ; INPUT : EAX, ECX ; OUTPUT : NONE ; USED : ALL General registers, FLAGS ; MODIFIES : pFreeTSS (and the dynamic array of TSSs) ; ; This routine initializes the free pool of Task State Segments. ; On entry: ; EAX points to the TSSs to initialize (allocated memory). ; ECX has the count of TSSs to initialize. ; The size of the TSS is taken from the constant sTSS. ; ; The pFreeTSS pointer is set to address the first TSS. The NextTSS ; field in each TSS is set to point to the next free TSS. The ; last TSS is set to point to nothing (NIL). The IOBitBase field is ; also set to FFFFh for NULL I/O permissions in each TSS. ; NOTE: The allocated memory area for the TSS MUST BE ZEROED before
MMURTL V1.0

-329-

RICHARD A BURGESS

; calling this routine. By deafult, we add the TSS descriptors at OS ; protection level. If we spawn or add a User level TSS we must ; OR the DPL bits with 3!

PUBLIC InitFreeTSS: MOV pFreeTSS,EAX MOV EDI, OFFSET rgTSSDesc ADD EDI, 16 MOV EDX, sTSS MOV EBX, 3

; ; ; ; ;

First one free to use ptr to TSS descriptors First two TSSs are Static (Mon & Dbgr) Size of TSS (in bytes) into EDX Number of first dynamic TSS

TSS_Loop: MOV ESI,EAX ; for I = 0 TO ECX ADD EAX,EDX ; EAX <= rgTSSs[I].Next MOV [ESI+NextTSS],EAX ; ^rgTSSs[I+1]; MOV WORD PTR [ESI+TSS_IOBitBase], 0FFFFh ; IOBitBase MOV [ESI+TSSNum], BX ; TSS Number MOV WORD PTR [ESI+TSS_DS], DataSel ;Set up for Data Selectors MOV WORD PTR [ESI+TSS_ES], DataSel MOV WORD PTR [ESI+TSS_FS], DataSel MOV WORD PTR [ESI+TSS_GS], DataSel MOV WORD PTR [ESI+TSS_SS], DataSel MOV WORD PTR [ESI+TSS_SS0], DataSel PUSH EAX ;Save pTSS MOV EAX,EDI ; Get offset of Curr TssDesc in EAX SUB EAX, OFFSET GDT ; Sub offset of GDT Base to get Sel of TSS MOV WORD PTR [ESI+Tid],AX ; Store TSS Selector in TSS (later use) PUSH EBX PUSH EDX MOV EAX,EDX MOV EDX,ESI MOV EBX,0089h CALL AddTSSDesc ADD EDI,8 ; Point POP EDX POP EBX POP EAX INC EBX LOOP TSS_Loop MOV DWORD PTR [ESI+NextTSS], 0 RETN to Next GDT Slot (for next one) ; Size of TSS (TSS + SOFTSTATE) ; Address of TSS ; G(0),AV(0),LIM(0),P(1),DPL(0),B(0)

; TSS Number ; ; rgFree[LastOne].Next <= NIL; ;

;========================================================================= ; DUMMY CALL for uninitialized GDT call gate slots ;========================================================================= DummyCall: MOV EAX, ercBadCallGate RETF Besides setting up all the call gates with a dummy procedure, InitCallGate() takes care of another problem. This problem is that the call to AddCallGate() is a public function called through a Call Gate. This means it can't add itself. This code manually adds the call for AddCallGate() so you can add the rest of them. As I mentioned before, each of the over 100 calls are placed in the GDT. There is room reserved for over 600.

MMURTL V1.0

-330-

RICHARD A BURGESS

InitIDT, a little further down, sets up each of the known entries in the IDT as well as filling in all the unknown entries with a dummy ISR that simply returns from the interrupts. I have left all of these in so that you can see which are interrupt gates and which are interrupt traps. The processor handles each of these a little differently. See listing 21.7. Listing 21.7. Initialize call gates amd the IDT

;========================================================================= ; InitCallGates inits the array of call gates with an entry to a generic ; handler that returns ErcNotInstalled when called. This prevents new code ; running on old MMURTLs or systems where special call gates don't exist ; without crashing too horribly. ; ; IN: Nothing ; Out : Nothing ; Used : ALL registers and flags ; PUBLIC InitCallGates: ;First we set up all call gates to point to a ;dummy procedure MOV ECX, nCallGates ;Number of callgates to init

InitCG01: PUSH ECX ;Save nCallGates DEC ECX ;make it an index, not the count SHL ECX, 3 ; ADD ECX, 40h ;Now ecx is selector number MOV EAX, 0EC00h ;DPL 3, 0 Params MOV DX, OSCodeSel MOV ESI, OFFSET DummyCall ;Same code as in PUBLIC AddCallGate MOVZX EBX, CX SUB EBX, 40 ;sub call gate base selector SHR EBX, 3 ;make index vice selector 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 POP ECX LOOP InitCG01 ;ignore error... ;This decrements ECX till 0

;Another chicken and egg here.. In order to be able ;to call the FAR PUBLIC "AddCallGate" though a callgate, ;we have to add it as a callgate... ok.... MOV EAX, 08C00h ;AddCallGate -- 0 DWord Params MOV ECX, 0C8h MOV DX, OSCodeSel MOV ESI, OFFSET __AddCallGate ;Same code as in PUBLIC AddCallGate MOVZX EBX, CX
MMURTL V1.0

DPL 0

-331-

RICHARD A BURGESS

OSCodeSel . TRAP GATE OSCodeSel . . OFFSET IntOverFlow MMURTL V1. IN : Nothing . Trap Gate (for Debugger) WAS 8F00 MOV EBX. . Out : Nothing .This will be filled in with TSS of Dbgr later MOV ESI.Now we add each of the known interrupts MOV ECX. 40 .DPL 3. TRAP GATE MOV EBX.Divide By Zero MOV EAX. GDTBase .DPL 3. Trap gate MOV EBX. software and hardware interrupt handlers. 0 . 08F00h .Extend selector into EBX ADD EBX. InitIDT . 3 .Single Step MOV EAX. 4 . ESI. 08F00h .0:15 of call offset SHR ESI.call DPL & ndParams RETN . MOV ESI. OFFSET IntDivBy0 CALL FWORD PTR _AddIDTGate MOV ECX. 16 . handler that does nothing (except IRETD) when called.Overflow 08F00h . Offset IntDbgSS CALL FWORD PTR _AddIDTGate . MOV ESI. .SUB EBX.move upper 16 of offset into SI MOV [EBX+06]. Second. OFFSET IntDebug CALL FWORD PTR _AddIDTGate MOV MOV MOV MOV ECX. OFFSET IntQ CALL FWORD PTR _AddIDTGate POP ECX LOOP InitID01 .========================================================================= . SI . .Put Code Seg selector into Call gate MOV [EBX]. OSCodeSel MOV ESI. PUBLIC InitIDT: MOV ECX.16:31 of call offset MOV [EBX+04].DPL 3. inits the IDT with 256 entries to a generic .make index vice selector MOVZX EBX. 08E00h . First.Trying 8E00 (Int gate vice trap gate which leave Ints disabled) MOV ECX.DPL 3.DPL 3.Breakpoint MOV EAX.NOW a true offset in GDT MOV WORD PTR [EBX+02].0 -332- RICHARD A BURGESS . 1 . EAX. 255 .sub call gate base selector SHR EBX. Used : ALL registers and flags . SI .Last IDT Entry InitID01: PUSH ECX MOV EAX. CX . 8 . TRAP GATE MOV EBX. adds each of the basic IDT entries for included . ISRs loaded with device drivers must use SetIRQVector. OSCodeSel . EBX. 08F00h . 3 . AX . OSCodeSel .

Int Page Fault MOV EAX. OFFSET IntPICU2 MMURTL V1.DPL 3. OFFSET IntTimer CALL FWORD PTR _AddIDTGate MOV ECX.Int Stack Overflow MOV EAX. TRAP GATE MOV EBX. 6 . OFFSET IntInvTSS CALL FWORD PTR _AddIDTGate MOV ECX.DPL 3. INTERRUPT GATE MOV EBX.Int PICU 2 (from PICU) IRQ2 08E00h . 21h . TRAP GATE MOV EBX. MOV ESI. OSCodeSel . TRAP GATE MOV EBX.0 -333- RICHARD A BURGESS .DPL 3. 0Ah . EBX. MOV ESI. MOV ESI. 08F00h . OSCodeSel . MOV ESI. INTERRUPT GATE OSCodeSel . MOV ESI.Int TIMER MOV EAX. 8 . MOV ESI. 08F00h . 08F00h . TRAP GATE MOV EBX.Invalid OPcode MOV EAX. 08F00h .Double Exception MOV EAX.Invalid TSS MOV EAX. 08F00h . 08E00h . 0Bh . OSCodeSel . IRQ0 IRQ1 22h . 08F00h . MOV ESI.DPL 3. INTERRUPT GATE MOV EBX. MOV ESI. 08F00h . 08E00h .Int KEYBOARD MOV EAX. OFFSET IntNoSeg CALL FWORD PTR _AddIDTGate MOV ECX.GP fault MOV EAX.DPL 3. OSCodeSel . TRAP GATE MOV EBX.Seg Not Present MOV EAX. OFFSET IntPgFlt CALL FWORD PTR _AddIDTGate MOV ECX. OFFSET IntKeyBrd CALL FWORD PTR _AddIDTGate MOV MOV MOV MOV ECX.DPL 3. OFFSET IntStkOvr CALL FWORD PTR _AddIDTGate MOV ECX. OFFSET IntOpCode CALL FWORD PTR _AddIDTGate MOV ECX. OSCodeSel . ESI. 20h .DPL 3. MOV ESI. OSCodeSel . OFFSET IntGP CALL FWORD PTR _AddIDTGate MOV ECX. 0Eh . OSCodeSel .DPL 3. 0Dh . TRAP GATE MOV EBX. OFFSET IntDblExc CALL FWORD PTR _AddIDTGate MOV ECX.DPL 3. 0Ch . EAX. OSCodeSel .DPL 3. TRAP GATE MOV EBX. OSCodeSel .CALL FWORD PTR _AddIDTGate MOV ECX.

. MOV ESI. INTERRUPT GATE MOV EBX..CALL FWORD PTR _AddIDTGate MOV ECX.Int . 08E00h .Int Floppy IRQ6 MOV EAX. ..Int . MOV ESI.DPL 3.DPL 3. MOV ESI. MOV ESI.FDD will set this himself CALL FWORD PTR _AddIDTGate MOV ECX. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX. 08E00h ... OSCodeSel ...DPL 3.Int .. MOV EAX.. OSCodeSel . IRQ7 IRQ8 IRQ9 IRQ10 IRQ11 IRQ12 MMURTL V1. 08E00h .DPL 3..Int .. OSCodeSel .. 28h . OSCodeSel .. MOV ESI.. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX.Int LPT2 MOV EAX. MOV ESI.. 08E00h .DPL 3. OFFSET IntQ .DPL 3.. 08E00h . OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV MOV MOV MOV ECX. 27h . 2Bh ... MOV ESI. 23h . OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX. MOV EAX. INTERRUPT GATE MOV EBX.. INTERRUPT GATE . 08E00h . INTERRUPT GATE MOV EBX. OSCodeSel .. INTERRUPT GATE MOV EBX.. MOV ESI... 2Ah . INTERRUPT GATE MOV EBX. INTERRUPT GATE MOV EBX... 08E00h .... OSCodeSel .Int .DPL 3... OSCodeSel . 25h . MOV ESI.....Int COM2 MOV EAX. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX.. INTERRUPT GATE MOV EBX... ESI...Int COM1 MOV EAX. 26h .DPL 3. MOV EAX..0 -334- RICHARD A BURGESS ..IntLPT1 MOV EAX. OSCodeSel ... 08E00h . 24h .DPL 3. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX.DPL 3. 29h . OSCodeSel .. OFFSET IntQ CALL FWORD PTR _AddIDTGate IRQ3 IRQ4 IRQ5 MOV ECX. 2Ch 08E00h OSCodeSel OFFSET IntQ . 08E00h . EAX. EBX. MOV EAX. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX. INTERRUPT GATE MOV EBX. INTERRUPT GATE MOV EBX..

08E00h . PUBLIC InitOSPublics: MOV EAX. DPL 3 MOV ECX.. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX. Out : Nothing ... OSCodeSel . OSCodeSel .Int .0 -335- RICHARD A BURGESS .. Used : ALL registers and flags .3 DWord params..DPL 3. 48h MOV DX. OFFSET IntQ CALL FWORD PTR _AddIDTGate RETN IRQ13 IRQ14 IRQ15 .. OSCodeSel . 08C03h .. OFFSET IntQ CALL FWORD PTR _AddIDTGate MOV ECX. 08E00h . INTERRUPT GATE MOV EBX. 40h MOV DX..2 DWord Params. OFFSET __SendMsg CALL FWORD PTR _AddCallGate MOV EAX.DPL 3. This can't ... ... 0EC01h .. IN : Nothing ... MOV ESI. before initcallgates.CALL FWORD PTR _AddIDTGate MOV ECX. MOV EAX.ISendMsg -.. MOV ESI.. 0EC03h ...Int .3 DWord Params. but MUST be called before the first far call to any . OFFSET __WaitMsg CALL FWORD PTR _AddCallGate MOV EAX. INTERRUPT GATE MOV EBX. InitOSPublics adds all OS primitives to the array of call gates...1Dword param. DPL 3 MOV ECX.DPL 3.SendMsg -.========================================================================== . INTERRUPT GATE MOV EBX.. DPL 3 MOV ECX. 2Dh . DPL 0 MOV ECX. MOV EAX. OSCodeSel MOV ESI. OFFSET __ISendMsg CALL FWORD PTR _AddCallGate MOV EAX... OFFSET __SetPriority CALL FWORD PTR _AddCallGate MMURTL V1. OSCodeSel MOV ESI. MOV EAX. OSCodeSel MOV ESI.WaitMsg -.Set Priority . 08E00h .. 50h MOV DX. MOV ESI.. 2Eh . IF YOU ADD AN OS PUBLIC MAKE SURE IT GETS PUT HERE!!!!! . 0EC02h . 58h MOV DX.Int . OSCodeSel MOV ESI. OS primitive thorugh a call gate!!! . 2Fh ..

RETN MMURTL V1.The rest of the call gate set ups go here.0 -336- RICHARD A BURGESS ..

Monitor JCB reference EXTRN _BootDrive DD . Pieces of related code in the kernel. PUBLIC InitNewJCB: . For Monitor stats EXTRN MonJCB DB . it is usually quite a mess. and other places provide assistance with resource reclamation. These calls.INC PUBLIC pFreeJCB PUBLIC pJCBs PUBLIC _nJCBLeft DD 0 DD 0 DD nJCBs .1. exchanges.asm .INCLUDE JOB. this is the where you'll end up. See listing 22. pictures of the complications of writing a loader floated in my head for quite a while.Ptr to JCB that is to be filled in .INC .Begin Code ================== . Job Management Subroutines . are used by almost every module in the operating system.ASM provides many small helper functions for job management.======= End data. I have tried to break it down logically and keep it all in a high-level language. Job Management Code Introduction After you get the kernel out of the way and you want to begin loading things to run on your system. The program loader is in this chapter.================================================================ .jobs (Monitor & Debugger) .0 -337- RICHARD A BURGESS . INPUT : EAX -. Job Management Helpers The file JobCode. Reclamation of Resources Equally as difficult as the loader.Chapter 22. Ptr to free Job Control Blocks . The process of allocating all the resources. It was every bit as difficult as I imagined. request blocks. TSSs. the JCB.INCLUDE MOSEDF. and finally starting the new task. The resources you reclaim include all of the memory. was the death of a job and the reclamation of its valuable resources. In the functions ExitJob() and Chain(). and finally the operating system memory. From Main. JCBs are in allocated memory . the monitor. Before I wrote this code. link blocks. many of them public functions. making sure that each was correctly set and filled out.InitNewJCB is used initially by the OS to fill in the first two .Linear Ptr to Page Directory for Job MMURTL V1. I drift in and out of assembly language to do things like switch to a temporary stack when the task that is running no longer has a valid one. EBX -.1 Listing 22.INCLUDE TSS.DATA .INC . can be a complicated scenario.CODE . When things don't work right.

EAX points to next one MOV [ESI+NextJCB]. The JCBs are also sequentially numbered. . . . 7 MOV EAX. This is used to initilaize a new JCB during OS init and when a new Job is loaded and run.cbJobName EDX -.ESI EFLAGS .EDI points to JCB . . . .EAX . EAX points to the first JCB.EBX. ESI. .EBX MOV EDI. . This routine will initialize the free pool of Job Control Blocks (JCBs). INPUT : EAX . ECX . EAX .EAX . . .pbJobName ECX -.============================================================================= .EBX has pointer to current one ADD EAX. OUTPUT : NONE .Make last one NIL RETN . while .Pointer to Job Virtual Video Buffer (all jobs have one!) OUTPUT : JOB Number in EAX USED : EAX. . . EDX .0 -338- RICHARD A BURGESS . . ECX.Size of JCBs .size is filled in . 3 .Count of JCBs . EDX MOV DWORD PTR [EAX+nCols].EAX . . .Now to JobName . 25 MOV DWORD PTR [EAX+NormAttr]. . . EDX.Video number is in JCB . CL INC EDI REP MOVSB MOV [EAX+pVirtVid]. MMURTL V1.Set up OS pointer to list MOV pJCBs.EDX .ECX. . . ECX is count of JCBs.EDX. Each element of rgJCBs is set to point to the next element of rgJCBs. MODIFIES: pFreeJCB. . MOV [EAX+JcbPD]. EDX is size of each JCB. EFlags MODIFIES : JCB pointed to in EBX This fills in a JCB with new information.Set up global ptr to first JCB MOV EBX. We can't use its position . . PUBLIC InitFreeJCB: MOV pFreeJCB. EBX. The last element of rgJCBs is set to point to nothing (NIL).Put Ptr to PD into JCB .1st number for Dynamic JCBs JCB_Loop: MOV ESI.first byte of name . pJCBs .Make current point to next MOV [ESI+JobNum]. USED: EAX. sbJobName MOV BYTE PTR [EDI]. 80 MOV DWORD PTR [EAX+nLines]. 0 .Address of JCBs to be initialized . others (the ones we are initializing now) are dynamicly allocated. [EAX+JobNum] RETN .Move it in .. in the array because some JCBs are static (Mon and Debugger). InitFreeJCB . EDI. EBX .Go back till done MOV DWORD PTR [ESI+NextJCB]. ESI -. EAX ADD EDI. The pFreeJCB pointer is set to address the first element in rgJCBs. . .Number it INC EBX LOOP JCB_Loop .

sJCB CALL InitFreeJCB RETN . INPUT : NONE .FLAGS . . .============================================================================= PUBLIC DisposeJCB: . REGISTERS : EAX.pFreeJCB . REGISTERS : EBX.Invalidate JCB MMURTL V1.FLAGS . JE NewJCBDone .EBX. NewJCBDone: RETN . This routine will return in EAX register the address of the next free jcb. . CMP EAX. Get 'em! . . . MODIFIES : pFreeJCB .Put it in OS pointer DEC DWORD PTR _nJCBLeft . pJCBs STOSD . This routine will also . . the free pool. If none exists. nJCBs MOV EDX.. . 0 .DWORDS!) where to store 0s Do it Ptr to JCBs Count of Job Control Blocks EDX is size of a JCB Init the array of JCBs MOV EAX. . then EAX will contain NIL (0). JE DispJCBDone . . . MOV EAX.EBX .============================================================================= PUBLIC NewJCB: . . 4096 EDI.0 . OUTPUT : EAX . . This routine will return to the caller a pointer to the next free jcb. MOV DWORD PTR [EAX+JcbPD].Get pointer to next free one MOV pFreeJCB.[EAX+NextJCB] . This invalidates the JCB by placing 0 in JcbPD. . MOV EBX. update the value of pFreeJCB to point to the next "unused" JCB in . . . Allocate 4 pages (16384 bytes) for 32 Job Control Blocks (structures). pool of JCBs pointed to by (pFreeJCB) if EAX is not NIL. OFFSET pJCBs PUSH EAX CALL FWORD PTR _AllocOSPage XOR MOV MOV REP EAX. EAX ECX.Get OS pointer to JCBs CMP EAX. pJCBs MOV ECX. OUTPUT : NONE . INPUT : EAX . 0 . . If pJCBin = NIL THEN Return. 4 pages for 32 JCBs (16384 bytes) .IF pFreeJCB=NIL THEN Return. MODIFIES : pFreeJCB . This routine will place the jcb pointed to by EAX back into the free . Clear allocated memory for JCBs (4*4096=16384 . Returns ptr to allocated mem in pJCBs . . The data used in this algorithm is the free jcb pointer (pFreeJCB).================================================================ . Then call InitFreeJCB PUBLIC InitDynamicJCBs: PUSH 4 MOV EAX. .0 -339- RICHARD A BURGESS .

PUBLIC GetCrntJobNum: CALL GetpCrntJCB MOV EAX. All TSSs are . All TSSs are assigned to a Job. . PUBLIC GetpJCB: PUSH EDX CMP EAX.============================================================ . . OFFSET MonJCB POP EDX RETN GetpJCB1: CMP EAX. USED: EAX. .0 -340- RICHARD A BURGESS .Linear Address of the JCB or 0 for invalid number . . . . This is based on which Task is executing. GetCrntJobNum .MOV EBX. A Job may have more than one Task.Job Number of desired pJCB . .Current JCB RETN . .Linear Address of current JCB . OUTPUT: EAX -.============================================================ . . Returns a pointer to a Job Control Block identified by number . [EAX+TSS_pJCB] . . OUTPUT: EAX -.EBX MOV pFreeJCB. USED: EAX.============================================================ . .Move it into newly freed JCB . GetpCrntJCB . USED: EAX.EAX INC DWORD PTR _nJCBLeft DispJCBDone: RETN . EFlags . . assigned to a Job.Pointer to JCB RETN . This is based on which Task is executing. . PUBLIC GetpCrntJCB: MOV EAX.pFreeJCB MOV [EAX+NextJCB]. EFlags . INPUT: Nothing . OUTPUT: EAX -.Current Task State Segment MOV EAX. in EAX. INPUT: Nothing . Returns the Job number for the currently executing task. assigned to a Job! A Job may have more than one Task. 2 MMURTL V1. All TSSs are .Current Job Number . Returns a pointer to the current Job Control Block in EAX.Move ptr to newly frred JCB to OS .EBX has OS ptr to free list . [EAX+JobNum] . GetpJCB . The Job number . . 1 JNE GetpJCB1 MOV EAX. INPUT: EAX -. EFlags . is a field in the JCB structure. Many OS functions deal with the Job number. pRunTSS .

is a field in the JCB structure. call to support the public job management calls in high level . OUTPUT: EAX -. [EAX+JobNum] . INPUT: EAX pJCB we want job number from. .Within range of JCBs . We got one! . .0 . EAX POP EDX RETN GetpJCB3: SUB EAX. This allocates a new JCB (from the pool). pdJobNumRet is the number of the new JCB. AllocJCB(pdJobNumRet.============================================================ . sJCB MUL EDX ADD EAX. This is a NEAR .JNE GetpJCB2 MOV EAX. PUBLIC GetJobNum: MOV EAX.Add in two static JCBs .Current JCB RETN . Returns the Job number for the pJCB in EAX in EAX. USED: EAX. OFFSET DbgJCB POP EDX RETN GetpJCB2: CMP EAX. . EAX JNZ SHORT AJCB01 MOV EAX. nJCBs+2 JLE GetpJCB3 XOR EAX. . Get a new JCB . pJCBRet is a pointer where you want the pointer to the new JCB is returned. .============================================================ . . AllocJCB (NEAR) .Now points to desired JCB . Sorry. 3 MOV EDX.Times size of JCB . pJCBs POP EDX RETN . . . ErcNoMoreJCBs MMURTL V1.Take off static JCBs+1 (make it an offset) . .Current Job Number . Many OS functions deal with the Job number. . . . Procedureal Interface : . . . pdJobNum EQU [EBP+12] . GetJobNum . EFlags . . out of them! -341- RICHARD A BURGESS . . The Job number .ESP CALL NewJCB OR EAX. ppJCBRet):ercType . . languages. ErcNoMoreJCBs will be returned if no more JCBs are avaialble. . . pJCBRet EQU [EBP+8] PUBLIC _AllocJCB: PUSH EBP MOV EBP. .

.0 -342- RICHARD A BURGESS . . ErcBadJobNum will be returned if dJobNum is out of range . .MOV ESP. [EBP+12] CALL GetJobNum MOV [ESI]. . pJCBRet):ercType . . pJCB EQU [EBP+8] PUBLIC _DeAllocJCB: PUSH EBP MOV EBP. pJCB is a pointer the JCB to be deallocated. dJobNum is the number of the JCB you want.EBP POP EBP RETN 4 . DeAllocJCB(pJCB):ercType . ErcNoMoreJCBs will be returned if no more JCBs are avaialble. [EBP+8] CALL DisposeJCB XOR EAX. . EAX MOV ESP. pJCBRet is a pointer where you want the JCB returned. EAX MOV ESP.============================================================ . EAX MOV ESI. languages in the OS code. . EAX XOR EAX. . MMURTL V1. . . .No error . call to support the public job management calls in high level . . [EBP+8] MOV [ESI]. . . . This PUBLIC returns a pointer to the JCB for the JobNum . .===== BEGIN PUBLIC FAR JOB CALLS =========================== . . . .EBP POP EBP RETN 8 .============================================================ . . GetpJCB .ESP MOV EAX. . . Procedureal Interface : .Job Num . . . This is a NEAR . . This Deallocates a JCB (returns it to the pool). Get a new JCB . DeAllocJCB (NEAR) . . . . pJCB . .EBP POP EBP RETN 8 AJCB01: MOV ESI. . you specifiy. .============================================================ . . Procedureal Interface : .pJCBRet . GetpJCB(dJobNum.

RETF 8 . MOV EAX. . dJobNum EQU [EBP+16] .ESP . GetpJcbOk: CALL GetpJCB . POP EBP . pJobNumRet EQU [EBP+12] PUBLIC __GetJobNum: PUSH EBP MOV EBP. EAX XOR EAX. ErcInvalidJCB . . .pJobNumRet . This PUBLIC returns the number for the current Job. . Procedureal Interface : . . . . This is . MOV ESP. . . EAX CMP DWORD PTR [EAX+JcbPD]. POP EBP .Dynamic + 2 static JBE GetpJcbOK GetpJcbBad: MOV EAX. [EBP+16] .puts address of JCB in EAX MOV ESI. GetJobNum . EAX MOV ESP. . RETF 8 . nJCBs + 2. MMURTL V1. GetpJcbOk1: XOR EAX. . POP EBP . 0 .0 -343- RICHARD A BURGESS . the job that the task that called this belongs to. .Job Num OR EAX.Leave jobnum in EAX .============================================================ . or 0 will be returned with the data. [EBP+12] MOV [ESI].EBP .0 is invalid CMP EAX. ErcBadJobNum will be returned if dJobNum is invalid . . . GetJobNum(pJobNumRet):ercType . [EBP+12] . EAX POP EBP RETF 4 .Is this a valid JCB JNE GetpJCBOk1 MOV EAX.JCB we are pointing to is unused MOV ESP.pJCBRet MOV [ESI]. .No Error . pJCBRet is a pointer where you want the JCB returned.EBP . ErcBadJobNum . pJCBRet EQU [EBP+12] PUBLIC __GetpJCB: PUSH EBP MOV EBP.. . RETF 8 .ESP CALL GetCrntJobNum MOV ESI.EBP . . EAX JZ GetpJcbBad . .

This code is here for lack of a better place. . . MOV EBP. pSysDiskRet is a pointer to a byte where .c source code /* This file contains functions and data used to support loading or terminating jobs (or services).asm. It return 0-n (which corresponds to A-x) . the number representing the system disk is returned. . [EBP+12] . It contains the public functions: Chain() LoadNewJob() ExitJob() GetExitJob() SetExitJob() SetCmdLine() GetCmdLine() GetPath() MMURTL V1.============================================================ . loads ExitJob if specified Gets run file name that will be loaded upon ExitJob Sets run file name to load upon ExitJob Sets the command line for next job Gets the command line for the current job Gets the path prefix for the current job -344- RICHARD A BURGESS .2.pJobNumRet MOV [ESI]. . . XOR EAX. Listing 22. pSysDiskRet EQU [EBP+12] PUBLIC __GetSystemDisk . And will . It's really not a filesystem function either. This is from the public .0 Loads new job run file in current JCB & PD Loads a new job into a new JCB & PD Exits current job. Procedureal Interface : . contains all of the major high-level functions for job management.c. This PUBLIC returns a single byte which represents the . Jobc. variable _BootDrive defined in Main. RETF 4 . disk that the system booted from. Functions for starting new jobs.================= MODULE END ================================= Job Management Listing This file. . still be needed if a loadable filesystem is installed. 7Fh . . MOV EAX. AL . and public functions for working with the job control block are contained in this module. EAX . PUSH EBP . . . GetSystemDisk(pSysDiskRet):ercType . GetSystemDisk . ending jobs.get rid of high bits MOV ESI. Jobc. .No Error POP EBP .ESP .. _BootDrive AND EAX.

extern U32 Dump(unsigned char *pb.h" #define MEMUSERD 7 #define #define #define #define #define ErcOpCancel ErcOutOfRange ErcBadJobNum ErcNoExitJob ErcBadRunFile 4 10 70 76 74 /* User Writable (Data) */ /* /* /* /* /* Operator cancel */ Bad Exchange specified in OS call */ A Bad job number was specified */ No ExitJob specified on ExitJob(n)*/ Couldn't load specified ExitRunFile */ /* Near Support Calls from JobCode.h" "MTimer.). MMURTL V1. SetExchOwner(long Exch. GetExchOwner(long Exch. static struct JCBRec *pCrntJCB.h" #include "runfile.h" "MFiles. . long cb). char *ppJCBRet)... /* 512 byte temporary stack */ /* Used for allocating/filling in new JCB */ static struct JCBRec *pNewJCB.0 /* Used to access a JCB */ -345- RICHARD A BURGESS . RemoveRdyJob(char *pJCB). static struct JCBRec *pTmpJCB.h" "MKbd. char *pNewJCB).h" "MVid. */ static long TmpStack[128]. long Exch). /* We switch to this stack when we clean out a user PD before we rebuild it. /* Temporary NEAR externals from the monitor program for debugging */ extern long xprintf(char *fmt.h" "MMemory. SendAbort(long JobNum.Setpath() SetUserName() GetUserName() */ Sets the path prefix for the current job Sets the Username for the current job Gets the Username for the current job #define #define #define #define #define #define #define #define #include #include #include #include #include #include #include #include U32 unsigned long S32 long U16 unsigned int S16 int U8 unsigned char S8 char TRUE 1 FALSE 1 "MKernel.ASM */ extern extern extern extern extern long long long long long AllocJCB(long *pdJobNumRet.h" "MJob.h" "MData. char *pJCBRet).

0. sStack. erc. 0. static long offCode = 0. *pPDE. iE. *pStack. unsigned long *pPT. *pExchJCBE KeyCodeE. offData = 0. When all PTs have been cleaned. /* For ExitJob and Chain cause They can't have local vars! */ static static static static static static long long long long char long JobNumE. /* Run file data */ static char *pCode. i<1024. 0. static char aFileE[80]. 0. BogusMsg[2]. for (i=768. static struct tagtype tag. static unsigned long oCode. ercE. *pData. static unsigned long oCDFIX nCCFIX oCCFIX nDDFIX oDDFIX nDCFIX oDCFIX nCDFIX = 0.static long JobNum. This leaves a blank PD for reuse if needed. ExchE.0 /* Look at each shadow PDE */ -346- RICHARD A BURGESS . 0. We get the pointer to the PD and "walk" through all the User PTs deallocating linear memory. This is used when we ExitJob to free up all the memory. /************* INTERNAL SUPPORT CALLS ******************/ /****************************************************** This deallocates all user memory in a PD. filetype. 0. j. k. NOTE: This must be called from a task that is running in the JCB that owns the memory! ******************************************************/ static void CleanUserPD(long *pPD) { long i. static long sCode. sData. job_fhE. extern unsigned long KillExch. oData. /* Ptrs in User mem to load to */ /* Size of segments */ /* Offset in file to Code & Data */ /* Virtual Offset for Code & Data Segs */ /* From the Monitor */ = = = = = = = static char *pStart. 0. i++) { MMURTL V1. we eliminate the user PDEs. static long cbFileE. char *pMem.

dret. offCode = 0.if (pPD[i]) { pPT = pPD[i] & 0xFFFFF000. while ((pPT[j++]) && (j<1024)) k++. &filetype. /* default to 0 */ /* Mode Read. char fDone. reads and validates the run file. else it closes the run file and returns the error to the caller. long cbFileName. /* nPages to deallocate */ pMem = ((i-512) * 0x400000) + (j * 4096). i. if (!erc) { /* File opened OK */ fDone = 0. If all is well. *pfhRet = 0.0 -347- RICHARD A BURGESS . fh. j++) { /* If it's a linear address (non 0)*/ /* Point to Page Table */ /* Get Beginning address for each run to deallocate */ k = 0. nDDFIX = 0. oCDFIX = 0. &tag.id) { case IDTAG: erc = ReadBytes (fh. nCCFIX = 0. 1. switch (tag. 0. 5. &dret). &dret). oDCFIX = 0. offData = 0. /* one more page */ if (k) { /* we have pages (one or more) */ erc = DeAllocPage(pMem. k).id = 0. cbFileName. erc = ReadBytes (fh. } } } } } /********************************************************* This opens. The caller is responsible for closing the file!!! *********************************************************/ static long GetRunFile(char *pFileName. long *pfhRet) { long erc. &fh). for (j=0. nDCFIX = 0. j<1024. nCDFIX = 0. MMURTL V1. while ((!erc) && (!fDone)) { tag. junk. if ((filetype < 1) || (filetype > 3)) erc = ErcBadRunFile. oCCFIX = 0. oDDFIX = 0. 1. it leaves it open and returns the file handle. Stream type */ erc = OpenFile(pFileName.

len). 4.len). oCCFIX+tag. &oDDFIX). nDDFIX = tag. &offCode. if (!erc) erc = SetFileLFA(fh. break. /* skip it break. &sData.len). case CODETAG: erc = GetFileLFA(fh. &oCode). case DCFIXTAG: erc = GetFileLFA(fh. oCode+tag. if (!erc) erc = ReadBytes (fh. &i).len/4. MMURTL V1. oCDFIX+tag. 4. &sCode. case SEGTAG: erc = ReadBytes (fh.len > 1024) erc = ErcBadRunFile. case CDFIXTAG: erc = GetFileLFA(fh. default: erc = GetFileLFA(fh. oDCFIX+tag. /* skip it break. oData+tag. &oDCFIX). if (!erc) erc = SetFileLFA(fh.len/4. if (tag. oDDFIX+tag. if (!erc) erc = SetFileLFA(fh. break. nCCFIX = tag.0 */ */ */ */ */ */ -348- RICHARD A BURGESS .len). /* skip it break. if (!erc) erc = SetFileLFA(fh. &dret). /* skip it break.len). nCDFIX = tag. &dret). &offData. &pStart. &oCDFIX). if (!erc) erc = SetFileLFA(fh. case CCFIXTAG: erc = GetFileLFA(fh.len). case DATATAG: erc = GetFileLFA(fh. /* skip it break. &oData). 4. 4. break. case ENDTAG: fDone = TRUE. &oCCFIX). case STRTTAG: erc = ReadBytes (fh. case DOFFTAG: erc = ReadBytes (fh. break. &dret). if (!erc) erc = SetFileLFA(fh. if (!erc) erc = ReadBytes (fh. case DDFIXTAG: erc = GetFileLFA(fh. &sStack. /* skip it */ if (erc) erc = ErcBadRunFile. nDCFIX = tag. &dret). i+tag.len/4.len/4. if (!erc) erc = SetFileLFA(fh. case COFFTAG: erc = ReadBytes (fh.len). &dret). &dret). break. 4.break. /* skip it break. 4.

long dcbPath) { long JobNum. long *pdcbRunRet) { long JobNum. dcbRunFile). if (dcbPath > 69) return (ErcBadJobParam). else if (!dcbRunFile) pTmpJCB->JcbExitRF[0] = 0. } if (!erc) *pfhRet = fh. &pTmpJCB). } /**************************************************/ long far _SetPath(char *pPath. return(0). GetpJCB(JobNum. MMURTL V1. if (i) CopyData(&pTmpJCB->JcbExitRF[1].break. erc).0 /* Get pJCB to current Job */ -349- RICHARD A BURGESS . pRunRet. } return(0). if (erc) xprintf("Erc from GetRunFile in JobC: %d\r\n". &pTmpJCB->JcbExitRF[1]. i. long dcbRunFile) { long JobNum. GetpJCB(JobNum. GetpJCB(JobNum. /* Get pJCB to current Job */ if (dcbRunFile > 79) return (ErcBadJobParam). GetJobNum(&JobNum). } /********************************************************/ /*********** PUBLIC CALLS FOR JOB MANAGEMENT ************/ /********************************************************/ /**************************************************/ long far _SetExitJob(char *pRunFile. pTmpJCB->JcbExitRF[0] = dcbRunFile. &pTmpJCB). return (erc). *pdcbRunRet = i. } } if (erc) CloseFile(fh). } /**************************************************/ long far _GetExitJob(char *pRunRet. /* Get pJCB to current Job */ i = pTmpJCB->JcbExitRF[0]. GetJobNum(&JobNum). &pTmpJCB). GetJobNum(&JobNum). else { CopyData(pRunFile. i).

return(0). MMURTL V1. i). char *pPathRet. long dcbUser) { long JobNum. } /**************************************************/ long far _GetPath(long JobNum. long *pdcbPathRet) { long i. &pTmpJCB). pTmpJCB->JcbCmdLine[0] = dcbCmd. i. dcbPath). &pTmpJCB). } return(erc). *pdcbCmdRet = i. /* Get pJCB to current Job */ i = pTmpJCB->JcbCmdLine[0]. else { CopyData(pPath. GetpJCB(JobNum. long *pdcbCmdRet) { long JobNum. GetJobNum(&JobNum). &pTmpJCB->sbPath[1]. if (i) CopyData(&pTmpJCB->JcbCmdLine[1]. GetpJCB(JobNum. } return(0). } return(0). erc = GetpJCB(JobNum. else { CopyData(pCmd. } /**************************************************/ long far _SetUserName(char *pUser. /* Get pJCB to JobNum */ if (!erc) { i = pTmpJCB->sbPath[0]. pTmpJCB->sbPath[0] = dcbPath. i). pPathRet. long dcbCmd) { long JobNum.else if (!dcbPath) pTmpJCB->sbPath[0] = 0. pCmdRet. erc. } /**************************************************/ long far _GetCmdLine(char *pCmdRet.0 -350- RICHARD A BURGESS . *pdcbPathRet = i. &pTmpJCB). else if (!dcbCmd) pTmpJCB->JcbCmdLine[0] = 0. } /**************************************************/ long far _SetCmdLine(char *pCmd. dcbCmd). GetJobNum(&JobNum). /* Get pJCB to current Job */ if (dcbCmd > 79) return (ErcBadJobParam). if (i) CopyData(&pTmpJCB->sbPath[1]. &pTmpJCB->JcbCmdLine[1].

dcbUser). long dcbName) MMURTL V1. } /**************************************************/ long far _SetSysIn(char *pName. } return(0). /* Get pJCB to current Job */ i = pTmpJCB->sbUserName[0]. pTmpJCB->sbUserName[0] = dcbUser. &pTmpJCB->JcbSysIn[1]. GetpJCB(JobNum. GetJobNum(&JobNum). return(0).0 -351- RICHARD A BURGESS . if (i) CopyData(&pTmpJCB->sbUserName[1]. else if (!dcbUser) pTmpJCB->sbUserName[0] = 0. GetJobNum(&JobNum). /* Get pJCB to current Job */ if (dcbUser > 29) return (ErcBadJobParam). long dcbName) { long JobNum. GetpJCB(JobNum. i). &pTmpJCB). &pTmpJCB). } return(0). long *pdcbFileRet) { long JobNum. else { CopyData(pName. GetJobNum(&JobNum). dcbName). *pdcbUserRet = i. &pTmpJCB). i. } /**************************************************/ long far _GetUserName(char *pUserRet. GetpJCB(JobNum. if (i) CopyData(&pTmpJCB->JcbSysIn[1]. i). pUserRet. } /**************************************************/ long far _GetSysIn(char *pFileRet. pFileRet. return(0). GetpJCB(JobNum. long *pdcbUserRet) { long JobNum. *pdcbFileRet = i. &pTmpJCB->sbUserName[1]. } /**************************************************/ long far _SetSysOut(char *pName.GetJobNum(&JobNum). /* Get pJCB to current Job */ if ((dcbName > 49) || (!dcbName)) return (ErcBadJobParam). else { CopyData(pUser. i. &pTmpJCB). pTmpJCB->JcbSysIn[0] = dcbName. /* Get pJCB to current Job */ i = pTmpJCB->JcbSysIn[0].

if (i) CopyData(&pTmpJCB->JcbSysOut[1]. GetpJCB(JobNum. &pTmpJCB->sbJobName[1]. /* Get pJCB to current Job */ if (dcbName > 13) dcbName = 13. fh. } /********************************************************* This creates and loads a new Job from a RUN file. long dcbName) { long JobNum. } return(0). *********************************************************/ long far _LoadNewJob(char *pFileName. GetpJCB(JobNum. &fh). long *pdcbFileRet) { long JobNum. *pOSPD. &pTmpJCB). long *pJobNumRet) { long erc. pTmpJCB->sbJobName[0] = dcbName. } /**************************************************/ long far _GetSysOut(char *pFileRet. &pTmpJCB). long *pFix.{ long JobNum. GetJobNum(&JobNum). *pdcbFileRet = i. return(0). i). return(0). dret. GetJobNum(&JobNum). unsigned long *pPD. nPages. pTmpJCB->JcbSysOut[0] = dcbName. /* Get pJCB to current Job */ i = pTmpJCB->JcbSysOut[0]. dcbName). if (dcbName) CopyData(pName. *pVid. dcbName). This returns ErcOK and new job number if loaded OK else an error is returned. pFileRet. U32 PhyAdd. GetpJCB(JobNum. erc = GetRunFile(pFileName. } /**************************************************/ long far _SetJobName(char *pName. cbFileName. i. if (!erc) { MMURTL V1. &pTmpJCB->JcbSysOut[1]. *pPT.0 -352- RICHARD A BURGESS . &pTmpJCB). i. GetJobNum(&JobNum). else { CopyData(pName. /* Get pJCB to current Job */ if ((dcbName > 49) || (!dcbName)) return (ErcBadJobParam). long cbFileName.

sStack = nPages * 4096. nPages = sCode/4096. erc = AllocJCB(&JobNum. &pStack). pPT). &PhyAdd). xprintf("PhyAdd : %08x\r\n". xprintf("pUserPD: %08x\r\n". /* Now we can allocate User Job Memory */ /* Allocate user memory for Stack Code and Data */ /* This is STILL done in the OS PD */ nPages = sStack/4096.0 /* set to whole pages */ -353- RICHARD A BURGESS . pPT = pPD + 4096. GetpJCB(1. pPD). pVid = 0. pOSPD[256] = PhyAdd. nPages = sData/4096. &pData). pPD = 0. FillData(pVid. /* Alloc OS memory pages required for new job */ if (!erc) erc = AllocOSPage(3. pNewJCB = 0. /* Job's PD. &pCode). */ /* Set user code bits in PDE */ /* Make User PDE in OS PD */ /* Shadow Linear Address of PT */ pOSPD). &pTmpJCB). PhyAdd). pOSPD = pTmpJCB->pJcbPD. if (sStack%4096) nPages++. pPT = 0. &pNewJCB). 0). 0). &pPD). if (sCode%4096) nPages++./* We set these to zero so we can tell if they have been allocated in case we fail so we know what to deallocate */ JobNum = 0. if (!erc) erc = AllocPage(nPages. PT * pVirtVid */ /* 1 page later */ /* 2 pages later */ /* Zero user PT */ /* Zero user video */ /* Get OS pJCB */ /* Pointer to OS PD */ GetPhyAdd(1. 1). if (sData%4096) nPages++. pOSPD[768] = pPT. /* xprintf("pOSPD : %08x\r\n". 4096. if (!erc) { FillData(pPT. pPT. erc = AllocPage(nPages. 4096. xprintf("pUserPT: %08x\r\n". pVid = pPD + 8192. if (!erc) erc = AllocPage(nPages. /* Get Phy Add for new PT */ PhyAdd |= MEMUSERD. ReadKbd(&KeyCodeE. MMURTL V1.

/* back to fixups */ while ((nCDFIX--) && (!erc)) { erc = ReadBytes (fh. &dret). /* Now that we have read in the code and data we apply fixups to these segments from the runfile */ if (!erc) { if (nCDFIX) { erc = SetFileLFA(fh. oDDFIX). &i. 4. &dret). /* back to fixups */ while ((nDCFIX--) && (!erc)) { erc = ReadBytes (fh. /* Where in DSeg */ *pFix = *pFix . /* back to fixups */ while ((nCCFIX--) && (!erc)) { erc = ReadBytes (fh. 4./* Right now. oDCFIX). /* Copy OS PD to User PD */ /* All Job memory is now allocated. oCCFIX). pFix = pData + i. &dret). &dret). &i. pData. sData. &dret). oCDFIX). so let's LOAD IT! */ if (!erc) erc = SetFileLFA(fh. /* Where in CSeg */ *pFix = *pFix . /* Where in DSeg */ *pFix = *pFix . pPD.offData + pData. pFix = pCode + i. } } if (nCCFIX) { erc = SetFileLFA(fh.offCode + pCode. &i. pCode. &i. } } if (nDCFIX) { erc = SetFileLFA(fh. pFix = pCode + i. pFix = pData + i. We will now copy the entire OS PD into the User's New PD. 4. sCode. oData). oCode). the OS PD looks exacly like we want the User PD to look. } MMURTL V1.offData + pData. &dret). } } if (nDDFIX) { erc = SetFileLFA(fh.0 -354- RICHARD A BURGESS . /* back to fixups */ while ((nDDFIX--) && (!erc)) { erc = ReadBytes (fh. if (!erc) erc = ReadBytes (fh. if (!erc) erc = SetFileLFA(fh. if (!erc) erc = ReadBytes (fh. 4096). 4. */ CopyData(pOSPD. /* Where in CSeg */ *pFix = *pFix .offCode + pCode.

pNewJCB->sbUserName[0] = 0.} } /* Clean the OS PD of User memory */ FillData(&pOSPD[256]. pNewJCB->fCursOn = 1. pNewJCB). /* Exch now belongs to new JCB */ } CloseFile(fh). pNewJCB->nLines = 25. &pNewJCB->JcbSysIn[1]. 0). pStart+pCode-offCode). pNewJCB->sJcbStack = sStack. /* Clean OS PD of User Shadow */ /* Now we fill in the rest of the User's JCB */ pNewJCB->pJcbPD = pPD. pNewJCB->pVirtVid = pVid. pNewJCB->VidMode = 0. if (!erc) SetExchOwner(i. if (!erc) erc = NewTask(JobNum. pNewJCB->pJcbData = pData. pNewJCB->CrntY = 0. pNewJCB->nCols = 80. 1024. pNewJCB->sJcbCode = sCode. pNewJCB->JcbSysIn[0] = 3. 0). &pNewJCB->JcbSysOut[1]. pNewJCB->CrntX = 0. pNewJCB->JcbSysOut[0] = 3.0 -355- RICHARD A BURGESS . 0x18. pStack+sStack-4. pNewJCB->fCursType = 0. 1024. /* /* /* /* /* /* Default to Virt Vid */ Virtual Video memory */ Vid X Position */ Vid Y Position */ Columns */ Lines */ /* 80x25 VGA Color Text */ /* Cursor On */ /* UnderLine */ /* Finally. pNewJCB->sJcbData = sData. /* Size */ pNewJCB->pVidMem = pVid. we crank up the new task and schedule it for execution! */ if (!erc) erc = AllocExch(&i). pNewJCB->pJcbCode = pCode. pNewJCB->sbPath[0] = 0. /* read run file OK */ } if (!erc) MMURTL V1. /* Clean OS PD of User PDEs */ FillData(&pOSPD[768]. 3). pNewJCB->JcbExitRF[0] = 0. 25. 0. pNewJCB->JcbCmdLine[0] = 0. /* Size */ CopyData("VID". 3). pNewJCB->pJcbStack = pStack. i. /* /* /* /* /* /* /* /* /* /* /* Lin Add of PD */ Add of Stack */ Size of Code */ Add of Code */ Size of Code */ Add of Code */ Size of Code */ Zero UserName */ No Default path */ No Exit Run File */ No Cmd Line */ CopyData("KBD".

The PD. sData. oCode). oData). sCode. if (!erc) erc = ReadBytes (fh. ExitJob and Chain are responsible for opening and validating the runfile and setting up the run file variables. sStack = nPages * 4096.0 /* set to whole pages */ /* back to fixups */ -356- RICHARD A BURGESS . &dret). pVid & first PT still exist in OS memory. return(erc). i. /* Allocate user memory for Stack. &pCode). long *pFix. if (sCode%4096) nPages++. so let's LOAD IT! */ if (!erc) erc = SetFileLFA(fh. long fh) { long erc. &dret). (CleanPD left the first PT for us). *********************************************************/ static long LoadJob(char *pJCB. erc = AllocPage(nPages. &pStack). Code and Data */ /* This is done in the context of the USER PD */ nPages = sStack/4096. &pData). pCode. oCDFIX). nPages. nPages = sCode/4096. } /********************************************************* This loads a job into an existing PD and JCB. nPages = sData/4096. /* Now that we have read in the code and data we apply fixups to these segments from the runfile */ if (!erc) { if (nCDFIX) { erc = SetFileLFA(fh. /* All Job memory is now allocated. pNewJCB = pJCB. dret. if (!erc) erc = ReadBytes (fh.*pJobNumRet = JobNum. erc = AllocPage(nPages. This is called by Chain() and may also be called by ExitJob() if an ExitJob was specified in the current JCB. if (sData%4096) nPages++. if (sStack%4096) nPages++. if (!erc) erc = SetFileLFA(fh. if (!erc) erc = AllocPage(nPages. while ((nCDFIX--) && (!erc)) { MMURTL V1. pData.

&i. /* Add of Stack */ pNewJCB->sJcbStack = sStack. } } if (nDCFIX) { erc = SetFileLFA(fh. /* Add of Code */ pNewJCB->sJcbData = sData. /* Where in DSeg */ *pFix = *pFix . } } /* Now we fill in the rest of the User's JCB */ pNewJCB->pJcbStack = pStack. /* Add of Code */ pNewJCB->sJcbCode = sCode. and TSS will be reclaimed by the monitor along with the JCB and all OS memory pages for the PD. pFix = pCode + i. return(erc). ******************************************************/ void _KillTask(void) { GetJobNum(&JobNumE). 4. &dret).offData + pData. &i.offCode + pCode. its exchange. } /****************************************************** This is started as new task in the context of a job that is being killed off. } } if (nDDFIX) { erc = SetFileLFA(fh. oDDFIX). pFix = pData + i.erc = ReadBytes (fh. MMURTL V1. 4. pFix = pCode + i. 4. &dret). &i. /* Size of Code */ pNewJCB->pJcbCode = pCode. oCCFIX).0 -357- RICHARD A BURGESS . /* Where in DSeg */ *pFix = *pFix . &dret).offData + pData. &dret). /* back to fixups */ while ((nCCFIX--) && (!erc)) { erc = ReadBytes (fh.PT and video. /* back to fixups */ while ((nDDFIX--) && (!erc)) { erc = ReadBytes (fh. pFix = pData + i. /* back to fixups */ while ((nDCFIX--) && (!erc)) { erc = ReadBytes (fh. &i. 4. This task. This is done to allow memory access and also reuse the code for ExitJob in the monitor.offCode + pCode. /* Where in CSeg */ *pFix = *pFix . } } if (nCCFIX) { erc = SetFileLFA(fh. /* Size of Code */ } CloseFile(fh). /* Size of Code */ pNewJCB->pJcbData = pData. /* Where in CSeg */ *pFix = *pFix . oDCFIX).

******************************************************/ long far _KillJob(long JobNum) { long erc. ercE = 0. /* It always returns ErcOk */ /* Deallocate ALL exchanges for this job */ MMURTL V1.0 -358- RICHARD A BURGESS . /* Make sure it's not the Monitor. This is used to kill run-away jobs. ExchE. /* Get pJCB to this Job */ /* Clean the PD of all user memory leaving OS memory to be deallocated by the caller (monitor or whoever). SetPriority(31). CleanUserPD(pPDE). if (erc) return(erc). Debugger or the current job. */ RemoveRdyJob(pTmpJCB). /* Get pJCB to the Job */ pTmpJCB->ExitError = ErcOpCancel. WaitMsg(ExchE. ISendMsg(KillExch. if ((JobNum == JobNumE) || (JobNum == 1) || (JobNum == 2)) return(ErcBadJobNum). */ GetJobNum(&JobNumE). /* Operator said DIE! */ /* Remove ALL tasks for this job that are at the ReadyQue. The task we are in does not belong to the job we are killing so we can remove them all. while(!ercE) /* clear the exchange */ ercE = CheckMsg(ExchE. BogusMsg). BogusMsg). /* He's History! */ } /****************************************************** This called from one job to kill another job or service. erc = GetpJCB(JobNum. A job may terminate itself with ExitJob(). GetTSSExch(&ExchE). ErcOpCancel). &pTmpJCB). */ pPDE = pTmpJCB->pJcbPD. This cleans up ALL resources that the job had allocated. This must never be called from a task within the job to be killed. It results in a violent death for the job specified. or terminate a job just for the fun ot it.GetpJCB(JobNumE. &pTmpJCB).

This cleans up ALL resources that the job had allocated.0 -359- RICHARD A BURGESS . /* Get pJCB to current Job */ /* Remove ALL tasks for this job that are at the ReadyQue. CodeSeg. ESP. &pCrntJCB). EIP */ erc = NewTask(JobNum. If no exit run file is specified we just kill the JCB entirely and if video and keyboard are assigned we assign them to the Monitor. pCrntJCB->ExitError = dError. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. &TmpStack[127]. /* It always returns ErcOk */ MMURTL V1. /* He's History! */ } /****************************************************** This called from Exit() in C or directly from a user job or service. /* Notify all services */ /*JobNum. GetpJCB(JobNumE. 0. &pExchJCBE). iE = 0. /* make him the owner */ SendAbort(JobNum. Priority. &_KillTask). return(erc). 0x08. /* Clear the error */ /* Now that the user can't make anymore requests. 3. ******************************************************/ void far _ExitJob(long dError) { /* NO LOCAL VARIABLES BECAUSE WE SWITCH STACKS!! */ GetJobNum(&JobNumE).ercE = 0. */ erc = AllocExch(&ExchE). We will allocate one exchange for the job that is being killed so we can use it for SendAbort and also as the exchange number we send to the KillExch in the monitor which will kill of the JCB completely (he also switches video and keyboard if needed). } ercE = 0. We send "Abort" messages to all services. iE++. if ((!ercE) && (pExchJCBE == pTmpJCB)) DeAllocExch(iE). Exch. fDebug. ExchE. pTmpJCB). ExchE). /* Get an Exch */ SetExchOwner(ExchE. This also checks for an exit run file to load if one is specified. The task we are in won't be removed because its RUNNING! */ RemoveRdyJob(pCrntJCB). This closes all files that were opened by the Job and frees up any other resources held for this job by any service.

/* Exit Run File!! */ MMURTL V1. EAX. CleanUserPD(pPDE). Clear the error */ /* We must now switch to a temporary stack so we can clean out the user PD (we are on his stack right now!). ExchE). */ /* Find out what our TSS exchange is so we don't deallocate it to! */ GetTSSExch(&ExchE). and will also free up RQBs and Link Blocks. The job will not be able to initiate requests or send messages after this unless it is done with the TSSExchange because it will get a kernel error (invalid exchange).0 -360- RICHARD A BURGESS . ercE = 0. iE = 0. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. iE++. EBP. } /* Now that the user can't make anymore requests. &cbFileE). */ #asm MOV ADD MOV MOV #endasm EAX. */ SendAbort(JobNumE. OFFSET _TmpStack 508 EAX EAX /* Clean the PD of all user memory leaving OS memory for next job if there is one. Send Abort messages to all services. ercE = 0. /* Clear the error */ clear the exchange */ BogusMsg)./* Deallocate all exchanges for this job except the one belonging to current TSS! The Dealloc Exchange call will invalidate all TSSs found at exchanges belonging to this user. ercE = 0. If no exit run file. /* while(!ercE) /* ercE = CheckMsg(ExchE. /* Look for Exit Run file to load if any exists. ESP. &pExchJCBE). we deallocate the PD and JCB then return to JOB 1. */ pPDE = pCrntJCB->pJcbPD. if ((!ercE) && (iE != ExchE) && (pExchJCBE == pCrntJCB)) DeAllocExch(iE). This closes all files that were opened by the Job and frees up any other resources held for this job by any service. */ GetExitJob(aFileE.

/* We are NO MORE */ } } /****************************************************** This is called to execute a program without changing the ExitJob. Information can be passed to Job B by calling SetCmdLine (if Job B reads it). This is so you can run program B from program A and return to program A when program B is done. if (!ercE) ercE = GetRunFile(aFileE. */ #asm MOV EAX. Then he will WIPE US OUT! We use ISend (vice send) so he can't run before we get to the exchange otherwise we will be placed back on the readyQueue! */ ISendMsg(KillExch. PUSH 18h MOV EAX. BogusMsg). MOV EBP. &job_fhE). ADD EAX. PUSH EAX RETF #endasm } if (ercE) { /* something failed or we don't have an ExitRF */ _pStack _sStack EBX 4 EAX EAX _pStart . job_fhE). WaitMsg(ExchE. ercE). and also by setting the ExitError value in the parameter. This runs Job B in the "context" of Job A which means Job B inherits the JCB and PD of job A so it can use things like the command line and path that were set up by A. ExchE.if (!cbFileE) ercE = ErcNoExitJob.We are history! /* In case there is no job to run or a fatal error has happened we send a message (ISendMsg) to the monitor status task with our TSSExch and the Error. MOV EBX.0 -361- RICHARD A BURGESS . Chain will only return to you if there was an MMURTL V1. if (!ercE) ercE = LoadJob(pCrntJCB. SUB EAX. if (!ercE) { pStart = pStart+pCode-offCode. SetPriority(31). MOV ESP. cbFileE. /* Now we RETURN to new job's address after we put him on his new stack.

The task we are in won't be removed because its RUNNING! */ RemoveRdyJob(pCrntJCB). if (ercE) { CloseFile(job_fhE). long dExitError) { /* NO LOCAL VARIABLES BECAUSE WE SWITCH STACKS!! */ CopyData(pFileName. iE++. This closes all files that were opened by the Job and frees up any other resources held for this job by any services. if ((!ercE) && (iE != ExchE) && (pExchJCBE == pCrntJCB)) DeAllocExch(iE). &job_fhE). GetpJCB(JobNumE. cbFileName. cbFileName). Send Abort messages to all services. /* It always returns ErcOk */ /* Deallocate all exchanges for this job except the one belonging to current TSS! The Dealloc Exchange call will invalidate all TSSs found at exchanges belonging to this user. } /* Now that the user can't make anymore requests. /* if it had a handle at all */ return(ercE). &pExchJCBE). aFileE. and will also free up RQBs and Link Blocks. pCrntJCB->ExitError = dExitError. ercE = 0.0 -362- RICHARD A BURGESS . } CloseFile(job_fhE). &pCrntJCB).error loading the Job. */ /* Find out what our TSS exchange is so we don't deallocate it to! */ GetTSSExch(&ExchE). if Chain fails in a critial section we try to load the ExitJob and pass it the error. ercE = GetRunFile(pFileName. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. /* we will open it again after SendAbort */ GetJobNum(&JobNumE). */ MMURTL V1. /* Get pJCB to current Job */ /* Remove ALL tasks for this job that are at the ReadyQue. long cbFileName. iE = 0. ******************************************************/ long far _Chain(char *pFileName. The job will not be able to initiate requests or send messages after this unless it is done with the TSSExchange because it will get a kernel error (invalid exchange). In other words. cbFileE = cbFileName.

EAX. if (!cbFileE) ercE = ErcNoExitJob. /* Clear the error */ clear the exchange of abort responses*/ BogusMsg). ercE = 0.SendAbort(JobNumE. ercE = 0. */ #asm MOV ADD MOV MOV #endasm EAX. if (!ercE) ercE = /* Exit Run File!! */ GetRunFile(aFileE. if (ercE) { /* We have errored in a critical part of Chain (LoadJob). Don't bother checking error cause it was valid if we got here! */ GetRunFile(aFileE. if (!ercE) ercE = LoadJob(pCrntJCB. /* while(!ercE) /* ercE = CheckMsg(ExchE. cbFileE. job_fhE). Clear the error */ /* We must now switch to a temporary stack so we can clean out the user PD (we are on his stack right now!). The original user's job is destroyed. job_fhE). ExchE). we kill this guy and return to the monitor if he had the screen. and we can't run the chain file (bummer). cbFileE. */ MMURTL V1. The only thing left to do is Look for Exit Run file to load if any exists. */ pPDE = pCrntJCB->pJcbPD. /* it was valid before!*/ ercE = LoadJob(pCrntJCB. } if (!ercE) { /* No error */ pStart = pStart+pCode-offCode. /* Now we RETURN to new job's address after we put him on his new stack. &job_fhE). OFFSET _TmpStack 508 EAX EAX /* Clean the PD of all user memory leaving OS memory for next job if there is one. &job_fhE). /* Try to load the Chain file.0 -363- RICHARD A BURGESS . CleanUserPD(pPDE). */ GetExitJob(aFileE. ESP. If no exit run file. EBP. &cbFileE).

0 -364- RICHARD A BURGESS . BogusMsg). PUSH 18h MOV EAX. MOV EBP. /* We are NO MORE */ } } /*********************** End of Module *****************/ /* ISend clears ints! */ MMURTL V1. MOV EBX. WaitMsg(ExchE. Then he will WIPE US OUT! We use ISend (vice send) so he can't run before we get to the exchange otherwise we will be placed back on the readyQueue! */ ISendMsg(KillExch. MOV ESP. ADD EAX. ExchE.We are history! /* In case there is no job to run or a fatal error has happened we send a message (ISendMsg) to the monitor status task with our TSSExch and the Error. ercE). PUSH EAX RETF #endasm } if (ercE) { /* Something failed loading the job (Chain or Exit) */ _pStack _sStack EBX 4 EAX EAX _pStart . SUB EAX. #asm STI #endasm SetPriority(31).#asm MOV EAX.

. I have left them as individual procedures. then copy the CR3 . This allows the debugger to operate in the memory context of the task that was interrupted. This code effectively replaces the interrupted task with the debugger . task (without going through the kernel). While writing the operating system. then it jumped here to enter the debugger. one of the exceptions has activated and has done a little work based . Listing 23. register from the current TSS into the debugger's TSS. See listing 23. on which exception is was.EnterDebug .===================== Debugger Entry (Int 3) =========================== PUBLIC IntDebug: POP POP POP JMP dbgOldEIP dbgOldCS dbgOldEFlgs EnterDebug . Next. First we copy the Page Dir .0 -365- RICHARD A BURGESS .1. but it is not entered directly from the breakpoint address as an interrupt task. entry from the current job into the debuggers job. This piece of code sets up to enter the debugger. .Chapter 23. I did this because on occasion I had to modify one individually.ASM which contains handlers for all system exceptions. Get EIP of offender for debugger . Get CS .1. are set up to execute a interrupt procedure that does some rather tricky manipulation of the debugger task state segment (TSS). so this is OK to do even if the offending task referenced a bogus address. If we get here. . The two major things that are changed are the pointer to the JCB and the CR3 register in the debugger's TSS. This makes the . Get Flags . Exception Handlers for Entering Debugger .Enter debugger . . . we save the current pRunTSS and place the debugger's TSS in MMURTL V1. debugger operate in the current tasks memory space. They are made to match those of the interrupted task.===================== Debugger Single Step (Int 1) ====================== PUBLIC IntDbgSS: POP POP POP JMP dbgOldEIP dbgOldCS dbgOldEFlgs EnterDebug . which are all debug exceptions. The debugger is a separate task. Breakpoints. It has grown substantially. . . Debugger Interrupt Procedure This code excerpt is taken from the file EXCEPT. Get EIP of offender for debugger .Enter debugger . code and data are in OS protected pages (which are shared with all tasks). small pieces of the debugger have been added as they were needed. All of the debugger's . Debugger Code Introduction The debugger initially began as a crude memory dumper built into the operating system. and ended up duplicating them several times after combining them. but is still very immature. You will note that even though the two exception procedures are the same.

INCLUDE MOSEDF.Set Dbgr as running task MOV BX.pDebuggerJCB -> EDX . Debugger Data and Code . and restore PUSH EDX .CR3 -> DebuggerTSS . pRunTSS EBX. then jump to the debugger's selector.INC TSS. with the exception of the disassembler. EBX EAX.DATA .current CR3 -> EBX .2. OFFSET DbgTSS [EDX+TSS_CR3]. [EAX+TSS_CR3] EDX.Install Debugger's as current .." The debugger also has its own read-keyboard call in the keyboard service to allow debugging of portions of the keyboard code. Listing 23.ALIGN DWORD .INC RQB.make his registers right! .2 presents the debugger code in its entirety.pRunTSS -> EAX . task switch MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV EAX.Save the current pRunTSS . caller's registers them before the into the debugger . EnterDebug: PUSH EAX . [EAX+JcbPD] [EDX+JcbPD]. pRunTSS DbgpTSSSave. [EAX+TSS_pJCB] EDX.Page Dir -> Debugger JCB .Go back to the caller Debugger Source Listing Listing 23.CrntJob Page Dir -> EBX . [EAX+Tid] MOV TSS_Sel.External near Variables MMURTL V1. OFFSET DbgTSS pRunTSS.INCLUDE . [EDX+TSS_pJCB] EBX.Switch tasks to debugger .INCLUDE . You'll see much of it is display "grunt work.we MUST save PUSH EBX .INC . we come here PUSH dbgOldEFlgs PUSH dbgOldCS PUSH dbgOldEIP IRETD . . . EBX EAX.Set up debugger selector . EAX This switches tasks. EAX EAX.INC JOB.0 -366- RICHARD A BURGESS . .pCrntJCB -> EAX .INCLUDE . BX POP EDX POP EBX POP EAX JMP FWORD PTR [TSS] .When the debugger exits. pRunTSS.pDebuggerTSS -> EDX .Put the stack back the way it was .

ESC to quit.40 bytes 42 0123456789012345678901234567890123456789012345678901234 DB 'Exch Owner dMsgLo dMsgHi Task' 0123456789012345678901234567890123456789012345678901234 DB 'Task# Job pJCB pTSS Priority ' . dbgExchMsg .' '.0B3h. .CR LF for Dump etc.To save interrupted video user DB '00000000' .0B3h.'CrntAddr' 'DumpB'.Boolean.0B3h.'Tasks'.For Debugger entry conditions dbgFltMsg DB 'FATAL Processor Exception/Fault: ' MMURTL V1.For line and column display coordination DD 0 DD 0 DD 0 DD 0 DD 0 DD 0 DB 0 DB 0 DB 0Dh.General use buffers DB '0000000000' .debugger variables and buffers PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC DbgpTSSSave dbgFAULT dbgFltErc dbgOldEIP dbgOldCS dbgOldEflgs DD DD DD DD DD DD 0 0FFh 0 0 0 0 . 0Ah DB 0 .Has Dbg Video been initialized? .EXTRN EXTRN EXTRN EXTRN rgTmrBlks pJCBs RdyQ rgPAM DD DD DD DB .0B3h. .Active bytes in buf2 .'DumpD'.Crnt Breakpoint Linear addresses . DB DB DB dbgSpace DB dbgCont DB dbgClear DB dbgAsterisk DB .'AddInfo ' ' ' 'ENTER to continue.0B3h.ALIGN DWORD cbBufLen2 DD dbgKeyCode DD NextEIP DD dbgGPdd1 DD dbgGPdd2 DD dbgGPdd3 DD dbgGPdd4 DD dbgGPdd5 DD dbgGPdd6 DD dbgNextAdd dbgCrntAdd dbgDumpAdd dbgX dbgY dbgBPAdd dbgfDumpD fDbgInit dbgCRLF dbgChar dbgMenu 0 0 0 0 0 0 0 0 0 . 0 = Exch .Address of instructions we are displaying .'CS:EIP ' 'Exchs'.'SetBP'. .0B3h.' ' ' .Address we are setting as next .0B3h. .0B3h.' '.Address we are dumping .TO save the TSS we interrupted .General purpose DDs (used all over) .'ClrBP'..are we dumping DWORDS? ..For ReadDbgKbd . dbgTaskMsg 'SStep'.For Disassem display (next ins to be executed) .0FFh is NO FAULT DbgVidSave dbgBuf dbgBuf2 DD 0 .0B3h.flag 1 = Msg.0 -367- RICHARD A BURGESS .

'EBX: 0B3h.==================== End Data.'CR3: 0B3h.' FS: 0B3h.sdbgFltMsg DD $-dbgFltMsg .' SS: 0B3h.' ES: 0B3h.'EFL: 0B3h.'CR2: 0B3h.Register display text dbgTxt00 dbgTxt01 dbgTxt02 dbgTxt03 dbgTxt04 dbgTxt05 dbgTxt06 dbgTxt07 dbgTxt08 dbgTxt09 dbgTxt10 dbgTxt11 dbgTxt12 dbgTxt13 dbgTxt14 dbgTxt15 dbgTxt16 dbgTxt17 dbgTxt18 dbgTxt19 dbgTxt20 dbgTxtAddr DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB 0B3h.'ECX: 0B3h. Begin Code ========================== .0 -368- RICHARD A BURGESS .'TSS: 0B3h.'Erc: ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' DB 'Linear address: ' . OFFSET DbgVidSave .' GS: 0B3h.'EDI: 0B3h.For Important Address Info Display dbgM0 dbgM1 dbgM2 dbgM3 dbgM4 dbgM5 dbgM6 dbgM7 dbgM8 dbgM9 dbgPA dbgMB DB DB DB DB DB DB DB DB DB DB DB DB 'IDT: 'GDT: 'RQBs: 'TSS1: 'TSS3: 'LBs: 'RdyQ: 'JCBs: 'SVCs: 'Exch: 'PAM: 'aTmr: ' ' ' ' ' ' ' ' ' ' ' ' .'EDX: 0B3h.'CR0: 0B3h.'ESI: 0B3h.' CS: 0B3h.'ESP: 0B3h.' DS: 0B3h.CODE EXTRN EXTRN EXTRN EXTRN DDtoHex NEAR HexToDD NEAR _disassemble NEAR ReadDbgKbd NEAR PUBLIC DbgTask: MOV EAX.'EAX: 0B3h.'EIP: 0B3h.'EBP: 0B3h.Save number of vid we interrupted PUSH EAX CALL FWORD PTR _GetVidOwner STI MMURTL V1.

.. . . .Display fault message and . Code Seg. so we shouldn't have to reset it..Was the dbgr entered on a FAULT? JE dbg000 .0FFh .0 .Store correct CS MOV [EAX+TSS_CS].the stack (entering the debugger) into the caller's TSS.0FFFFFEFFh MOV [EAX+TSS_EFlags].dbgFAULT PUSH EAX PUSH OFFSET dbgBuf MMURTL V1. MOV EBX. .0 .dbgOldEflgs .EBX .BX MOV EBX.dbgFltMsg PUSH EAX PUSH sdbgFltMsg PUSH 40h CALL FWORD PTR _TTYOut MOV EAX. CMP DWORD PTR dbgFAULT. is always entered as a procedure. I guess I'm not reading it right or ROD SERLING LIVES! . This only applies if .Store correct flags MOV [EAX+TSS_EFlags].We set the FAULT variable based on which interrupt . DbgpTSSSave .Dbgr is Job 2 . 0 JNE DbgInitDone CALL FWORD PTR _ClrScr MOV fDbgInit.EBX .way they were when the exception fired off becuase of the .NO . the values of .See page 3-4 System Software Writer's Guide XOR EAX.We make them the same by putting the values we got from .Reset TF in case single steping AND EBX. the handler is a procedure (NOT a task).dbgOldCS . The debugger .dbgOldEIP .NOTE: The "book" says the TF flag is reset by the processor .When a fault or debug exception occurs.REF . MOV EBX.procedure was entered. and number at 0 .the Instruction Pointer.NOTE: Must eventually add SS/ESP for a change in CPL on faults!!! .interrupt procedure they enterred to get to the debugger. But we do. when the handler is entered. and flags are not the .Store correct EIP MOV [EAX+TSS_EIP]. (we chanage the tasks) .EBX . 1 DbgInitDone: MOV EAX. EAX PUSH EAX PUSH EAX CALL FWORD PTR _SetXY LEA EAX.EAX still has DbgpTSSSave MOV EBX.[EAX+TSS_EFlags] .Color Black on RED -369- RICHARD A BURGESS .PUSH 2 CALL FWORD PTR _SetVidOwner CMP fDbgInit.

Get USER pUserTSS MOV EAX.Display BackLink's Register values CALL dbgDispMenu .Color White on black .Display menu PUSH dbgX .1 . 0FFh LEA EAX.Display Instruction at CS:EIP MOV EBX.DbgpTSSSave MOV ECX.00000100h MOV [EBX+TSS_EFlags].ESCAPE (Exit) .CALL DDtoHex LEA EAX.[EBX+TSS_EFlags] OR ECX.ECX JMP dbgExit dbg02: CMP EAX.dbgBuf PUSH EAX PUSH 8 PUSH 70h CALL FWORD PTR _TTYOut MOV DWORD PTR dbgFAULT. dbgKeyCode AND EAX.Set TF in flags for single step . 10h JNE dbg03 MMURTL V1.Lop off key status bytes CMP EAX. 0Fh JNE dbg02 MOV EBX. OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd .Set BP (Current Address .=========================================================== . MOV EAX.DbgpTSSSave . EAX CALL dbgCheckScroll . EAX CALL dbgShowBP PUSH EAX CALL _disassemble . EAX INC EAX MOV dbgY.Get USER pUserTSS .dbgCRLF PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut XOR EAX. 0FFh .Reset X & Y to 0.=================================================================== dbg000: CALL DbgRegVid .Back to where we were PUSH dbgY CALL FWORD PTR _SetXY .Single Step (F1) .This puts the instruction on the line MOV NextEIP. 1Bh JE dbgExit CMP EAX.Fall through to keyboard loop . [EBX+TSS_EIP] MOV dbgCrntAdd. EAX MOV dbgX.Now we read the keyboard dbg00: MOV EAX. EAX . .reset fault indicator .0 .F2) -370- RICHARD A BURGESS .

Sets NextEIP . EAX DR6. [EBX+TSS_EIP] MOV dbgCrntAdd.Clear BP (Current Address .Move into BreakPoint Reg 0 . EAX dbgBPAdd. EAX dbg00 .This puts the instruction on the line . . 11h dbg04 EAX.See if we need to scroll up . 14h JNE dbg07 CALL dbgDispTasks JMP dbg000 dbg07: CMP EAX. EAX dbg00 EAX. EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP. . .Memory Dump Bytes (F9) -371- RICHARD A BURGESS .Task array display (F6) .Return to CS:EIP (F4) . 12h JNE dbg05 MOV EBX. EBX DR0.BP0 Set global .This puts the instruction on the line . EBX EAX.Full display .Get USER pUserTSS CMP EAX.Save it so we know where BP is . EAX EAX. 00 MMURTL V1. 16h JNE dbg09 CALL dbgSetAddr PUSH dbgX PUSH dbgY CALL FWORD PTR _SetXY MOV EAX. EAX DR7. .Address displayed . 17h JNE dbg10 MOV BL.0 . EAX CALL dbgCheckScroll JMP dbg00 dbg05: CMP EAX. 15h JNE dbg08 JMP dbg00 dbg08: CMP AL.Back to where we were .Display Exchanges (F5) . dbgCrntAdd dbgBPAdd.F3) .MOV MOV MOV XOR MOV MOV MOV JMP dbg03: CMP JNE XOR MOV MOV JMP dbg04: EBX. EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP.Set Disassembly Address (F8) . . 13h JNE dbg06 CALL dbgDispExchs JMP dbg000 dbg06: CMP EAX. 00000002h DR7.Clear BP saved address .See if we need to scroll up . EAX CALL dbgCheckScroll JMP dbg00 dbg09: CMP AL. . NextEIP MOV dbgCrntAdd.DbgpTSSSave MOV EAX.Not used yet .

we are about to display.============================================================== dbgShowBP: . . EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP.EAX must be preserved MOV EBX. 02h JNE dbg14 MOV EAX. BL CALL dbgDump JMP dbg000 dbg12: CMP AL.Memory Dump DWORDS (F10) . BL CALL dbgDump JMP dbg000 dbg10: CMP AL.Set up caller's TSS selector JMP FWORD PTR [TSS] .Next time we enter the debugger task it will be here! JMP DbgTask . [EAX+Tid] TSS_Sel. 01Ah JNE dbg13 CALL DbgInfo JMP dbg00 dbg13: CMP AL.See if we need to scroll up dbg14: JMP dbg00 DbgExit: LEA EAX.Display next Instruction (Down Arrow) .dbgX PUSH EAX LEA EAX.MOV dbgfDumpD. .Change screens back MOV MOV MOV MOV EAX. NextEIP MOV dbgCrntAdd. EAX CALL dbgCheckScroll JMP dbg00 .Info Address dump (F12) .we put up an asterisk to indicate it's the breakpoint . 18h JNE dbg12 MOV BL. 0FFh MOV dbgfDumpD. dbgCrntAdd MMURTL V1.Address displayed -372- RICHARD A BURGESS .This puts the instruction on the line . EAX BX.Return saved pRunTSS .dbgY PUSH EAX CALL FWORD PTR _GetXY .0 . If they are the same. BX .Back to begining .GO back for another key PUSH DbgVidSave CALL FWORD PTR _SetVidOwner .Query XY . DbgpTSSSave pRunTSS.This compares the current breakpoint the address . . .

BP Address .05 MOV ESI.EDX Display .[EBX+TSS_EDI] CALL DispRegs MOV ECX.07 MOV ESI.EBX Display .ECX Display .[EBX+TSSNum] CALL DispRegs MOV ECX.MOV ECX.04 MOV ESI.OFFSET DbgTxt02 MOV EAX.[EBX+TSS_EBP] CALL DispRegs .Number of this TSS .EBX MUST be DbgpTSSSave .OFFSET DbgTxt06 MOV EAX.00 MOV ESI.02 MOV ESI.information on the right side of the debugger video display DbgRegVid: MOV EBX.OFFSET DbgTxt01 MOV EAX.TSS Display .[EBX+TSS_EDX] CALL DispRegs MOV ECX.This sets up and calls to display all of the regsiter .Save EAX across call .OFFSET DbgTxt04 MOV EAX. ECX JZ dbgShowBP1 RETN dbgShowBP1: PUSH EAX PUSH OFFSET dbgAsterisk PUSH 01h PUSH 47h CALL FWORD PTR _TTYOut POP EAX RETN .03 MOV ESI.EAX MOV AX.01 MOV ESI.ESI Display .[EBX+TSS_EAX] CALL DispRegs MOV ECX.============================================================== .[EBX+TSS_ECX] CALL DispRegs MOV ECX.OFFSET DbgTxt05 MOV EAX.[EBX+TSS_ESI] CALL DispRegs MOV ECX.EBP Display MMURTL V1.3 params to TTYOut . dbgBPAdd CMP EBX.EDI Display .Reverse Vid WHITE on RED .0 -373- RICHARD A BURGESS .OFFSET DbgTxt07 MOV EAX.06 MOV ESI.DbgpTSSSave MOV ECX.OFFSET DbgTxt00 XOR EAX.[EBX+TSS_EBX] CALL DispRegs MOV ECX.EAX Display .OFFSET DbgTxt03 MOV EAX.

SS Display .12 MOV ESI.16 MOV ESI.20 MOV ESI.Fault Error Code Display -374- RICHARD A BURGESS .EIP Display .EAX MOV AX.15 MOV ESI.GS Display .OFFSET DbgTxt08 XOR EAX.CS Display .EFlags Display .[EBX+TSS_CS] CALL DispRegs MOV ECX.CR2 Display .OFFSET DbgTxt19 MOV EAX.EAX MOV AX.[EBX+TSS_EIP] CALL DispRegs MOV ECX.CR3 CALL DispRegs MOV ECX.EAX MOV AX.OFFSET DbgTxt12 XOR EAX.ESP Display .EAX MOV AX.OFFSET DbgTxt20 MOV EAX.EAX MOV AX.dbgFltErc MMURTL V1.11 MOV ESI.19 MOV ESI.13 MOV ESI.[EBX+TSS_EFlags] CALL DispRegs MOV ECX.CR3 Display .CR0 Display .18 MOV ESI.OFFSET DbgTxt10 XOR EAX.[EBX+TSS_GS] CALL DispRegs MOV ECX.OFFSET DbgTxt18 MOV EAX.DS Display .0 .CR0 CALL DispRegs MOV ECX.[EBX+TSS_DS] CALL DispRegs MOV ECX.ES Display .[EBX+TSS_SS] CALL DispRegs MOV ECX.OFFSET DbgTxt11 MOV EAX.OFFSET DbgTxt16 MOV EAX.CR2 CALL DispRegs MOV ECX.[EBX+TSS_FS] CALL DispRegs MOV ECX.[EBX+TSS_ES] CALL DispRegs MOV ECX.OFFSET DbgTxt13 XOR EAX.OFFSET DbgTxt17 MOV EAX.09 MOV ESI.[EBX+TSS_ESP] CALL DispRegs MOV ECX.OFFSET DbgTxt14 XOR EAX.08 MOV ESI.14 MOV ESI.17 MOV ESI.OFFSET DbgTxt15 XOR EAX.FS Display .10 MOV ESI.EAX MOV AX.MOV ECX.OFFSET DbgTxt09 MOV EAX.

Call with: EAX loaded with value to display (from TSS reg) .============================================== . This is for Debugger Register display .CALL DispRegs RETN .Get number back for display POP EAX PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex PUSH OFFSET dbgBuf PUSH 8 PUSH 07h CALL FWORD PTR _TTYOut POPAD RETN . ECX loaded with number of text line to display on .dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut MMURTL V1.========================================================================== .Display Debugger FKey Menu PUSH 24 CALL FWORD PTR _SetXY LEA EAX.Save number to display PUSH 66 PUSH ECX CALL FWORD PTR _SetXY PUSH PUSH PUSH CALL ESI 05h 07h FWORD PTR _TTYOut . This displays the debugger function key menu dbgDispMenu: PUSH 0 . ESI loaded with EA of text line to display . .0 -375- RICHARD A BURGESS .dbgMenu PUSH EAX PUSH 78 PUSH 70h CALL FWORD PTR _TTYOut PUSH 25 PUSH 24 CALL FWORD PTR _SetXY LEA EAX. We save all registers cause the vid calls don't DispRegs: PUSHAD PUSH EAX .

ptr to destination DD . cbBufLen2 .ptr to size returned LEA EAX.PUSH 51 PUSH 24 CALL FWORD PTR _SetXY LEA EAX.Allows the user to pick the currently displayed address dbgSetAddr: PUSH 0 PUSH 23 CALL FWORD PTR _SetXY LEA EAX. EAX dbgSetAddrDone: CALL dbgClearQuery RETN . dbgNextAdd MOV NextEIP.pEdString PUSH cbBufLen2 . dbgNextAdd PUSH EAX PUSH cbBufLen2 CALL HexToDD CMP EAX. 0 JNE DumpDone LEA EAX. dbgChar CMP AL.dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut RETN . dbgBuf2 PUSH EAX LEA EAX. .did they exit with CR? .=========================== . 0 JNE dbgSetAddrDone .0 -376- RICHARD A BURGESS . dbgTxtAddr PUSH EAX PUSH 16 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX. PUSH EAX .Max size LEA EAX.Goto Query Line .length of string .dbgDumpAdd has address to dump! MOV EAX.Black On White CALL FWORD PTR _EditLine .Ignore error if any MOV AL.ptr to char returned PUSH 70h . dbgChar ..=========================== .Crnt size PUSH 8 .. PUSH EAX .ptr to string . .Convert String to DD .Queries user for address then dumps data to screen dbgDump: MMURTL V1. 0Dh JNE dbgSetAddrDone LEA EAX.Go home. DbgBuf2 . PUSH EAX .

line counter begins at 24 dbgDump01: MOV DWORD PTR dbgGPdd3. 24 .ptr to size returned LEA EAX.Black On White CALL FWORD PTR _EditLine .PUSH 0 PUSH 23 CALL FWORD PTR _SetXY LEA EAX.length of string .. cbBufLen2 .Ignore error if any MOV AL. .Go home. PUSH EAX . 6 LEA EAX.convert address to text LEA EAX. 0Dh JE dbgDoDump CALL dbgClearQuery RETN dbgDoDump: LEA EAX.byte offset begins at 6 -377- RICHARD A BURGESS .dbgDumpAdd has address to dump! .0 . dbgTxtAddr PUSH EAX PUSH 16 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX.Crnt size PUSH 8 .ptr to char returned PUSH 70h .ptr to destination DD .did they exit with CR? .pEdString PUSH cbBufLen2 . PUSH EAX .. 0 JNE DumpDone CALL FWORD PTR _ClrScr dbgDump00: MOV DWORD PTR dbgGPdd1.Convert String to DD . dbgSpace MMURTL V1. 0 JNE DumpDone .Goto Query Line . dbgChar CMP AL. dbgChar . DbgBuf2 . 0 JNE DumpDone dbgDump02: MOV DWORD PTR dbgGPdd2.Max size LEA EAX. PUSH EAX . dbgDumpAdd PUSH EAX PUSH cbBufLen2 CALL HexToDD CMP EAX. dbgBuf PUSH EAX PUSH 8 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX.ptr to string . 4 . dbgBuf2 PUSH EAX LEA EAX.number of quads per line PUSH dbgDumpAdd . dbgBuf PUSH EAX CALL DDtoHex LEA EAX. LEA EAX.

dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX.0 . dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.Get what it's pointing to PUSH EAX .NO . dbgBuf .make it a DD Text LEA EAX. dbgDumpAdd .Display First byte .ignore error . 0 JNE DumpDone MOV EBX. dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h MMURTL V1.PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX. dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX. dbgBuf ADD EAX.get dump address MOV EAX.ignore error .Yes display Quad PUSH EAX PUSH 8 PUSH 07 CALL FWORD PTR _TTYOut JMP DumpDin dumpB: LEA EAX.go to display bytes LEA EAX. dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.display 3rd byte -378- RICHARD A BURGESS . [EBX] . display 1 space .Display 1 spaces . dbgBuf PUSH EAX CALL DDtoHex MOV AL. dbgBuf ADD EAX. 0 JE DumpB . dbgfDumpD .Dumping DWORDS CMP AL.display 2st byte .point to second 2 bytes . dbgBuf ADD EAX.

Yes .dbgY PUSH EAX CALL FWORD PTR _GetXY . LEA EAX.done with 4 quads?? . dbgBuf ADD EAX.0 -379- RICHARD A BURGESS .Display 2 spaces LEA EAX.a space .go back for next 4 bytes . dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX.ignore error .dbgX PUSH EAX LEA EAX. dbgCRLF . dbgSpace PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.display 4th byte .Do CR/LF PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut . DEC dbgGPdd1 . dbgCont .size of "cont text" PUSH 07h CALL FWORD PTR _TTYOut dbgDump03: MMURTL V1."Continue" Text PUSH EAX PUSH 31 . dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut DumpDin: INC INC INC INC DEC JNZ dbgDumpAdd dbgDumpAdd dbgDumpAdd dbgDumpAdd dbgGPdd3 dbgDump02 .23lines yet?? JNZ dbgDump01 .NO . 16 PUSH EAX PUSH 16 PUSH 07h CALL FWORD PTR _PutVidChars . dbgDumpAdd SUB EAX.ignore error .Put 16 TEXT chars on right PUSH dbgY MOV EAX.CALL FWORD PTR _TTYOut LEA EAX.Query XY PUSH dbgX .ignore error LEA EAX.NO .

1Bh . 1 .Col dbgGPdd1 . dbgGPdd2 EDX.messages or tasks that may be wiating at them dbgDispExchs: MOV DWORD PTR dbgGPdd2.prgExch .Exch# we are on . DumpDone: CALL FWORD PTR _ClrScr MOV DWORD PTR dbgX. 0 dbgDE00: CALL FWORD PTR _ClrScr PUSH 0 . OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd MOV EAX. Add offset of rgExch => EAX MMURTL V1.Convert Exch number for display . Compute offset of Exch in rgExch . 0 RETN . . 0 MOV DWORD PTR dbgY.ExchNum 8 07h FWORD PTR _PutVidChars .line we are one .Escape (Quit??) JE DumpDone LEA EAX.MOV EAX.First we do the exchange on the current line dbgDE01: PUSH dbgGPdd2 PUSH OFFSET dbgBuf CALL DDtoHex PUSH PUSH PUSH PUSH PUSH CALL .Line we are one OFFSET dbgBuf .Do CR/LF 0 . MOV DWORD PTR dbgGPdd1.=========================== .0 -380- RICHARD A BURGESS .Line for labels PUSH OFFSET dbgExchMsg . dbgKeyCode AND EAX. .Lop off key status bytes CMP EAX.Length of message PUSH 07h CALL FWORD PTR _PutVidChars . 0 JE dbgDump03 CMP EAX. sEXCH EDX EDX. 0FFh .Then we do the Exch Owner (Job Number) next to it MOV MOV MUL MOV EAX.Col PUSH 0 . dbgCRLF PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut JMP dbgDump00 . Exch number . PUSH 54 .Displays exchanges that are allocated along with .

Line OFFSET dbgBuf .Col dbgGPdd1 . EBX JZ dbgDE03 MOV EAX.Set pNextMsg to 0 MOV dbgGPdd5.0 . .EDX MOV dbgGPdd3.Yes. MOV EAX. [EAX+Owner] XOR EAX.pExch -> EAX . [EBX+DataHi] MOV EDX.Col dbgGPdd1 . Go check for tasks .Is is 1 (a message)? .For next msg in chain (if it exists) . [EAX+EHead] OR EBX. 0 . EAX OR EBX.pMsg -> EBX 20 . EAX MOV EBX. .Could have left it on stack. EAX MOV EAX.pMsg -> EBX . [EAX+EHead] .Flag to indicate we are doing messages .ADD EAX. 8 07h FWORD PTR _PutVidChars .Is is NIL (no msg or task)? . [EAX+fEMsg] OR EBX.. .See if there is a first message MOV EAX.Convert dMsg2 -381- RICHARD A BURGESS .Convert dMsg1 .Get dMsg1 . .Display Messages dbgDE04: MOV DWORD PTR dbgGPdd6. [EBX+NextLB] MOV dbgGPdd5. dbgGPdd3 MOV EBX.Save dMsg2 .Save for loop . Go to next Exch ."simplicity of maintenance is as important as simplicity of design" PUSH EDX PUSH OFFSET dbgBuf MMURTL V1.MsgFlag -> EBX .Get dMsg2 .Line OFFSET dbgBuf . . EBX JZ dbgDE05 MOV EBX. [EBX+DataLo] PUSH EDX PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex PUSH PUSH PUSH PUSH PUSH CALL 0 .Get dMsg2 back POP EDX .. . EAX now pts to Exch pExch into save variable pJCB of Owner into EBX Clear for use as JobNum pNIL? (No owner if so) . . but would be confusing later. . [EBX+JobNum] dbgDE03: PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex PUSH PUSH PUSH PUSH PUSH CALL .No.Convert Job Number 10 . EBX JZ dbgDE13 MOV EBX. 8 07h FWORD PTR _PutVidChars .

[EAX+EHead] OR EBX. 23 .Line . dbgKeyCode AND EAX.See if there are tasks waiting dbgDE05: MOV DWORD PTR dbgGPdd6.pTSS -> EBX .Next line please .Line OFFSET dbgCont .23 lines yet? JB dbgDE09 .Next line CMP DWORD PTR dbgGPdd1.Convert Task Number 1 .Get Number of Task at exch . 8 07h FWORD PTR _PutVidChars . [EBX+NextTSS] dbgGPdd5. MOV EAX. MOV DWORD PTR dbgGPdd5. . MOV EAX.No .Col dbgGPdd1 . dbgGPdd3 MOV EBX.Is is 0 (no TSS)? . 0FFh CMP EAX.Escape (Quit??) CMP DWORD PTR dbgGPdd2. EAX EAX. 31 . .Clear pNextTask . INC dbgGPdd1 .Col 24 .0 . dbgDE08: PUSH PUSH PUSH PUSH PUSH CALL 0 . EBX JZ dbgDE07 dbgDE06: MOV MOV XOR MOV EAX.pExch -> EAX . nDynEXCH . [EBX+TSSNum] . EAX AX.CALL DDtoHex PUSH PUSH PUSH PUSH PUSH CALL JMP 30 dbgGPdd1 OFFSET dbgBuf 8 07h FWORD PTR _PutVidChars dbgDE07 . -382- RICHARD A BURGESS .Line OFFSET dbgBuf . All Exchs displayed CALL FWORD PTR _ClrScr MMURTL V1.Lop off key status bytes .length of Cont string 07h FWORD PTR _PutVidChars . PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex PUSH PUSH PUSH PUSH PUSH CALL dbgDE07: 50 . 1Bh JE dbgDEDone . OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd MOV EAX. Number of dynamic exchanges JAE dbgDEDone .Col .Flag to indicate we are doing tasks 0 .Save ptr to next task if it exists .

EAX.=========================== .0 -383- RICHARD A BURGESS . PUSH 54 . nDynEXCH . .We get pJCB out of TSS and get JobNum from JCB MOV CMP JNE MOV JMP dbgDT02: CMP JNE MOV JMP EAX. MMURTL V1.Task# we are on dbgDT00: CALL FWORD PTR _ClrScr PUSH 0 .pertinent address info about them dbgDispTasks: MOV DWORD PTR dbgGPdd2.Col .PUSH 0 PUSH 0 PUSH OFFSET dbgExchMsg PUSH 54 PUSH 07h CALL FWORD PTR _PutVidChars MOV DWORD PTR dbgGPdd1.Messages INC dbgGPdd2 .line.Line for labels .First we get pTSS and see if it is valid . MOV DWORD PTR dbgGPdd1. Is this TSS 1 (Static memory) . Number of dynamic exchanges JAE dbgDE08 . 1 dbgDT02 EBX. Is this TSS 2 (Static memory) .Length of message .No . We are on line 1 again .Set up to loop for next msg/task . we increment TSS number and go back . we get all the data BEFORE we display it . Go back for prompt (to pause) JMP dbgDE01 .Line for labels PUSH OFFSET dbgTaskMsg . dbgGPdd6 OR EAX.Displays Tasks that are active along with . 1 dbgDE09: MOV EBX. Back to display new exch num dbgDEDone: CALL FWORD PTR _ClrScr MOV DWORD PTR dbgX. dbgGPdd5 XOR EBX. 1 . dbgGPdd2 EAX. 1 . OFFSET DbgTSS SHORT dbgDT04 . EAX JNZ dbgDE06 JMP dbgDE04 dbgDE13: .Col PUSH 0 .Another pointer in the link? . Exch number CMP DWORD PTR dbgGPdd2.for the next one dbgDT01: . 0 MOV DWORD PTR dbgY. .If so.Tasks .NonZero if we are doing tasks . OFFSET MonTSS SHORT dbgDT04 .line we are one . 2 dbgDT03 EBX. Task number . .Length of message PUSH 07h CALL FWORD PTR _PutVidChars .If not. 0 RETN . EBX JZ dbgDE13 MOV EAX.

EAX now pJCB MOV dbgGPdd4.of TSSs .Save pJCB for display . .Convert and display pJCB OFFSET dbgBuf .Line we are one OFFSET dbgBuf . 512 ECX EBX. go for next Task number INC EAX CMP EAX. 8 . 8 .EBX has pTSS of interest MOV dbgGPdd5.TaskNum 8 07h FWORD PTR _PutVidChars .NON zero means it's valid JNZ dbgDT05 MOV EAX. ECX .Line we are one OFFSET dbgBuf . dbgGPdd2 .0 -384- RICHARD A BURGESS . [EBX+Priority] . ECX MOV EAX. pDynTSSs EAX EAX EAX ECX.Size 07h FWORD PTR _PutVidChars . EAX .Job number is first DD in JCB EBX.EBX points to TSS! dbgGPdd2 .Save pTSS for display XOR ECX. EBX .Col dbgGPdd1 . nTSS .Col dbgGPdd1 .dbgDT03: MOV DEC DEC DEC MOV MUL ADD dbgDT04: . EAX MOV EBX.Col dbgGPdd1 .Make TSS Num offset in dynamic array . EBX PUSH PUSH CALL PUSH PUSH PUSH PUSH PUSH CALL PUSH PUSH CALL PUSH PUSH PUSH PUSH PUSH CALL PUSH PUSH CALL PUSH PUSH PUSH PUSH PUSH CALL . dbgGPdd4 .Convert TSS number for display OFFSET dbgBuf DDtoHex 0 .Line we are one OFFSET dbgBuf . [EAX] MOV dbgGPdd3. DDtoHex 20 . EAX JMP SHORT dbgDT01 .Convert and display pTSS PUSH dbgGPdd5 MMURTL V1. EAX . DDtoHex 10 .Size 07h FWORD PTR _PutVidChars .Priotity of this task MOV dbgGPdd6. MOV CL. JE dbgDT06 MOV dbgGPdd2.Convert and display Job number OFFSET dbgBuf .back for next one dbgDT05: .EAX has pJCB OR EAX. [EBX+TSS_pJCB] .Not used. dbgGPdd3 .

length of Cont string 07h FWORD PTR _PutVidChars . 0FFh . nTSS JE dbgDT06 MOV dbgGPdd2.Lop off key status bytes CMP EAX. DDtoHex 30 . ESI loaded with EA of text line to display .go for next Task number .Line OFFSET dbgCont . dbgKeyCode AND EAX. OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd MOV EAX. 23 . This is for Debugger Address Info display .Save number to display MMURTL V1.Col 24 .Next line DWORD PTR dbgGPdd1.Back for next screen dbgDTDone: CALL FWORD PTR _ClrScr MOV DWORD PTR dbgX. 1Bh . go back for next 0 .Line we are one OFFSET dbgBuf . .PUSH CALL PUSH PUSH PUSH PUSH PUSH CALL PUSH PUSH CALL PUSH PUSH PUSH PUSH PUSH CALL OFFSET dbgBuf .Escape (Quit??) JE dbgDTDone JMP dbgDT00 . EAX INC CMP JAE JMP dbgDT06: PUSH PUSH PUSH PUSH PUSH CALL dbgGPdd1 .Save it for the top MOV EAX.Line we are one OFFSET dbgBuf . dbgGPdd6 . 0 MOV DWORD PTR dbgY. DDtoHex 40 .Col dbgGPdd1 . dbgGPdd2 INC EAX CMP EAX.Col dbgGPdd1 .Size 07h FWORD PTR _PutVidChars . MOV EAX. 8 . . Call with: .23 lines yet? dbgDT06 . 31 . 0 RETN . EAX loaded with address to display (Linear Address) . 8 .=================================================================== DispAddr: PUSHAD PUSH EAX .====================================================================== .Yes. We save all registers cause the vid calls don't .0 -385- RICHARD A BURGESS .No.Size 07h FWORD PTR _PutVidChars . continue prompt dbgDT01 .Convert and display Priority OFFSET dbgBuf .

pRQBs CALL DispAddr MOV ESI.OFFSET DbgM5 .OFFSET DbgM7 .Displays important linear address for the OS DbgInfo: MOV ESI.Length of line .OFFSET DbgM9 . pJCBs CALL DispAddr MOV ESI.OFFSET DbgM3 .Do it .GDT LEA EAX.OFFSET DbgM6 .0 -386- RICHARD A BURGESS .OFFSET DbgM8 .DbgInfo .MonTSS MOV EAX.PAM (Page Allocation map) LEA EAX.pTSS3 MOV EAX.OFFSET DbgM0 .OFFSET DbgM1 . OFFSET MonTSS CALL DispAddr MOV ESI.SVCs LEA EAX.=============================================== .OFFSET DbgM2 . prgExch CALL DispAddr MOV ESI.RdyQ LEA EAX.RQBs MOV EAX. rgSVC CALL DispAddr MOV ESI. rgPAM CALL DispAddr MMURTL V1.OFFSET DbgPA .ptr to line . RdyQ CALL DispAddr MOV ESI.LBs LEA EAX. pDynTSSs CALL DispAddr MOV ESI.IDT LEA EAX.Vid Attribute . GDT CALL DispAddr MOV ESI. rgLBs CALL DispAddr MOV ESI.OFFSET DbgM4 .JCBs MOV EAX.Exchs MOV EAX. IDT CALL DispAddr MOV ESI.Get number back for display POP EAX PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex PUSH PUSH PUSH CALL PUSH PUSH PUSH CALL OFFSET dbgBuf 8 07h FWORD PTR _TTYOut OFFSET dbgCRLF 2 07h FWORD PTR _TTYOut CALL dbgCheckScroll POPAD RETN .PUSH PUSH PUSH CALL ESI 06h 07h FWORD PTR _TTYOut .

Clear the query line (Line 23.If so.dbgX PUSH EAX LEA EAX.ignore error RETN . Line 0-24) 0 66 .and the register display. 23 JB dbgNoScroll PUSH PUSH PUSH PUSH PUSH CALL .Col 0.between colums 0 and 66.Columns 0-65 24 .MOV ESI.dbgY PUSH EAX CALL FWORD PTR _GetXY CMP DWORD PTR dbgY.fUp (1) FWORD PTR _ScrollVid PUSH 0 .Yes.Are we at bottom (just above menu)?? .areas are resrved for the menu. and line 0 to 24.======================================================= .No.0 -387- RICHARD A BURGESS .Got to Column 0. dbgCheckScroll: LEA EAX. rgTmrBlks CALL DispAddr RETN . .======================================================= .OFFSET DbgMB . . query line. . go back for next key 0 .Timer Blocks LEA EAX.Lines 0-23 1 .Query XY (See what line and Col) .All of the debugger text is displayed in a window . Scroll test area (Col 0-64. Line 23 PUSH 23 PUSH OFFSET dbgClear PUSH 40 PUSH 07h CALL FWORD PTR _PutVidChars . Line 22 PUSH 22 CALL FWORD PTR _SetXY dbgNoScroll: RETN . .This checks to see if the cursor is on line 23. we scroll up the text area by one line. The other . 40 chars) dbgClearQuery: PUSH 0 .===================== module end ====================== MMURTL V1.

.

Selected Device Driver Code Introduction This chapter contains the source code for two MMURTL device drivers. IDE Device Driver Data (Defines and Externs) #define #define #define #define #define #define U32 unsigned long S32 long U16 unsigned int S16 int U8 unsigned char S8 char /* MMURTL OS PROTOTYPES */ extern far AllocExch(U32 *pExchRet). but I think you're OK. and they are also fairly compatible with the MFM hard disk controllers. You will find these little #define statements at the top of almost every C source file I have. this device driver should work with MFM drives.Chapter 24. you're on your own.g. They simply save some typing and help my brain realize what is signed and unsigned. Even some of the ISRs are done in C. The CM32 C compiler requires ANSI prototypes for functions. Time was the deciding factor.1. I feel this is the best way to try to pass information to someone in a source file. but I try not to. The two device drivers are for the IDE disk drives and the RS232 asynchronous communications device (UARTS). This seems to be the best. S8 *pDCBs. Unlike large portions of the rest of the MMURTL code. You may also notice that almost everything in MMURTL is unsigned anyway. All of the operating system calls are prototyped here. You'll see that I sometimes slip back to the long-hand notation (e. In fact. One of the things you'll note is that I don't depend on CMOS RAM locations to provide the hard disk drive geometry. They were designed that way. and maybe the only dependable way to do it. so you don't have to go digging to see what I'm doing.0 UnMaskIRQ(U32 IRQNum). U32 dfReplace). though I would rather have done them in assembler. precede sections of the code to explain the purpose of an entire section. There are no include files. If you have MFM drives. See listing 24. otherwise I would have just included the standard MMURTL header files. many device drivers are written in C. U32 nDevices. I found so many variations in the different ROMs that I gave up and actually read the disk itself to find out. but I haven't owned any for a couple of years and can't find any to test it. All of the hardware commands are well documented. extern far U32 MMURTL V1. unsigned long int). Listing 24. extern far U32 InitDevDr(U32 dDevNum.. This eliminates a lot of confusion for me. IDE Disk Device Driver The IDE (Integrated Drive Electronics) device driver was actually one of the easiest drivers to write. Comments. -389- RICHARD A BURGESS . in addition to those in the code itself.1. The code comments should suffice for the detailed explanations of otherwise confusing code.

U32 ISendMsg(U32 Exch. U8 *pDataRet). U32 dBytes). U8 InByte(U16 wPort). U32 CheckMsg(U32 Exch. U8 ReadCMOS(U16 Address). U32 dnBlocks. static U32 hd_wait (void). static U32 send_command(U8 parm). U32 dOpNum. static U32 hd_write(U32 dLBA. U32 EndOfIRQ(U32 IRQNum). U8 *pDataOut). U32 msg1. static U32 hd_recal(U8 drive). void MicroDelay(U32 us15count). static U32 hd_init(U8 drive). U8 *pDataIn. U32 msg2). MMURTL V1. U32 msg1.0 -390- RICHARD A BURGESS . U32 KillAlarm(U32 Exch). U32 WaitMsg(U32 Exch. U8 *pDataIn). static U32 hd_read(U32 dLBA.. While writing the operating system. /* The following 3 calls are required in every MMURTL device driver */ static U32 hddev_op(U32 dDevice. U32 *pMsgRet). U32 *pMsgRet). U32 SendMsg(U32 Exch.extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern extern far far far far far far far far far far far far far far far far far far far U32 MaskIRQ(U32 IRQNum). static void hd_reset(void). You must simply keep in mind that it writes to the video screen for the job that called the device driver. Any section of the operating system code included at build time can use this function for displaying troubleshooting information. static U32 hd_status(U8 LastCmd). /* The HD interrupt function */ static U32 hd_format_track(U32 dLBA. U32 nBlks).2. U32 dLBA. U32 msg2). U32 dnBlocks). static U32 hd_seek(U32 dLBA). U16 InWord(U16 wPort). The monitor has a function that works very similar to printf in C. static U32 check_busy(void). . U8 *pDataOut. U32 Alarm(U32 Exch. U16 wPort). It's called xprintf(). static U32 ReadSector(U32 Cylinder. Continuation of IDE Driver Data (protos and defines) /* Near External for troubleshooting */ extern long xprintf(char *fmt.). static void interrupt hdisk_isr(void). S8 *pIRQ). void CopyData(U8 *pSource. U32 Sleep(U32 count).2. void OutWord(U16 Word. U8 *pDestination. I needed a method to get data to the screen. U32 SetIRQVector(U32 IRQNum. static U32 setupseek(U32 dLBA. See listing 24. U16 wPort). void OutByte(U8 Byte. U32 dBytes). Listing 24. OutWords(U32 dPort. U32 count). U32 HdSect. U32 dnBlocks.. /* LOCAL PROTOTYPES */ U32 hdisk_setup(void). InWords(U32 dPort. U32 dBytes). U32 dnBlocks.

bummer */ #define ErcMissHDDInt #define ErcHDDMsgBogus #define ErcHDDIntMsg #define ErcHDDAlarmMsg /* Commands accepted by this HD driver */ #define #define #define #define #define #define #define #define #define CmdNull CmdRead CmdWrite CmdVerify CmdFmtBlk CmdFmtTrk CmdSeekTrk CmdSetMedia CmdResetHdw 0 1 2 3 4 5 6 7 8 /* Not used unless mountable */ /* Used to reset controller hardware */ MMURTL V1...0 -391- RICHARD A BURGESS . U32 dStatusMax. hddev_stat(U32 dDevice.U8 static U32 *pData). S8 * pStatRet. static U32 /* LOCAL DEFINITIONS */ #define ok 0 /* Error Codes to return */ #define ErcNoMsg 20 #define ErcNotInstalled 504 #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define ErcBadBlock ErcAddrMark ErcBadECC ErcSectNotFound ErcNoDrive0 ErcNotSupported ErcBadHDC ErcBadSeek ErcHDCTimeOut ErcOverRun ErcBadLBA ErcInvalidDrive ErcBadOp ErcBadRecal ErcSendHDC ErcNotReady ErcBadCmd ErcNeedsInit ErcTooManyBlks ErcZeroBlks ErcWriteFault 651 652 653 654 655 656 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 675 676 677 678 /* The controller can only do 128 max */ /* 0 Blocks not allowed for this cmd */ /* WriteFault bit set. U32 sdInitData). U32 *pdSatusRet). hddev_init(U32 dDevNum. S8 *pInitData.

head and Sector number.sector number (1F3h) 4 . and Sector number is LowWord in dnBlocks. This allows you to read ONE sector specified by Cylinder.sector count (1F2h) 3 .size/drive/head (1F6h) 7 . Set = More than 8 heads 4 Not used 5 Not used 6 Disable retries 7 Disable retries (same as six.sector number (1F3h) 4 . either one set) */ /* HDC Status Register Bit Masks (1F7h) */ #define #define #define #define #define #define #define BUSY READY WRITE_FAULT SEEKOK DATA_REQ CORRECTED REV_INDEX 0x80 0x40 0x20 0x10 0x08 0x04 0x02 /* /* /* /* /* /* /* busy.low cyl (1F4h) 5 . Bit Desc 0 Not used 1 Not used 2 Reset Bit .low cyl (1F4h) 5 . */ #define CmdReadSect 256 /* only device specific call in HDD */ /* HDC port definitions */ #define HD_PORT 0x1f0 /* When writing to the port+X (where X =): 0 .16 bit) 1 .0 -392- RICHARD A BURGESS .pre-comp (1F1h) 2 .status register (1F7h) */ #define HD_REG_PORT 0x3f6 /* This is a byte wide write only control port that allows reset and defines some special characteristics of the hard drives.16 bit) 1 .write data (1F0h . then Reset 3 Mucho Heads Flag.sector count (1F2h) 3 . Head is LoWord of dLBA in DeviceOp call..error register (1F1h) 2 ./* CmdReadSect is the only device specific call in the IDE/MFM hard disk device driver.high cyl (1F5h) 6 . wait 50us.Set. can't talk now! */ Drive Ready */ Bad news */ Seek Complete */ Sector buffer needs servicing */ ECC corrected data was read */ Set once each disk revolution */ MMURTL V1.size/drive/head (1F6h) 7 . Cylinder is HiWord of dLBA in DeviceOp call.read data (1F0h .high cyl (1F5h) 6 .command register (1F7h) When reading from the port+X (where X =): 0 .

or bad seek */ data address mark not found */ /* HDC internal command bytes (HDC_Cmd[7]) */ #define #define #define #define #define #define #define #define #define #define HDC_RECAL HDC_READ HDC_READ_LONG HDC_WRITE HDC_WRITE_LONG HDC_VERIFY HDC_FORMAT HDC_SEEK HDC_DIAG HDC_SET_PARAMS 0x10 0x20 0x22 0x30 0x32 0x40 0x50 0x70 0x90 0x91 /* /* /* /* /* /* /* /* /* /* 0001 0010 0010 0011 0011 0100 0101 0111 1001 1001 0000 0000 0010 0000 0010 0000 0000 0000 0000 0001 */ */ */ */ */ */ */ */ */ */ You may notice that I don't initialize any variables in structures because that would place them in a different place in the data segment.3.#define ERROR 0x01 /* data address mark not found */ /* HDC Error Register Bit Masks (1F1h) */ #define #define #define #define #define #define BAD_SECTOR BAD_ECC BAD_IDMARK BAD_CMD BAD_SEEK BAD_ADDRESS 0x80 0x40 0x10 0x04 0x02 0x01 /* /* /* /* /* /* bad block */ bad data ecc */ id not found */ aborted command */ trk 0 not found on recalibrate. hd0_cyls.n sectors to read/write */ hd_sector. 0 or 1 */ hd_head. hd1_heads. /* Calculated from LBA . /* Current Physical Drive. Listing 24. If I did one member. fDataReq. Continuation of IDE Driver Data (data structures) /* L O C A L static U8 static U8 static U8 static static static static static static U8 U8 U8 U8 U8 U8 D A T A */ /* For all 8 command bytes */ /* Flag to indicate is fDataRequest is active */ /* From HDC status register last time it was read */ hd_Cmd[8]. static U8 static U8 static U8 MMURTL V1. /* Calculated from LBA . /* Current control byte value */ hd_command.0 -393- RICHARD A BURGESS . hd0_heads.3. statbyte. cylinders. */ /* Current number of heads. /* Calculated from LBA .which head */ hd_nsectors. hd0_secpertrk.Starting sector */ /* Current type drive 0 & 1 found in CMOS or Set by caller. and sectors set by caller */ static static static static U8 U8 U8 U16 hd0_type. hd1_type. hd1_secpertrk. I would have to do them all to ensure they would be contiguous. See listing 24. /* Current Command */ hd_drive. hd_control. The operating system requires the DCB and status record field to be contiguous in memory.

U32 OS6. U32 nBlocks. static struct dcbtype hdcb[2]. LastStatByte0. /* Status Byte immediately after RESET */ U32 resvd1[2]. U32 BlocksMax. U32 last_erc. U32 OS4.static U16 hd1_cyls. LastErcByte1. S8 *pDevOp. U32 OS3. /* Number of bytes per sect. fIntOnReset. U8 fSingleUser. 32 bytes out to here. ResetStatByte. S8 *pDevSt. /* current fdisk_table for drive selected */ U8 resvd0[2].0 /* two HD device control blocks */ -394- RICHARD A BURGESS . #define sStatus 64 static struct statstruct { U32 erc. /* total physical cylinders */ U32 nHead. /* total heads on device */ U32 nSectors. U32 blocks_done. S8 type. U32 OS1. static struct dcbtype { S8 Name[12]. S8 sbName. filler1. U8 type_now. MMURTL V1. LastSeekErc1. /* padding for DWord align */ U32 nCyl. }. }. U8 fNewMedia. U8 fDevReent. S16 nBPB. /* out to 64 bytes */ static struct statstruct hdstatus.*/ U32 U32 U8 U8 U8 U8 U32 U32 U8 U8 U8 U8 LastRecalErc0. static struct statstruct HDStatTmp. S16 wJob. /* Sectors per track */ U32 nBPS. /* Interrupt was received on HDC_RESET */ filler0. LastSeekErc0. LastErcByte0. LastStatByte1. S8 *pDevInit. LastRecalErc1. U32 OS5. U32 OS2.

0 -395- RICHARD A BURGESS .Name[1] hdcb[1].nBlocks hdcb[0].nBPB hdcb[0]. /* largest device handled ./* Exch and msgs space for HD ISR */ static U32 hd_exch.pDevSt = = = = = = = = = = = = = = = = = = = = 'H'.Name[2] hdcb[1]. /* Max */ hd0_secpertrk = 17.2Gb disks*/ &hddev_op. MMURTL V1. 524288. 1. /* largest disk handled .2Gb disks*/ &hddev_op. 'D'.Name[0] hdcb[1]. &hddev_init.nBlocks hdcb[1]. &hddev_init. static U32 hd_msg.sbName hdcb[1].nBPB hdcb[1]. this would be the main() section of the C source file.type hdcb[0].pDevInit hdcb[1]. /* These are defaulted to non zero values to ensure we don't get a divide by zero during initial calculations on the first read. static U32 hd_msg2. 1. 3. 524288. 3. Listing 24. hd1_secpertrk = 17. '1'. static long HDDInt. hd1_heads = 16.4. &hddev_stat. '0'. /* first we set up the 2 DCBs in anticipation of calling InitDevDr */ hdcb[0]. The hdisk_setup() function is called from the monitor to initialize the driver. */ hd0_type = ReadCMOS(0x19). hd1_cyls = 1024.4.pDevInit hdcb[0]. 'H'. /* Random */ 512.Name[0] hdcb[0]. /* Max */ hd1_type = ReadCMOS(0x1A). IDE Device Driver Code (Initialization) U32 { U32 hdisk_setup(void) erc.pDevOp hdcb[0]. /* Random */ 512. 'D'.sbName hdcb[0].type hdcb[1].pDevOp hdcb[1]. See listing 24.Name[1] hdcb[0].Name[2] hdcb[0]. In a loadable driver. /* read this but don't use it */ hd0_heads = 16.pDevSt hdcb[1]. &hddev_stat. /* most common */ hd0_cyls = 1024.

*/ hd_reset(). drive 0 looks OK and the controller is functioning. They SHOULD simply return a Bad Command bit set in the Error register.0 -396- RICHARD A BURGESS . We have to make this sucker work the hard way. /* Guess it's not a valid drive */ if (!erc) /* We must redo drive 0 cause some cheap controllers lockup on us if drive 1 is not there.erc = AllocExch(&hd_exch). but they don't. then try both physical drives. Now we try drive 1 if type > 0. hd0_type = 0. UnMaskIRQ(14).last_erc = erc. They vary from BIOS to BIOS.hd_reset resets the controller (which controlls both drives). but to make it function with the widest range of IDE and MFM controllers this is the only way I have found that works. /* try to recal */ if (erc) { /* try one more time! */ hd_reset(). /* Must not be a valid drive */ return(ErcNoDrive0). and also shows them at 19h and 1Ah. */ hd_reset(). */ if (hd1_type) { erc = hd_recal(1).last_erc = erc. SetIRQVector(14. some controllers will lock-up (the el-cheapos). We don't actually read them because they are not dependable. The driver MUST be able to recal the first physical drive or the Driver won't inititalize. In this case we have to reset it again so it will work. */ erc = hd_recal(0). We have to do it once. erc = hd_recal(0). /* try to recal */ if (erc) { hdcb[0]. hd1_type = 0. /* Exhange for HD Task to use */ /* Documentation lists the fixed disk types at CMOS 11h and 12h. /* no error is returned */ /* Now we attempt to select and recal both drives. &hdisk_isr). /* try to recal if CMOS says it's there */ if (erc) { hdcb[1]. } } /* if we got here. MMURTL V1. */ /* Reset the HDC . It seems like a lot of work. If the second drive is not there.

if (i) hdstatus.0 */ /* The ISR gets statbyte */ -397- RICHARD A BURGESS . 1)). 2. This tells the HD task currently running that it's got some status to act on! ****************************************************************/ static void interrupt hdisk_isr(void) { statbyte = InByte(HD_PORT+7). This should only be called by DeviceInit or hdisk_setup. &hd_msg). gets the single status byte from the controller (which clears the interrupt condition) then sends an empty message to the exchange where the HD Driver task will be waiting.seems some controllers are SLOW!! */ i = CheckMsg(hd_exch. EndOfIRQ(14). } } /* recal drive 0 */ return(erc = InitDevDr(12. HD_REG_PORT). /* Eat Int if one came back hdstatus. We will wait up to 3 seconds then error out. &hdcb. The caller should call check_busy and check the error. It just waits for an interrupt. 0xfffffff0). *************************************************************/ static void hd_reset(void) { U32 i.erc = hd_recal(0). 0xfffffff0.last_erc = erc. } /************************************************************ Reset the HD controller. /* 200ms . MicroDelay(4).ResetStatByte = statbyte. } /************************************************************* The ISR is VERY simple. This resets the controller and reloads parameters for both drives (if present) and attempts to recal them. UnMaskIRQ(14). else hdstatus.fIntOnReset = 0.fIntOnReset = 1. and clear the reset bit */ OutByte(hd_control & 0x0f. OutByte(4. If it's 0 then the controller became ready in less than MMURTL V1. ISendMsg(hd_exch. Sleep(20). HD_REG_PORT). hdcb[0]. } /************************************************************* This checks the HDC controller to see if it's busy so we can send it commands or read the rest of the registers. /* /* /* enable the IRQ */ reset the controller */ Delay 60us */ /* bit 3 of HD_REG must be 1 for access to heads 8-15 */ /* Clear "MUCHO" heads bit. HDDInt = 1.

See listing 24. erc = hd_wait().3 seconds. Listing 24.5. Continuation of IDE Driver Code (code) /* Send the command */ /* wait for interrupt */ /****************************************** MMURTL V1. /* 50ms shots */ } return(ErcNotReady). You expect the hard disk controller to come back in a short period of time by sending us a message from the ISR. ****************************************************************/ static U32 hd_init(U8 drive) { U32 erc. /* controller out to lunch! */ } /************************************************************* This sends the SetParams command to the controller to set up the drive geometry (nHeads. hd_Cmd[4] = 0. hd_Cmd[3] = 0. /* cyl = 0 for init */ erc = send_command(HDC_SET_PARAMS). ****************************************************************/ static U32 { S16 count. check_busy(void) count = 0.).5. /* hds & drv */ } else { /* Drive 1 */ hd_Cmd[2] = hd1_secpertrk. /* sector count */ hd_Cmd[6] = (drive << 4) | ((hd1_heads-1) & 0x0f) | 0xa0. if ((statbyte & BUSY) == 0) return(ok). return(erc). if (!erc) erc = hd_status(HDC_SET_PARAMS). etc. /* sector count */ hd_Cmd[6] = (drive << 4) | ((hd0_heads-1) & 0x0f) | 0xa0. while (count++ < 60) { statbyte = InByte(HD_PORT+7). We also set the Alarm() function so we get a message at that exchange. /* cyl = 0 for init */ hd_Cmd[5] = 0. even if the controller goes into Never-Never land. ErcNotReady will be returned otherwise.0 -398- RICHARD A BURGESS . Sleep(5). /* hds & drv */ } hd_Cmd[1] = 0. It leaves the status byte in the global statbyte. nSectors. sectors and cylinders */ if (drive == 0) { /* Drive 0 */ hd_Cmd[2] = hd0_secpertrk. } The hd_wait() function has a good example of using the Alarm() function. /* set max heads.

*********************************************/ static U32 { U32 erc. erc = send_command(HDC_RECAL). &hd_msg). hd_wait(void) /* Set alarm for 3 seconds */ HDDInt = 0. 300). /* Alarm sends 0xffffffff } else { KillAlarm(hd_exch). ********************************************/ static U32 { U32 erc. /* Set it up again */ if (erc) return(erc). else hdstatus. /* kill any pending alarm */ erc = Alarm(hd_exch. return(ok). else return(ErcHDCTimeOut).0 -399- RICHARD A BURGESS . /* bad problem */ erc = WaitMsg(hd_exch.Wait for the hardware interrupt to occur. Time-out and return if no interrupt. if (drive) hdstatus. Clear the Echange of any left over MMURTL V1. hd_recal(U8 drive) */ hd_Cmd[6] = (drive << 4) | (hd_head & 0x0f) | 0xa0. } } /******************************************** Recalibrate the drive. KillAlarm(hd_exch). KillAlarm(hd_exch).LastRecalErc0 = erc. /* wait for interrupt */ if (!erc) erc = hd_status(HDC_RECAL). } /******************************************** Send the command to the controller. if (hd_msg != 0xfffffff0) { /* HD interrupt sends fffffff0 */ if (HDDInt) return(ErcMissHDDInt). if (!erc) erc = hd_wait().LastRecalErc1 = erc. return(erc).

if (!erc) OutByte(hd_Cmd[1]. if (!erc) OutByte(hd_Cmd[4]. Cmd) while (CheckMsg(hd_exch. &msg) == 0). hd_nsectors = nBlks. if (!erc) OutByte(Cmd. msg[2]. verify. j = dLBA % (hd0_heads * hd0_secpertrk). *************************************************************/ static U32 { U32 j. write. HD_PORT+3). return(erc). U16 cyl. if (hd_nsectors == 256) hd_nsectors = 0. HD_PORT+6). if (!erc) erc = check_busy(). nBlks ca NOT be greater than the hardware can handle.0 /* 0==256 for controller */ /* remainder */ RICHARD A BURGESS -400- . if (!erc) erc = check_busy(). The caculated values are placed in the proper command byte in anticipation of the command being sent. if (!erc) erc = check_busy(). /* Empty it */ /* bit 3 of HD_REG must be 1 for access to heads 8-15 */ if (hd_head > 7) { hd_control |= 0x08. HD_PORT+5). } /************************************************************* This sets up the cylinder. HD_REG_PORT). format. if (!erc) erc = check_busy(). HD_PORT+4). if (!erc) OutByte(hd_Cmd[5]. *********************************************/ static U32 send_command(U8 { U32 erc. seek). if (!erc) erc = check_busy(). } erc = check_busy(). MMURTL V1. if (!erc) OutByte(hd_Cmd[2]. head and sector variables for all commands that require them (read. if (!erc) erc = check_busy(). if (!erc) OutByte(hd_Cmd[3]. setupseek(U32 dLBA. if (hd_drive == 0) { /* drive 0 */ cyl = dLBA / (hd0_heads * hd0_secpertrk). OutByte(hd_control. if (!erc) OutByte(hd_Cmd[6]. HD_PORT+1). For IDE/MFM controllers this is 128 sectors. U32 nBlks) if (nBlks > 256) return ErcTooManyBlks. if (nBlks == 0) return ErcZeroBlks. HD_PORT+7).alarm or int messages before we send a command. HD_PORT+2). /* set bit for head > 7 */ hd_control &= 0xf7.

hd_sector = j % hd1_secpertrk + 1.LastSeekErc0 = erc. 1). but simply indicate status or indicate an action we must take next. j = dLBA % (hd1_heads * hd1_secpertrk). *********************************************************/ static U32 { U32 erc.0 -401- RICHARD A BURGESS . return(erc). /* sector number start at 1 !!! */ } hd_Cmd[2] hd_Cmd[3] hd_Cmd[4] hd_Cmd[5] hd_Cmd[6] return ok. /* (hd_drive << 4) | (hd_head & How many sectors */ Which sector to start on */ cylinder lobyte */ cylinder hibyte */ 0x0f) | 0xa0. } /* sets up for implied seek */ /* Not implied anymore. if (!erc) erc = hd_wait(). The error checking is based on the command that we sent.. if (!erc) erc = send_command(HDC_SEEK). hd_seek(U32 dLBA) erc = setupseek(dLBA. /* remainder */ /* We now know what cylinder. calculate head and sector */ hd_head = j / hd0_secpertrk. /* cyl & 0xff. *********************************************************/ static U32 { U32 erc. hdstatus. /******************************************************* Move the head to the selected track (cylinder)./* we now know what cylinder. hd_sector = j % hd0_secpertrk + 1. This is done because certain bits in the status and error registers are actually not errors. /* (cyl >> 8) & 0xff.. ZERO returned indicates no errors for the command status we are checking. if (!erc) erc = hd_status(HDC_SEEK). /* hd_sector. /* sector number start at 1 !!! */ } else { /* drive 1 */ cyl = dLBA / (hd1_heads * hd1_secpertrk). hd_status(U8 LastCmd) MMURTL V1. */ /* wait for interrupt */ /******************************************************* Called to read status and errors from the controller after an interrupt generated by a command we sent. } = = = = = nBlks. Calculate head and sector */ hd_head = j / hd1_secpertrk.

else if (errbyte & BAD_SECTOR) erc = ErcBadBlock. /* puts status byte into global StatByte */ if (!erc) statbyte = InByte(HD_PORT+7). else if (errbyte & BAD_ECC) erc = ErcBadECC. else if (errbyte & BAD_CMD) erc = ErcBadCmd. After all. */ erc = check_busy(). break. /* We shouldn't see the controller busy. case HDC_SET_PARAMS: case HDC_VERIFY: case HDC_FORMAT: case HDC_DIAG: break. /* default */ switch (LastCmd) { case HDC_READ: case HDC_READ_LONG: case HDC_WRITE: case HDC_WRITE_LONG: case HDC_SEEK: case HDC_RECAL: if (statbyte & WRITE_FAULT) erc = ErcWriteFault. MMURTL V1.LastErcByte0 = errbyte. errbyte. he interrupted us with status. } return(erc). if (errbyte & BAD_ADDRESS) erc = ErcAddrMark. else hdstatus. else return(erc). if (!erc) errbyte = InByte(HD_PORT+1). else hdstatus.U8 statbyte.0 -402- RICHARD A BURGESS . if (hd_drive) hdstatus. else if ((statbyte & SEEKOK) == 0) erc = ErcBadSeek.LastStatByte1 = statbyte. default: break. } else { erc = check_busy(). else return(erc). else if (errbyte & BAD_SEEK) erc = ErcBadSeek.LastErcByte1 = errbyte.LastStatByte0 = statbyte. else if (errbyte & BAD_IDMARK) erc = ErcSectNotFound. if ((statbyte & ERROR) == 0) { /* Error bit not set in status reg */ erc = ok. if (hd_drive) hdstatus.

/* sets up for implied seek */ erc = send_command(HDC_WRITE). nBPS. dnBlocks). This writes 1 or more whole sectors from the calculated values in hd_head. pDataRet. erc = setupseek(dLBA. nBPS = hdcb[hd_drive]. nBPS = hdcb[hd_drive]. U32 dnBlocks. U8 *pDataRet) { U32 erc. nBPS). and hd_cyl *************************************************************/ static U32 hd_read(U32 dLBA.else erc = ErcBadHDC. hd_sector. dnBlocks). /* no error bits found but should have been! */ } return erc. erc = check_busy(). --nleft. nBPS. erc = setupseek(dLBA. U32 dnBlocks. nSoFar++. pDataOut+=nBPS. U8 *pDataOut) { U32 erc. nleft. } /************************************************************* This is called for the DeviceOp code Write. if (!erc) /* && (statbyte & DATA_REQ)) */ { InWords(HD_PORT. /* wait for interrupt */ if (!erc) erc = hd_status(HDC_READ). nSoFar. /* From nBytesPerBlock in DCB */ nleft = dnBlocks. } /************************************************************* This is called for the DeviceOp code Read. while ((nleft) && (!erc)) { erc = hd_wait(). nBPS). pDataRet+=nBPS.nBPB. /* sets up for implied seek */ if (!erc) erc = send_command(HDC_READ). } while ((nSoFar < dnBlocks ) && (erc==ok)) { MMURTL V1. } } return(erc). /* No INT occurs for first sector of write */ if ((!erc) && (statbyte & DATA_REQ)) { OutWords(HD_PORT.nBPB.0 -403- RICHARD A BURGESS . pDataOut. This reads 1 or more whole sectors from the calculated values in hd_head. and hd_cyl *************************************************************/ static U32 hd_write(U32 dLBA. hd_sector. /* From n BytesPerBlock in DCB */ nSoFar = 0.

*/ hd_Cmd[2] hd_Cmd[3] hd_Cmd[4] hd_Cmd[5] hd_Cmd[6] = = = = = 1. /* For testing xprintf("\r\nCYL %d. } /****************************************************************** ReadSector is the only device specific call in the IDE/MFM hard disk device driver. hd_head = HdSect & 0xffff. HD %d. if ((erc==ok) && (statbyte & DATA_REQ)) { OutWords(HD_PORT. This allows you to read ONE sector specified by Cylinder. erc = setupseek(dLBA. /* cyl & 0xff. U32 HdSect. return(erc). } } if (!erc) erc = hd_wait(). and Sector number is HiWord in dnBlocks. /* (cyl >> 8) & 0xff. RICHARD A BURGESS MMURTL V1. U32 dnBlocks) { U32 erc. *******************************************************************/ static U32 ReadSector(U32 Cylinder. return(erc).0 -404- . SEC %d\r\n".erc = hd_wait(). head and Sector number. /* (hd_drive << 4) | (hd_head & How many sectors */ Which sector to start on */ cylinder lobyte */ cylinder hibyte */ 0x0f) | 0xa0. hd_sector = (HdSect >> 16) & 0xffff. pDataOut. hd_head. /* hd_sector. Cylinder is LoWord of dLBA in DeviceOp call. dnBlocks). dLBA must always be a multiple of the number of sectors per track minus 1 for the disk type. nBPS). cyl = Cylinder. U8 *pDataRet) { U32 erc. hd_sector). } /************************************************************* This formats the track beginning at the block address given in dLBA. nSoFar++. pDataOut+=nBPS. *************************************************************/ static U32 hd_format_track(U32 dLBA. Head is LoWord of dnBlocks in DeviceOp call. /* wait for final interrupt */ if (!erc) erc = hd_status(HDC_WRITE). /* sets up for implied seek */ erc = send_command(HDC_FORMAT). erc = hd_wait(). /* wait for interrupt */ if (erc==ok) erc = hd_status(HDC_FORMAT). cyl. /* wait for interrupt */ if (erc==ok) erc = hd_status(HDC_WRITE). U16 cyl.

U32 dnBlocks. if (hd_drive==0) { if (hd0_type==0) erc = ErcInvalidDrive. U32 dLBA. See listing 24. “Programming Interfaces. U8 *pData) { U32 erc. For Hard disk. if (!erc) InWords(HD_PORT. pDataRet.blocks_done = 0. The three calls that all MMURTL device drivers must provide are next. return(erc). } Chapter 8. else hd_drive = 1. U32 dOpNum. /* Reset values in Status record */ hdstatus. Continuation of IDE Driver Code (Device Interface) /****************************************** Called for all device operations. This assigns physical device from logical number that outside callers use. This will check to make sure a drive type is assigned and check to see if they are going to exceed max logical blocks. 12=0 and 13=1. /* wait for interrupt */ if (!erc) erc = hd_status(HDC_READ).6. erc = hd_wait().6. If so then we eat it (and do nothing) */ CheckMsg(hd_exch.0 /* Ignore error */ -405- RICHARD A BURGESS . /* Set drive internal drive number */ if (dDevice == 12) hd_drive = 0. MMURTL V1. } else { if (hd1_type==0) erc = ErcInvalidDrive. /* Check to see if we have a leftover interrupt message from last command.” discussed device driver interfaces.erc = send_command(HDC_READ). 512). there I provided an overview of MMURTL's device driver interface. erc = 0. Listing 24. &hd_msg). *******************************************/ static U32 hddev_op(U32 dDevice.

} } hdcb[hd_drive].nBlocks) erc = ErcBadLBA. U32 *pdStatusRet) MMURTL V1. pData).} /* make sure they don't exceed max blocks */ if (!erc) if (dLBA > hdcb[hd_drive]. case(CmdFmtTrk): /* Format Track */ erc = hd_format_track(dLBA. dnBlocks). S8 * pStatRet. /* Null Command */ break. return(erc). case(CmdSeekTrk): /* Seek Track */ erc = hd_seek(dLBA). case(CmdWrite): erc = hd_write(dLBA. case(CmdRead): erc = hd_read(dLBA. break. dnBlocks. pData). dnBlocks.last_erc = erc. if (!erc) { switch(dOpNum) { case(CmdNull): erc = ok. /* Read */ /* Write */ /* Verify */ /* hd_verify is not supported in this version of the driver */ /* erc = hd_verify(dLBA. case(CmdResetHdw): /* Reset Ctrlr */ hd_reset().0 /* update DCB erc */ -406- RICHARD A BURGESS . pData). break. break. erc = 0. This is called by the PUBLIC call DeviceStat! *******************************************/ static U32 hddev_stat(U32 dDevice. default: erc = ErcBadOp. break. */ break. dnBlocks. break. } /****************************************** Called for indepth status report on ctrlr and drive specified. case(CmdVerify): erc = ErcNotSupported. case(CmdReadSect): /* Read Sector(s) */ erc = ReadSector(dLBA. Returns 64 byte block of data including current drive geometery. U32 dStatusMax. break. break. dnBlocks. pData).

The DCB values are updated if this is successful.nCyl = hd1_cyls.nBPB.0 -407- RICHARD A BURGESS . hdstatus.erc = hdcb[0]. This is called by the PUBLIC call DeviceInit. hdstatus. Return no more than asked for! */ if (dStatusMax <= sStatus) i = dStatusMax. *******************************************/ static U32 hddev_init(U32 dDevice. hdstatus.nBPS = hdcb[0].nCyl = hd0_cyls. /* Set status for proper device */ if (dDevice == 12) { hdstatus. U32 sdInitData) /* copy to their status block */ /* tell em how much it was */ MMURTL V1. } else { hdstatus. makes changes to certain fields and calls DeviceInit pointing to the block for the changes to take effect.nBPS = hdcb[1]. hdstatus.nHead = hd1_heads.nSectors = hd0_secpertrk.nBPB.type_now = hd1_type. hdstatus. hdstatus.last_erc. } /****************************************** Called to reset the hard disk controller and set drive parameters. pStatRet.last_erc.). hdstatus. hdstatus. The Initdata is a copy of the 64 byte status block that is read from status. CopyData(&hdstatus. hdstatus. S8 *pInitData. *pdStatusRet = i.{ U32 i. } /* Calculate size of status to return.type_now = hd0_type. return ok. The caller normally reads the block (DeviceStat). else i = sStatus.erc = hdcb[1]. This should ONLY be called once for each HD to set its parameters before it is used the first time after the driver is loaded. hdstatus.nSectors = hd1_secpertrk.nHead = hd0_heads. or after a fatal error is received that indicates the controller may need to be reset (multiple timeouts etc. i).

nCyl. update corresponding DCB values */ if (!erc) { hdcb[hd_drive].last_erc = erc. hd1_heads = HDStatTmp.nBlocks = HDStatTmp. erc = 0. /* Set internal drive number */ if (dDevice == 12) hd_drive=0.type_now.{ U32 erc. } else erc = ErcInvalidDrive. if (hd0_type) { hd0_cyls = HDStatTmp.nBPS. hd1_secpertrk = HDStatTmp.type_now. hdcb[hd_drive].nCyl * HDStatTmp. return(erc).nHead.nHead. hd0_secpertrk = HDStatTmp.nSectors. if (hd_drive==0) { hd0_type = HDStatTmp. /* Read the init status block in */ if (sdInitData > sStatus) i = sStatus.nBPB = HDStatTmp. hdcb[hd_drive]. hd0_heads = HDStatTmp.0 -408- RICHARD A BURGESS .last_erc = 0. i. } /* update DCB erc */ /* no more than 64 bytes! */ /* copy in their init data */ MMURTL V1.nSectors. else i = sdInitData. CopyData(pInitData. initialize it and recal */ if (!erc) erc = hd_init(hd_drive).nSectors * HDStatTmp. } hdcb[hd_drive]. &HDStatTmp. if (hd1_type) { hd1_cyls = HDStatTmp.nCyl. else hd_drive = 1. } else { hd1_type = HDStatTmp. } else erc = ErcInvalidDrive. if (!erc) erc = hd_recal(hd_drive).nHead. } /* If no error. i). /* If no error.

They are two different animals with the same basic interface. MMURTL V1. */ 150-38400 */ 0. You may notice the differences between the IDE driver.” The header file defines certain parameters to functions. unsigned long Baudrate. unsigned long IOBase. 2=odd */ nDatabits for this port. the driver. 1=even.38400 */ Parity for this port. 16450. unsigned long RBufSize. 0 for not in use */ Result of last device operation */ Total bytes moved in last operation */ Baudrate for this port.. “MMURTL Sample Software. unsigned char IRQNum. which is a sequential byte-oriented device. The C structure for the status record is also defined here and will be needed to initialize. as well as status. 1 or 2 */ Must be 5-8 */ Must be 1 or 2 */ if 0 */ < 3 */ At least 40 bytes for this version */ struct statRecC{ unsigned long commJob. such as complete flow control for XON/XOFF and CTS/RTS. Unlike the IDE disk device driver. See listing 24.The RS-232 Asynchronous Device Driver The RS-232 driver is designed to drive two channels with 8250.. unsigned long LastErc. which is a block oriented random device. */ It's opened by someone else. but. a header file is included in the RS232 driver source file. unsigned char parity. other than that. This header file was also designed to be used by programs that use the driver. Listing 24.. 0=none. 5-8 */ stop bits for this port. */ It's already open. or 16550 UARTs. unsigned long LastTotal. unsigned long XTimeOut.7. unsigned long resvd[6]. as well as by the driver itself. it is a fully functional driver. It needs more work. This same header file is used in the sample communications program in chapter 16.h Header File #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define MIN_BAUD MAX_BAUD NO_PAR EV_PAR OD_PAR ErcRecvTimeout ErcXmitTimeout ErcRcvBufOvr ErcBadPort ErcRcvBufOvr ErcNotOpen ErcChannelOpen ErcNotOwner ErcBadBaud ErcBadParity ErcBadDataBits ErcBadStopBits ErcBadIOBase ErcBadCommIRQ ErcBadInitSize 150L 38400L 0 1 2 800 801 802 803 805 807 809 812 820 821 822 823 824 825 827 /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* Recv Buffer Empty */ Xmit Buffer never Emptied */ Receive buffer overrun */ Invalid port on OpenCommC */ Buffer full!!! */ Channel not open.0 /* /* /* /* /* /* /* /* /* /* /* /* /* /* Owner of this comms port. 150 . and this driver.. as well as explaining all the error or status codes that the driver will return.. 1 or 2 */ IRQNum for this channel */ IO base address for hardware */ Size of Xmit buffer */ Size of Recv Buffer */ Xmit Timeout in 10ms increments */ Recv Timeout in 10ms increments */ out to 64 bytes */ -409- RICHARD A BURGESS .. unsigned long RTimeOut.7. unsigned long XBufSize. unsigned char databits. RS-232. unsigned char stopbits.

8.0 *pIRQ). The only commands that are shared with the default command number are reading and writing blocks of data (command 1 and 2). extern far U32 DeAllocPage(U8 *pOrigMem. RS-232 Device Driver Code (defines and externs) #define #define #define #define #define #define #define #define U32 unsigned long S32 long U16 unsigned int S16 int U8 unsigned char S8 char TRUE 1 FALSE 0 #include "RS232. See listing 24. extern far U32 SetIRQVector(U32 IRQNum. extern far U32 InitDevDr(U32 dDevNum. /* Device Driver interface commands (Op numbers) */ #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define CmdReadRec 1 CmdWriteRec 2 CmdOpenC 10 CmdCloseC 11 CmdDiscardRcv 12 CmdSetRTO 13 CmdSetXTO 14 CmdSetDTR 15 CmdSetRTS 16 CmdReSetDTR 17 CmdReSetRTS 18 CmdBreak 19 CmdGetDC 20 CmdGetDSR 21 CmdGetCTS 22 CmdGetRI 23 CmdReadB 31 CmdWriteB 32 /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* Read one or more bytes */ Write one or more bytes */ Open Comm Channel */ Close Comm Channel */ Trash input buffer */ Set Recv timeout 10ms incs in dLBA */ Set Xmit timeout 10ms incs in dLBA */ Set DTR (On) */ Set CTS (On) */ Set DTR (On) */ Set CTS (On) */ Send BREAK (10ms incs in dLBA) */ Returns byte TRUE to pData if CD ON */ Returns byte TRUE to pData if DSR ON */ Returns byte TRUE to pData if CTS ON */ Returns byte TRUE to pData if RI ON */ Recv a single byte */ Xmit a single byte */ Once again. U32 nDevices.h" /* MMURTL OS Prototypes */ extern far U32 AllocExch(U32 *pExchRet).}. extern far U32 MaskIRQ(U32 IRQNum).8. you will notice the shorthand for the rather lengthy C-type declarations. extern far U32 UnMaskIRQ(U32 IRQNum). S8 MMURTL V1. All other command are specific to this driver. U8 **ppMemRet). U32 nPages). RICHARD A BURGESS -410- . U32 dfReplace). Further down in the file you will notice that commands are defined 1 though 32. S8 *pDCBs. Listing 24. extern far U32 AllocOSPage(U32 nPages.

void CopyData(U8 *pSource. U32 msg1. */ #define CTS MMURTL V1. extern far long GetJobNum(long *pJobNumRet). U32 count). U32 Sleep(U32 count). U32 msg2). static S32 comdev_init(U32 dDevice. U32 CheckMsg(U32 Exch. U32 sdInitData). These will be called form the device driver interface. U16 wPort). U32 KillAlarm(U32 Exch). S8 *pInitData. U32 SendMsg(U32 Exch. void OutByte(U8 Byte. #define CmdReadRec #define CmdWriteRec #define #define #define #define #define #define #define #define #define #define #define #define #define #define 1 2 /* Read one or more bytes */ /* Write one or more bytes */ /* Open Comm Channel */ /* Close Comm Channel */ CmdOpenC 10 CmdCloseC 11 CmdDiscardRcv 12 CmdSetRTO 13 CmdSetXTO 14 CmdSetDTR 15 CmdSetRTS 16 CmdReSetDTR 17 CmdReSetRTS 18 CmdBreak 19 CmdGetDC 20 CmdGetDSR 21 CmdGetCTS 22 CmdGetRI 23 31 32 /* /* /* /* Set Set Set Set DTR CTS DTR CTS (On) (On) (On) (On) */ */ */ */ #define CmdReadB #define CmdWriteB /* The following definitions are used to identify. U8 *pDestination. U32 msg2). U32 msg1. */ static U32 comdev_stat(U32 dDevice. U32 *pdStatusRet). U32 WaitMsg(U32 Exch. U32 dStatusMax. S8 *pMsgRet). U8 *pData).extern extern extern extern extern extern extern extern extern extern extern extern extern far far far far far far far far far far far far far U32 EndOfIRQ(U32 IRQNum). U32 dnBlocks. U32 Alarm(U32 Exch. U32 dOpNum. void MicroDelay(U32 us15count). S8 *pMsgRet). U32 dBytes). U32 GetTimerTick(U32 *pTickRet). set and reset signal line condition and functions. U32 dLBA. static U32 comdev_op(U32 dDevice. S8 *pStatRet. U8 InByte(U16 wPort).0 0x10 /* Clear To Send */ RICHARD A BURGESS -411- . /* local prototypes. U32 ISendMsg(U32 Exch.

LSR[2]. int_id[2]. IER[2]. head_recv[2]. MSR[2]. *pRecvBuf[2]. FCR[2]. IIR[2]. /* /* /* /* /* /* /* /* /* Transmitter Holding Register */ Interrupt Enable Register */ Interrupt Id Register */ FIFO control for 16550 */ Line Control Register */ Modem Control Register */ Line Status Register */ Modem Status Register */ same address as THR */ /* same address as IER */ MMURTL V1. DLAB_LO[2]. sRecvBuf[2]. /* NON-ZERO = an error has occurred */ /* /* /* /* /* /* /* /* /* /* pointers to Xmit bufs */ Next char to send */ Where next char goes in buf */ Count of bytes in buf */ Size of buffer (allocated) */ pointers to Recv bufs */ Next char from chip */ Next char to read for caller */ Count of bytes in buf */ Size of buffer (allocated) */ static U32 recv_error[2]. mstat_byte[2]. DLAB_HI[2]. /* variables for ISRs */ static static static static static U8 U8 U8 U8 U8 f16550[2]. sSendBuf[2]. head_send[2].H */ #define SSENDBUF 4096 #define SRECVBUF 4096 static U32 static U32 /* 1 Page Send Buf Default */ /* 1 Page Recv Buf Default */ /* 10ms intervals */ /* 10ms intervals */ recv_timeout[2] = 1. fExpectInt[2].#define DSR #define RI #define CD #define DTR #define RTS #define OUT2 0x20 0x40 0x80 0x01 0x02 0x08 /* Data Set Ready /* Ring Indicator /* Carrier Detect */ */ */ /* Data Terminal Ready */ /* Request To Send */ /* Not used */ /* Values from IIR register */ #define #define #define #define #define MDMSTAT NOINT TXEMPTY RVCDATA RCVSTAT 0x00 0x01 0x02 0x04 0x06 /* For errors returned from Comms calls see COMMDRV. static static static static static static static static static static U8 U32 U32 U32 U32 U8 U32 U32 U32 U32 *pSendBuf[2]. tail_send[2]. static U8 control_byte[2] = 0.0 -412- RICHARD A BURGESS . stat_byte[2]. xmit_timeout[2] = 10. cSendBuf[2]. /* array of registers from port base for each channel */ static static static static static static static static static static U16 U16 U16 U16 U16 U16 U16 U16 U16 U16 THR[2]. LCR[2]. tail_recv[2]. MCR[2]. cRecvBuf[2].

1 char 01 . \___________ = 0.Interrupt Enable.Interrupt Identification Register 7 | | | | | | | | 4 3 2 1 0 | \_\_\_\__________ 0001 = no interrupt | 0110 = rcvr status | 0100 = rcvd data | 0010 = THR empty | 0000 = Modem status | 1101 = Rcv Fifo Timeout (16550) \_________ = 0. Divisor Latch MSB 7 | | | | \ 6 | | | | _ 5 | | | | _ 4 3 2 1 0 | | | | \_ | | | \___ | | \_____ | \_______ _\_________ Data Available Xmit Holding Reg Empty Receiver Line Status Modem Status Always 0000 IIR -. RS-232 Device Driver Code continued (register bits) /* Complete description of Register bits follows: THR -.TX data.9 in some documentation while working on an AM65C30 USART. \ \_____________ = (16550 only) 00 = FIFOs disabled non-zero = 16550 enabled 6 | | | | | | | | 5 | | | | | | | FCR -.4 chars 11 .FIFO Control Register (16550 only) This is a write only port at the same address as the IIR on an 8250/16450 7 6 5 4 3 2 1 0 | | | | | | | \__= | | | | | | \___ = | | | | | \_____ = | | | | \_______ = | | | \_________ = | | \___________ = \__\_____________ = 1 = FIFO Enable 1 = Recv FIFO Reset 1 = Xmit FIFO Reset (DMA) 0 = Single char.8 chars LCR -- Line Control Register 7 6 5 4 3 2 1 0 | | | | | | | \_ Word Length Select Bit 0.I saw register bits defined like those in listing 24. FIFO Rcv Trigger Level 00 . Listing 24. 1 = Multichar 0. RX data.9. MMURTL V1.2 chars 10 . Divisor Latch LSB IER -. 0. You can very easily see the break-out of the bits and follow what is being tested or written throughout the source code. I liked it so much because it was easy to read in a bit-wise fashion.0 -413- RICHARD A BURGESS .

0 -414- RICHARD A BURGESS .| | | | | | | | | | \___ Word Length Select Bit 1. static struct statRecC *pCS. Listing 24. The members of this structure are defined in the header file RS232. RS-232 Device Driver Code continued (status record) /* Record for 64 byte status and init record */ /* This structure is peculiar to the comms driver */ #define sStatus 64 static struct statRecC comstat[2].10.Modem Status Register 7 | | | | | | | 3 2 1 0 | | | \_ Delta Clear to Send | | \___ Delta Data Set Ready | \_____ Trailing Edge Ring Indicator \_______ Delta Rx Line Signal Detect \_________ Clear to Send \___________ Data Set Ready \_____________ Ring Indicator \_______________ Receive Line Signal Detect 6 | | | | | | 5 | | | | | 4 | | | | */ The status record for each driver is specific to that particular driver.Line Status Register 7 | | | | | | | 3 2 1 0 | | | \_ Data Ready | | \___ Overrun Error | \_____ Parity Error \_______ Framing Error \_________ Break interrupt \___________ Transmitter Holding Reg Empty \_____________ Transmitter Shift Reg Empty \_______________ Recv FIFO Error (16550 Only) 6 | | | | | | 5 | | | | | 4 | | | | MSR -.10.H. See listing 24. It is documented in the preceding section in this chapter.Modem Control Register 7 | | | | | \ 6 | | | | | _ 5 4 3 2 1 0 | | | | | \_ | | | | \___ | | | \_____ | | \_______ | \_________ _\___________ Data Terminal Ready Request to Send Out 1 Out 2 (= 1 to enable ints. 1=2) | | \_______ Parity Enable | \_________ Even Parity Select \___________ Stick Parity \_____________ Set Break \_______________ Divisor Latch Access Bit | | | | | MCR -.) Loop = Always 0 LSR -. | | | \_____ Number Stop Bits (0=1. MMURTL V1.

Name[0] comdcb[0]. S8 *pDevInit.12. as is required by MMURTL. See listing 24. U32 last_erc. Listing 24. /* first we set up the 2 DCBs in anticipation of calling InitDevDr */ comdcb[0]. S8 type. Listing 24. U32 OS3. U32 nBlocks.11.12. See listing 24. 8N1. U32 OS2. S16 wJob. U32 OS1.Name[1] comdcb[0]. RS-232 Device Driver Code continued (DCB) static struct dcbtype { S8 Name[12]. This means it's ready for business. RS-232 Device Driver Code continued (DCB init) /********************************************************* This is called ONCE to initialize the 2 default comms channels with the OS device driver interface. /* Two RS-232 ports */ /* THE COMMS INTERRUPT FUNCTION PROTOTYPES */ static void interrupt comISR0(void). S8 fSingleUser. U32 OS6. the following initialization routine is called once to set up the driver.0 = 'C'. = 'O'. It calls InitDevDr() after it has completed filling out the device control blocks and setting up the interrupts. S8 *pDevOp. S8 *pDevSt. Just as with the IDE hard disk device driver.Name[2] MMURTL V1. static struct dcbtype comdcb[2]. S8 sbName. S16 nBPB. *********************************************************/ U32 { U32 coms_setup(void) erc. -415- RICHARD A BURGESS . Note that there are two of DCB’s. U32 OS4. }. One for each channel. static void interrupt comISR1(void). It sets up defaults for both channels to 9600.This is the same device control block (DCB) record structure used in all MMURTL device drivers. and they are contiguously defined. S8 fDevReent. = 'M'.11. U32 OS5.

IOBase = comstat[0]. 'M'.IRQNum = comstat[0].type comdcb[0]. = 0.comdcb[0].Name[0] comdcb[1]. = 9600. 1)).Baudrate comstat[1]. } MMURTL V1. MaskIRQ(4).RTimeOut comstat[1]. = 100. 0x2F8.pDevOp comdcb[1]. &comdev_stat. SetIRQVector(3.XBufSize comstat[1]. 4.pDevInit comdcb[1].Name[2] comdcb[0]. comstat[1]. 2. 4.sbName comdcb[0].XBufSize comstat[0].0 -416- RICHARD A BURGESS . 2.stopbits comstat[1].IOBase = comstat[1]. MaskIRQ(3). = 4096.XTimeOut comstat[1]. /* 1 byte per block */ 0. return(erc = InitDevDr(5.pDevSt = = = = = = = = = = = = = = = = = = = '1'.stopbits comstat[0].XTimeOut comstat[0]. &comISR0). = 1.databits comstat[1]. &comdcb. SetIRQVector(4. sRecvBuf[1] = 4096.pDevOp comdcb[0]. /* 1 byte per block */ 0. = 2. /* Set default comms params in stat records */ comstat[0]. 2.nBlocks comdcb[0]. '2'. 3.Baudrate comstat[0].parity comstat[1].pDevSt comdcb[1].Name[2] comdcb[1]. /* Sequential */ 1. &comISR1). /* none */ = 8.nBPB comdcb[1]. 4. /* 0 for Sequential devices */ &comdev_op. &comdev_init. /* COM1 */ /* COM2 */ = 9600.parity comstat[0]. &comdev_stat. 0x3F8. = 2. /* Sequential */ 1. = 4096. /* none */ = 8.type comdcb[1]. = 1. &comdev_init.RBufSize sSendBuf[1] = 4096.pDevInit comdcb[0].RBufSize sSendBuf[0] = 4096.databits comstat[0].nBPB comdcb[0].IRQNum = comstat[1].sbName comdcb[1]. = 4096.nBlocks comdcb[1]. sRecvBuf[0] = 4096. 'O'.RTimeOut comstat[0]. 'C'. = 100. = 4096. /* 0 for Sequential devices */ &comdev_op.Name[1] comdcb[1].Name[2]