Richard A. Burgess
Previously published by MacMillan Publishing/SAMS Computer Books as "Developing Your Own 32 Bit Computer Operating System"

Author: Richard A. Burgess Copyright © 1995 Richard A. Burgess, Copyright © 1999 Sensory Publishing, Inc. & Richard A. Burgess All Rights Reserved
Portions of this electronic book were formatted with software produced by:

Adobe Corporation for the Acrobat Reader Version, and Sensory Publishing, Inc. & Microsoft Corp. for the Word and HTML versions. Copyright 1999, Sensory Publishing, Inc.

Please treat the electronic version of this book as you would a printed copy of any book. You may pass this text on to another individual or institution when you are through with it upon deleting your copy as if you no longer held a printed copy of the book. No portion of this book may be made publicly available by electronic or other means without the express written permission of Sensory Publishing, Inc.


Page 1 of 667

First 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, 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 to attempt to find more copies of the original edition of the book and I can tell you they are very scarce. Another printing was denied by the original publisher which turned the rights to my book back over to me. Of the 10,000plus 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, the paper book itself isn’t what you’re interested in, it’s the information in the book. 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 and I had always been interested in publishing - the not so simple art of information dissemination - and I offered some technical assistance. Electronic publishing is a new art - or science as some would lead you to believe. I have not had much time to work on MMURTL since the book was first published. As near as I can tell, a few people have been playing with it and it has been used mostly as a learning tool. Sensory Publishing is willing to put up a section on their servers for a BBS (Bulletin Board System) for MMURTL and other books that they will publish for those that want a place to 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 to add their two cents. The book is being sold online in several unprotected formats which some people think is risky. I don’t think so. I put a little bit more faith in the human race than most. And besides, it will cut the cost of distribution in half and I may actually be able to pay for all those computers and books I bought to support the original development effort - those of you that think authors of books of this type make any money from them have a few lessons to learn. Of the approximately one half million dollars the book grossed (between the publisher and book sellers - I got about $1.80 a book... pitiful. That translates to roughly 1/2 minimum wage for the hours invested to produce this book. That hardly repays the creditors who will gladly lend you money to feed the "MEGAHERTZ habit" that is needed to stay on top of the computing world. Remember when a 386 - 20 MHz cost $5000? I do, I bought one hot off the assembly line to write MMURTL back in ‘83 (whoa - I'm getting old...). Anyway, I hope you use MMURTL V1.0 to learn, enjoy and explore. You have my permission to use any of the code (with the exception of the C compiler) for any project you desire - public or private - so long as you have purchased a copy of the book. My way of saying thanks. The only requirement is that you give some visible credit to MMURTL for the assistance with your project. Most sincerely, Richard A. Burgess Alexandria Virginia, 1999


Page 2 of 667

Chapter 1, Introduction
Computer programmers and software engineers work with computer operating systems every day. They use them, they work with them, and they even work "around" them to get their jobs done. If you’re an experienced programmer, I’m willing to bet you’ve pondered changes you would make to operating systems you use, or even thought about what you would build and design into one if you were to write your own. You don’t have to be Albert Einstein to figure out that many labor years go into writing a computer operating system. You also don’t have to be Einstein to write one, although I’m sure it would have increased my productivity a little while writing my own. Unfortunately, I have Albert’s absent-mindedness, not his IQ. Late in 1989 I undertook the task of writing a computer operating system. What I discovered is that most of the books about operating system design are very heavy on general theory and existing designs, but very light on specifics, code, and advice into the seemingly endless tasks facing someone that really wants to sit down and create one. I’m not knocking the authors of these books; I’ve learned a lot from all that I’ve read. It just seemed there should be a more "down to earth" book that documents what you need to know to get the job done. Writing my own operating system has turned into a very personal thing for me. I have been involved in many large software projects where others conceived the, and I was to turn them into working code and documentation. In the case of my own operating system, I set the ground rules. I decided what stayed and what went. I determined the specifications for the final product. When I started out, I thought the “specs” would be the easy part. I would just make a list, do some research on each of the desirables, and begin coding. NOT SO FAST THERE, BUCKO! I may not have to tell you this if you've done some serious "code cutting," but the simplest items on the wish list for a piece of software can add years to the critical path for research and development, especially if you have a huge programming team of one person. My first list of desirables looked like a kid's wish list for Santa Claus. 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). Realizing this, I whittled away to find a basic set of ingredients that constitute the real make-up of the "guts" of operating systems. The most obvious of these, as viewed by the outside programmer, are the tasking model, memory model, and the programming interface. As coding began, certain not-so-obvious


Page 3 of 667

ingredients came into play such as the OS-to-hardware interface, portability issues size and speed. One of the single most obvious concerns for commercial operating system developers is compatibility. If you write something that no one can use, it’s unlikely they’ll use it. Each of the versions of MS-DOS that were produced had to be able to run software written for previous versions. When OS/2 was first developed, the "DOS-BOX" was a bad joke. It didn’t provide the needed compatibility to run programs that business depended on. In the early days, Unix had a somewhat more luxurious life, because source-code compatibility was the main objective; you were pretty much expected to recompile something if you wanted it to run on your particular flavor of the system. I’m sure you’ve noticed that this has changed substantially. I did not intend to let compatibility issues drive my design decisions. I was not out to compete with the likes of Microsoft, IBM, or Sun. You, however, may want compatibility if you intend to write your own operating system. It’s up to you. One thing about desiring compatibility is that the interfaces are all well documented. You don’t need to design your own. But I wanted a small API. That was part of my wish list. Along my somewhat twisted journey of operating-system design and implementation, I have documented what it takes to really write one, and I’ve included the "unfinished" product for you to use with very few limitations or restrictions. Even though I refer to it as unfinished, it’s a complete system. 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.

What This Book Is About
In this book I discuss the major topics of designing your own OS: the tasking model, the memory model, programming interfaces, hardware interface, portability issues, size and speed. I back up these discussions showing you what I decided on (for my own system), and then finally discuss my code in great detail. Your wish list may not be exactly like mine. In fact, I’m sure it won’t. For instance, you’ll notice the ever so popular term "object oriented" blatantly missing from any of my discussions. This was completely intentional. It clouds far too many real issues these days. Don’t get me wrong - I’m an avid C++ and OOP user. But that’s not what this book is about, so I’ve removed the mystique. Once again, your goals may be different. Throughout this book, I use the operating system I developed (MMURTL) to help explain some of the topics I discuss. You can use pieces of it, ideas from it, or all of it if you wish to write your own. I have included the complete source code to my 32-bit; message based, multitasking, real-time, operating system (MMURTL) which is designed for the Intel 386/486/Pentium processors on the PC Industry Standard Architecture (ISA) platforms.


Page 4 of 667

The source code, written in 32-bit Intel based assembly language and C, along with all the tools necessary to build, modify, and use the operating system are included on the accompanying CD-ROM. The hardware requirement section below tells you what you need to run MMURTL and use the tools I have developed. It’s not really required if you decide on a different platform and simply use this book as a reference. One thing I didn’t put on my wish list was documentation. I always thought that many systems lacked adequate documentation (and most of it was poorly written). I wanted to know more about how the system worked internally, which I felt, would help me write better application software for it. For this reason, I have completely documented every facet of my computer operating system and included it here. You may or may not want to do this for your system, but it was a "must" for me. I think you will find that it will help you if you intend to write your own. Well-commented code examples are worth ten times their weight in generic theory or simple text-based pseudo-algorithms. The “Architecture and General Theory” section of this book along with the sections that are specific the MMURTL operating system, will provide the information for you to use my system as is, redesign it to meet your requirements, or write your own. If you don't like the way something in my system is designed or implemented, change it to suit your needs. The only legal restriction placed on your use of the source code is that you may not sell it or give it away.

Who Should Use This Book
This book is for you if you are a professional programmer or a serious hobbyist, 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). • 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, Embedded or dedicated systems using the 32 bit PC ISA architecture real-time, message based operating systems • PC Industry Standard Architecture hardware management including: DMA Controllers, Hardware Timers, Priority Interrupt Controller Units, Serial and Parallel Ports, 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 386, 486, and Pentium processors as if they were all the same. If you know a fair amount about the Intel 32-bit processors, you know that a quantum leap was made between the 286 and 386 processors. This was the 16- to 32-bit jump.


Page 5 of 667

The 486 and Pentium series have maintained compatibility with the 386-instruction set. Even though they have added speed, a few more instructions, and some serious "turbo" modifications, they are effectively super-386 processors. In my opinion, the next quantum leap really hasn’t been made, even though the Pentium provides 64-bit access.

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, and what my end result was. If you’re going to write your own, I recommend you take care not to make it an "endless" list. You may start with my list and modify it, or start one from scratch. You can also gather some serious insight to my wish list in chapter 2, General Discussion and Background. Here was my wish list: • True Multitasking - Not just task switching between programs, or even sharing the processor between multiple programs loaded in memory, but real multithreading - 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. I’ll discuss some of your options, and also what I decided on, in chapters 3 (Tasking Model), and 4 (Interprocess Communications). Real-time operation - The ability to react to outside events in real time. This design goal demanded MMURTL to be message based. The messaging system would be the heart of the kernel. Synchronization of the messages would be the basis for effective CPU utilization (the Tasking Model). Real-time operation may not even be on your list of requirements, but I think you’ll see the push in industry is towards real-time systems, and for a very good reason. We, the humans, are outside events. We want the system to react to us. This is also discussed in chapters 3 (Tasking Model), and 4 (Interprocess Communications). Client/Server design - The ability to share services with multiple client applications (on the same machine or across a network). A message-based operating system seemed the best way to do this. I added the Request and Respond message types to the more common Send and Wait found on most message based systems. Message agents could be added to any platform. If you don’t have this requirement, the Request and Respond primitives could be removed, but I don’t recommend it. I discuss this in chapter 4 (Interprocess Communications). Common affordable hardware platform with minimal hardware requirements - The most common 32-bit platform in the world is the PC ISA platform. How many do you think are out there? Millions! How much does a 386SX cost these days? A little more than dirt, or maybe a little less? Of course, you may have a different platform in mind. The hardware interface may radically change based on your choice. I discuss this in chapter 6, “The Hardware Interface.”


Page 6 of 667

Flat 32-Bit Virtual Memory Model - Those of you that program on the Intel processors know all about segmentation and the headaches it can cause (Not to mention Expanded, Extended, LIM, UMBs, HIMEM, LOMEM, YOURMOTHERSMEM, and who knows what other memory schemes and definitions are out there). Computers using the Intel compatible 32-bit processors are cheap, and most of them are grossly under used. 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. Chapter 5 covers memory management, but not all the options. Many detailed books have been written on schemes for memory management, and I stuck with a very simple paged model that made use of the Intel hardware. You could go nuts here and throw in the kitchen sink if desired. Easy programming - The easiest way to interface to an operating system from a procedural language, such as C or Pascal, is with a procedural interface. A procedural interface directly into the operating system (with no intermediate library) is the easiest there is. Simple descriptive names of procedures and structures are used throughout the system. I also wanted to maintain a small, uncluttered Applications Programming Interface (API) specification, adding only the necessary calls to get the job done. Power without BLOAT... the right mix of capability and simplicity. I would shoot for less than 200 basic public functions. I discuss some of your options in chapter 8, “Programming Interfaces.” Protection from other programs - But not the programmer. I wanted an OS that would prevent another program from taking the system down, yet allow us the power to do what we wanted, if we knew how. This could be a function of the hardware as well as the software, and the techniques may change based on your platform and processor of choice. Use the CPU Instruction Set as Designed - Many languages and operating systems ported between processors tend to ignore many of the finer capabilities of the target processor. I didn't want to do this. I use the stack, calling conventions, hardware paging, and task management native to the Intel 32-bit x86 series of processors. I have attempted to isolate the API from the hardware as much as possible (for future source code portability). You may want your operating system to run on another platform or even another processor. If it is another processor, many of the items that I use and discuss may not apply. I take a generic look at it in chapter 6, “The Hardware Interface,” and chapter 8, “Programming Interfaces.” Simplicity - Keep the system as simple as possible, yet powerful enough to get the job done. We also wanted to reduce the "jargon" level and minimize the number of terse, archaic names and labels so often used in the computer industry.

A Brief Description of MMURTL
From my wish list above, I put together my own operating system. I will describe it to you here so that you can see what I ended up with based on my list.


Page 7 of 667

MMURTL (pronounced like the girl’s name Myrtle) is a 32-bit, Message based, Multitasking, Real-Time, operating system designed around the Intel 80386 and 80486 processors on the PC Industry Standard Architecture (ISA) platforms. The name is an acronym for Message based MUltitasking, Real-Time, kerneL. If you don’t like my acronym, make up your own! But, I warn you: Acronyms are in short supply in the computer industry. MMURTL is designed to run on most 32-bit ISA PCs in existence. Yes, 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. If you intend to run MMURTL, or use any of the tools I included, see the “Hardware Requirements” section later in this chapter. MMURTL is not designed to be compatible with today's popular operating systems, nor is it intended to directly replace them. It is RADICALLY different in that SIMPLICITY was one of my prime design goals. If you don't want to start from scratch, or you have an embedded application for MMURTL, the tools, the code, and the information you need are all contained here so you can make MMURTL into exactly what you want. Sections of the source code will, no doubt, be of value in your other programming projects as well.

Uses for MMURTL
If you are not interested in writing your own operating system (it CAN be a serious timesink), and you want to use MMURTL as is (or with minor modifications), 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. It is an ideal learning and/or reference tool for programmers working in 32-bit environments on the Intel 32-bit, x86/Pentium processors, even if they aren't on ISA platforms or using message based operating systems. MMURTL can be the foundation for a powerful dedicated communications system, or as a dedicated interface between existing systems. The real-time nature of MMURTL makes it ideal to handle large numbers of interrupts and communications tasks very efficiently. Dedicated, complex equipment control is not beyond MMURTL's capabilities. Vertical applications of any kind can use MMURTL where a character based color or monochrome VGA text interface is suitable.

• •


Page 8 of 667

MMURTL would also be suitable for ROM based embedded systems (with a few minor changes). One of the goals was to keep the basic system under 192Kb (a maximum of 128K of code, and 64K of data), excluding dynamic allocation of system structures after loading. The OS could be moved from ROM to RAM, and the initialization entry point jumped to. If you’re not worried about memory consumption, expand it to your heart’s desire. In the educational field, MMURTL can be used to teach multitasking theory. The heavily commented source code and in-depth theory covered in this book makes it ideal for a learning/teaching tool, or for general reference. And of course, MMURTL can be the basis or a good starting point for you very own microcomputer operating system.

Similarities to Other Operating Systems
MMURTL is not really like any other OS that I can think of. It’s closest relative is CTOS (Unisys), but only a distant cousin, and only because of the similar kernel messaging schemes. Your creation will also take on a personality of it’s own, I’m sure. The flat memory model implementation is a little like OS/2 2.x (IBM), but still not enough that you could ever confuse the two. Some of the public and internal calls may resemble UNIX a little, (or even MS-DOS), but still, not at all the same. The file system included with MMURTL uses the MS-DOS disk structures, but only out of convenience. It certainly wasn’t because I liked the design or file name limitations. There are some 100 million+ disks out there with MS-DOS FAT formats. This makes it easy to use MMURTL, and more importantly, eases the development burden. No reformatting or partitioning your disks. You simply run the MMURTL OS loader from DOS and the system boots. Rebooting back to DOS is a few keystrokes away. If you want to run DOS, that is. MMURTL has it’s own loader to boot from a disk to eliminate the need for MS-DOS altogether. If you don’t want to write a completely new operating system, some serious fun can had by writing a much better file system than I have included.

Hardware Requirements
This section describes the hardware required to run MMURTL (as included) and to work with the code and tools I have provided. If you intend to build on a platform other that described here, you can ignore this section. The hardware (computer motherboard and bus design) must be PC ISA (Industry Standard Architecture), or EISA. Other 16/32 bit bus designs may also work, but minor changes to specialized interface hardware may be required.


Page 9 of 667

The processor must be an Intel 80386, 80486, Pentium, or one of the many clones in existence that executes the full 80386 instruction set. This includes Cyrix, AMD, IBM and other clone processors. VGA videotext (monochrome or color) is required. 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). One Megabyte of RAM is required, 2 MB (or more) is recommended. MMURTL will handle up to 64 Megs of RAM. MMURTL itself uses about 300K after complete initialization. The Floppy disk controller must be compatible with the original IBM AT controller specification (most are). Both 5.25" and 3.5" are supported. The hard disk controller must be MFM or IDE (Integrated Drive Electronics). IDE is preferred. Some RLL controllers will not work properly with the current hard disk device driver. A standard 101 key AT keyboard and controller (or equivalent) is required. The A20 Address Gate line must be controllable as documented in the IBM-PC AT hardware manual, via the keyboard serial controller (most are, but a few are not). If yours isn’t, you can change the code as needed, based on your system manufacturer’s documentation (if you can get your hands on it). 8250, 16450 or 16550 serial controllers are required for the serial ports (if used). The parallel port should follow the standard IBM AT parallel port specification. Most I have found exceed this specification and are fully compatible. MMURTL does NOT use the BIOS code on these machines. Full 32-bit device drivers control all hardware. Hence, BIOS types and versions don’t matter.


Page 10 of 667

Chapter 2, General Discussion and Background
This section discusses the actual chore of writing an operating system, as well what may influence your design. I wrote one over a period of almost 5 years. Things change a lot in the industry in five years, but I’m happy to find that many of the concepts I was interested in have become a little more popular (small tight kernels, simplified memory management, client server designs, etc.). Reading this chapter will help you understand why I did things a certain way; my methods may not seem obvious to the casual observer. I also give you an idea of the steps involved in writing an operating system.

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. 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, write your own operating system, or even just use MMURTL the way it is. A friend of mine and I began by day dreaming over brown bag lunches in a lab environment. 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. 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. That was 1989. As you can see, Fud’s Law of Software Design Time Estimates came into play rather heavily here. You know the adage (or a similar one): Take the time estimate, multiply by two and add 25 percent for the unexpected. That worked out just about right. Five years later, here it is. I hope this book will shave several years off of your development effort. In Chapter 1, “Overview,” I covered what my final design goals were. 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. Far too often, a piece of software becomes too complex to maintain efficiently without an army of programmers. It usually also happens that software will expand to fill all available memory (and more!). I would not let this happen.

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, and borrowed with great respect, I might add). It does, however, make a lot of sense. MMURTL's design is influenced by all of my experiences with software and hardware, my schooling, my reading of various publications and periodicals, and what I have been introduced to by friends and coworkers. Even a course titled Structured Programming with FORTRAN 77, which I took on-line from the University of Minnesota in 1982, has had an effect. I have no doubt that your background would have a major effect on your own design goals for such a system. Borrow from mine, add, take away, improve, and just make it better if you have the desire. What I don't know


Page 11 of 667

fills the Library of Congress quite nicely, and "Where You Were When" will certainly give you a different perspective on what you would want in your own system. Every little thing you’ve run across will, no doubt, affect its design. My first introduction to computers was 1976 onboard a ship in the US Coast Guard. I was trained and tasked to maintain (repair and operate) a Honeywell DDP-516 computer system. It had a "huge" 8K of hand-wound core memory and was as big as a refrigerator (including icemaker). It was a 32-bit system, and it was very intriguing (even though punching in hundreds of octal codes on the front panel made my fingers hurt). 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. It whet my whistle, and from there I did everything I could to get into the "computer science" end of life. I bought many little computers (remember the Altair 8800 and the TRS-80 Model I? I know, you might not want to). 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, for no apparent reason). In 1981 I was introduced to a computer system made by a small company named Convergent Technologies (since swallowed by Unisys, a.k.a. Burroughs and Sperry, merged). It was an Intel 8086-based system and ran a multitasking, real-time operating system from the very start (called CTOS). Imagine an 8086 based system with an eight-inch 10-MB hard drive costing $20,000. This was a good while before I was ever introduced to PC-DOS or MS-DOS. In fact, the convergent hardware and the CTOS operating system reached the market at about the same time. After I got involved with the IBM PC, I kept looking for the rich set of kernel primitives I had become accustomed to for interprocess communications and task management, but as you well know, there was no such thing. 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, I also tasted "real-time" operation in a multitasking environment. The ability for a programmer to determine what was important while the software was running, and also the luxury of changing tasks based on outside events in a timely manner, was a must. There was no turning back. Nothing about my operating system is very new and exciting. The messaging is based on theory that has been around since the early 1970’s. It is just now becoming popular. People are just now seeing the advantages of message-based operating systems. Message-based, modular micro kernels seem to be coming of age.

The Development Platform and the Tools
My desire to develop an operating system to run on an inexpensive, popular platform, combined with my experience on the Intel X86 processors, clinched my decision on where to start: It would definitely be ISA 386-based machines (the 486 was in it’s infancy). The next major decision was the software development environment. Again, access and familiarity played a role in deciding the tools that I would use. MS-DOS was everywhere, like a bad virus. I had two computers at that time (386DX and a 386SX) still running MS-DOS and not enough memory or horsepower to run UNIX, or even OS/2, which at the time was still 16-bits anyway. I thought about other processors. I purchased technical documentation for Motorola’s 68000 series, National


Page 12 of 667

Semiconductor’s N80000 series, and a few others. The popularity of the Intel processor and the PC compatible systems was, however, a lure that could not be suppressed. As you may be aware, one of the first tools required on any new system is the assembler. It’s the place to start. Utilities and compilers are usually the next things to be designed, or ported to a system. Not having to start from scratch was a major advantage. Having assembler’s available for the processor from the start made my job a lot easier. Of course, in the process of using these other tools, I found some serious bugs in them when used for 32-bit development. The 32-bit capabilities were added to these assemblers to take advantage of the capabilities of the new processors, but not many people were using them for 32-bit development five or six years ago on the Intel based platforms. Operating system developers, people porting UNIX systems and those building 32-bit DOS extenders were the bulk of the users. I began with Microsoft’s assembler (version 5.0) and later moved to 5.1. Two years later I ran into problems with the Microsoft linker and even some of the 32-bit instructions with the assembler, these problems pretty much forced the move to all Borland-tools. Then those were the 2.x assembler and 2.x linker. I’m not a Borland salesman, but the tools just worked better. There were actually several advantages to using MS-DOS in real mode during development. 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.

The Chicken and the Egg
The egg is first... maybe. Trying to see the exact development path was not always easy. In fact, at times it was quite difficult. I wanted the finished product to have it’s own development environment - including assembler, compiler, and utilities - yet I needed to have them in the MSDOS environment to build the operating system. I wanted to eventually port them to the finished operating system. 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 - all but ancient history now. It was definitely a "chicken-and-egg" situation. You need the 32-bit OS to run the 32-bit tools, yet you need the 32-bit tools to develop the 32-bit OS. It can be a little confusing and frustrating. Once again, 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. 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. Memory access was limited, but the tools worked. I even put a 32-bit C compiler in the public domain for MS-DOS as freeware. It was limited to the small memory model, but it worked nonetheless. I really do hate reinventing the wheel, however, in the case of having the source code to an assembler I was comfortable with, I really had no choice. Some assemblers were available in the "public domain" but came with restrictions for use, and they didn’t execute all the instructions I needed anyway. I even called and inquired about purchasing limited source rights to some tools


Page 13 of 667

“CM32: A 32-Bit C Compiler. 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.” Assembly language has its place in the world. in itself. you know that it's usually NOT everything you need. I think many people started there. the cost was prohibitive (they mentioned dollar figures with five and six digits in them.from the major software vendors. even though ANSI standards have fixed much of it. and a few other minor utilities.). The source code to this assembler (DASM) is included with this book on the CD-ROM.) This lead to the development of my own 32-bit assembler. a disassembler. in which case. you will need to read chapter 28. Those that have to maintain code or read code produced by others understand the advantages if they get past the "popularity concept". took a good look at Dave Dunfield's excellent Micro-C compiler. This is obviously for variable length parameter blocks. The C compiler produces assembly language. The details of CM-32 (C-Minus 32) are discussed in chapter 29.0 Page 14 of 667 . There is no linker. including most of the digital logic families (ECL. etc. C can be made almost unreadable by the macros and some of the language constructs.. It’s simply not needed. Now THAT’S a digital nightmare. or may not. I mention this now to explain my need to do some serious C compiler work. Nothing is harder to troubleshoot than a stack gone haywire.) in the machine was actually a little exciting. etc. I started with a public-domain version of Small-C (which has been around quite a while). and the called function cleans the stack. but another port is in the works. Some very important pieces are still missing from the compiler. Developing this assembler was. no linker. Most of the machine-dependent items are left completely external to the language in libraries that you may. The biggest problem was accumulating all of the technical documentation in adequate detail to ensure it was done right. but it leads to some serious trouble on occasion. DMA. you understood it perfectly) MMURTL V1. parameters passed from right to left and the fact that the caller cleans the stack. But there is nothing like a high level language to speed development and make maintenance easier. I have worked for years with various forms of hardware. I actually prefer Pascal and Modula over C from a maintenance standpoint. I dealt with both the electrical and timing characteristics. But it gave me some serious insight into the operation of the Intel processor. Communications controllers. (Unless you were the person that wrote that particular manual. TTL. a major task. That’s right. but I pretty much built from scratch. The thought of controlling all of the hardware (Timers. want or need. 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. MMURTL uses the Pascal conventions (refereed to as PLM calling conventions in the early Intel days). DC. as well as the software control of these devices. I don't like the calling conventions used by C compilers. Parameters are passed left to right. “DASM: A 32-Bit IntelBased Assembler. But C's big strength lies in its capability to be ported easily. which is fed to the assembler.. That set me back four months. To fully understand this. I was into Pascal long before I was into C. Needless to say. at least.

x. 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. all implementations of UNIX). "Thunking" was out of the question. Windows version 3. This easily consumed the first six months. the BIOS code is not 32-bit (not even on 32-bit machines).0 Page 15 of 667 . anyway (e. Even machines that were technically superior in most every respect died horrible deaths (remember the Tandy 2000?). [Thunking is when you interface 32.x. Some processors (such as the Intel 32-bit X86 series) force a specific element size for the stack. A variety of more recent operating systems go directly to a lot of the hardware just to get things done more efficiently. such as Chips & Technologies. but this is transparent to the programmer. BIOS code brings new meaning to the words "spaghetti code. An example would be making a call to a 32-bit section of code.).g. and ancillary logic design form the basis for the 32-bit ISA internal architecture. Many bus designs now out take care of this problem (PCI." It’s not that the programmer’s that put it together aren’t top quality. 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. and the Priority Interrupt Controller Unit (PICU). I do so because it’s bus. 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. and technical manuals were used throughout the early development of my operating system. This generally involves manipulating the stack or registers depending on the calling conventions used in the destination code.and 16-bit code and data.. etc. OS/2 version 2.] 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). I’m not going to tell you I did some disassembly of some BIOS code. even the operating system writer. articles. IBM published theirs. I MMURTL V1. They give you the overall picture of how everything in the system ties together. discreet devices in the IBM AT system. from a 16-bit code segment. as well as the stack storage. It is also not designed to be of any real value in most true multitasking environments. supply excellent little documents that give even finer details on their particular implementation of the AT series ISA logic devices.You will need to accumulate a sizable library of technical documentation for your target hardware platform if you intend to write an operating system. This made the IBM designs the natural place to start. which is 32 bits for the most part. or even port MMURTL to it. Computer chip manufacturers. I knew from the start that this would be a necessity. Besides. Many different books. In fact. timers. the external bus interface on the ISA machines is still 16 bits. The IBM PC-AT and Personal System/2 Technical Reference Manuals were a must. EISA. I plead the "Fifth" on this. During this time. SX machines still have 16-bit memory access limitations. Following superfluous JMP instructions is not my idea of a pleasant afternoon. it’s the limitations of size (memory constraints) and possibly the need to protect trade secrets that create the spaghetti-code effect. interface design. But as you may be aware. It is tied to the internal bus. These documents are usually free. I keep referring to the IBM AT system even though it was only an 80286 (16-bit architecture). as you may understand. documents.

There wasn't even a file system! Test programs were actually built as small parts of the operating system code. If two programs in memory must exchange data and they are based on different selectors. My initial thought on memory management centered on segmentation. If you intend to use the Intel processors and start with MMURTL or the information presented in this book. The largest of which would mean that all memory access would be based on 48-bit (far) pointers.0 Page 16 of 667 . don't lose sight of the forest for the trees. If you write your own system. and the primary emphasis was on the kernel and all that that implies. but it's not at as complicated. this is a year you won’t have to go through. In chapter 5. gentler" environment I was after. The Real Task at Hand (Pun intended) All of the hardware details aside. It took almost two years to get a simple model up and running. powerful. and the entire system was rebuilt for each test. There was no loader. my original goal was a multitasking operating system that was not a huge pig. no way to get test programs into the system. Memory Management. you must create an alias selector to be used by one of the programs. One other consideration on the memory model is ADDRESS ALIASING. a speed penalty is paid because of the hardware overhead (loading shadow registers. I touched on what I wanted in the overview. It would have appeared as a flat memory space to all the applications. easy to use system with a tremendous amount of documentation.was also building the kernel and auxiliary functions that would be required to make the first executable version of an operating system. etc. All of the early test programs were in assembler. This would have given us a zero-based addressable memory area for all code and data access for every program. It has turned out very nicely. The thought was rather sickening. When the 386/486 loads a segment register.two segments per program and two for the OS. If you use Intel-based platforms. but not a fully segmented model . I had to meet my design goals. The second six months was spent moving in and out of protected mode and writing small programs that used 32-bit segments. you'll be ahead of the game. If you start from scratch on a different processor you will go through similar contortions. and some initial testing showed that it noticeably slowed down program execution. because of what you get on this book’s CD-ROM. This simple model could allocate and manage memory. and had only the most rudimentary form of messaging (Send and Wait). as you will see. but putting in on paper and getting it to run are two very different things. This was time-consuming. aliasing is also required. of course. anyway. It was a "kinder. MMURTL V1. but instead a lean. but it presented several serious complications. In a paged system. I discuss several options you have. The segmented memory idea was canned. but necessary to prove some of my theories and hunches.). and I went straight to fully paged memory allocation for the operating system. all based on their own selectors.

But.0 Page 17 of 667 . This is called the critical path." It is difficult for me to do this also. It may be RISC. you know that certain items in a project must work.A Starting Point If you have truly contemplated writing an operating system. and overall concepts concerning operating system design. it may be CISC. If you've ever done any large-scale project management. If this has never happened to you. how much knowledge you have. I have provided such an operating system with this book. I wish there had been some concrete information on really where to start. If you are writing an operating system for the learning experience (and many people do). Select your hardware platform. if you are interested in embedded applications for equipment control. The Critical Path Along your journey. • MMURTL V1. or how much time you really have to invest. and there will also be background noise. only to find out that it had incorporated many of the pieces I needed – I simply hadn’t understood them at the time. get to know it very well. it is by no means the only operating system source code available. Chapter 3 and 4 (Tasking Model and Interprocess Communications) will give you plenty of food for thought on the tasking model. Many times I have taken a piece of software that I didn't fully understand and "gutted" it. The books I have purchased on operating system design gave me a vast amount of insight into the major theories. I was really interested in communications. but there wasn’t. you know that it can be a very daunting task. The theory behind what you want is the key to a good design. and you may find you really have something when you're done with it. but whatever you decide on. If you choose to modify an existing operating system. but I'm very serious. I'll give you a list of these items in the approximate order that I found were required while writing my system. you'll NEVER really be done with it. you'll find that there are major items to be accomplished. Your target application will help you determine what is important to you. Of course. You may need to choose a hardware platform prior to making your memory model concrete. Know your desired application. at the beginning of the yellow brick road. Or at least when you get it working. but they just didn't say. The easy way out would be to take a working operating system and modify it to suite you. This includes the tasking model. memory model and basic programming interface. I'm not going to recommend any others to you. "You start here. • Decide on your basic models. study what you have and make sure you completely understand it before making modifications. But I'll try. Things change pretty rapidly in the computer industry. Try to ensure it will still be in production when you think you'll be done with your system. or theories must be proved in order for you to continue along the projected path. For example. because I don't know exactly where you're coming from. I envy you. This may sound a little humorous. the actual application may not be so important. You'll probably be able to see that as you read further into this book. I say this from experience. you may want to concentrate on the device and hardware interface in more detail.

of course.everything from device drivers. I'll admit that I had to do some major backtracking a time or two. There are so many platforms out there. Even though you may think that the programmers that wrote those tools know all there is to know about them. and plenty of "theory food" for the engine (your brain).• • • • Investigate your tools. I had my wish list well in advance. garbage collection. Working Attitude Set your goals. Go for the kernel. will depend on the memory model you choose.anything that you think you’ll need to remember. hints are about all I CAN give you. Don't wish for a Porche if what you need is a good. I blamed myself several times for problems I thought were mine but were not. 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. I spent a good six months playing with assemblers. and also dive into some more challenging possibilities. The level of complexity. dependable pickup truck. This can be tricky because you may be playing with hardware and software in equal quantities. Work with small. As I mentioned above. But don't set them too high. do it. unless you go with the same hardware platform I did. Memory management is next. If you have to build a prototype running under another operating system. Get to know your assemblers. Sure the Porche is nice…but think of the maintenance (and can you haul firewood in it?)Make sure your tools work. Document the problems. I actually had to switch assemblers in midstream. and overall program management – as you see fit. Problems with programming tools were something I couldn't see in advance. easily digestible chunks. the rest is real work. If you've gotten past the fun parts (kernel and memory). In chapter 5 (Memory Management) I give you some options. It will be worth the time spent.0 Page 18 of 667 . Play with the hardware Ensure you understand how to control it. This includes understanding some of the more esoteric processor commands. Nothing is worse than finding out that the foundation of your pyramid was built on marshmallows. You can then “port” the prototype. You can pretty much take these pieces . compilers. These tools include documentation for the hardware. Creating a realistic wish list may be one of the hardest jobs. I moved on from there. and utilities to ensure I was “armed and dangerous.” Once satisfied. the discoveries . Make certain you understand the capabilities and limitations of them. MMURTL V1. Even if you have to write four or five small test programs to ensure each added piece functions correctly. you will no doubt find bugs. it's worth the time. compilers and utilities. Chapter 6 (The Hardware Interface) will give you some hints on this. I'll tell you the easiest. Operating systems use processor instructions and hardware commands that applications and even device drivers simply never use.

It will be blood. Good luck. it won’t be luck. MMURTL V1. I write the documentation.Document as you go. I actually wrote major portions of my programming documentation before the system was even usable. Sounds backwards? It is. I lay out my notes for what I want. all the way. and then I write the code. but it gave me great insight into what a programmer would see from the outside.0 Page 19 of 667 . sweat and maybe some tears. But really.

be specific about it. but some of it can also be attributed to laziness (yes. its hardware and software state are saved while the next task’s state is restored and then executed. Generally.Chapter 3. I use the same definition as the Intel 80386 System Software Writer's Guide and the 80386 and 80486 Programmer's Reference Manuals. 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." which means communications between tasks. The Tasking Model This chapter discusses the tasking model and things that have an affect on it. it probably isn't much more than two or three kilobytes of code. I have seen the term thread applied to additional tasks all belonging to one program. it is responsible for the way the CPU's time is allocated. After all. however. Terms We Should Agree On One thing the computer industry lacks is a common language.” while others call it a “thread. I admit some guilt here too). When a task is suspended from execution. It also seems that a lot of people use the term and they don't even know what it is. KERNEL . If you write your own. or at least coming to agreement on them. One computer term may mean six different things to six different people. This might allow me to use the latest techno-buzz-word "microkernel" if I were into buzzwords. Some operating systems call a task a “process. at least temporarily. Tell the programmers what you mean. while your reading this book or working with MMURTL.This term is not disputed very much.A task is an independently scheduled thread of execution. coffee and soda spilled on them. You may not agree with the definitions. A single computer program can have one or more tasks. In other words. CPU utilization. but I hope you will accept them. so I imagine most of you will be working with it. Most of the problem is caused by hype and deceptive advertising.0 Page 20 of 667 . and I’m not talking about programming languages. But this is not cast in stone either. The kernel of an operating system is the code that is directly responsible for the tasking model. Most of the discussion centers on resource management. People use terms in everyday conversation without really trying to find out what they mean. may use other words that mean the same thing such as "interprocess communications. You can transform the same 50 processor instructions in memory into 20 independent tasks.” In most instances I call it a TASK. more highlighter and chicken scratching than you can imagine). It really depends on the documentation with the operating system you are using. but it is used a lot in the computer industry. The operating system MMURTL V1. This makes it a very small kernel. Some documentation. TASK . The Intel based hardware is by far the most available and least expensive. resource management is supposed to be what a computer operating system provides you. In it's executable form. and more specifically. I have to define some terms just to make sure we’re on common ground. Before I begin a general discussion or cover your options on a tasking model. This is convenient if you want to refer to these documents while reading this book.

PREEMPTIVE MULTITASKING . MS-DOS TSRs . 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).Terminate and Stay Resident programs). you will see the rest of your operating system fit around this decision. 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). Interrupt Controller Units. good old Albert). and most people refer to. Consider your resources when planning your operating system. Hence. but doesn’t expect the interrupt. 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. disk. These resources must be managed properly for all these things to work effectively in a multitasking environment. The resources include CPU time sharing (tasking model). including video.). One of my major goals in designing MMURTL was simplicity. It’s not because they’re from the days of Neanderthal computing. Input/Output (I/O) devices. all the grumbling and heated discussions I see on the on-line services. but because they are the lowest level entry points into the kernel code of the operating system. memory management. One of my friends suggested the motto. DMA. It shouldn’t be. but not simpler" (otherwise it wouldn't work!). it’s PREEMPTIVE. Managing these resources in a simple. I have attempted to live up to that motto while building my system as a resource manager. and communications makes use of these basic resources to do their jobs. The preemptive multitasking I’m talking about. One type of preemption that occurs all the time is hardware interrupts. To paraphrase a famous scientist (yes. MMURTL V1. There are many ways to do this in a computer operating system. etc. save it’s state. These are the real resources. I’m not including them in this discussion because they generally return control of the processor back to the same interrupted task. "Everything should be as simple as possible. Everything else uses these resources to accomplish their missions. and non-multitasking systems use them to steal time from programs and simulate true multitasking (e. How and when it does this is driven by its tasking model. When you determine exactly what you want for a tasking model. An operating system that you use may be preemptive. it didn't sit too well with me. keyboard. "simple software for simple minds. and hardware (Timers. The word preempt simply means to interrupt something that is currently running. but it is.functions that directly affect CPU tasking are called kernel primitives.0 Page 21 of 667 .g. Resource Management An operating system manages resources for applications and services." but needless to say.. yet effective fashion is paramount to building a system you can maintain and work with. This is its only real purpose in life.This is a hotly disputed term (or phrase) in the industry. then start another task running.

On the other hand. I have written many commercial applications that span a wide variety of applications. It may have memory assigned to it. and why. My operating system is designed for microcomputers. 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. 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.0 Page 22 of 667 . and how resources are assigned to these tasks was easy for me to decide. In each case.” In some operating systems each task may be considered a completely independent entity. Tasks end. but it may not be so easy for you. when I look back at the various jobs this software had to handle. It is not a UNIX clone by any stretch of the imagination. then still be there after all the child processes are long gone (for the next user). MMURTL V1. But in almost all of these systems. Some are communications programs (FAX software. Multitasking. keeping track of tasks is no simple “task. If you decide that a single task may exist on it's own two feet. rather than a mini or mainframe environment (although this line is getting really blurry with the unbelievable power found in micro CPUs these days). When I wrote these programs. take a good look at the TSS (Task State Segment). when the mission was accomplished. there seemed to be no purpose to have additional tasks that continued to run. Nor did I want it to be. The operating system I wrote is not multi-user. or even network connections for logons. When you get to Section IV (The Operating System Source Code). All of these things must be taken into consideration when managing tasks. or the user was done. You may want to deal with many.Task Management Tasks begin. each program had a set of things that it had to accomplish to be successful (satisfy the user). Quite obviously. comms programs). Tasks crash. 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 ability to spawn additional tasks to handle requirements for simultaneous operations came in very handy. In a system that is designed for multiple terminals/users. The largest factor was that I had a CPU in front of me. some deal with equipment control (interaction with a wide variety of external hardware). Single User. tasks may be created to monitor serial ports.In other words. each of the functions dealt with a specific well-defined mission. I deal with one user at a time. and it may even run after the task that created it is long gone. execute programs as the new users desire. In other words. How tasks are handled. This is where all of the resources for my tasks are managed. and some are user-oriented software. Probably the best way to get this point across is to describe what I decided. it may be completely dependent on its parent task or program. and each of the places these programs executed also had a CPU . then you must make sure that your task management structures and any IPC mechanisms take this into account. Single vs. From this information you will be able to judge what's right for you.

Do you know where your CPU is?" A funny question. 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. This will depend on the processor you are using and possibly on external paging circuitry. You can define a structure to maintain the items you need for each task. there are instructions on some processors to save all of the necessary registers onto the stack. this is usually a no-brainer. then simply change a pointer to the current state structure for the task that is executing. 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. they generally assume that it’s the only thing running on the computer system. you change this variable to tell you which task is currently executing. Then I asked. "What is that thing doing?" When I used my first multi-user system. My first programs were written with that attitude. but an important one. You will have to determine this from the technical manuals you accumulate for your system. or it may be dozens of things that have to be saved and restored with each task switch. I didn’t worry about how long the operating system took to do something unless it seemed like it wasn’t fast enough. the task that is running must have its hardware context saved. you switch to its stack and pop the registers off. The hardware context in its simplest form consists of the values in registers and processor flags at the time the task stopped executing. "It’s 11:00PM.0 Page 23 of 667 . albeit MMURTL V1. When you change tasks. When you restore a task. The hardware state may also consist of memory context if you are using some form of hardware paging or memory management. If you must do it yourself. What instructions are executing? Where is it spending all it’s time? And why? When an application programmer writes a program. Yet. 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 software state can consist of almost nothing. there were many times that I thought it had died and gone to computer heaven. CPU Time CPU time is the single most important resource in a computer system. In the case of hardware interrupts. What WAS that thing doing? It was being shared with a few dozen other people staring at their terminals just like I was. Then all you have to do is switch stacks. The CPU takes care of it for you. It’s actually easier than it sounds.The Hardware State When a task switch occurs. We were each getting a piece of the pie. This is where the hardwarestate is saved. it would seem to return at its leisure.

In this case. In a multitasking operating system. Multitasking I categorize forms of multitasking into two main groups. There were so few factors to be concerned with on that early system. and single thread of instructions suited me just fine. management of CPU time is easy. In fact. a single threaded system may be the best solution. I had my very own processor. many operating-system tasking models are a combination. In a single tasking operating system. and in some cases. I also knew exactly how many microseconds it took to switch between them. each task runs until it decides to give up the processor. Cooperative Multitasking In a solely cooperative environment. or it is preempted (the processor is taken away from the task).0 Page 24 of 667 .not a very big piece it seemed. but usually to wait for a resource that is not immediately available. A task that has the processor gives it up. The major point that you must understand here is that there are really only two ways a task switch will occur on ANY system. ensuring that the CPU time is properly shared is the job of kernel and scheduling code. interrupt servicing. and I could pretty much figure out what it was doing most of the time. however. These forms of CPU time-sharing suffer from the disadvantage that they are not in complete control of all resources. You may also be aware that multitasking is simulated quite often in single tasking systems by stealing time from hardware interrupts. you basically have a hardware manager. Many methods have been devised to allow single-tasking operating systems to share the CPU among pseudo-tasks. do an adequate job in many cases. if the system allows this (a sleep or delay MMURTL V1. or a combination of the two. Microcomputers spoiled me. These have been in the form of special languages (Co-Pascal). which I cover in this chapter. Many of the programs I wrote didn’t require multitasking. This will be the heart and brains of the system. I knew exactly how long each of my two tasks were going to run. It was crude. Cooperative and Preemptive. There are other important factors that apply to these two types of multi-tasking. An operating system’s tasking model can be one of these. Single Tasking What if you don’t want a multitasking system? This is entirely possible. and basic input/output. This can be for a number of reasons. The only thing for the programmer to worry about is how efficient the operating system is at its other jobs such as file handling. They can. It was 1980 that I wrote my first time slicer which was built into a small program on an 8-bit processor. but effective. or to pause when it has nothing to do. it was a breeze. This is done by tacking your code onto other code that is executed as a result of an interrupt. and also can be done with mini-multitasking kernels you include in your programs.

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.). or a few other hardware-related things. however. disk drive. This may be in milliseconds. a program. In many single tasking systems. 10ths of seconds or even seconds. it is a good idea to have some form of a preemptive system. Preemptive Multitasking The opposite end of the spectrum from a fully cooperative system. the operating system decides which of the tasks will run next (if any are ready to run at all). if necessary. the screen and everything else looks frozen. For instance. As I also mentioned earlier. If the operating system uses this period of time to suspend your task and execute another. he then monopolizes the CPU. and the computer looks dead for all practical purposes.0 Page 25 of 667 . after the interrupt is finished. and maybe memory context. you are cooperating with the operating system.function). hardware timer. I’m sure you’ve run across the need to delay the execution of your program for a short period of time. Because such "CPU hogs" are not only possible but also inevitable. the entire "state" of your task is not saved." From there. a program that is running makes an operating system call to get the time of day. this actually happens every time a hardware interrupt is activated. etc. It is then making wise use of your request to delay or sleep. programs (actually the programmer) may have to be aware of things that must be done to allow a task switch to happen. it doesn’t expect it. This implies that the programmer does part of the scheduling of task execution. If this isn’t the task that the user is involved with. or the operating system from an external device (e. And yes. the CPU is returned to the task that was interrupted. communications device. there’s some task in there calculating pi to a zillion digits. To expand on that definition. Most of the time. you pass a parameter that indicates how long the program should pause before resuming execution. and you have used a library or system call with a name such as sleep() or delay(). MMURTL V1.. I defined preemptive at the beginning of this chapter. This can occur between any two CPU instructions that it is executing (with some exceptions discussed later). the ability to cut a task off at the knees. Meanwhile. This is in the form of the task saying to the operating system. when a hardware interrupt occurs on most hardware platforms. This is an operating system that can stop a task dead in it’s tracks and start another one. when a task is preempted. hardware interrupts occur to pass information to a device driver. It does nothing to prepare for it. is a preemptive system.I’m done with the CPU for now. 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. Generally. Also.g. However. There is a major problem with a fully cooperative system. it’s only the hardware state which includes registers. "Hey . 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). It may not always be obvious when it happens. that’s a waste of good CPU time if something else can be done. An important point here is that in a cooperative system.

The rest of the tasks are waiting for their shot at the CPU. MMURTL V1. or waiting for outside resources such as I/O. This synchronization may be required for human interaction with the computer which will not be that critical. The next section is the tricky part. this adds the requirement of timing. only one task is running at a time (excluding the possibility of multiprocessor systems). the scheduler simply determines who’s next. This is usually until it willingly surrenders the processor. quite often. Just to review these ways (to refresh your RAM). On most platforms. 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. 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). They are also.communicating. When do we switch tasks. For the preemptive system. we must now decide which task runs next. and How Long? The procedure or code that is responsible for determining which task is next is usually called the Scheduler. 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!). In a preemptive system. controlling. Task Scheduling Given the only two real ways a task switch can occur. and how long does that task get to run? Who. This is where the scheduling fun really begins. The currently running task is preempted. the scheduler also determines how long a task gets to run. there is hardware (programmable support circuitry) that allows you to cause a hardware interrupt either at intervals. or it may be for something extremely critical. Scheduling Techniques In a multitasking system. these two reasons are: 1. 2. When. to which task do we switch. or monitoring. which means the processor is taken away from the task without its knowledge. The timer can help determine when to preempt. connected to the outside world . or after a set period of time. The programmer (or the task itself) determines how long a task will run. A task that has the processor decides to give it up. In fully cooperative systems.0 Page 26 of 667 .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. "a slice of time. and if a user interface is involved. This gives MMURTL V1. For example. there is going to be a desire. the scheduler.) 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. A simple queuing system suffices. When a task indicates it is done by making certain calls to the operating system. the task that’s running. each task will get a fixed amount of time . Many early multitasking operating systems were like this. The others may see a slow down. For example. but they used a time slicing scheduler based on a hardware timer to divide CPU time amongst the tasks that were ready to run.As you can see. But it may be just fine in some circumstances. it is suspended and goes to the end of the queue.0 Page 27 of 667 . This would even take care of the situation where your favorite employee falls asleep on the terminal and orders 8000 HuMonGo Burgers by mistake. This way you can picture what each technique has to offer. Time Slicing Time slicing means we switch tasks based on set periods of execution time for each task.all tasks will get the same slice . Prioritized Cooperative Scheduling A logical step up from a simple first-in/first-out cooperative queue is to prioritize the tasks. This is usually not very effective. and the next task in line is restored and executed. We’re going to look at several techniques as if they were used alone. but it won’t be on their terminals." This implies the system is capable of preempting tasks without their permission or knowledge. or even a requirement.and they will be executed in the order they were created. In the simplest time sliced systems. 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. When a task’s time is up. 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. In some systems. for some form of communications between several parties involved in the scheduling process. (the others will be flipping 8. Each task can be assigned a number (highest to lowest priority) indicating it’s importance. outside events. each task gets 25 milliseconds.000 burgers. These parties include the programmer. its complete hardware and software state is saved. simple time slicing may be adequate or at least acceptable. Not quite that simple. it can make for some pretty choppy user interaction.

otherwise. We want good. or by the programmer (or user) telling the scheduler it needs it. They do so many things for us that we simply accept them in our everyday lives.0 Page 28 of 667 . They can be waiting for something such as I/O. computers are everywhere. This also means we just can’t assign simple priorities in a time sliced system.the scheduler something to work with. In a cooperative system. And the programmer can’t even determine in advance how much time he or she will need. and we want our machines responsive to us. Some form of signaling can even increase time allocation based on other outside events. 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. Other Scheduling Factors You know as well as I do. When combined with an adequate form of interprocess communications or signaling (discussed in Chapter 4). Who determines which task gets more time. the one with the highest priority will be executed. this means that of all the tasks that are ready to run. Because it is still based on a preset period of time. or how much time there is to go around on the system. MMURTL V1. if we preempted a task without it telling us it was OK to do so. Variable Time Slicing In a preemptive system. things are never as simple as they seem. This also implies that some may not be ready at all. dependable. than to execute the tasks in a sequence of equal time segments based solely on a priority assignment. only the very highest would ever get to run. 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. These are communications and user interface issues. you must assume that it’s ready to run immediately. the possibility of being preempted before a certain important function was accomplished always exists. it makes more sense to increase the time allocation for more important tasks. But it has some major drawbacks. 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-torun and select the best based on what the programmer thinks is important. it can serve admirably. Many of these things depend on real-time interaction between the machine and the world to which it is connected. To know in advance if it will actually be used at that rate is another story entirely. clean communications. The tasks that need more time can get it by asking for it. In a time sliced preemptive system. Time slicing is an old technique. Two very important areas of computing have been moving to the forefront of the industry.

Some of the best examples of real-time computing are in robotics and equipment control. Being forced to wait 200 or more milliseconds to perform a critical safety task may spell certain disaster. This is a simple example (actually a common problem).g. This implies that some form of communication is required between the sensors (e. MMURTL V1. (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. or his FAX is garbled because buffers went a little bit too long before being emptied or filled. The buffer will overflow. If I thought my operating system would be used in this fashion. The reaction is always near meltdown proportions. 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 not to say that time-sliced systems have poor interrupt latency. This IS real-time stuff. In a message based system. but one channel is handling five times the data.0 Page 29 of 667 . Let’s look at one that controls a nuclear reactor and all other events at a nuclear power plant. Thus. outside events MUST be able to force (or at least speed up) a task switch. take two communications programs that handle two identical ports with two independent Interrupt Service Routines (ISRs). and prioritization of these tasks will be critical. In the case of the nuclear reactor operating system. For instance. we definitely want to be able to preempt a task.. I would have spent more time on the tasking model and also on interrupt service routines. 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. The programs that run on this computer may have some snap decisions to make based on information from outside sensors. the ISR can send a message to the program servicing it to tell it the buffer is almost full ("Hey. hardware interrupt service routines) and the scheduler. If the programs have an equal time slice and equal buffer sizes. Now. come do something with this data before I lose it"). 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 program servicing the busier port may lose data if it can’t get back to the buffer in time to empty it. it requires the ability to juggle tasks on the system in real-time. I think you can see the advantages of a priority based system. A computer in charge of complete climate control for a huge building may be a good example. but it’s not dramatic enough. but they have problems with unbalanced jobs being performed by the tasks. but it makes the point. But I opted to "get real" instead. Therefore. you know that it can lead to serious stress and maybe even hair loss. one of the most volatile reaction I can think of is when a user losses data from a communications program. If you have ever had the pleasure of writing communications-interrupt service routines on a system that has a purely time-sliced model. 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. or the user.

solely time-sliced system will see the system slowdown as the time is equitably divided between an increasing number of users or tasks. all of the examples in the next sections have preemptive capabilities. and I discuss all of that in the next chapter. I didn’t want to cloud the tasking issue with the required communications methods. More Cooperative Building on the previous mix (some cooperation). A task may. all tasks will get to run. you have many options for scheduling techniques: • • • • • 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. To be honest. additional signaling or messaging could be incorporated in the system to allow a task to indicate that it requires more time to execute. This MMURTL V1. I can’t picture a system without it. Time Sliced. time-slicing all the tasks is suitable (even desired) to accomplish what was initially intended by the system builders. give up the CPU if it’s no longer needed. although such systems exist. and even help define the tasking model. even though these methods are very important. This would put the task back into the queue to be executed when surrendered the CPU. Some Cooperation In this type of model. it would still be scheduled to execute. you have a few ideas of your own. No doubt. however. and will execute for a predetermined amount of time. I invite you to experiment with your own combinations. Fully Time Sliced. If such were the case (nothing to do) it would simply surrender the CPU again.0 Page 30 of 667 . In addition. That’s the only way you’ll really know for sure. You may opt to give certain tasks a larger execution time based on external factors or the programmer's desires.Mixed Scheduling Techniques As you can see. In this type of system. Users on a larger. all tasks will be scheduled for execution. In a system that was designed for multi-user application sharing (such as UNIX originally was). Writing communications tasks for these types of systems has always been a challenge (and that's being nice about it!). In the next sections we’ll look at a few ways to mix them. All of these ways require some form of signaling or inter-process communications. See what works for your applications. But even when the task has nothing to do.

The Tasking Model I Chose I’ll tell you what I wanted. Limited Time Slicing In this type of system. This means that the programmer sets the priority of the task and the scheduler abides by those wishes. real-time operating system. I wanted a priority based. Remember the saying: "Be careful what you ask for. and no. I made MMURTL listens to the programmer. the outside events are even more important than placating the user. MMURTL task switches occur for only two reasons: MMURTL V1. "Experience is a wonderful thing. To me. rapid calculation would hold the reins on a CPU hog. the user IS an outside event! This put my system in the Primarily Cooperative. This could be in the form of a maximum percentage of CPU time allowed. multitasking. My goal was not just to equitably share the processor among the tasks. 0-15 (the highest) may be sliced together giving the lowest numbers (highest priorities) the greatest time slice. you may get it!" Let me repeat that my goal was a real-time system. tasks are prioritized and generally run until they surrender the CPU. To briefly explain my tasking model. After all. if you have 256 priorities. There would have to be additional mechanisms to prevent poorly written tasks from simply gobbling up the CPU." I borrowed that quote from a sugar packet I saw in a restaurant. 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). the task that is executing was always the highest priority found on the queue. prioritization of tasks is VERY important. then you can make your own choice based on my experiences. 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. Primarily Cooperative. These tasks would always be the highest priority task queued on the system. based on the number of tasks scheduled. In such a system. It allows you to recognize a mistake when you make it again. For instance.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. I never wanted to miss outside events.0 Page 31 of 667 . Primarily Cooperative. preemptive. 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. A very simple. but to use the programmer’s desires as the primary factor. and in reality. Only in circumstances where multiple tasks of the same priority are queued to run would time slicing be invoked. Keep in mind that on a prioritized cooperative system. it wasn’t Ferdinand’s Burger Emporium. Limited Time Slicing category.

This could have presented some protection problems. 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. The currently running task can’t continue because it needs more information from the outside world (outside it’s own data area) such as keystrokes. In chapters 18 and 20 (The Kernel and Timer Management) I discuss in detail how I melded these two models. which provides a piece of the scheduling function. much faster. If it detects that one or more tasks with priorities equal to the current task are in the ready queue. In such a case it sends a "request" or non-specific message. 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. goes into a "waiting" state. and another one to return it to the task that was interrupted. and they will also be used for execution timing on some preemptive systems. It’s faster – actually. The task based ISR is slower. Other processors may offer similar options. This is a speed issue. I decided that MMURTL ISRs would execute in the context of the task that they interrupted. This is the preemptive nature of MMURTL. and limit the functionality if at all possible. and also the possibility of memory-management headaches. Keep them short. 2. it will initiate a task switch after a set period of time.PERIOD. timer services. An outside event (an interrupt) sent a message to a task that has an equal or higher priority than the currently running task. file access. A complete processor task change must occur for the ISR to execute. When it detects that a higher priority task is in a ready-to-run state. but because ISRs all execute code at the OS level of protection. monitors the current task priority. This will usually be during the allocation of critical shared operating system resources and certain hardware I/O functions. and all programs on the system share this MMURTL V1. or whatever. This in itself does not immediately cause a task switch. I bring them up in this chapter because how an operating system handles ISRs can affect the tasking model. and the next task with the highest priority executes. as well as checking for the highest priority task that is queued to run. 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 less time the better. Interrupt Handling Hardware Interrupt Service Routines (ISRs) have always been interesting to me. This is the only "timeslicing" that MMURTL does. Go for the fastest option . The timer-interrupt routine. it will initiate a task switch as soon as it finds this.1. I have learned two very valuable lessons concerning ISRs.0 Page 32 of 667 .

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. you switch tasks. It is determined by hardware that may be completely out of your control. the problems didn’t surface. and they are surely out of your control most of the time. 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). In a cooperative environment. who’s to say when Mr. the generic clock interrupt may be the basis of most task switches. (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. When you get to chapter 4 (Interprocess Communications) you’ll how messaging ties into and even helps to define your tasking model. or to suspend one or more through the Priority Interrupt Controller Unit. This means that your operating system must be prepared at all times to be interrupted. For instance.0 Page 33 of 667 .only the user knows. After a period of time has elapsed. or Ms. This was due to MMURTL’s memory management methods. and MMURTL’s specific methods in chapter 19. or even possibly the users.common OS-base addressing. I discuss memory management in chapter 5. On systems that are primarily time sliced. User is going to press a key on the keyboard? That’s right . MMURTL V1. An important thing you should understand is that you don’t have a big choice as to when an ISR executes. 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).

portions of the code will not be reentrant. Of course.Chapter 4. nor should they have to. only one task can execute at a time. or is suspended when it can’t run. As I described in earlier chapters. This means that unless interrupts are suspended while one task is MMURTL V1. I can’t get away from it here because it’s tied in very tightly with Inter Process Communications. How the operating system decides when a task is ready to execute. I discuss reentrancy in a later section in this chapter. everyone can allocate memory in an operating system.0 Page 34 of 667 . In other words. he or she doesn’t see most of the action occurring in the system. Some resources may be available to more than one task at a time. If you remember the discussion in chapter 3 (Task Scheduling) then you know that a task switch can occur at just about anytime. the IPC mechanisms play a very important part in the overall makeup of the system. On a single CPU. their task may be suspended until that resource is available or until another task is done with it. you should have good idea what your operating system will be used for before you determine important things like it’s tasking model and what forms of interprocess communications it will use. 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). You will also notice that even though I covered task scheduling in the previous chapter. Synchronization Synchronization of the entire system is what IPC all boils down to. When the programmer makes a simple system call for a resource or I/O. As you will see in this chapter. Interprocess Communications Introduction This chapter introduces you to some of your options for Interprocess Communications (IPC). while others are only available to a single task at any one point in time. and also how it is scheduled will depend on the messaging facilities provided by the system. 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. where it waits. An example of a resource that may only be available for one task at time is system memory allocation routine. Messaging and Tasks A task is a single thread or series of instructions that can be independently scheduled for execution (as described earlier). Looking at this synchronization from the application programmer’s point of view.

This is one place where the IPC mechanism comes into play and is very important. As you can see. They also are used to signal the beginning or ending of an event. and more than likely will be used to control access to one or more functions that the owner task provides. or to ensure some other required condition on the system is met before the calling task is scheduled to execute." In most cases. some systems allow private semaphores that only related tasks can access. You can’t suspend interrupts for extended periods of time! I also discuss this in detail later in this chapter. MMURTL V1. or schedule tasks for execution. there are public calls that allocate semaphores. When a semaphore operation is performed and the calling task is suspended. The arguments (or parameters) to these calls usually include the semaphore ID (or where to return it if it’s being allocated).in the middle of a memory-allocation routine. In operating systems that use semaphores. Semaphores One common IPC mechanism is Semaphores. destroy tasks. what operation you want to perform. This allows you to lock out tasks as required to prevent simultaneous execution on non-reentrant code (they play traffic cop). However. semaphores are used for signaling and exchanging information. 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. Semaphores are code routines and associated data structures that can be accessed by more than one task. such as those procedures that create new tasks. and finally destroy (deallocate) the semaphore and it’s associated structures when no longer required. it could cause some serious problems.0 Page 35 of 667 . and the library code or code contained in the system will perform the semaphore operation which ensures synchronized access to it’s code (if that’s it’s purpose). 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. This identifier is generally associated with the ID of the task that created it. As the name implies. it is waiting at that semaphore for a condition to change before proceeding (being restarted to execute). He or she will make a system call for some resource. semctl() which controls certain semaphore operations and semop() which performs operations on a semaphore. perform operations on the semaphore structures. the application programmer won’t even know that a semaphore operation is being performed. 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. Typical semaphore system calls are semget() which allocates a semaphore. this task could be suspended and another task could be started. the semaphore mechanism would have to be coupled with all of the other kernel code.

If you intend to implement semaphores. One thing to note is that portability may be an issue for you. Pipes Another popular IPC mechanism is called a pipe. OS/2 is one such system. tasks are not wholly independent. Messages Messaging methods can be implemented several different ways (although not in as many different ways as semaphores from my experience). OpenPipe(). In my humble opinion. public pipes are very handy. Semaphore functions on some systems are actually implemented as an installable device instead of as an integrated part of the kernel code. some systems allow standard I/O to be redirected through pipes. Pipes are a pseudo input/output device that can be used to send or receive data between tasks. They also don’t have to be so tightly coupled with the kernel code. In my system. ReadPipe(). If you’re not worried about size or complexity in your system. The parameters to these calls would be very similar to equivalent file operations. but I used a second form of messaging instead. and was my opinion that they added an unnecessary layer of complexity. which means they can be given names so that unrelated tasks can access them. Quite often they are not public and are used to exchange data between tasks that are part of one program. etc. They are normally stream-oriented devices and the system calls will look very much like file system operations.I opted not use semaphores in my operating system. Pipes can be implemented as public devices.0 Page 36 of 667 . you should study how they are implemented in the more popular systems that use them (UNIX. They rely on a certain data contained in structures that are assigned to a program or job.). If you intend to implement pipes as part of your IPC repertoire. and WritePipe(). OS/2. ClosePipe(). In fact. MMURTL V1. I studied how they were used in UNIX systems and in OS/2. Common system calls for pipe operations would be CreatePipe(). you should study how they are used on other systems. Another reason I avoided semaphores is that are usually associated specifically with tasks (processes). which will consist of one or more tasks. All of the functions that they performed are available with simpler messaging facilities. This is much more efficient. I initially considered public named pipes for MMURTL to allow data transfer between system services and clients. I think they are best implemented as tightly integrated pieces of the kernel code.

” The initial task in a program may create additional tasks as needed. or similar ones. such as MMURTL. form of messaging which adds two more kernel message functions. or whatever you call it. In order to send or receive a message you must have an exchange. or just about any other name you like. a program is made up of one or more tasks. In your system you will provide a function to allocate a mailbox. and a non-specific "message" that doesn't expect a response. This would provide the needed synchronization for task management on the system. In fact. on most real-time operating systems. "Tell Mr. It is even possible to implement messaging routines using semaphores and shared memory. the entire kernel should be built around whatever messaging scheme you decide on. request() and respond(). It is called AllocExch() and is defined like this in C: MMURTL V1. I have added a second. it is generally an allocated resource that can be called a mailbox. for which you expect no response. In a message-based system. such as. his offer is the pits. One key element in a message system is WHERE you leave your messages. The best way to describe messaging is to provide the details of my own messaging implementation. The request/respond concept is what I consider the key to the client-server architecture which was one of my original goals. Sending and receiving messages in any message-based system is not unlike messaging of any kind (even phone messages). only the basic form of messaging is implemented. In the system I’ve included for you (MMURTL). tasks exchange information and synchronize their execution by sending messages and waiting for them. In MMURTL's case. an exchange point. These new tasks inherit certain attributes from the initial task.Send and Wait The most rudimentary form of messaging on any system is a pair of system calls generally known as send() and wait(). It is a place where you send messages or wait for them to exchange information. MMURTL provides a call to allocate exchanges for jobs.0 Page 37 of 667 ." This is an example of a one way message. In Bob's case he will get the message from his secretary. Other operating systems use this type of messaging scheme also. On many message-based systems for smaller embedded applications. I called it an Exchange. one which is "request" for services which should receive a response. This will afford an overview of messaging for the novice as well as the details for the experienced systems programmer. 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. I am describing this because you may decide to implement tasks as wholly independent entities. more powerful. In an operating system's case. I refer to a program as a “job. With the send() and wait() types of messaging. You can send one way messages to a person. I didn't. But once again. This now gives MMURTL two forms of messaging. you must keep the code maintenance aspects in mind for whatever implementation you choose. Zork to forget it. the only other resource required is somewhere to send a message and a place to wait for one (usually the same place). a bin. You will see these commands.

If there is no task waiting at that exchange.unsigned long AllocExch(long *pdExchRet). but large enough for a couple of pointers. but tasks can wait there too? This is an extremely important concept. You tell it what exchange. This is an added messaging function to allow you to check an exchange for a message without actually waiting. dMsgPart1 and dMsgPart2 are the two dword in a message. char *pMsgRet).0 Page 38 of 667 . It returns 0 if all goes well. The task is the human. The parameter pdExchRet points to a dword (32-bit unsigned variable) where the exchange number is returned. pMsgRet points to an eight-byte (2 dwords) structure where the message will be placed. It does just what it says . give it the message and away it goes. The most common is the send() function I described earlier. long dMsgPart1. The C definitions for these calls and their parameters as I have implemented them are: unsigned long SendMsg(long dExch. You also need a way to send the message. dExch is the exchange where we will wait to check for a sends a message. Consider the phone again. char *pMsgRet). the message will wait there until a task waits at the exchange or checks the exchange with CheckMsg(). I have implemented this as SendMsg(). unsigned long WaitMsg(long dExch. Not too large to move around. Did you notice (from my definitions above) that not only messages wait at exchanges. In MMURTL I call this WaitMsg(). long dMsgPart2). You can leave a message on the machine (at the exchange) if no one (no task) is waiting there. unsigned long CheckMsg(long dExch. If you’re lucky. the task that you want to get the message is "waiting" at the exchange by calling the wait() function I described earlier. Note that my messages are two 32 bit values. the answering machine is the exchange. MMURTL V1. If a human is there waiting (a task is at the exchange waiting). The function return value is the kernel error if there is one (such as when there are no more exchanges). dExch is the destination exchange. the message is received right away. Operating system calls to send a message come in several varieties.

time is passing. The only reason it’s discussed with the rest of the kernel primitives is because of its importance. You point to a piece of code.. SendMsg().).Now. Yes. whenever you call a kernel primitive you enter the KERNEL ZONE.1 Task and Kernel Interaction Task Action Kernel Action Task1 is Running Task1 allocates Exch1 Task1 calls SpawnTask (to start Task2) Kernel checks priority of new task. Task2 is higher.. You start with a single task executing. and NewTask() are five very important kernel primitives. consider this: In a single processor system that is executing a multitasking operating system. Table 4. but we have enough to finish the basic theory. AllocExch is an important auxiliary function. and VIOLA. All the other tasks are WAITING somewhere.0 Page 39 of 667 . 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. I don’t consider it part of the kernel because it has no effect on the tasking model (CPU time allocation). One more quick topic to round off what we’ve already covered. very small. I apologize that none of the items I’ve introduced have names that are confusing or buzzwordish. CheckMsg(). Now I have some very important terms and functions . Tasks are started with the kernel primitives SpawnTask or NewTask. but enough that we can talk about them. There are only two places for a task to wait in MMURTL: At an Exchange or on the Ready Queue. What it’s doing isn’t important. As we move down the table in our example. Kernel places Task1 on the Ready Queue. WaitMsg(). In this example. 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. You also know about Exchanges and the Ready Queue. Just kidding. provide some other pieces of information. just the "kernel" (a small tribute to Rod Serling. Kernel makes Task2 run MMURTL V1.not in detail yet. Table 4. it’s a little more complicated than that.1 (Task and kernel Interaction) shows actions and reactions between a program and the operating system. I’ll try to liven it up some later on. a task is born. SpawnTask(). it’s not called the kernel zone. only one task is actually executing instructions.

Task2 calls WaitMsg at Exch2. and if the task that just received the message is a higher priority than the task that sent it. the message is associated with that task and it is placed on the Ready Queue. None are waiting..Task2 is running. Kernel checks for a task waiting at Exch1. Kernel places Task2 on Exch2. Task2 sends a message to Exch1. The Ready Queue is immediately reevaluated. Actually it really does absolutely nothing.0 Page 40 of 667 . Kernel checks for a message waiting at Exch2. you can see that the kernel has its job cut out for it. What would happen if both Task1 and Task2 waited at their exchanges and no one sent a message? You guessed it .the processor suddenly finds itself with nothing to do. I didn’t do this with MMURTL. It’s still task 2. Task1 is ready to run. I actually HALT the processor with interrupts enabled. Task1 is running Task1 sends a message to Exch2 Kernel checks for task at Exch2 and find Task2 there. the receiving task is made to run. Task2 allocates Exch2. It gives task2 the message. None found. From the example you can also see that when you send a message. If MMURTL V1. If there is a task at the exchange. This low priority task can even be made to do some chores such as garbage collection or a system integrity check. dummy tasks are created with extremely low priorities so there is always a task to run. Kernel places task1 on Ready Queue Kernel makes Task2 run (It’s a Higher priority) Task2 is running . the kernel attaches the message to an exchange. Kernel attaches message to Exch1. Kernel makes Task1 run. Task2 is still running.. This can simplify the scheduler.1. Kernel evaluates Ready Queue. In some systems. 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). .. Kernel evaluates Ready Queue to see who runs next. From the simple example in table 4..

BBS systems. The interface to the Request primitive is procedural. it will be made to run. 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. The functions and what they do are defined by the service. The OS knows nothing about the service codes (except for one discussed later). only the service name. queued file management. 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). you will more than likely not use these types in your system. You will have to dig into the technical aspects of the processor you are using to ensure a scheme like this will work. When the service is first installed. With MMURTL. More than likely it is something from the outside world such as a keystroke. Look at it prototyped in C: unsigned long Request(char *pSvcName. A fully loaded system may have 5. the user of the system service doesn’t need to know the exchange number.533 different functions as identified by the Service Code. the list could go on and on. If a very small embedded kernel is your goal. The MMURTL FAT-file system and keyboard are both handled with system services. they must be waiting for something. It doesn’t need to because the Request interface is identical for all services. If there is one. each service installed is given a name. Each time an interrupt occurs. MMURTL V1. you could implement named pipes (discussed earlier in this chapter) as a system service. 10 or even 15 system services. but the name will always be the same. FAX services. the processor is activated and it checks the Ready Queue. it registers its name with the OS Name Registry and tells the OS what exchange it will be serving. The exchange number may be different every time it’s installed. printing services. long dRespExch. there should be a task sitting on the ready queue. Request() I have added two more messaging types in my system. This may not work on all types of processors. unsigned int wSvcCode. They are dedicated types. Each service can provide up to 65. 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.0 Page 41 of 667 . In fact.everyone is waiting at an exchange. The name must be unique on the machine. The processing is carried out by the service. If the interrupt that caused it to wake up sent a message. This way. but has quite a few more parameters than SendMsg. They are ideal for things like file systems. keyboard input. e-mail services. and a response (with the results and possibly data) is returned to the requester.

If pSend1 was a pointer to some data the service was getting from your memory area. If you made more than one request. this would be the size (length) of the filename. The service already knows this. If both data items were being sent to you from the service then this would be 0.Exchange that the service will respond to with the results (an exchange you have allocated). long dcbData2SR. MMURTL V1. dnpSend would be 1.This is a second pointer exactly like pData1 described above. and pSend2 was not used or was pointing to your memory area for the service to fill.Pointer to the service name you are requesting. long dcbData1SR.long *pdRqHndlRet. and space-padded on the right. The service name is eight characters.How many bytes the pDataSend points to.Pointer where the OS will return a handle to you used to identify this request when you receive the response at your exchange. but network transport mechanisms do not. 1 or 2) of the two data pointers that are moving data from you to the service. you’ll see that the call for making a request is a little bit more complicated than SendMsg(). dnpSend . At first glance.Pointer to memory in your address space that the service must access (either to read or write as defined by the service code). These are documented independently by each service.This is the same as the number (0.0 Page 42 of 667 . long dData2 ). *pData1 . For instance. Using the Open File() example. This may even point to a structure or an array depending on how much information is needed for this particular function (service code). long dData0. pData1 points to the file name (data being sent to the service). long dData1. *pData2 . wSvcCode . dcbData1 . all capitals. as the plot unfolds you will see just how powerful this messaging mechanism can be (and how simple it really is). *pdRqHndlRet . char *pData2SR. in the file system OpenFile() function. This is needed because you can make multiple requests and direct all the responses to the same exchange.Number of the specific function you are requesting. you’ll need to know which one is responding. dcbData2 . Lets look at each of the parameters: pSvcName . dRespExch . long dnpSend char *pData1SR. But.

the kernel has aliased this pointer for the service as well. This will be explained later in memory management. Here is the C prototype: unsigned long Respond(long dRqHndl.0 Page 43 of 667 . Second. if the request was serviced (with no errors).the handle to the request block that the service is responding to. A little further into this chapter we cover memory management as it applies to messaging which should clear it up considerably. Not as difficult as you expect.These are three dwords that provide additional information for the service. Respond() The Respond() primitive is much less complicated than Request(). Zero (0) usually indicates no error. They are described below: dRqHndl . If it is 80000000h or above. but will simply fill in a value in one or more of these three dwords.the status or error code returned to the requester. the second dword is the status code or error from the service. A job (your program) calls Request() and asks a service to do something. The convention is the value of the first dword in the message. long dStatRet). MMURTL V1. this dword should match the Request Handle you were provided when you made the Request call (remember pRqHndlRet?). then it calls WaitMsg() and sits at an exchange waiting for a reply. dStatRet . it is NOT a response. the data has already been placed into your memory space where pData1 or pData2 was pointing.dData0. There’s still a little work to do. In many functions you will not even use pData1. If you remember. if you were sending data into the service via pData1 or 2. If this is the response. dData1. The parameters are also a little less intimidating than those for Request(). Two questions arise: 1. Where is the response data and status code? First. Also. These can never be pointers to data. This is possible because the kernel provides alias pointers to the service into your data area to read or write data.this aliasing thing with memory addresses is still a bit muddy. This doesn’t mean the system service has it easy. How do we know that this is a response or just a simple message sent here by another task? 2. and the service has already read your data and used it as needed. right? But let me guess . Second. although its exact meaning is left up to the service. the message that comes in to an exchange is an eight-byte (two dwords) message. or pData2 to send data to the service. The content of the eight-byte message tells you if it is a message or a response. and dData2 .

I say "almost" because really only one task is running at a time. Not very big. This can add up to hundreds in a real hurry. but still very important. It’s called GetMeSomeMem() In this example there are two variables that the code accesses to carry out it’s mission.0 Page 44 of 667 . To really understand this issue. An exchange is a small structure in memory. You will need some way (a method) to attach messages to your mailbox. I’ll use a pseudo text algorithm to define a series of instructions that are NON-reentrant. The example is a function that allocates buffers and returns a pointer to it. This can lead to serious problems if the code that is being executed is not designed to be reentrant. lets look at an example. I guess you could use Elmer’s Glue. but this would slow things down a bit. It will no doubt be a linked list of sorts. or pauses in the middle of them. 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 MMURTL V1. Reentrant means that if a task is executing a series of instructions and gets preempted. and show you what can happen. 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. (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. I opted to use something called a Link Block (LB).Link Blocks I keep referring to how a message or a task just "sits" or "waits" at an exchange. it’s OK for another task to enter and execute the same code before the first task is done with it. 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.

000. it sends back the message so the next task can get in. Allocate a mailbox or exchange and send it one message. some method is required to prevent two tasks from being inside this code at the same time. and maybe even not often enough. task1 calls GetMeSomeMem(). What’s usually not so simple for the operating-system writer is ensuring you identify and protect all of the NON-reentrant code. The call to manage and check the semaphore would actually be part of the allocation code itself. task1 gets to run again. Now. and will be suspended until the last task is done with it. The next sections discuss this. In theory. Interrupt Latency Interrupt latency is when interrupts don’t get serviced fast enough. it successfully makes it all the way through and gets that last buffer. You should always know how long this will be and it shouldn’t be too long. Now. For the sake of understanding this. let’s look at a real-world example. You will lose data. Semaphores are used on many systems. it is preempted. this means you could execute as many as 1730 instructions in the interrupt interval. Each task that enters the’s crash time. The typical processor running at 25 MHz executes most of it’s instructions in 2 or 3 system-clock periods. As you know.Return Error Lets suppose two tasks are designed to call this function. Each task will wait() at the exchange until it has a message.task switches. It’s really quite simple. The code has already checked and there was 1 buffer left. As you can see. checks the semaphore.0 Page 45 of 667 .000 * 3). Messages can also provide exactly the same functionality. You must take into consideration that there are more interrupts than just that communications channel.400 bits per second will interrupt every 208 microseconds. MMURTL V1. even in a multitasking system. The communications interrupt itself will have interrupts disabled for a good period of time. WHOA THERE . He is restarted by the scheduler and starts up at PointX. The timer interrupt will be firing off every so often. This is 1/38. That would be an average of 120 nanoseconds (1/25. So the code goes in and WHAMO . there was only one buffer left. The buffer is gone. pointers are off the ends of arrays. When each task is done with the allocation routine. But when it reaches PointX. I mentioned in the above section that you shouldn’t disable interrupts for too long a period of time. you can simply disable interrupts for the critical period of time in the code. task2 is running and calls GetMeSomeMem(). only one task can really be running at a time.that was only in theory! Now we have to do a reality check. So. A nonbuffered communication UART (Universal Asynchronous Receiver Transmitter) operating at 38. and also "the biggie" . it’s a mess. If you disable interrupts too long then you will suffer interrupt latency problems.400 * 8 because they will interrupt for every byte (8 bits). Just to give you an idea of what periods of time we are talking about. If the instruction sequence is short enough.

200 baud in Windows 3. The actual amount of time will depend on whether you use hardware task switching or do it yourself.but it will increase the headaches elsewhere. This means that if paging is used along with the segmentation. then you have to find away to translate (alias) addresses that are contained in messages that will be passed between different programs. In the kernel code. Good tight code in the kernel is a must. but my testing shows I did OK. they’re doing some things I personally consider impossible. but you will still have interrupts disabled for a rather scary period of time. you can’t use the kernel code to lock things out of the kernel! You MUST disable interrupts in many sections. it will be even a greater chore to translate addresses that are passed around on the system. Have you ever run a communications program for an extended period of time at 19. You will have to do some serious testing on your system to ensure interrupt latency is not going to be a problem. If you implement independent-linear memory arrays through paging hardware (as I did with MMURTL). It will be the only way. “Memory Management.Why are task switches considered "the biggie?" A task switch can consume as many as 30 or 40 microseconds. Memory Management Issues The last hurdle in Interprocess Communications is memory. if you remember the GetMeSomeMem() example above. Don’t blame Windows though.” MMURTL V1. I could probably tighten mine a whole lot. it will simplify IPC greatly . It doesn’t sound like it would be a problem. you know you locked out multiple users with the kernel messaging (or semaphores). Segmented memory models will also suffer from this aliasing requirement to even a greater extent. Fully segmented models allow any memory allocation to be zero-based. I recommend you avoid a fully segmented model. You can find out about some of your memory management options in chapter 5. Also. but it can be depending on how you implement your memory model. You didn’t disable interrupts. I started with one and abandoned it early on. If you use a completely flat model for the entire system.x? You will lose data. This means you can freely pass pointers in messages back and forth (even between different programs) and no one gets confused.0 Page 46 of 667 . 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.

so does an operating system. Intel uses this term to describe memory addresses that have been translated by paging hardware. The memory model you choose. Memory management code in an operating system is affected by the following things (this list is not all inclusive. How the processor handles addressing. MMURTL V1. I am addressing the second byte of physical memory. There are some major differences though. 4. Some do it with internal paging hardware. An operating system’s view of memory management is quite different than that of a single application. This greatly affects the next three items. 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. This will depend on whether or not some form of hardware translation takes place. I do make some assumptions about your processor. 2. If I put address 00001 on the address bus of the processor.0 Page 47 of 667 . I can do this because most of the processors afford roughly the same memory management schemes. Whether or not you use paging hardware. Memory Management Introduction This chapter provides you with some fairly detailed ideas for memory management from an operating system’s perspective. Either way. if available. Physical memory is the memory chips and their addresses as accessed by the hardware. or another complicated applications. These difference vary considerably based on how you choose to handle the memory on the system. Just as your memory management routines keep track of what they hand out. Linear memory is the address space a program or the operating system sees and works with. it’s all basically the same. Whether you allow variable-sized chunks of memory to be allocated. Address 0 is the first. Basic Terms Once again. If you’ve ever written memory-allocation routines for compiler libraries. These addresses may or may not be the same as the physical addresses.Chapter 5. For instance. I need to define some terms to ensure we are talking about the same thing. but hits the important things): 1. 3. some do it with an external page memory management unit (PMMU).

Keep in mind that the easiest to understand may not necessarily be the easiest to implement. As programs are loaded. No address translations take place in hardware. With the Intel processors. Figure 5.1 (Simple Flat Memory) shows this very simple-shared memory arrangement. 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). Memory Model Do not confuse my term "memory model" for the MS-DOS/Intel term. MMURTL V1. they are given a section of memory (a range of address) to use as their own. Simple Flat Memory Fimple Flat Memory means that the operating system and all programs share one single range of linear memory space. I will discuss segmentation in greater detail in this chapter for those of you who may want to use it. I’ll start with the easiest to understand and work from there. the operating system is usually loaded at the very bottom or very top of memory. You may be restricted from using some of these options. this refers to how and where programs are loaded. There are several basic memory models to choose from and countless variations.Logical memory is more an Intel term than a generic term that can be applied across different manufacturers processors. In this type of system. depending on what your processor or external memory management hardware allows. Instead. and how the operating system allocates and manages processor memory space.0 Page 48 of 667 . 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. It does not refer specifically to size or any form of segmentation. and that physical addresses and linear addresses are the same.

). different memory allocation schemes (top down. Paging hardware usually affords some form of protection. Simple Flat Memory Unless some form of protection is used. the physical memory can come from MMURTL V1. etc. Hardware memory paging would possibly provide a solution to the bad pointer problem.Figure 5. bottom up.1. The good part is that no address translations are required for any kind of interprocess communications.0 Page 49 of 667 . and possible partitioning for each program. The bad part is that an invalid pointer will more then likely cause some serious problems. chunks of physical memory called pages. This scheme requires the use of paging hardware. at least for the operating system. they are partitioned off into their own space with a lower and upper bound. are allocated and assigned to be used as a range of linear memory. and bad. If the paging hardware only provides two levels of protection. 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. This can be good. With this scheme.2 (Paged Flat Memory) shows how the blocks of physical memory are turned into addressable chunks for the system. Partitioning means you would allocate additional memory for a program it would use as it’s unallocated heap. With paged memory. Considerations and variations of this simple model include paging. Paged Flat Memory This is a variation on simple flat memory. As programs are loaded. Figure 5. This is still a flat scheme because all the programs will use the same linear addresses. This would keep all of the program’s memory in one range of linear addresses. any program can reach any other program’s memory space (intentionally or unintentionally). They can still share and get to each other’s address space if no protection is applied.

Paged Flat Memory Demand-Paged Flat Memory This is a major variation on Paged Flat Memory. or even 64 MB). but you can’t actively allocate more linear address space than you have physical memory. your system’s actively-allocated linear address range may actually be extended to a much greater size (say 32. the linear address range can also be much larger than the amount of physical memory. direct access storage device. if you have 16 MB of physical memory. Figure 5. With demand-paged memory.3 (Demand Paged Flat memory) shows an example of the page translations that can occur. The word "demand" means that they are sent out to the disk when not needed. MMURTL V1.anywhere in the physical range to fit into a section of linear address space.The additional pages of physical memory will actually be contained on an external.2 . and brought back into real physical address space on demand or when required. For example.0 Page 50 of 667 . Figure 5. more than likely a hard disk drive. With this scheme. For instance. This gives the operating system some options for managing memory allocation and cleanup. you extend the active size of your linear memory beyond your physical memory’s limits. Figure 5. your linear address space may run from 0 to 1 Gigabytes. but only 16 Megabytes of this linear address range may be active if that’s all the physical memory your system has.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.

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

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

The same type of on-demand external storage is used as with demand-paged Flat Memory. Demand-Paged Memory Management Management of demand-paged memory is no trivial task. and you need to understand the implications. and now you want to move it out to disk because it hasn’t been used lately. you may already have a "clean" copy on the disk. and maybe to use the Intel Object Module format. Likewise. When Intel built the first of its 32-bit processors (80386). When a page has been written to. If you had read in a page and it was accessed for reading. they made sure that it was downward compatible with code written for the 16-bit processors. This can be a big time saver when you’re talking about thousands of pages being swapped in and out. or page creation (memory allocation) requirements. when they built the 16 bit MMURTL V1. This additional task will try to keep a certain percentage of physical space cleared for near term page-in. If you need to move someone’s physical pages back into their linear space. it is usually termed as "dirty. The most common. and generally the most efficient. or even an 8-bit one. The LRU schemes have many variations. demand-paged virtual memory is when you can allocate more pages than you physically have on your system. You may even want to use it to make your system compatible with some of the most popular operating systems in use (e..0 Page 53 of 667 . In fact. This means you can save the time rewriting it. we had some pretty limited address spaces. Segmented Memory I will discuss segmented memory because the Intel processors allow it.g. When this was a 16-bit world. This compatibility may be in the form of the ability to use code or object modules from other systems that use segmentation. The most common method is with a Least Recently Used (LRU) algorithm." The idea of a page being dirty has some significance. It’s just what it sounds like. paging hardware will tell you if a page has been accessed only for reading or has actually been modified. The major difference is that these pages can be for any one of several independent linear address spaces. Segmentation is really a hold over from the past.Demand Paged Virtual Memory Just like demand-paged flat memory. 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. A multitude of software was written for 16-bit addressing schemes. the pages that are moved out to make room are the ones that haven’t been used recently. OS/2 or Windows). is to use a separate thread or task that keeps track of how often pages are used.

but it can become cumbersome to manage because it can force you to use something called FAR pointers to access almost everything. 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. With a 32-bit system. However. and 80286). To make this work. 20 to be exact. They allowed multiple sequential.0 Page 54 of 667 . What they came up with is called Segmentation. It also meant that when they built processors with more address lines. this makes a far pointer 48 bits. Management of memory involves the following things: • Initialization of memory management.processors (8086. It is really a form of virtual memory itself. What they needed was a transparent way to use the extra four bits to give you access to a full 1 Mb of RAM. This is a very handy feature. They are not limited to 64K.” A selector is a number that is an index into a table that selects which segment you are working with. This can be a real hassle. they added a very important concept called Protected mode. This means you can locate code or data anywhere you want in the 4 GB of linear memory. that would identity which particular 64-K addressable area you were working with. and still reference it as if it were at offset zero. It also slows things down because the processor must do some house keeping (loading shadow registers) each time the selector is changed. the processor uses the entry in the table indicated by the selector and performs an address translation. The FAR pointer is a pointer that includes the selector as part of the address. Memory Management Once you've chosen a memory model. 8088. This made it possible to manage these segments with something called a “selector. you'll face many decisions on how to manage it. The selectors are managed in tables called the Global Descriptor Table (GDT) or Local Descriptor Tables (LDT). this introduction leads us to another very important point about segmentation that you need to understand. When the 80286 came on the scene. The segment registers gave you an addressable granularity of 16 bytes. When you use a selector. so I won’t go into further detail. or overlapping 64-K sections of memory called segments. They used something called a segment register. This meant you could have a whopping 64K of RAM. the 16-bit processors were also compatible with object code for the 8-bit processors (8080 and 8085. The segment registers in the 16-bit processors were a simple extension to the address lines. one for code and several for data access. they needed a way to use all of the 8-bit processor code that used 16-bit addressing. All of the Intel products are very well documented. The descriptor tables allow you to set up a zero based address space that really isn't at linear address zero. 80186. they needed to identify and manage those segments. MMURTL V1. The 8-bit processors had 16 address lines. Protected mode operation with selectors carried over into the 32-bit processors. and the popular Zilog Z80). This was actually several registers.

if your hardware supports it. You make the rules. Remember. and one of the easiest ways to manage memory. I learned this the hard way. This type of algorithm is most common when there is a large linear address space shared by one or more applications.• • • • • Allocating memory to load applications and services. such as I described earlier with the Simple Flat Memory Model. you should decide on your basic allocation unit. especially if you have a very small programming team (say. Allocating memory for applications. You can let applications allocate as little as one byte. and how much time and effort you can put into to the algorithms. or force them to take 16 Kilobytes at a time. the type of applications you cater to.0 Page 55 of 667 . you have one or more linked lists that define allocated and/or free memory. and it will be based on many factors. you may want to make the allocation unit the same size as a page. This is because you have to figure out how you’ll manage memory before you know how to initialize or track it. and Handling protection violations. There have been hundreds of algorithms designed for linked list memory management. You'll have to make a decision.” Before you decide which method to use. MMURTL V1. you ARE the operating system. Linked List Management Using linked lists is probably the most common. which will be defined by the paging hardware. One of the important ones will be whether or not you use hardware paging. Tracking client linear memory allocations. If you do. but not in the order I listed them. I will give you a good idea what it's like. and my advice to you is not to bite of more than you can chew. With this scheme. but it's a very realistic part of the equation. This will simplify many things that your memory management routines must track. Tracking physical memory if paging is used. then it's up to you to do some serious research to find the best method for your system. Other factors include how much memory you have. I’ll discuss each of these things. 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. They are “linked lists” and “tables. Tracking Linear Memory Allocation Two basic methods exist to track allocation of linear or physical memory. one person). Basic Memory Allocation Unit The basic allocation unit will define the smallest amount of memory you will allow a client to allocate.

One points to the first linked block of free memory (pFree) and the other points to the first linked block of allocated memory (pMemInUse). Ignoring the fact that MS-DOS uses segmented pointers. In the process of memory management initialization. you have two structure variables defined in your memory management data. each block holds its own link. In the following simple linked list scheme. pFree and pMemInUse. You would set up the base structures as follows: void InitMemMgmt(void) MMURTL V1. it is simply terminated and the resources it used are recovered. In operating systems that don’t provide any type of protection. along with the allocated size and maybe the owner of the memory block. /* a backlink so we can find previous link */ long size. struct MemLinkType pMemInUse. /* " " */ struct MemLinkType *pPrevLink. you would set up the two base link structures.0 Page 56 of 667 . The links are small structures that contain pointers to other links. These are the first links in your list. The easiest and most efficient way is to store the link at the beginning of the allocated block. I haven’t disassembled their code. you can see that there is a conspicuous few bytes missing from two consecutive memory allocations when you do the arithmetic. but I’ll touch on it some. The word cleanup also implies that the memory space will become fragmented as callers allocate and deallocate memory. This is another topic entirely. /* " " */ long Client = 0. How you store the links in this list can vary. For my description. struct MemLinkType pFree. it usually takes the whole system down.The linked lists are setup when the operating system initializes memory. char *pPrev. and can be located almost instantly with the memory reference. This way. struct MemLinkType *pLink. when one application crashes. /* Memory Management Variables */ struct MemLinkType /* 16 bytes per link */ { char *pNext. 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). I recommend you track which client owns each link for purposes of deallocation and cleanup. You now have an idea where they went. But in many systems. long owner. /* to work with links in memory */ struct MemLinkType *pNewLink. MS-DOS appears to use links in the allocated space. 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 *pNextLink. when one application crashes.

pFree. This divides the free */ /* block into two pieces (one of which we’ll allocate) */ /* Set up new link pointer. pLink->pNext = 0. or a NULL pointer if we can’t honor the request.owner = 0.. it was in an effort to save memory and make the code tight and fast. */ Backlink to pFree */ 15 Megs-16 for the link */ OS owns it now */ When a client asks for memory.{ pFree.16. pLink->size = 0xf00000 . This is done by following pLink. WantSize is the size the client has requested.pNext until pLink.size = 0. /* Start at the base link */ /* Keep going till we find one */ /* As long as we have a valid link. /* /* /* /* /* /* /* /* 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. you follow the links beginning at pFree to find the first link large enough to satisfy the request. The following example code is expanded to show each operation. Client is the owner of the new block. The following case of the very first client will show how all allocations will be. pMemInUse. pFree. Of course. which means you can’t honor the request. Granted.size = 0. fix size and owner */ MMURTL V1.pNext = 0.owner = 0. you must remember that pLink. 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. char *AllocMem(long WantSize) { pLink = &pFree. pMemInUse. but it sure didn’t seem like it was worth the headaches.size is large enough.size must be at least as large as the requested size PLUS 16 bytes for the new link you’ll build there..pNext = 0x100000. Let’s assume the first request is for 1 MB. pLink->owner = 0. I would suggest you leave it as is for maintenance purposes and take the hit in memory usage.*/ /* Next link please */ while (pLink->pNext) { pLink = pLink->pNext. If you use this code. pLink->pPrev = &pFree. pFree. 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. The function returns the address of the new memory. pMemInUse. pMemInUse.pPrev = 0.0 Page 57 of 667 .pPrev = 0. or you reach a link where pNext is null. No next link yet. } /* /* /* /* /* Set structure pointer to new link */ NULL. You could make this type of operation with pointers almost "unreadable" in the C programming language.

This will add time to deallocation routines. If they tend to allocate a series of blocks and then free them in the same order.pNext = &pLink. depending on the order of deallocation. /* Point to the new link */ pLink->size = WantSize. This makes the most sense to me.WantSize . /* OS owns this for now */ /* Hook it into free links after pLink */ pNewLink->pPrev = &pLink. you go to that address (minus 16 bytes). but a lot depends on the patterns of allocation by your clients.pNext. pLink->pNext = &pNewLink. but cleanup will be a snap. Even though the links themselves are next to each other in memory. Another method is to keep the pFree list sorted as you add to it. it WILL get fragmented. I personally have not done any research on the fastest methods. then move it to the pFree list.pNewLink = &pLink + (WantSize + 16). You can add code in the allocation and deallocation routines to make it easier.0 Page 58 of 667 . pNewLink->size = pLink->size . The deallocation routine is even easier than the allocation routine. The cleanup of fragmented space in this type of system is not easy. When they hand you a pointer to deallocate. /* Backlink */ /* Fix size of pLink */ /* Remove pLink from the pFree list and put it in the */ /* allocated links! */ pPrevLink = pLink->pPrev. } /* Sorry . validate the link. MMURTL V1. } } return(0). pPrevLink->pNext = &pNewLink. In other words. This is where the fragmentation comes it. And believe me. change its owner. You have the linear address. they can end up anywhere in the pFree list.16. pLink->size = WantSize. /* This is who owns it now */ return(&pLink+16). There are several thoughts on cleanup. pNewLink->owner = 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. and you insert it there. /* get previous link */ /* Unhook pLink */ pLink->pNext = pMemInUse. /* Put pLink in USE */ pMemInUse. Combining blocks can be done at the same time. or you can do it all after the fact. do a simple insertion at the proper point in the list. A simple scan of the list can be used to combine links that are next to each other into a single link. /* How much we allocated */ pLink->owner = Client. the links may end up next to each other in the free list anyway. pNewLink->pNext = pLink->pNext. But you can’t depend on this. you simply walk the list until you find the links that the newly freed memory belongs between.

But we can’t add our size to pFree. return(0). He’s a dummy */ if (pNextLink->pPrev != &pFree) /* Can’t be pFree */ { if (&pNewLink == (&pPrevLink + (pPrevLink->size + 16))) { /* add our size and link size (16). /* scan till we find out where we go. int FreeMem(char *MemAddress) { /* We will use pNewLink to point to the link to free up. then go away! */ pPrevLink->size += pNewLink->size + 16. which returns memory to the free pool. This starts at the first link pFree is pointing to.Here is a deallocation routine that defragments the space as they free up memory. */ pNewLink = MemAddress . because we can assume there will always will be a link at a higher address.0 Page 59 of 667 . and that they really own the */ memory by checking the owner in the link block.pNext. This provides instant cleanup. This will be useful in the following routine. */ /* It’s actually a MemInUse link right now. This means we would simply add our */ /* size + 16 to the previous link’s size and this link would */ /* disappear. You should notice that the allocation algorithm ensured that there was always a link left at a higher address than the link we allocated. */ while ((pNextLink->pNext) && (&pNextLink < &pNewLink)) { pNextLink = pNextLink->pNext. We will stop when */ /* we find the link we belong between. */ /* Start at pFree */ pNextLink = pFree. We try to combine the newly freed link with the one before it or after it. You can do things like checking to */ ensure the pointer isn’t null. */ /* 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. /* /* /* /* /* /* 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. "walks" up the links and finds its place in linear memory order. we should be there! */ /* We could just stick the link in order right here. } pPrevLink = pNextLink->pPrev. } MMURTL V1. if at all possible.16. This would be the link */ validation I talked about. /* this will be handy later */ /* If memory isn’t corrupted.

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

If you use paging.” but once the operating system is loaded. Another chore you'll have is to find out just how much memory you have on the system. You may want your operating system code and data at the very top of you linear memory address space.” If you use a processor that uses hardware I/O from the memory space. the relationship between physical and linear memory. you may also have to take into account any hardware addresses that may not be allocated to applications or the operating it. you need to ensure that you initialize memory so that it takes into account all of memory used by the OS code and data. you have a lot of work to do during the initialization process. You will be allocated these up front. 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. Some processors use memory addresses from your linear space for device I/O. From there. such as battery backed-up CMOS memory MMURTL V1. add the translations you need and are already using. You could also use linked lists to manage the physical memory underneath the linear address space if you like. “the Hardware Interface.0 Page 61 of 667 . It will be the page’s size. The hardware paging is usually not active when the processor is reset. Wherever you load it. When you work with paging hardware. “OS Initialization. But it's up to you. or load the rest of it after you turn on paging. Intel has a different scheme called Port I/O. it will generally be in a block of addresses. It will be greater if you keep a certain number of pages grouped together in a cluster. is that you can't address it until it has been identified in the hardware page tables that you manage. Your operating system is responsible to ensure they are updated properly. 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. I'll discuss that in Chapter 6. Something to remember about working with physical memory when paging is used. and turn on the paging hardware. This means your operating system will be tracking linear and physical memory at the same time. Initialization of Memory Management I won't discuss loading the operating system here because it's covered in chapter 7. 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. you will deal with a fixed size allocation unit. This is one of the first things you will do during initialization of memory management. it becomes allocated memory right away. you will have to move it all. I recommend you to leave the initial operating system memory block in an address range that matches physical memory. This can get complicated. You must set up tables.

and you should take that into consideration. set them all to one value and forget them. This signaling will be in the form of a hardware trap or interrupt. I may add demand paging to it at some later point in time. For instance. I do use a very small amount of segmentation. the concept of program segments is an old one. 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 in a very limited fashion. but how I use it may give you some ideas I haven’t even thought of. it will generally be associated with page tables that you are using to manage your linear address space. you will have independent tables for each application. The trap may not actually be signaling a problem. and I even use the Intel segment registers. but it serves my purposes quite well without it. You will have to investigate each type of interrupt. You may not always be able to depend on this to be accurate. An Intel Based Memory Management Implementation I chose the Intel processors to use for MMURTL. and many others. It generally means the application has done some bad pointer math and probably would be a candidate for shut-down.0 Page 62 of 667 . MMURTL V1. trap. A Few More Words On Segmentation Even though I continue to refer to Intel as the "segmented" processors. If you have no desire to use them. I use a Virtual Paged memory model as described earlier. 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. For instance. but may be part of the system to help you manage memory. 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. uninitialized data segment. If your processor gives you these types of options. I depend on the paging hardware very so the best thing I can do is to provide you with a very detailed description of the memory model and my implementation. the initialized data segment. In some cases. Other forms of traps may be used to indicate that an application has tried to access memory space that doesn’t belong to it. It can be very complicated to use on any system. programs are quite often broken down into the code segment. Batteries fail.

Small. The operating system code segment is 08h.If you are familiar with segmented programming. and even larger with demand page memory. I wanted only one program memory model. and one data segment for all programs and applications. but will have no effect on applications. These will never change in MMURTL as long as they are legal on Intel and work-alike processors. Large. This may sound like a restriction until you consider that a single segment can be as large as all physical memory. but nice to have. Making the operating system code zero-based from it’s own selector is not a necessity. The selector values must be a multiple of eight. This could change in future versions. If you want just a few bytes. you know that with MS-DOS. In the 80x86 Intel world there are Tiny. It also speeds up the code by maintaining the same selectors throughout most of the program’s execution. Compact.0 Page 63 of 667 . How MMURTL Uses Paging MMURTL uses the Intel hardware-based paging for memory allocation and management. and Huge models to accommodate the variety of segmented programming needs. I use almost no segmentation. The common data segment is 10h. This greatly simplifies every program we write. and is no longer necessary on these powerful processors. Medium. I figured that an operating system should be a "wholesale" dealer in memory. This was commonly referred to as a "Medium Memory Model" program. go to your language libraries for this trivial amount of memory. The user code segment is 18h. 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. My thought was for simplicity and MMURTL V1. the application code segment. and I chose them to reside at the low end of the Global Descriptor Table. and one or more code segments. One is for code and the other is for data and stack. The operating system and all applications use only 3 defined segments: The operating system code segment. MMURTL’s memory management scheme allows us to use 32-bit data pointers exclusively. The fact that the operating system has it’s own code segment selector is really to make things easier for the operating system programmer. This means the only 48-bit pointers you will ever use in MMURTL are for an operating system call address (16-bit selector. and for protection within the operating system pages of memory. The only selector that will change is the code selector as it goes through a call gate into the operating system and back again. programs generally had one data segment which was usually shared with the stack. This was too complicated for me. 32-bit offset). The "selectors" (segment numbers for those coming from real mode programming) are fixed. The program memory model I chose is most analogous to the small memory model where you have two segments. The concept of hardware paging is not as complicated as it first seems.

(Aren't acronyms fun? Right up there with CTS . its not magic. With 1024 entries (PTEs) each representing 4 kilobytes. the kernel . 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. Every Job gets its own PD. Here's the tricky part (like the rest was easy?). The kernel itself is never scheduled for execution (sounds like a "slacker. If you get our calculator out. The Page Tables that show where the operating system code and data are located get mapped into every job's memory space. Page Directories (PDs) The paging hardware needs a way to find the page tables. 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. Linear memory is what applications see as their own address space. Because of this. Each entry in a PD is called a Page Directory Entry (PDE). This means we can have 1024 PDEs in the PD.0 Page 64 of 667 . For instance. It is always on a 4Kb boundary of physical as well as linear addressing. one 4K page table can manage 4MB of linear/physical memory. I hand out (allocate) whole pages of memory as they are requested. but it’s close. This is done with something called a page directory (PD). it has code and data and a task or two. A page is Four Kilobytes (4Kb) of contiguous memory. which can have 1024 entries. each representing 4Kb of physical memory." huh?). Each PDE is also four bytes long. That's not too much overhead for what you get out of it. The operating system itself is technically not a job. I manage all memory in the processor’s address space as pages. Paging allows us to manage physical and linear memory address with simple table entries.runs in the task of the job that called it. No. These table entries are used by the hardware to translate (or map) physical memory to what is called linear memory. Each of the PDEs points to a PT. You could designed your system with only one page directory if you desire. but most of the operating system code – specifically. There are 1024 PTEs in every PT. Each PDE holds the physical address of a Page Table. and return them to the pool of free pages when they are deallocated. The operating system is shared by all the other jobs running on the system.Carpal Tunnel Syndrome). you'll see that this allows you to map the entire 4 GB linear address space. Each PTE is four bytes long.efficiency. the operating system really doesn't own any of it's memory. Page Tables (PTs) The tables that hold these translations are called page tables (PTs). MMURTL V1. Sure. Each entry in a PT is called a page table entry (PTE).

The Map of a single job and the operating system is shown in Table 5. Why so high? This gives the operating system one gigabyte of linear memory space.294. Read on and see what I did. It needs a fast way to get to them for memory management functions. is that you really do need most of this capability. The map is identical for every Job and Service that is installed. I could have built a separate table and managed it. if I add demand paging to MMURTL’s virtual memory. But it was for a good cause (No. In the scheme of things.967. The processor translates these linear (fake) addresses into real (physical) addresses by first finding the current Page Directory. Data) 0Gb + operating system Memory 0Gb 0Gb Now the pundits are screaming: "What about the upper 2 gigabytes – it’s wasted!" Well.MMURTL Memory Map Description Linear Top Dead address space Linear Max. Besides. because I haven’t explained it yet. the operating system has to know where to find all these tables that are allocated for memory management. yes. but it wasn't needed. an application of a hundred megabytes or more is even conceivable. 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. Code.1024 * 1024 * 4K (4096) = 4. in a word.0 Page 65 of 667 . Besides.296 (4 GB) You won’t need this capability any time soon. I didn't give it to the Red Cross). Sure. and each application the same thing. I wanted to keep the overhead down. A job’s memory space actually begins at the 1GB linear memory mark. but you need it in pieces. Table 5. It does this by looking at the value you (the OS) put into MMURTL V1.1.1 . The Memory Map The operating system code and data are mapped into the bottom of every job’s address space. Right? Wrong. What you don’t see.

2 Page Directory Example Entry # 0 1 . Sounds like a lot of work.2. If you remember. The operating system has no special privileges as far as addressing physical memory goes. What does this make the page directory look like? Below. these entries are marked "not used" so the operating system doesn’t take a bad pointer and try to do something with it. Finding the PD for an application is no problem. in Table 5. Exactly 2048 bytes above each real entry in the PD is MMURTL’s secret entry with the linear address of the PT.0 Description Physical address of operating system PT (PDE 0) Empty PDE Physical address of Job PT Empty PDE Page 66 of 667 . However.. but when you start talking 10. 20. certainly less overhead than this explanation). Likewise. 256 257 MMURTL V1. The operating system uses linear addresses (fake ones) just like the applications do.Control Register CR3. This is fine for one address per job. This is fine until you have to update or change a PDE or PTE. This is how I use the upper 2 Kb of the page directories. I keep the linear address of all the PTs there. Of course. it uses the upper 10 bits of the linear address it’s translating as an index into the PD. I make this upper 2K a shadow of the lower 2K. but it does this with very little overhead. then I put the linear address of the PD in the Job Control Block. is a list of the entries in the PD for the memory map shown in Table 5. Well. Table 5. each PDE has the physical address of each PT. Now that it knows where the PD is. MMURTL needs to know the physical address of a PT for aliasing addresses. This assumes the operating system consumes only a few PTEs (one-page table maximum). now we’re talking dozens or even hundreds of linear addresses for all the page tables that we can have.. CR3 is the physical address of the current PD.1. 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). the secret is out. Page Directory Entries (PDEs) I know you’re trying to picture this in your mind. or even 30 jobs running it starts to add up. I built the PD and stored the physical address in the Task State Segment field for CR3. Now it’s got the PTE. you can’t just take a physical address out of a PDE and find the PT it points to. The PTE is the physical address of the page it’s after. The entry it finds is the physical address of the Page Table (PT). When I started the application. 2K doesn’t sound like a lot to save. The processor then uses the next lower 10 bits in the linear address it’s translating as an index into the PT. possibly several for each application. and it needs it fast.

all of the entries with nothing in them are marked not present. AllocOSPage(). This is 0 to 1Gb. 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. They simply don't exist as far as the processor is concerned. it would be transparent to applications anyway. 768 769 .. As shown.. It's 20 bits because the last 12 bits of the 32bit address are below the granularity of a page (4096 bytes). and AllocDMAPage() are the only calls to allocate memory in MMURTL.. AllocOSPage() allocates contiguous linear pages in the operating system address range.0 Page 67 of 667 . AllocPage(). The pages are all initially marked with the user protection level Read/Write. AllocDMAPage() allocates contiguous linear pages in the operating system address range. If I decided to do it. If I desired. and how I use the processor's paging hardware.. 512 513 . 1023 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. all the shadow entries are marked not present. but it ensures that these pages are below the 16MB physical address boundary.. 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.. I don't keep duplicate operating system page tables for each job. AllocPage() allocates contiguous linear pages in the Jobs address range. That would really be a waste. MMURTL V1. This is 1Gb to 2Gb. This is accomplished with three different calls depending what type of memory you want.. The low-order 12 bits for a linear address are the same as the last 12 bits for a physical address. in fact. Direct Memory Access hardware on ISA machines can't access physical memory above 16MB. but you get the idea. I could move the shadow information into separate tables and expand the operating system to address and handle 4Gb of memory. but I was more interested in conservation at this point in time. Allocation of Linear Memory You now know the mechanics of paging on the Intel processors. Now you need to know how MMURTL actually allocates the linear space in each job or for the OS.

Now comes that easy part . there will be no error. It’s more likely you will run out of physical memory (the story of my life). All AllocPage() calls return an address to contiguous linear memory. The main goal of physical memory management is simply to ensure you keep track of how much physical memory there is. If fewer pages than were allocated are passed in. it’s unlikely that it won’t find a contiguous section of PTEs. Now if I could only afford 64 MB of RAM. If it does. if pages of memory in a particular job are physically next to each other (with the exception of DMA). The current version of MMURTL is designed to handle 64 MB of physical memory which makes the PAM 2048 bytes long. I’ve discussed how MMURTL handles linear addresses. the operating system will attempt to deallocate as many pages as requested which may run into memory that was allocated in another request. but only from this caller’s memory space. The pages are all initially marked with the System protection level Read/Write. The PAM is similar to a bit allocation map for a disk. Deallocation of Linear Memory When pages are deallocated (returned to the operating system). the caller passes in a linear address. If enough free PTEs in a row don’t exist. Physical memory allocation is tracked by pages with a single array. The caller will never know. The caller is responsible for ensuring that the number of pages in the DeAllocMem() call does not exceed what was allocated. The PAM is an array of bytes from 0 to 2047. and to keep up family relations I told her I named this array after her). Each byte of the array represents eight 4Kb pages (one bit per page). with the least significant bit of byte 0 representing the first physical 4K page in memory (Physical Addresses 0 to 4095).0 Page 68 of 667 . MMURTL V1. If the physical memory exists. nor should it try to find out. If so. 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. All AllocPage() calls first check to see if there are enough physical pages to satisfy the request. it will create a new PT. from a previous AllocPage() call. which is also my sister’s name. then they must find that number of pages as contiguous free entries in one of the PTs. With a 1Gb address space.Managing physical memory. nor do you even care. and whether or not it’s currently in use. along with the number of pages to deallocate. or will return an error if it’s not available. where the physical memory is located with the exception of DMA users (device drivers). It is not important.AllocDMAPage() also returns the physical address needed by the user’s of DMA. The array is called the Page Allocation Map (PAM. only that number will be deallocated. but the memory will not be available for later use. This means the PAM would be 512 bytes long for 16 Mb of physical memory.

Device Drivers have code and data. Some systems allow DLLs to contain data. how could they be swapped?). Message-based system services are exactly like applications from a memory standpoint. and no stack. there will be physical memory below 16MB available (if any is left at all). For AllocDMAPage() you allocate physical memory from the bottom up. They become part of the operating system and are reached through the standard entry points in the call gate). Messaging and Address Aliases If you design your memory management so that each application has its own independent range of linear addresses. They are there to be used as shared code only. MMURTL V1. The PAM only shows you which pages of memory are in use. They have only code. initial stack. and data. Device drivers. To get this information we must go to the PDs and PTs. This means you will have to translate addresses for one program to reach another’s data. but no stack. 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. This ensures that even if you install a device driver that uses DMA after your applications are up and running. you’ll have to tackle address aliasing. This can introduce re-reentrancy problems.For AllocPage() and AllocOSPage(). They become part of the operating system in the operating-system address space accessible to everyone. and DLLs. This means they can be accessed from the user’s code with near calls. you allocate physical memory from the top down. It’s loaded into these initial pages. must all be loaded into memory somewhere. I wanted them to be fully re-entrant with no excuses. Memory overhead per application is one thing you will have consider in your design. This is 8 Kb of initial memory management overhead for each job. DLLs are also loaded into operating system address space. This is allocated along with the new Job Control Block (JCB). It also get as many pages as it needs for it’s code. It does not tell you whom they belong to. Loading Things Into Memory Applications. Dynamic Link Libraries are the weird ones. no data. They are actually loaded into the operating system’s memory space with freshly allocated operating system pages.0 Page 69 of 667 . but the pages they occupy are marked executable by user level code. PDs and PTs are always resident (no page swapper yet. Operating System page tables are aliased as the first tables in each job’s PD and marked as supervisor. They are simply new jobs. System Services. But this gets away from memory management. It will still only be 8Kb for a 4Mb application. Each application (job) gets it own PD.

Paging makes messaging faster and easier. A PTE that is aliased is marked as such and can’t be deallocated or swapped until the alias is dissolved. The caller makes a request 2. then you can also alias addresses using selectors. If it tries to deallocate them. This will make more sense if you do. Of course.pointer to caller’s Job Control Block .Places the following into the RqBlk: . you should do it before you read this section. an alias address is actually just a shared PTE. This request block is allocated in operating system memory space.With a page based system such as MMURTL. an error will occur. are aliased into the services memory area and are placed in the request block.Returns a request handle to the caller .Service Code .linear address of pData2 (if not 0) . the kernel allocates a Request Block from the operating system and returns a handle identifying this request block to the caller. Memory and Pointer Management for Messaging If you haven’t read chapter 4 (Interprocess Communications). but that’s trivial (a few instructions).linear address of pData1 (if not 0) . There is no new allocation of physical memory. the kernel copies a PTE from one job’s PT to another job’s PT. the second job has access to other job’s data.Response Exchange MMURTL V1. When an application requests a service. They share physical memory. which means you have to fix-up a pointer or two in the request block. 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. but at the user’s protection level so the service can access it. If you decide you want to use the full segmentation capabilities of the Intel processors. The Intel documentation has a very good description of this process. The user’s two pointers pData1 and pData2.sizes of the pData1 and 2 . If two programs need to share memory (as they do when using the Interprocess Communications such as Request/Respond messaging).Allocates a request block . The Request() primitive (on the caller’s side) does the following: . Of course they probably won’t be at the same linear address. 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. The memory management aspects of the request/respond messaging process work like this: 1.0 Page 70 of 667 . Instantly.

5. Jobs can deallocate one or more pages at a time.Places the message on the caller’s response exchange. The respond() primitive (on the service’s side) does the following: .Places aliased linear addresses into RqBlk .Adds aliases to the service’s Page Table(s) for pData1 and 2 . 4.Returns the message to the service The service does it’s 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. For MMURTL.dData0 and dData1 . MMURTL V1. One or more Page Tables (PT) for each Job. or use pieces of it) are: • • • • • • • One Page Directory (PD) for each job.Reevaluates the ready queue (switch tasks if needed).Reevaluates the ready queue. They don’t need to know any of this at all to write a program for MMURTL. . switching tasks if needed The Wait() primitive (on the service’s side): .3. Only those of you that will brave the waters and write your own operating system will have to consider documentation directed at the programmers. 6. . The programmer tracks his own memory usage. Jobs can allocate one or more pages at a time. One or more PTs for the OS. OS PTs are MAPPED into every Job’s Page Directory by making entries in the Job’s PD. When it’s done its work. The Wait() primitive (back on the caller’s side): passes the response message to the caller. What does all this mean to the applications programmer? Not much I’m afraid.Places a message on the Service’s exchange with the Request handle in it .Removes the aliased memory from the service’s PTs. it responds.0 Page 71 of 667 . Memory is allocated in 4Kb increments (pages). 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). .Schedules the service for execution . use it. reading & writing data to the caller’s memory areas. Physical memory is tracked with a bit map. Linear address of the PD is kept in the Job Control Block. How they see the interface is very important.

it implies an additional programmatic interface layer between the hardware and the parts of the operating system that control it. The two that are obvious are code size.Chapter 6. however. well defined interface to do things like move data to and from a device. This well-defined interface has no hardware-specific information required for it’s operation from the point of view of the operating system. You provide a common. The most obvious place this is evident is in the video arena. This concept is called hardware abstraction or hardware isolation. The implementation of this hardware isolation layer also means that you must thoroughly investigation all of the platforms you intend to target. You can keep your device interfaces as hardware non-specific as possible. The most elegant method (and the most difficult) is when it is done below the device driver code. you can port the operating system to almost any platform. in theory. This chapter discusses your approach to hardware interfaces and highlights some of he pitfalls to avoid. Adding a somewhat bulky layer between a program and the video hardware definitely slows down operation. Any additional layers between the users of the hardware and the hardware itself adds to size and reduces the speed of the interface. Therefore. You need to define logical device nomenclatures from the operating system’s point of view. Hardware Interface As a resource manager. and speed. there are drawbacks to this idea. or for specific timing requirements. The interface can be designed above or below the device driver interface. the operating system provides access to hardware for the programs on the system. As good as it sounds. 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. MMURTL V1. 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. Don’t use any fancy non-standard functions that might not be supported on some platforms – for example.0 Page 72 of 667 . Not even device driver writers have to know about the hardware aspects. and physical device nomenclatures to the code that will be below this abstraction layer (which is actually controlling the hardware). memory-to-memory DMA transfers. as long as it has similar hardware to accomplish the required functions.

One other thing this may affect is how you manipulate the stack or access data that is on it. With some CPUs you can actually switch tasks on a hardware interrupt instead of just calling a procedure. This affects everything from the programmatic interfaces. and most useful. This is handy. but it can consume a good deal of valuable bandwidth. and I recommend interrupt procedures over interrupt tasks if at all possible. An important point concerning the CPU interface is how you handle interrupts.0 Page 73 of 667 . and if you’ve worked with the processor for any length of time it will be second nature. and it simply runs. MMURTL V1. Most 32-bit CPUs have relatively large internal instruction and data caches.The CPU The interface to the Central Processor Unit (CPU) seems almost transparent. were purchased directly from the processor manufacturer. and the most significant at the lowest. These types of caches can affect timing of certain OS-critical functions. I have learned how to enable and disable caching on the systems I work with for the purposes of debugging. which means not being able to handle all your interrupts in a timely fashion. Switching the complete hardware and software context of a task for every interrupt will simply slow you down. An important consideration is how the processor stores and manipulates its data . The Intel and compatible CPUs have been referred to as "backward" in this respect. but I found the most valuable. I read many books about my target processor. The most common. Timing issues are also involved with the CPU. aside from the instructions you give it. But there’s always more than meets the eye. Other methods may be in the form of registers that you can read to gather information on the state of the processor. It executes your instructions. I haven’t run into any timing problems I could attribute to caching. Little-ENDian" problem when dealing with the exchange of information across networks or in data files. Your assembler and compilers will take care of all the details of this for you. There is no source of information like the manufacturer’s documentation to familiarize yourself with all of the aspects of the CPU. The CPU has well defined methods of communicating with the operating system. Your interpretation of their documentation may be superior to that of those who write secondary books on these topics (like mine).how it is accessed in memory. although I never thought so. This gets into the issue of interrupt latency. Some processors store the least significant byte of a four byte integer at the highest memory address of the four bytes. This is often referred to as the "Big-ENDian. or tasks that are running (or running so poorly). to interoperability with other systems. I’ve tried both methods. but that doesn’t mean that you won’t. jumps where you tell it. is the interrupt or trap.

just to name a few. The bus structure on any hardware platform is usually comprised of several buses. such as those dealing with interrupts. Before you plan some grand interface scheme. EISA. There are many extensions to this bus . and not all of the I/O addresses or interrupt lines can be accessed on this bus. in that case. or CPU bus. The electronic devices that handle asynchronous MMURTL V1. Serial I/O Many forms of serial Input/Output exist. The most common is the relatively low-speed. make sure you understand the capabilities of the platform you’re working with. the CPU bus and the main bus are the same. The CPU bus will not usually be the bus that is connected directly to the external interface devices that you must control. Bus designs are usually based on standards and given names or numbers such as IEEE-696. which determines where in memory is being read from or written to or which I/O device is being accessed. PCI. The interface bus may not carry all of the data. Multibus. The first set is the data bus. The connections between busses are usually through gated devices that will be turned on and off depending on who is accessing what bus. I refer to this bus as the interface bus. For instance. On smaller systems. and especially the interface bus. but in some of the processor documentation. The CPU bus is usually connected to the main bus. the main bus may be composed solely of the CPU signals.some proposed. or ISA Bus. and some that are available now. along with things like processor interrupt lines and clock signals. they are fairly explicit about the effect of certain instructions concerning some of these signal lines. It would help you to be familiar with the basic bus structures on your target hardware platform. Quite honestly. the external interface bus is called the Industry Standard Architecture. This is called the main bus. the control bus usually carries a wide variety of signals. Finally. You really don’t need to know the exact hardware layouts for these busses and signals.0 Page 74 of 667 . ISA. The CPU bus is usually comprised of three distinct sets of signals for most processors. or control lines that are found on the main or CPU bus. On the PC-ISA platforms. Only half of the data lines make there way out to this bus (it’s really a 16-bit bus).The Bus Structure A bus is a collection of electrical signals that are routed through a computer system between components. The most obvious to note is the bus from the CPU to the rest of the computer system. I’ve had to "drop back 10 yards and punt" a time or two. including read and write access lines for memory and I/O. asynchronous serial communications (RS-232). address. the PC-ISA bus is really quite crippled compared to the CPU bus. These actions are under hardware control so you generally don’t have to worry about them. which carries the values that are read from and written to memory or I/O devices. The second is the address bus. internal bus.

the IEEE-488 General Purpose Interface Bus (GPIB). except for the hardware clock timing. They work with frames or packets of data. Quite often. even the main bus may be accessible as a parallel interface of sorts (this is most common on laptops and notebook computers). no matter which device you must code. They are actually very forgiving devices. and on some systems. Buffered USARTs assist with this issue. These may include the keyboard or a mouse port to name few. USART (the added "S" is for synchronous) devices are very timing critical. in many cases. These devices provide interrupt-driven capabilities just like the serial devices.g. I recommend that you forget about polling and stick with interrupts. This is done by repetitively reading the status registers on the UART/USART in a loop. a delay causes them to abort the frame they’re working on and send an error instead. Other devices besides communications channels may also be controlled through UARTs. They have no way. the infamous Centronics interface). but still very important. Less commonly found. 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.. and they take much of the drudgery away from the programmer that is implementing serial communications. SDLC.communications are called UARTs (Universal Asynchronous Receiver Transmitters). If you intend to design a true multitasking system. All these synchronous communications standards have one thing in common from the operating system writer’s point of view: critical timing. Therefore. they are much easier to control than serial devices because they have fewer communications options. you must ensure that you can get out the entire frame (byte by byte) without a large delay factor involved between each byte. Concentrate on the efficiency of all the Interrupt Service Routines (ISRs) on your system. 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. In fact. Parallel I/O Parallel Input/Output may include devices for printer interface (e. Parallel buses can move data faster. or HDLC. Most varieties of these devices are very similar. USARTs generally expect the device driver to provide these packets with very little delay.25 (an international link and network layer standard). Unlike UART devices. 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. The issue of polling versus interrupt-driven control of communications devices crops up in documentation you read about UARTs and USARTs. The RS-232 device driver included with the MMURTL operating system should prove valuable to you. to determine when one byte ends and the next begins.0 Page 75 of 667 . are synchronous communications such as those used with X. MMURTL V1.

and you will need to know how to set it up and operate the device. The keyboard can usually be treated as just another device on your system as far as a standardized interface. Nothing infuriates an experienced user more than an undersized type-ahead buffer or MMURTL V1. You are responsible for the translations required from the keyboard device if they aren't already in the desired format. the method is determined by the interface hardware (the disk or tape controller hardware. but is really a device connected to the bus. The interfaces to these devices enable large volumes (blocks) of data to be moved between the devices and memory in a rapid fashion. the keyboard hardware will be accessed through I/O port (or memory address). This block movement of data is accomplished in one of several ways. Direct Memory Access (DMA) is one method. This hardware is usually a small processor or a UART/CPU combination. which is discussed in detail later in this chapter. or network card). and tape drives. and all you do to transfer the block is write or read those addresses then tell the device you're done.0 Page 76 of 667 . Keyboard users prefer a familiar set of codes to work with such as ASCII (American Standard Code for Information interchange).Block-Oriented Devices Block-oriented devices can include disk drives. If you have a platform that communicates with its users through external terminals. you may simply have to deal with a serial device driver. or Unicode. The keyboard on many systems is preprogrammed to provide series of bytes based on some translation table within the keyboard microprocessor. This requires an interrupt service routine. Another method is shared memory blocks – a process in which a hardware device actually takes up a portion of your physical address space. Another way is programmed I/O. during unused portions of the CPU clock cycle. but you must realize the importance of this device. network frame handlers. which is available on some processors such as the Intel series. to name a few. You will usually not have a choice as to which method you use. 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. or even memory to memory. If this is the case. which is a relatively new multibyte international standard. This is how the Integrated Drive Electronics (IDE) interface works on the PC-ISA systems. Interrupts are the normal method for the keyboard device to tell you that a key has been struck. On many systems. DMA uses the least CPU bandwidth because it's a hardware device designed to transfer data between memory and devices. Programmed I/O uses special instructions to send data to what appears to be an address. Keyboard The keyboard may or may not be a part of the hardware to control on your system. the keyboard is an integral part of the hardware. The major difference between these forms of block-data transfer is the consumption of CPU time (bandwidth).

Each of these requires a completely different approach to how you interface video with your operating system. ASCII) that the video hardware translates into individual scan lines on-screen to form the characters. On an 80-column-by-25-line display. most platforms have the video hardware built in or directly accessible through the interface bus. you have direct access to "video memory" somewhere in the array of physical addresses that your CPU can reach. 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. Many books have been written on control of video hardware. You can even perform a CPU hardware reset through these same control ports. the keyboard hardware is tightly integrated into the system. this only consumes 4000 bytes. and even the device manufacturer’s manuals (builders of the integrated circuits).. On the PC-ISA platform. Keyboard handling in a multitasking system can present some pretty interesting problems. A common resolution on many systems is 1280 x 1024. Your tasking model and your programmatic interfaces will determine how this is accomplished. In character-based systems. This is especially true for the IBM PC-AT compatible platforms. devices perform more than one function on a particular platform.0 Page 77 of 667 . Bit-mapped graphics-based systems are much more complicated to deal with. Graphics systems must have an address for each bit or Pixel (Picture element) of information you wish to manipulate or display. In most cases. All the output would be via a serial port. One of these is how to assign and distinguish between multiple programs asking for keystrokes. this video memory will contain character codes (e. 600 x 500 divided by 8(size of a byte in bits)=37. Video Like the keyboard. MMURTL V1. The two basic forms of video display are character-based and bit-mapped graphics.losing keystrokes. However. It’s worth repeating that having the manufacturer’s documentation for the platform. I suppose you can imagine the veins popping out on my forehead when I reach the huge 15 keystroke limit of MS-DOS.g. This consumes the least amount of your address space. this can be 600 or more bits across and as many as 500 vertically. One byte per character and maybe one byte for the character’s color or attribute is required. and can consume a much larger area of your address space.500 bytes of memory space. you may not have any video hardware at all if your user I/O is through external terminals. the keyboard serial device allows more than just keyboard interaction. is worth more than I can indicate to you. In fact. Quite often. In a monochrome system with a decent resolution.

Chapter 26. and allow you to switch between them. Video-handling code may even be provided in the form of a BIOS ROM (Basic Input/Output System). it is still a vast amount of memory to access. I recommend you purchase a book that details the video hardware that you intend to control long before you implement your memory management. That equates to 600.If you want 16 colors for each bit. How much memory you require will be determined by what methods your video hardware uses. Even though they have paralleled access to this memory. Each bit array is on its own plane.. Other aspects of the video subsystem will also require your attention. This could consume a vast amount of memory. 16-bit code in a 32-bit system). This code may also be useless if it will not execute properly in your system (e. and your tasking model. because the video interface cards have RAM that they switch in and out of your address space. In some cases. I was interested in character-based. For a 256-color system. and I also allocated memory for large graphics arrays during initialization. this would be 16 planes at 37. In many cases. I considered future video and graphics requirements with MMURTL. nonoverlapping video users. I did.500 bytes (using the 600 x 500 size sample). In this age of graphics-based systems and graphical user interfaces. Manufacturers provide methods to allow these arrays to occupy the same physical address space. The concept of a message-based operating system plays well into event-driven program control (such as a windowing system). The video hardware must usually be programmed to tell it where this memory is located. I took this into consideration also. One thing I should mention is that many video subsystems may provide video code in ROM to control their hardware. Then they break video memory into four or more sections. This is known as planar pixel access. This includes cursor positioning. Your graphics implementation might have an effect on your memory management design or even your tasking model. which is hardware-controlled on character based systems. Another method is to make each nibble of a byte represent the four color bits for each displayed bit on-screen. Video hardware manufacturers realize this. These methods of displaying video information require you to allocate an array of OS-managed memory that is for the video display subsystem.0 Page 78 of 667 . this code will be of little use to you after the system is booted. Make MMURTL V1. and they give you a way to switch between the arrays.” describes each of the calls I designed for the video interface. have to research hardware cursor control. and timing when you write to the video display area. this would require three additional arrays to provide the independent color information.g. the video ROM's only purpose is to test and setup the video hardware during the testing and initialization phases of the boot process. The efficiency of your video routines has a large effect on the apparent system speed as seen by the user. Only one plane of the total memory consumes your address space. multiple. This is known as packed pixel access.000 bytes. you need to take graphics into consideration even if you build a character-based system first. I even cheated a little and left the video subsystem set up the way the boot ROM left it because it suited my purposes. “Video Code. interprocess communications. however. Your requirements will more than likely be different.

while 23-17 are put in the page register. DMA moves (channels 5-7). A better plan is to provide users with a programmatic interface to use the DMA hardware. .certain that the video documentation you acquire documents hardware access and not simply access to the code found in ROM. The DMA hardware calls are SetUpDMA(). the lower word (bits 15-0) is placed into the address registers of the DMA. except maybe an embedded system board. With word. The code is larger than it could be because I have expanded out each channel instead of using a simple table for the registers. Optimize it as you please. and the page register is the next most significant byte (bits 23-16). and to query the status of the move. 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). “Memory Management.0 Page 79 of 667 .DATA .INC . The code for these two calls is shown below. which takes care of the synchronization and details of DMA programming. I’ll show you what I provided for users of DMA. The page registers determine which 64K or 128K section of memory is being accessed. DMA devices have several control lines that connect to the CPU and to the devices that use it. Bit 16 is ignored by the page register.CODE With 8-bit DMA. In a multitasking system that may have several DMA users. These control lines provide the necessary "handshakes" between the device and the DMA hardware. MMURTL V1. Programmers that write device drivers have often faced the chore of learning all the intricacies of DMA hardware. and GetDMACount(). If the programmers all follow the rules for programming the DMA hardware to the letter. the operating system must ensure harmony between these users.INCLUDE MOSEDF. but this means that the programmers must be well versed on the DMA hardware-control requirements.” I also gave users high level calls to set up the DMA hardware for a move.INC is used for error codes that may be returned to the caller. I provided a method to allocate memory that returns the physical address they require which I touched on in chapter 5. There really is no data segment for DMA. Direct Memory Access (DMA) You’ll find DMA hardware on almost all platforms you work with. you’ll have no problems. taking into account the differences in control registers and commands on the target system’s DMA hardware. but the standard include file MOSEDF. address bits 16-1 are placed in the address registers. With minimal changes you could use this code on almost any system. DMA hardware also requires that the programmer know the physical addresses with which they are working.

which is DMA channel 0 on DMA chip 2.Read/Write Mode register .Read Status/Write Command .Ch 2 Word Count .Read/Write DMA Rq Register DMA1RCmdWbm EQU 0Ah . etc.Read Command/Write Single bit mask DMA1Mode EQU 0Bh .========== DMA Equates for DMA and PAGE registers ========= .Ch 3 Address . .Ch 0 Address .There are two 4-channel DMA devices.Writing this address clears byte ptr flip flop .Ch 0 Address DMA10Cnt EQU 01h .Write causes MASTER Clear (Read from Temp Reg) .Rd clears mode reg count/Wr Clr ALL mask bits DMA1MskBts EQU 0Fh . This is called one time during initialization of the operating system.Writing this address clears byte ptr flip flop DMA1Clear EQU 0Dh . PUBLIC InitDMA: MMURTL V1. This is called a cascaded device.0 Page 80 of 667 . This includes the cascade mode for generic channel 4.Ch 1 Address DMA11Cnt EQU 03h .Ch 1 Address .Read/Write DMA Rq Register .DMA Page register by DRQ/DACK number DMAPage0 EQU 87h .Ch 0 Word Count DMA11Add EQU 02h .Ch 2 Word Count DMA13Add EQU 06h .Read/Write DMA Rq Mask Register .Ch 2 Address DMA12Cnt EQU 05h .Ch 0 Word Count .Ch 2 Address . One of the channels from the second device is fed into a channel on the first device. DMA 1 Port addresses and page registers DMA10Add EQU 00h .Read Status/Write Command DMA1RqReg EQU 09h .Ch 3 Word Count .Ch 1 Word Count DMA12Add EQU 04h . etc.Rd clears mode reg count/Wr Clr ALL mask bits .Write causes MASTER Clear (Read from Temp Reg) DMA1ClrMode EQU 0Eh .Ch 3 Address DMA13Cnt EQU 07h .Ch 1 Word Count .Read/Write DMA Rq Mask Register . 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 .DMA DACK0 Page register DMAPage1 EQU 83h .Read/Write Mode register DMA1FF EQU 0Ch . The following equates are the register addresses for these two devices on ISA hardware. DACK1 (etc.) 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.Read Command/Write Single bit mask .Ch 3 Word Count DMA1StatCmd EQU 08h .

and data is moved into the lower part of the current 64K segment (not into the next segment. even on word moves. AL OUT DMA2ClrMode. (Floppy Disk I/O uses 1) MMURTL V1.CH 0 DMA 1 . dChannel. the address and count must be divided by two for the DMA hardware. 1 Single Cycle.0 Demand Mode. DmaSetUp(dPhyMem. we do math) dChannel (0. . DMA is crippled because it can’t move data across 64K physical boundaries for a byte-oriented move.CH 3 DMA 1 & 2 . . Verify). I do this for the caller. AL MOV AL. For channels 5.MASTER CLEAR (same as hardware reset) . .CH 1 DMA 1 & 2 . AL OUT DMA2Mode.Enable ALL DMA 1 Channels . The PUBLIC call DMSASetUp() follows: . . 42h OUT DMA1Mode.AL OUT DMA1ClrMode. AL OUT DMA2StatCmd. or 128K boundaries for a word move.0 Page 81 of 667 . (Cascade Mode) MOV AL. AL OUT DMA2StatCmd. . Out. I left it up to the caller to know if their physical addresses violate this rule. dType.CH 2 DMA 1 & 2 . AL RETN . AL OUT DMA1StatCmd.CH 0 DMA 2 . . 0C0h DMA2Mode. as you might assume). 41h OUT DMA1Mode. so they always specify byte count for the setup call. . . . .Master disable . 43h OUT DMA1Mode.6.7) dType . AL OUT DMA1Clear.1. AL OUT DMA2Mode. AL MOV AL. AL XOR AL.5. AL AL. 2 = Out (Read memory) dMode .3.All commands set to default (0) . sdMem. AL OUT DMA2Clear. dMode) EBP+ 28 24 20 16 12 dPhyMem is physical memory address sdMem is nBytes to move (STILL bytes for word xfers. . 1 = In (Write Mem). the segment is wrapped around. .2. . 40h DMA1Mode. AL XOR AL. If they do.Enable ALL DMA 2 Channels The PUBLIC call DMASetUp() sets up a single DMA channel for the caller. 04 OUT DMA1StatCmd.MOV AL.6 and 7.0 = Verify. The caller sets the type of DMA operation (In. . AL OUT DMA2Mode. AL MOV OUT MOV OUT AL. AL XOR AL. AL .

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

AL EAX. AL EAX. AL DMA1FF.0 Page 83 of 667 . [EBP+24] EAX DMA11Cnt. AL EAX. AL AL. AL AL. CH2 . AL EAX. AL EAX. AL EAX. AL AL. Lo byte address . 00000001b DMA1RcmdWbm. AL EAX. OR with MODE/TYPE . BL DMA1Mode. 00000101b DMA1RCmdWbm. [EBP+24] EAX DMA12Cnt. 8 DMA11Add. AL EAX. channel 1 Set Mask for DRQ . Clear FlipFLop (Val in AL irrelevant) . channel 0 Clear Mask for DRQ AL. 00000010b AL. AL EAX. EAX DMAEnd . AL DMA1FF. dPhyMem . Highest byte (to page register) . 8 DMA12Add. Lo byte address . AL EAX. Clear FlipFLop (Val in AL irrelevant) . Highest byte (to page register) . AL EAX. Hi Byte . OR with MODE . 8 DMAPage0. channel 2 Set Mask for DRQ . Highest byte (to page register) . AL . [EBP+28] DMA12Add. 00000110b DMA1RCmdWbm. 8 DMA11Cnt. dPhyMem . [EBP+24] EAX DMA10Cnt. 8 DMAPage1. 8 DMA12Cnt. channel 1 Clear Mask for DRQ AL. [EBP+28] DMA11Add. 8 DMA10Cnt. EAX DMAEnd . AL EAX. BL DMA1Mode. AL AL. AL EAX. 00000000b DMA1RcmdWbm. 8 DMAPage2. Hi Byte . sdMem . CH1 . AL EAX. 00000001b AL. sdMem MMURTL V1.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 EAX.

0 Page 84 of 667 . 8 DMA21Add. AL EAX. 00000111b DMA1RCmdWbm. AL EAX. Highest byte (to page register) . channel 1 DMA2 Set Mask for DRQ . . CH1 on DMA 2 . EAX EAX. AL . . 00000001b AL. AL EAX. . . CH3 . OR with MODE . EAX DMAEnd . Clear FlipFLop (Val in AL irrelevant) . . AL EAX. 8 DMA13Add. AL EAX.MOV OUT STI XOR JMP 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. We only need 23-17 for the page Highest byte (to page register) sdMem DIV by 2 for WORD Xfer One less word MMURTL V1. BL DMA1Mode. 15 DMAPage5. . 8 DMA13Cnt. Hi Byte . 00000011b DMA1RcmdWbm. dPhyMem . Hi Byte . AL AL. 1 DMA21Add. [EBP+28] EBX. AL DMA1FF. . 00000101b DMA2RCmdWbm. 8 DMA21Cnt. AL DMA2FF. 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 . . AL AL. BL DMA2Mode. AL AL. AL EAX. OR with MODE . [EBP+24] EAX DMA13Cnt. EAX DMAEnd . EBX EAX. channel 3 Clear Mask for DRQ . 0FFFFh EAX. AL EAX. AL EAX. [EBP+24] EAX. 00000011b AL. AL EAX. 00000010b DMA1RcmdWbm. AL EAX. 1 EAX DMA21Cnt. .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 AL. channel 2 Clear Mask for DRQ AL. [EBP+28] DMA13Add. AL EAX. Lo byte address . AL EAX. channel 3 Set Mask for DRQ . 8 DMAPage3. sdMem .

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

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

program. or at set intervals. 8 bytes to dump from the stack Timers Hardware timers are a very important piece of any operating system. 6 SHORT DMAC7 DX. Quite often.AL IN AL. . DMA13Cnt SHORT DMACDoIt . DMA23Cnt CMP EAX. Some platforms may provide multiple hardware timers that you can access. 5 SHORT DMAC6 DX. . . DMA22Cnt SHORT DMACDoIt EAX. Internally. 7 JE SHORT DMACDoIt MOV EAX. EAX. hardware timers have a counting device (a register) that is usually decremented based on a system-wide clock signal.DX MOV CH. .CX has words/bytes left in DMA . . and read as required.EBP POP EBP RETF 8 DMACDoIt: CLI IN AL. DMA21Cnt SHORT DMACDoIt EAX.0 Page 87 of 667 . ErcDMAChannel MOV ESP. 20 bytes of junk to dump . . A hardware timer is basically a clock or stopwatch-like device that you can program to interrupt you either once.DX MOV CL.AL STI MOV WORD PTR [ESI]. . . No Error . these devices allow you to read this register to obtain the countdown value at any time. .DMAC3: CMP JNE MOV JMP DMAC5: CMP JNE MOV JMP DMAC6: CMP JNE MOV JMP DMAC7: MOV DX. Almost everything you do will be based around an interrupt from a hardware timer. EAX MOV ESP. You can generally set a divisor value to regulate how fast this register is decremented to zero. One or more of these timers may be dedicated to hardware functions on the system MMURTL V1. . . CX XOR EAX.EBP POP EBP RETF 8 . 3 SHORT DMAC5 DX.No such channel! . This interval or time period can range from microseconds to seconds. or even hours in some cases. which determines the interrupt interval. .

and also has the logic to select one of these incoming signals to actually interrupt the CPU. but you must generally set values to indicate what will be placed on the bus. In the process of an interrupt.0 Page 88 of 667 . Priority Interrupt Controller Unit (PICU) The PICU is a very common device found on most platforms. Your job is to know how to program the PICU. but it is critical that it’s done correctly. Therefore.board. This is not very complicated. and eventually feeds one signal to the CPU is the PICU. however. they will more than likely be initialized at boot time and you can forget them. The device that handles all of the devices that may interrupt. or at least make sure it doesn’t interfere with them after they have been set up by ROM initialization prior to boot time. There are instructions to tell the PICU when your interrupt service routine is finished and interrupts can begin again. be responsible to program these dedicated timers. The PICU takes care of this for you. This ISR is usually performed by the CPU which places a value on the data lines when the CPU acknowledges the interrupt. ways to tell the PICU to block a single interrupt. the CPU must know which device is interrupting it so it can dispatch the proper Interrupt Service Routine (ISR). A hardware interrupt is a signal from a device that tells the CPU (the operating system) that it needs servicing of some sort. As you can imagine (or will have realized). MMURTL V1. The operating system may. All of this information will be very specific to the PICU that your platform uses. and not available to the operating system interrupt mechanisms directly. some form of multiplexing that takes place on the CPU interrupt signal line. many devices interrupt the processor. If you have timers that perform system functions for the hardware. Usually. A PICU has several lines that look like the CPU interrupt line to the devices. These timers may be for things like DMA refresh or some form of external bus synchronization. and ways to tell the PICU how to prioritize all of the interrupt lines that it services. the processor has one or two electrical signal lines that external hardware uses to indicate an interrupt. You will need the hardware manual for code examples.

memory. because this address is executed by a boot ROM. be organized with a file system. and important structures. Your operating system’s tasking and memory models may differ from MMURTL’s. OS Initialization Initializing an operating system is much more complicated than initializing a single program. Getting Booted If you've ever done any research on how an operating system gets loaded from disk. This will give you a very good head start on your studies. The processor's purpose is to execute instructions. This device will. where boot ROMs usually reside. you'll know there is no definitive publication you can turn to for all the answers. This information can be found in the processor hardware manual. including hardware. Processor designers are very specific about what state they leave the processor in after they accomplish their internal hardware reset. what instruction does it execute first? The instruction pointer register or instruction counter. If you're using another platform. The question is. and it's not exactly easy." that's exactly what I mean. we start with loading the operating system.Chapter 7. it executes from a reset state. but the basic steps to get your operating system up and running will be very similar.) The operating system writer may not really need to know the processor’s initial execution address. I'm going to give you a good idea what you need to know. Boot ROM When a processor is first powered up. the first executed address is usually somewhere at the high end of physical memory. no doubt. tasks. In this chapter. The boot ROM is read-only memory that contains just enough code to do a basic system initialization and load (boot) the operating system. (The initial address that is executed will also be documented in the processor manual. The operating system will be located on a disk or other block-oriented device. If you're using a PC ISA-compatible platform. MMURTL V1. then go through each section of the basic initialization. For this reason. (whatever they call it for your particular processor) tells the processor which instruction to execute first. 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. or in the processor programming manual from the manufacturer. you'll have to do some research. I've done all your homework.0 Page 89 of 667 . When I say "just enough code. it's not everyday someone wants to write an operating system. which you can apply to most systems in a generic fashion.

3. it may have loaded it right where you want to put your operating system. or BIOS calls needed to load it into memory). the hardware commands. Where the boot ROM expects your boot sector or sectors to be on disk. 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). This small amount of boot sector code has to know how to get the operating system loaded and executed. such as a whole track). 8. Where this boot sector is loaded and executed. loader program that in turn loads the operating system. When the boot ROM loaded your boot sector. It is usually the first logical sector of the disk. 2. how does it load a file (the operating system)? It generally doesn’t . If this is true. more complicated. This amounts to reading a sector into a fixed address in memory. In the PC world. How to find your operating system or first-stage boot program on disk from the boot sector code once it’s executed. If the operating system you are loading is located in a special place on the disk. This can be done in more than one step if required.g. 5. 512 bytes (standard hardware sector size on many systems) is not a heck of a lot of code. 7. this is known as the Boot Sector of the disk or diskette. 4. This is known as a multistage boot. and whether or not you have to move it after you load it. then life is easy. The factors you will have to consider when planning how your operating system will boot are: 1. This means the boot sector code has to relocate itself before it can even load the operating system.0 Page 90 of 667 . Where your operating system will reside in memory. Where your operating system’s first instruction will be. so you can begin execution. How the boot sector code will read the operating system or first-stage boot program from disk (e. then execute it. MMURTL V1. To be honest. The Boot Sector The boot ROM code knows only enough to retrieve a sector or two from the disk to some location in RAM. the boot sector code may load least not as a file from the file system. 6.. then jumping to the first address of the sector. 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. such as a fixed track or cylinder. You only need enough hardware and file-system knowledge to read the sectors from disk to the location you want them in RAM. In other words. 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. I don’t pretend to know how to boot your system if you’re not using the platform I chose. But it gets more complicated. Whether there are any additional set-up items to perform before executing the operating system.Consider that the boot ROM code knows little or nothing about the file system.

then I’ll show you some boot sector code in grueling detail. and the code segment loads at a fixed address. and runs contiguously for about 160Kb. There are usually a few things you must do before you actually execute the operating system. so I relocated my boot sector code (moved it and executed the next instruction at the new address). as were also the active interrupt vector table and RAM work area for the BIOS. I wanted mine at the very bottom and it would have overwritten this memory address. 6. then you can use the BIOS calls to load the operating system or first-stage boot program. This was the case with my system. MMURTL V1. 8.I will go over each of the eight items on the above list to explain how it’s handled. A single boot sector (512 bytes) is loaded to address 7C00 hex which is in the first 64Kb of RAM. 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. I read in the operating system above the actively used memory then relocate it when I didn’t need the BIOS anymore. I moved the boot sector code way up in memory. If your operating system’s first execution address varies each time you build it. it may not always be in the same place if it’s located after variable-sized structures on the first portion of the disk. This lead to two relocation efforts. and also with MS-DOS boot schemes. The boot sector was in the way (at 7C00h). 4. Just what these things are depends on the hardware platform. My operating system data and code must start at address 0 (zero). The 7C00 hex address may be fine for your operating system if you intend to load it into high memory. because they provide the BIOS. and I also go into 32-bit protected mode (Intelspecific) before executing the very first operating system instruction. I have to turn on a hardware gate to access all the available memory. In my case. then Drive C. Keep in mind that the PC ISA systems have a BIOS (Basic Input/Output System) in ROM. In an ISA system. then executed the next instruction at the new address. I know the address of the first operating system instruction because my design has only two areas (Data and Code). which they store in a small work area in RAM. How you find your operating system on disk will depend on the type of file system. and where you stored it. First. Second. I was lucky with the PC ISA systems. and they have some knowledge of the drive geometry from their initialization routines. 1. but the standard order on ISA systems is drive A.0 Page 91 of 667 . 2. 3. 5. This was a double problem. Some systems enable you to change the boot order for drives. 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. 7. 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.

be stored contiguously in each of the following logical sectors. MMURTL V1. DS:CSEG. . Why not? I do own desc.a loadable image beginning at cluster 2 on the disk. This contains additional information about the file system . There’s no operating system here now.Drive boot sector came from .nSectors/track .The following code is a boot sector to load an operating system from a floppy on a PC ISAcompatible system.Used for temp storage of Sector to Read .The following are pointers to my IDT and GDT after my OS loads .disks.386P .stage boot of the MMURTL OS which is about 160K stored as .nFATs .The . . It must be assembled with the Borland assembler (TASM) because the assembler I have provided (DASM) doesn’t handle 16-bit code or addressing. CSEG SEGMENT WORD ’Code’ USE16 ASSUME CS:CSEG.This boot sector is STUFFed to the gills to do a single . I stick the stack anywhere in the first megabyte of RAM I desire.nTotal Sectors (0 = <32Mb) .nSect in FAT .11 bytes for volume name . Examine the following code: .and is found on all MS-DOS FAT compatible . One thing you’ll notice right away is that I take over memory like I own it.needs some of this information if this is a bootable disk.instructions. and are not part of the above boot sector data structure.Sect/Cluster . Ext Boot Signature (always 29h) .Resvd sectors . 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 ’ .8 bytes for FAT name (Type) .This 59 byte structure follows the 3 jump bytes above . The OS must .nHidden Sectors (first whole track on HD) . The boot sector code also .The actual number of sectors is stored in the data param nOSSectors.nBytes/Sector .Root Dir entries max .386P directive is required because I use protected .nHeads . ES:Nothing 0h JMP SHORT Boot up NOP ORG .0 Page 92 of 667 .on this disk that the operating needs to know. (worthless) .nTotalSectors if > 32Mb . (140 sectors max) Was Volume ID number ’ .Padding to make it 3 bytes .

BASE (Linear) .Stick the stack at linear 98000h (an arbitrary location) MOV MOV MOV AX.This is done by finding out where it is and writing .0 Page 93 of 667 .Now we must update the BIOS drive parameter . Push the . table so it can read the disk for us. AX SP. SI CX. then "return" to it.Offset to new location (Next PUSH) . AX DI. AX MOV BX. Now we "jump" UP to where we moved it.LIMIT 256 IDT Slots . . It’s pseudo jump .Destination segment . MOV CX. 8000h . 09000h PUSH AX MOV AX. It works.Clear interrupts . nSectPerTrack . 0078h . at the beginning of this code CLI . DI AX.Source segment .LIMIT 768 slots . AX SI. that I calculated after assembling it once.Int 1E FDC Params! MMURTL V1.Get this while DS is correct XOR AX. Now set DS equal to ES which is 9000h PUSH ES POP DS . return address. 6Fh PUSH AX RETF .9000h SS. vector location. 7C0h DS.IDTptr DW 7FFh DD 0000h DW 17FFh DD 0800h .The BIOS left a pointer to it at the 1E hex interrupt . MOV AX.This is where we jump from those first 3 bytes BootUp: . some of the parameters to it from the boot sector.Segment for new location . .Move this boot sector UP to 90000h Linear (dynamic relocation) MOV MOV XOR MOV MOV XOR MOV REP AX. 09000h ES. AX MOV DS. 512 MOVSB .BASE (Linear) GDTptr .This is the Boot block’s first instruction from the initial jump .

Calculate the size of the root directory and skip past it .logical sector order. Additional Reserved sectors (optional) . FATS (1 or more) .on the disk and read in the total number of OS sectors in .Save DS through the reset . POP DS . WORD PTR nHidden+2 ADD AX. (3rd cluster is really the first allocated . CX MMURTL V1. ADC DX. nFATS MUL WORD PTR nSectPerFAT ADD AX.CX now has a Word that contains the sector of the Root . Hidden Sectors (optional) .Reset Disk Controller (DL has drive num) .Size of Dir Entry WORD PTR nRootDirEnts BX. MOV SI.The layout of the Disk is: . 0Fh .What we do now is calculate our way to the third cluster .We are gonna skip checking to see if the first file .Required for Disk System Reset PUSH ES POP DS PUSH DS STI MOV DL. nRsvdSect MOV CX. now let’s read some stuff!! . Boot Sector (at logical sector 0) .Make DS correct again .LDS MOV MOV the first allocated sectors (this is where the OS or .The controller is reset. AX . AX MOV AL.0020h . CL BYTE PTR [SI+9]. OFFSET MsgLoad CALL PutChars . We need the space for other code.0 Page 94 of 667 . Root Directory (n Sectors long) XOR AX. AX INT JC 13h SHORT BadBoot .Reset failed.Must set interrupts for BIOS to work . MOV MUL MOV DIV ADD AX.stage one of the a two stage loader should be). WORD PTR nHidden . DS:[BX] BYTE PTR [SI+4]... cluster because the first 2 are unused). bBootDrive XOR AX.Save in CX .really IS the OS. . nBytesPerSect BX AX.

Logical Track left in AX WORD PTR nHeads . AX .so we are really at cluster 2 like we want to be. but works well. 2 . 0Eh MOV BX. MOV DIV INC MOV XOR DIV MOV XCHG MOV XCHG SHL OR MOV MOV . BX . DL .waits for a key to reboot (or tries to) via int 19h BadBoot: MOV SI. nOSSectors . . 1 . CH .This is a single sector read loop .AX 16h 19h . shifted to bits 6 and 7 CL. 06000h MOV ES. .Number of sectors AH.Bad boot goes here and displays a message then .This is segment where we load the OS. DH .AL JZ SHORT Done MOV AH.Number of OS sectors to read JMP SHORT ContinueBoot . Hi 2 bits to CL CL. MOV CX. Read a . Cyl in AX DH. BX NextSector: PUSH AX PUSH CX PUSH DX PUSH ES XOR BX.OR with Sector number AL.Sys Reboot PutChars: LODSB OR AL.Read MMURTL V1. OFFSET MsgBadDisk CALL PutChars XOR INT INT AX. 6 .0 Page 95 of 667 .Low 8 bits of Cyl to CH. Drive to DL CX.. It’s slow. nSectPerTrack SI .Cyl into CX CL.Sector to read DX.Divide LogicalSect by nSectPerTrack DL . but cluster 0 and 1 don’t exist .Leaves Head in DL.0007 INT 10h JMP SHORT PutChars Done: RETN ContinueBoot: MOV BX. before we move it down to 0000h .Head to DH. DX . ES:BX points to sector read address logical sector to ES:BX Logical Sector Number SI. AX has .AX is at sector for cluster 0. BYTE PTR ResvdByte .Sector numbering begins at 1 (not 0) ResvdByte.Wait for keystroke . bBootDrive DL.

CX IBEmm2: IN AL.Next Sector .I increment ES and leave BX 0 .So move the OS MOV DX.02h LOOPNZ IBEmm1 MOV AL.64h TEST AL. move . SI AX. turn on the A20 line.Read that sucker! MOV SI.64h TEST AL.INT JC 13h SHORT BadBoot .CX IBEmm1: IN AL.0 Page 96 of 667 . AX ES.A20 line should be ON Now .Now we disable interrupts. DX MMURTL V1. BX SI.512 bytes for segment .DI CX.AL XOR CX.from 60000h linear up to about 80000h linear.the OS down to address 0. Move 64K data chunk from linear 60000h to linear 0 MOV MOV XOR XOR MOV XOR MOV BX.0D1h OUT 64h. 06000h DS.0DFh OUT 60h. . set protected mode and JUMP! CLI XOR CX. OFFSET MsgDot CALL PutChars POP POP POP POP MOV ADD MOV INC LOOP ES DX CX AX BX.02h LOOPNZ IBEmm2 . 8000h . 20h ES.At this point we have the OS loaded in a contiguous section . ES BX.AX DI.64h TEST AL.CX IBEmm0: IN AL. BX AX NextSector .02h LOOPNZ IBEmm0 MOV AL.AL XOR CX.BX will be zeroed at top of loop .

referencing a 32 bit segment with a 32 bit instruction.1 MOV CR0.Control Register . DX SI. 9000h MOV defining the bytes that would make the FAR jump .call JMP FAR 08h:10000h.BX .We define a far jump with 48 bit pointer manually .Clear prefetch queue with JMP .Load Processor ITD Pointer .DI CX.Set protected mode bit .OS can find boot drive in DL on entry .bytes (66h and 67h) are required to indicate we are .BX GS. SI AX.0 Page 97 of 667 . SI AX.BX ES. DX MOVSW . 10h DS. bBootDrive LIDT FWORD PTR IDTptr LGDT FWORD PTR GDTptr MOV EAX.Load Processor GDT Pointer . BX SI.CR0 OR AL.1000h ES. BX XOR EDX.BX FS. Move first 64K code chunk from linear 70000h to 10000h MOV MOV XOR MOV MOV XOR MOV REP BX.Set up segment registers .2000h ES.DX is 8000h anyway .WORD move . DB DB DB DD 66h 67h 0EAh 10000h MMURTL V1. DX MOVSB . 07000h DS. Move last code (32K) from linear 80000h to 18000h MOV XOR MOV MOV XOR MOV REP DS.AX DI.EAX JMP $+2 NOP NOP MOV MOV MOV MOV MOV MOV BX.DI CX. EDX MOV DL.CLD REP MOVSW .WORD move .BX SS.BYTE move MOV BX.AX DI. The segment and code size escape .

00h ’ DW 0AA5Fh ENDS END . Most operating systems provide utilities to make a disk bootable.EXE header. 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. 0Ah. To assemble and link this into a usable boot sector. and Write commands as follows: MMURTL V1.0 Page 98 of 667 . such as the dynamic relocation address (if you needed to relocate).map). such as how to write the boot sector to the disk or read a boot sector from the disk and inspect it. logical sector number. It’s quite easy to do with the Load. find out where Debug’s work buffer is by doing an Assemble command (just type A while in Debug). and a map file from the link (Bootblok. Press enter without entering any instructions and use the address shown for the Load command along with the drive. 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. As you change and experiment with the boot sector. I left it in.lst). The last 512 bytes is the boot sector binary code and data in a ready-to-use format. and number of sectors following it.EXE file exactly 1024 bytes in length.DW 8h MsgNone MsgBadDisk MsgLoad MsgDot BootSig CSEG DB DB DB DB ’ This is actually padding! 0Dh. or after the fact. 00h 0Dh. It’s worthless. Write. This will show you a segmented address. use TASM as follows: TASM Bootblok Bootblok Bootblok <Enter> You need some additional information to experiment with this boot sector if you are on the PC ISA platforms. The first 512 bytes is the .MS-DOS used this. and Name commands. To generate a list file from the assembly (Bootblok. use Borland’s Turbo Assembler: TASM Bootblok. They may do this as they format the disk.’. you need to inspect the actual size of the sector and location of some of the code. To read the boot sector from a disk.asm <Enter> This will make a . ’Bad Boot Disk!’. 0Ah. Register. ’Loading MMURTL’. 00h ’.

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. you can use Debug to load it as a . From the MS-DOS prompt.EXE file and write the single 512 sector directly to the disk or diskette. you can use the Dump command to display it.exe <Enter> -D <Enter> 219F:0000 EB 3E 90 (etc. type in the Debug command followed by the name of your new executable 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.bin in the root directory of drive C. MMURTL V1. After you have assembled your boot sector. If so. Hopefully. you should not do this to your active development drive. I’m happy. Format a disk with MS-DOS 5. The commands are: C:\> Format A: /U <Enter> C:\> Copy OS. After that you can copy any thing you like to the disk. To find where Debug loaded it. you only need to put the operating system where it expects to find it.IMG A:\ <Enter> The new boot sector and the operating system are on the disk and ready to go. Then copy the operating system image to the disk. Good luck. then write it to the disk. -W 219F:0000 0 0 1 <Enter> After the boot sector is on the disk. 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. The results can be disastrous. and WITHOUT the /S (System) option. C:\>Debug BootBlok. Debug refers to the drives numerically from 0 to n drives.0 or higher using the /U (Unconditional) option. It will be the first file on the disk and will occupy the correct sectors. I’ve saved you some serious hours of experimentation. I only had to do it thirty or forty times to get it right.) This will dump the first 128 bytes and provide the address you need to the Write command. This is easy to do.0 Page 99 of 667 . Keep in mind.N C:\Bootblok.

the boot code may have already set up the segment registers. In a non-segmented processor (such as the 68x0 series) this wouldn’t even be necessary. I couldn’t. It does everything that the boot sector code does. you may face the chore of booting it from another operating system. but it’s not necessary. Your operating system may already have some of the addresses hard-coded in advance. it may not be in a form you can load directly from disk. along with reading the operating system from it’s native executable format (a RUN file). It is called MMLoader. In a segmented processor (such as the Intel series). The data and code is aligned where it would have been had it been loaded with a loader that understood my executable format.Operating System Boot Image Depending on the tools you use to build your operating system. This program basically turns MS-DOS into a $79.00 loader. My MakeImg utility is included on the CD-ROM and must be built with the Borland C compiler. If it is in an executable format from a linker. This is how I run MMURTL most of the time. At this point. This includes setting up the OS stack.0 Page 100 of 667 . This reads the MMURTL executable and turns it into a single contiguous executable image to be placed on the disk. With MMURTL. understands. and loads the image into RAM then executes it. I have a program (also provided on CD-ROM) that reads.C) should also be built with the Borland C compiler as it has imbedded assembly language. 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. you might be able to avoid a need for a utility like this. It has some additional debugging display code that you can easily eliminated if you like. Instructions are included in the single source file (MakeImg. Other Boot Options If the operating system you write will be run from another environment. interrupts are usually disabled because there are no addresses in the interrupt vector table. MMURTL V1. If no address fix-ups are required in your operating system executable. Disabling interrupts is one of the chores that my initialization code handles. This single source file program (MMLoader. there will be a header and possibly fix-up instructions contained in it. 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. I have provided a utility called MakeImg.c) to properly build it.

and also some basic variables. It's not that big.All of the hardware that is resident in the system . Once the basic hardware and tables are set up. is not trivial. setting up for multitasking. but you may also be performing your own tasks management.0 Page 101 of 667 . I had to set up the basic interrupt vector table. 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. Chapter 21 shows how I go about this. What you do may simply depend on how much time you want to spend on it. “the Tasking Model. or not even initially allocate it with your memory-initialization code. 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.the first of which may be to eliminate it from the operating system after it is executed so it doesn't take up memory. They must simply be initialized to their default states. Either way. “Initialization Code. up to the point we jump the monitor program. It is all contained in the source file Main. You'll have think about which tables are static and which are dynamically allocated.” Static Tables and Structures How your operating system handles interrupts and memory. and basic timers . Chapter 3.such as the programmable interrupt controller unit. the array of call gates (how users get to OS calls). These first tables are all static tables and do not require any memory allocation. Its job is to set up the hardware and to get things ready to create additional tasks.” discussed some of your options for a tasking model. If you desire. the Direct Memory Access hardware.asm on CD-ROM. some of the hardware-related tasks you may have to accomplish are discussed in chapter 6. you must understand that the code you are executing on start up can be considered your first task. you can position it at the end of your code section and simply deallocate the memory for it. “The Hardware Interface. you may realize that most of this code may never be executed again. It should help give you a picture of what's required. The processor you're working with may provide the task management in the form of structures that the processor works with. Initialization of Task Management Initialization of tasks management. I just left it alone. you can let this first thread of execution just die once you MMURTL V1. This leaves you with a few options . Chapter 21.” contains all of the code from MMURTL that is used to set up the operating system from the very first instruction executed. because this will determine whether their initialization occurs before or after memory-management initialization. Additionally. In all of the cases that were discussed. If you want to eliminate it. Another option is just to forget the code if it doesn't take up too much space.must be initialized before you can set up the rest of the system (the software state). you've got your job cut out for you.

It seems that memory address calculations wrapped around in real mode (from 1Mb to 0). as well as one to switch to. If you use one more bit. Additional taskmanagement accomplishments may include setting up linked lists for the execution queue (scheduler) and setting variables that might keep track of statistical information. and you can’t manage or allocate it. you can address above 1Mb. but may also depend on hardware task management if you use it.0 Page 102 of 667 . Being a little conservative. As most of you are aware (if you’re programmers). This seems to work on most machines. Because I used the Intel task management hardware I really had no choice. I could have gone back and added code to reuse it. the real mode addresses are 20 bits wide. but you aren’t cognizant of memory yet.have set up a real first task. but to ensure it was turned on and left that way. and ended up sticking with the way it was documented in the original IBM PC-AT documentation. For instance. although some people may still have problems with it. you need to set up a Task State Segment (TSS) to switch from. It may be to turn on or off a range of hardware addresses. The option is usually up to you. Initialization of Memory During the basic hardware initialization. I think it’s kind of like inheriting baldness from your father. you may have to do some tweaking of the code. This depends on the platform. or even to set up the physical range of video memory if your system has internal video hardware. back to the initialization (already in progress). If your machine doesn’t support this method. One particular item that falls into this category on the PC ISA hardware platform is the A20 Address Line Gate. I didn’t want to waste a perfectly good TSS by just ignoring it. you may be required to send a few commands to various pieces of hardware to set up physical addressing the way you need it. 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. some which didn’t work on all machines. on the Intel processors. Meanwhile. MMURTL V1. You don’t know how much you have. This is enough bits to cover a one full megabyte. You learn to live with it. What is the A20 Line Gate? It all stems back to IBM’s initial PC-AT design. At this stage of the initialization. the A20 address line serves no purpose. you have basic tables set up and tasks ready to go. I suppose that this line even caused some problems because programmers provided a way in hardware to turn it off (always 0). These address lines are numbered A0 through A19. I tried several methods. and programmers wanted the same problem to exist in the AT series of processors. My goal was not to discover why it existed. 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. While in real mode. It is controlled through a spare port on the 8042 keyboard controller (actually a dedicated microprocessor).

This is where you call the routine that initializes memory management. This piece of code may have a lot to do. The following list is not all-inclusive, but gives you a good idea of what you may need to do. Most of the list depends on what memory model you chose for your design. If you went with a simple flat model then you’ve got a pretty easy job. If you went with a full blown demand paged system, this could be a fairly large chunk of code. Chapter 19, “Memory Management Code,” shows what I did using the Intel paging hardware. It is explained in a fair amount of detail. You may need to consider the following points: • • • • • • Find out how much physical memory you have. Know where your OS loaded and allocate the memory that it is occupying. Allocate other areas of memory for things like video or I/O arrays if they are used on your system. Set up the basic linked list structures if you will be using this type of memory management, otherwise, set up the basic tables that will manage memory. To make a transition to a paged memory environment, if you used paging hardware and your processor doesn't start out that way (Intel processors don't).

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. This is important because it forces you to have a section of code that remains, or is at least loaded, where the address match will occur. 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. If you chose not to do this, it may mean force dynamic relocation of your entire operating system. It's not that much of a headache, but one I'm glad I didn't face.

Dynamic Tables and Structures
After memory management has been initialized, you can begin to allocate and set up any dynamic tables and structures required for your operating system. These structures or tables may be allocated linked lists for resources needed for interprocess communications (IPC), memory areas for future tables that can't be allocated on the fly, or any number of things. The point is, that you couldn't allocate memory until memory-management functions were available. After all of the dynamic structures are allocated and set up, you're ready to execute a real program (of sorts). 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. At this point you usually load a command-line interpreter, or maybe even load up your windowing environment.


Page 103 of 667

Chapter 8, Programming Interfaces
For an operating system to be useful, the resources it manages must be accessible to the programmers who will implement applications for it. The programmers must be able to understand what the system is capable of and how to make the best use of the system resources. Entire books have been written to describe “undocumented system calls” for some operating systems. This can happen when operating system developers add a call that they think may not be useful to most programmers – or even confusing, because the call they add, and leave undocumented, may be similar to another documented call on the system. In the following sections, I describe both the application programming interface and the systems programming interface, including the mechanics behind some of the possible ways to implement them.

Application Programming Interface
The Application Programming Interface (API) is how a programmer sees your system. The functions you provide will be a foundation for all applications that run on your system. The design and implementation of an API may seem easy at first, but there are actually many decisions to be made before you begin work. Documenting Your API Your operating system may be for a single application, an embedded system, public distribution, or your own consumption and enjoyment. In any and all of these cases, you'll need to properly document the correct use of each call. This may sound like a no-brainer, but it turned out to be a time consuming and very important task for me. Even though I designed each of the calls for the operating system, I found, that on occasion, I had questions about some of the very parameters I selected when I went back to use them. If this happens to me, I can only imagine what will happen to someone walking into it from a cold start. Chapter 15, “API Specification,” shows you how I documented my calls. I'm still not entirely happy with it. I wanted to provide a good code example for each call, but time simply wouldn't allow it (at least for my first version). Procedural Interfaces Procedural interfaces are the easiest and most common type of interface for any API. You place the name of the call and it's parameters in a parenthetical list and it's done. The call is made, the function does its job and returns. What could be easier? I can't imagine. But there are so many underlying complications for the system designer it can give you a serious headache. This is


Page 104 of 667

really a layered problem that application programmers shouldn’t have to worry about. Quite often though, they do. I’ll go over the complications that have crossed my path. 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. Some of the considerations include: • • • • • Where the parameters are placed (on the stack or in registers?), If the parameters are on the stack, what order are they in? If the stack is used, are there alignment restrictions imposed by hardware? If registers are used, 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. No kidding! With UNIX, life was easy in this respect. UNIX was written with C; it used the standard defined C calling conventions. With newer operating systems always looking for more speed (for us power junkies), faster, more complicated calling conventions have been designed and put in place. It leads to a lot of confusion if you’re not prepared for it when you go use a mixed-language programming model. OS/2, for example, has three or four calling conventions. Some use registers, some use the stack, some use both. Even the IBM C/Set2 compiler has it’s very own unique calling conventions that only work with CSet/2 (no one else supports it). 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, as if you couldn’t guess). When a high-level language has it’s own calling convention, it won’t be a problem if this language also provides a method to reach the operating system if its conventions are different. The two most popular procedural calling conventions include: 1. Standard C - 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. 2. Pascal - (also known as PLM conventions in some circles) Parameters are pushed left to right, and the called function is responsible for making the stack correct. There is a plethora of not-so-standard conventions out there. I’m sure you know of several. Execution speed, code size, and hardware requirements are the big considerations that drive the need to deviate from a standardized calling convention. I’m fairly opinionated on this issue, especially when it comes to speed. In other words, 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. Not many calls are made recursively or in a tightly looped repetitive fashion to many operating system functions. Your opinion may vary on this. It’s up to you.


Page 105 of 667

The hardware requirements may also be one of the deciding factors. I used an Intel-specific processor hardware function for my calls (Call Gates), and it required a fixed number of parameters. I had no need for variable-length parameter blocks into the operating system, so the C calling conventions had no appeal to me. I decided on the Pascal (PLM) conventions. This makes porting a C compiler a little hectic, but I wasn’t going to let that drive my design. You may not want to go to the lengths that I did to support a specific calling convention on your system. You may be using your favorite compiler and it will, no doubt, support several conventions. If you want to use code generated by several different compilers or languages, you will surely want to do some research to ensure there is full compatibility in the convention that you choose.

Mechanical Details
Aside from the not-so-simple differences of the calling conventions, there are many additional details to think about when designing the API. Will your call go directly into the operating system? In other words, 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. Many systems use an intermediate library, that is linked into your code that performs some translation before the actual operating system code is executed. There are two reasons this library may be required. First, your code expects all of the data to be in registers, and you want to maintain a standard procedural calling convention for the sake of the high-level language interface. Second, the actual execution of the call may depend on a hardware mechanism to actually execute the code. An example of a hardware-calling mechanism is the Software Interrupt. A software interrupt is a method provided by some processors that allow an pseudo-interrupt instruction to trigger a form of a call. This causes the instruction pointer to be loaded with a value from a table (e.g., the interrupt vector table). The stack is used to save the return address just like a CALL instruction, but the stack is not used for parameters. A special instruction is provided to return from the software interrupt. MS-DOS uses such a device on the Intel-compatible processors. This device is extremely hardware dependent, and forces an intermediate library to provide a procedural interface. The advantages of using the software-interrupt function include speed (from assembly language only), and also the fact that operating system code relocation doesn’t affect the address of the call because it’s a value in the interrupt vector table. The address of the operating system calls may change every time you load it, or at least every time it’s rebuilt. However, this is not sufficient reason to use this type of interface. A simple table fixed in memory to use for indirect call addresses, or some other processor hardware capability can provide this functionality without all the registers directly involved.


Page 106 of 667

When the software-interrupt mechanism is used, you may only need one interrupt for all of the calls in the operating system. This is accomplished by using a single register to identify the call. The intermediate library would fill in this value, then execute the interrupt instruction.

Portability Considerations
Looking at portability might not seem so important as you write your system for one platform. However, Murphy’s law says that as soon as you get the system completed, the hardware will be obsolete. I really hope not, but this is the computer industry of the 90s. I can remember reading the phrase "30 or 40 megahertz will be the physical limit of CPU clock speeds." Yea, right. The portability I speak of is not the source-code portability of the applications between systems, but the portability of the operating system source code itself. For application source code portability, the procedural interface is the key. For the operating system source, 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. I considered this before I used the Intel-specific call gates, and the call gates are really not a problem. 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). However, things like software interrupts and register usage leave you with subtle problems to solve on a different platform. If portability is near the top of your wish list, I recommend you avoid a register-driven interface if at all possible.

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 either make you go find a public variable that you’re not sure is named correctly for the language you’re using, or they make you call a second function to find out why the first one failed. This may be a personal issue for me, 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. 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. When you need to return data from the call, have the caller provide a pointer as one of the parameters. Error reporting has special implications in a multitasking environment, especially with multiple threads that may share common data areas or a single data segment. A single public variable linked into your data to return an error code doesn’t work well anymore. Give this some serious thought before you begin coding.


Page 107 of 667

Systems Programming Interface
I divide the systems programming interface into two distinct categories. The first is the interface seen by programmers that write device drivers or any kind of extension to the operating system. The second is how internal calls are made from inside the operating system to other operating system procedures (public or internal). I never considered the second aspect until I wrote my own operating system.

Internal Cooperation
The second aspect I mentioned above is not as easy as it sounds. This is especially true if you intend to use assembler and high-level languages at the same time. You more than likely will. Many operating systems now are written almost entirely with high-level languages, but underneath them somewhere is some assembly language coding for processor control and status. Suddenly, naming conventions and register usage can become a nightmare. Select a registerusage plan and try to stick with it. I had to deviate from my plan quite often as you’ll see, but there was at least a plan to go back to when all else failed. You also need to investigate exactly how public names are modified by the compiler(s) you intend to use. Is it one underscore before the name? Maybe it was one before and one after, or maybe it was none if a particular compiler switch was active. Do you see what I mean? Internally, I ended up with two underscores for the public names that would be accessed by outside callers, and this wasn’t my original plan.

Device Driver Interfaces
For all practical purposes, device drivers actually become part of the operating system. They work with the hardware, and so much synchronization is required with the tasking and memorymanagement code, that it’s almost impossible to separate them. Some device drivers will load after the operating system is up and running, so you can’t put the code entry point addresses into a table in advance. 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. This capability requires some form of indirect calling mechanism. 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. In your system you will also have to consider how to make the device appear at least somewhat generic. For instance, 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.


Page 108 of 667

I was familiar with two different device driver interface schemes before I started writing my own system. 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, and Statusing the driver.

You need to consider these requirements in your design. The possibility exists that you may be writing a much more compact system than I did. If this is the case, all of your device drivers may be so deeply embedded in your code that you may not want a separate layered interface. You must also realize that it isn’t just the interface alone that makes things easier for the device driver programmer. 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. You may want to satisfy these requirements by adding calls to isolate them from some of the system hardware such as DMA, timers, and certain memory-management functions.

A Device-Driver Interface Example
With the four basic requirements I mentioned above, I set out to design the simplest, fully functional device driver interface I could imagine. It’s purpose is to work generically with external hardware, or hardware that appeared external from the operating system’s standpoint. The interface calls I came up with to satisfy my design were:
InitDevDr(dDevNum, pDCBs, nDevices, fReplace) DeviceOp(dDevice, dOpNum, dLBA, ndBlocks, pData):dError DeviceInit(dDevice, pInitData, sdInitdata):dError DeviceStat(dDevice, pStatRet, sdStatRetmax):dError

In chapter 10, “Systems Programming,” I provide detail on the use of these calls, so I won't go over it here. What I want to discuss is the underlying code that allows them to function for multiple device drivers in a system. This will give you a good place to start in your design. 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). This is a common requirement on many systems. 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. The first call listed in this section, InitDevDr(), is called from the driver to provide the operating system with the information it needs to seamlessly blend it into the system. You'll note that a pointer to its DCB is provided by the driver. 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).


Page 109 of 667

In a multitasking system, you will also find that some device drivers may not be re-entrant. Two simultaneous calls to the driver may not be possible (or desirable). For example, when one user is doing serial communications, this particular device must be protected from someone else trying to use it. 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. I use system IPC messaging to block subsequent calls to a driver that indicates it is not re-entrant. The following code example is from the file DevDrvr.ASM. This is the layer in MMURTL that initializes device drivers and also redirects the three device driver calls to the proper entry points. When you look at the code, pay attention to the comments instead of the details of the code. The comments explain each of the ideas I’ve discussed here. It’s included in this chapter to emphasize the concepts and not to directly document exactly what I’ve written. 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. You’ll see certain validity checks, and also that we allocate an exchange for messaging if the driver is not re-entrant.
; InitDevDr(dDevNum, ; EBP+24 ; ;Local vars dDevX EQU DWORD PTR prgDevs EQU DWORD PTR nDevs EQU DWORD PTR dExchTmp EQU DWORD PTR pDCBs, EBP+20 nDevices, 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, 16 MOV EAX, [EBP+24] MOV dDevX, EAX MOV EAX, [EBP+20] MOV prgDevs, EAX MOV EAX, [EBP+16] MOV nDevs, EAX InitDev00: CMP dDevX, nDevices JB InitDev01 MOV EAX, ErcBadDevNum JMP InitDevEnd

; ; ;Set up local vars

;Valid DCB number? ;Not valid DCB number

InitDev01: ;Now check to see if device is already installed ;and whether it’s to be replaced LEA EBX, rgpDCBs MOV EAX, dDevX ;Point EBX to rgpDCB ;dDevNum


Page 110 of 667

SHL EAX, 2 ADD EBX, EAX CMP DWORD PTR [EBX], 0 JZ InitDev02 CMP DWORD PTR [EBP+12], 0 JNZ InitDev02 MOV EAX, ErcDCBInUse JMP InitDevEnd InitDev02:

;pDCBx = 0 if not used yet ;Empty, OK to use ;OK to replace existing driver? ;Yes ;No - error exit

;If we got here, we can check DCB items then move ptr

MOV EAX, prgDevs ;EAX points to DCB CMP BYTE PTR [EAX+sbDevName],12 ;Check Device name size JA InitDev03 CMP BYTE PTR [EAX+sbDevName], 0 ;is Devname OK? JA InitDev04 InitDev03: MOV EAX, ErcBadDevName JMP InitDevEnd InitDev04: ;Now see if there are more devices for this driver DEC nDevs JZ InitDev05 ADD prgDevs, 64 INC dDevX JMP SHORT InitDev00 ;Decrement nDevices ;NONE left ;Next caller DCB ;Next devnum ;

;All error checking on DCB(s) should be done at this point InitDev05: ;Alloc Exch if driver in NOT reentrant MOV EBX, [EBP+20] ;pDCBs CMP BYTE PTR [EBX+fDevReent], 0 JNZ InitDev06 ;device IS reentrant! LEA EAX, dExchTmp ;Allocate device Exchange PUSH EAX ;into temp storage CALL FWORD PTR _AllocExch CMP EAX, 0 JNZ SHORT InitDevEnd InitDev06: ;All went OK so far, now move the DCB pointer(s) into array ; and assign exchange from temp storage to each DCB MOV MOV LEA MOV SHL ADD MOV MOV EAX, [EBP+16] nDevs, EAX EBX, rgpDCBs EAX, [EBP+24] EAX, 2 EBX, EAX EAX, [EBP+20] ECX, dExchTmp ;nDevices ;Set nDev to number of devices again ;Point EBX to OS rgpDCBs ;dDevNum ;EBX now points to correct pointer ;EAX points to first DCB ;ECX has semaphore exchange

InitDev07: ;Now that EBX, EAX and ECX are set up, loop through each


Page 111 of 667

;DCB (if more than 1) and set up OS pointer to it, and ;also place Exchange into DCB. This is the same exchange ;for all devices that one driver controls. MOV MOV ADD ADD DEC JNZ XOR [EAX+DevSemExch], ECX [EBX], EAX EBX, 4 EAX, 64 nDevs InitDev07 EAX, EAX

;next p in rgp of DCBs ;next DCB ;Any more DCBs?? ;Set up for no error

;If the device driver was NOT reentrant ;we send a semaphore message to the exchange for ;the first customer to use. MOV EBX, [EBP+20] ;pDCBs CMP BYTE PTR [EBX+fDevReent], 0 JNZ InitDev06 ;device IS reentrant! PUSH ECX ;ECX is still the exchange PUSH 0FFFFFFFEh ;Dummy message PUSH 0FFFFFFFEh CALL FWORD PTR _SendMsg ;Let erc in EAX fall through (Was ISend) InitDevEnd: MOV ESP,EBP POP EBP RETF 16

; ; ;

; A Call to the driver This is the call that the user of the device driver makes to initially set up the device, or reset it after a failure. 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. The other two calls, DeviceOp() and DeviceStat(), are almost identical to DeviceInit(), and are not presented here.
; ; DeviceInit(dDevNum, pInitData, sdInitData); ; EBP+20 EBP+16 EBP+12 Count = 12 ; PUBLIC __DeviceInit: ; PUSH EBP ; MOV EBP,ESP ; CMP DWORD PTR [EBP+20], nDevices ;Valid Device number? JB DevInit01 MOV EAX, ErcBadDevNum ;Sorry no valid DCB JMP DevInitEnd DevInit01: LEA EAX, rgpDCBs MOV EBX, [EBP+20] ; SHL EBX, 2 ; ADD EAX, EBX ; MOV EBX, [EAX] ;now EBX points to DCB (maybe) CMP EBX, 0 ;Is there a pointer to a DCB? JNZ DevInit1A ;Yes


Page 112 of 667


EAX, ErcNoDriver DevInitEnd BYTE PTR [EBX+DevType], 0 DevInit02 EAX, ErcNoDevice DevInitEnd

;NO driver!

;Is there a physical device?

DevInit02: ;All looks good with device number ;so we check to see if driver is reentrant. If not we ;call WAIT to get "semaphore" ticket... CMP BYTE PTR [EBX+fDevReent], 0 JNZ DevInit03 PUSH EBX PUSH DWORD PTR [EBX+DevSemExch] LEA EAX, [EBX+DevSemMsg] PUSH EAX CALL FWORD PTR _WaitMsg POP EBX CMP EAX, 0 JNE DevInitEnd DevInit03: PUSH EBX PUSH DWORD PUSH DWORD PUSH DWORD CALL DWORD POP EBX PUSH EAX

;Device IS reentrant ;save ptr to DCB ;Push exchange number ;Ptr to message area ;Get semaphore ticket ;Get DCB ptr back ;Serious kernel error!


[EBP+20] [EBP+16] [EBP+12] [EBX+pDevInit]

;Save ptr to DCB ;Push all params for call to DD

;Get ptr to DCB back into EBX ;save error (if any)

CMP BYTE PTR [EBX+fDevReent], 0 ;Reentrant? JNZ DevInit04 ;YES PUSH DWORD PTR [EBX+DevSemExch] PUSH 0FFFFFFFEh PUSH 0FFFFFFFEh CALL FWORD PTR _SendMsg DevInit04: POP EAX DevInitEnd: MOV ESP,EBP POP EBP RETF 12 ;No, Send semaphore message to Exch ;Bogus Message ; ;Ignore kernel error ;Get device error back

; ; ;dump params

Your interface doesn’t have to be in assembly language like the preceding example, but I’m sure you noticed that I added examples of the procedural interface in the comments. It you use assembly language and provide code examples, adding the procedural call examples is a good idea. This chapter (8) ends the basic theory for system design. The next chapter begins the documentation of and for an operating system.


Page 113 of 667

Chapter 9, Application Programming
This section introduces the applications programmer to the MMURTL operating-system interface and provides guidance on programming techniques specific to the MMURTL environment. 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. This section should be used with the reference manual for the language you are using. Before writing your first MMURTL program, you should be familiar with the material covered in Chapter 4, “Interprocess Communications,” and Chapter 5, “Memory Management.” These chapters provide discussions on the concept behind message based operating systems and paged memory management. These are important concepts you should understand before you attempt to create or port your first application. Some of the information provided in this section may actually not be needed to code a program for MMURTL. I think you will find it useful, though, as you discover the methods employed by this operating system. It may even be entertaining.

Throughout this section, several words are used that may seem language-specific or that mean different things to different programmers. The following words and ideas are described to prevent confusion: Procedure - A Function in C or Pascal, or a Procedure in Pascal, or a Procedure in Assembler. All MMURTL operating system calls return values (called Error Codes). Function - Same as Procedure. Call - This is also used interchangeably with function and procedure. Byte - An eight bit value (signed or unsigned) Word - a 16-bit value (signed or unsigned) DWord - a 32-bit value (signed or unsigned) Parameter - A value passed to a function or procedure, usually passed on the stack. Argument - Same as Parameter

Understanding 32-BIT Software
If you have been programming with a 16-bit operating system (e.g., MS-DOS), you will have to get used to the fact that all parameters to calls, and most structures (records) have many 32-bit components. If you are used to the 16-bit names for assembler variables, you will be comfortable with MMURTL because I have maintained the Intel and Microsoft conventions of BYTE,


Page 114 of 667

WORD, and DWORD. Most MMURTL parameters to operating system calls are DWords (32-bit values). If I were a perfectionist, I would have called a 32-bit value a WORD, but alas, old habits are hard to break and one of my key design goals was simplicity for the programmer on Intelbased ISA systems. One of the things that make processors different from each other is how data is stored and accessed in memory. The Intel processors have often been called "backwards" because they store the least significant data bytes in lower memory addresses (hence, the byte values in a word appear reversed when viewed with a program that displays hexadecimal bytes). 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. Look at the table 9.1 to visualize what you find in memory: Table 9.1 - Memory Alignment

Address Access As Byte Word Dword

0 01 01 01




Hex Value 01h 0201h 04030201h

02 02



This alignment serves a very useful purpose. Conversions between types is automatic when read as a different type at the same address. This also plays a useful role in languages such as C, Pascal, and assembly language, although it’s usually transparent to the programmer using a high level language. When running the Intel processors in 32-bit protected mode, this also makes a great deal of difference when values are passed on the stack. 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 always uses a DWord stack (values are always pushed and popped as dwords – 32-bit values.) This is a hardware requirement of the 386/486 processors when 32-bit segments are used.

Operating System Calling Conventions
All operating system calls are listed alphabetically in chapter 15, “API Specification.” Most operating system calls in MMURTL return a dword (32-bit) error code which is often abbreviated as ERC, erc, or dError. The descriptions in the API Specification (Chapter 15) show it as dError to indicate it is a 32-bit unsigned value. These codes convey information to the caller about the status of the call. It may indicate an error, or simply convey information that may, or may not be an error depending on what you expect. A list of all documented status or error codes can be found in header files included with the source code.


Page 115 of 667

It is also very easy to use from assembly language. while the offset is ignored by the 386/486 processor. The CM32 compiler uses this convention. In some C compilers. and are expanded to 32-bit values. Memory Management MMURTL uses all of the memory in your system and handles it as it as one contiguous address span. It doesn’t matter where the operating system code is in memory. 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). This will be done by the compiler. not the real address of the SetXY function. The addresses of the calls are documented with the source code (header or Include files).0 Page 116 of 667 . this is referred to as the Pascal Calling Convention. Most return a value that is a an error or status code. even the assembler or processor itself. 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). Some do not. SetXY is actually a far pointer stored in memory that normally would contain the address of the SetXY function. “The Tasking Model.” describe this in great detail. MMURTL V1. If you need a 2Mb array. The called operating-system function always cleans the stack. and in some cases.” and Chapter 4. All parameters are pushed from left to right. This is because the processor enforces a four-byte aligned stack. Stack Usage Each of the MMURTL operating-system calls is defined as a function with parameters. Chapter 3. 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. A far call address to the operating system consists of a selector and an offset. You can even hard code the far address of the operating-system call because the system calls are at call gates. 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. But the address is really a call gate. “Interprocess Communications. you simply allocate it. They are defined as far (this may be language-dependent) and require a 48bit call address.All Public operating-system calls are accessible through call gates and can be reached from "outside" programs. The selector is the call gate in the Global Descriptor Table (GDT). All but a few of the operating-system calls use the stack to pass parameters to the operating system functions.

and returns them to the pool of free pages when they are turned in (deallocated).. 00000000h Top of user address space Application base (1Gb) Top of OS Address space OS base address Even if six applications running at once. they all see this same "virtual memory map" of the system. There are no far data pointers. The paging features of the 386/486 allow us to assign physical memory pages to any address ranges you like.MMURTL has control of all Physical or Linear memory allocation and hands it out it as programs ask for it... Your application sees the memory map shown in table 9. 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. MMURTL is a Paged memory system.. MMURTL hands out (allocates) pages of memory as they are requested.0 Page 117 of 667 . The library code for the high-level language will allocate pages and manage them for you. Paged virtual memory is implemented with the paging capabilities of the 386/486 processor. 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.. Heap-management functions that allow you to allocate less than 4Kb at a time should be provided by your high-level language.. while all applications and system services begin at the one-gigabyte mark. Application programs all reside at the one-gigabyte memory location inside of their own address space. This would let you allocate 30 or 40 bytes at a time for dynamic structures such as linked lists (e.g.2: Table 9. MMURTL V1.Basic Memory Map 7FFFFFFFh . This doesn’t change any programming techniques you normally use. malloc() in C). A MMURTL application considers itself as being the only thing running on the system aside from the operating system and device drivers. This also means that a pointer to allocated memory will never start at 0. The operating system resides at address 0. .. .. 40000000h 3FFFFFFFh . The operating system and device drivers reside in low linear memory (0). What does change your techniques is the fact that you are working with a flat address space all referenced to one single selector.. Read up on Memory Management in Chapter 5 if you’re really interested. In fact.2 . The operating system provides three basic calls for application memory management.

Now you allocate 4 pages.They are listed below: AllocPage(nPages. but pointer arithmetic may get you into trouble if you allocate and deallocate often and lose track of what your active linear addresses are. She may pick up your dirty socks. 2. Consider the following scenario: 1. but it’s bad manners not to cleanup after yourself. You allocate 5 pages. The memory allocation routines use a first fit algorithm in your memory space. The operating system prevents ill-behaved programs from intentionally or accidentally accessing another program’s data or code (from "a pointer in the weeds"). The memory-allocation algorithm can’t fit it into the 3 page hole you left. Your program can address up to one-gigabyte of space. You are left with a hole in your addressable memory area. Memory-page allocation routines always return a pointer to the new memory. The high-level language allocation routine may help you out with this. It is up to you to manage your own addressable memory space. MMURTL is kind of like your mother. Operating System Protection MMURTL provides protection for the operating system and other jobs using the paginghardware protection. while System Services and Applications run at user-level protection. but I have to address the issues faced by those that write the library allocation routines for the languages. This is not a problem. so it will return a pointer to 4 pages just above the original 5 you allocated first (address 40105000h). It even prevents applications from executing code that doesn’t belong to them. 4. ppMemRet): dError DeAllocPage(pMem. When you are done with it. Address 40100000h is returned to you. and Device Drivers run and can only be accessed at the system-protection level (level 0) . this doesn’t mean you can allocate a gigabyte of memory. Now you have 6 pages beginning at 40103000h.0 Page 118 of 667 . you allocate it. dnPages): dError QueryPages(pdnPagesRet): dError When your program needs a page of memory. If you attempt to address this "unallocated" area a Page Fault will occur. The operating system Code. so long as you know it can’t be addressed. Then you deallocate 3 of these pages at address 40100000h (12K). The address of the pointer is always a multiple of the size of a page (modulo 4096). but she would much rather that you did it. you should deallocate it. This will TERMINATE your application automatically (Ouch!). even if they know the MMURTL V1. Data. Your program takes exactly 1Mb before you allocate. Of course. 3. There are cleanup routines in the operating system that will deallocate it for you if your program exits without doing it. You now have 20K at the 40100000h address.

MMURTL is not a mind reader. Application Messaging The most basic applications in MMURTL may not even use messaging directly. You may not even have to call an operating-system primitive-message routine throughout the execution of your entire program. pRqHndlRet. pData1. SendMsg (dExch. This means that in a multitasking environment. This is done with the SendMsg() and WaitMsg() calls. a program that crashes and burns doesn’t eat the rest of the machine in the process of croaking. cbData1. The operating system is brought to its knees in a hurry. dMsg1. npSend.pqMsgRet):dError Request(pSvcName. protect programmers from themselves. 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 Normally. MMURTL V1. but can also be destructive (to the entire system). Messaging is very powerful. however. but you can easily eat up all of the system resources and cause the entire system to fail.0 Page 119 of 667 . You may also require the use of a system service that there isn’t a direct procedural interface for. The following messaging calls are provided by the operating system for applications. MMURTL does NOT. This eats all of your link blocks. However.where it’s located (it’s physical or linear address). The operating system acts more like a referee than a mother. pData2. or you will need to know of one you send messages to. dData1. For instance. in which case you will use Request() and WaitMsg(). pMsgsRet) : dError WaitMsg(dExch. This is where non-specific intertask messaging will come into play. applications will allocate all of the exchanges they require somewhere early in the program execution and return them (DeAllocExch) before exiting. wSvcCode. dRespExch. Memorymanagement protection provided by MMURTL will help some. or "queue up" events of some kind between the threads (tasks). dData2 ) : dError Before you can use messaging you will need an exchange where you can receive messages. accidentally sending messages in an endless loop to an exchange where no task is waiting to receive them will cause the system to fail. dMsg2): dError CheckMsg(dExch. cbData2. dData0. when you write multithreaded applications (more than one task) you will probably need a way to synchronize their operation.

You should be concerned about reentrancy if you have more than one task executing the same code. struct JCBRec { long JobNum.Starting a New Thread Some programs need the capability to concurrently execute two or three things. you need to know your job number. which the GetJobNum() call will provide. you basically have two programs running in the same memory space. 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. only your program will be trashed. and user interaction. such as data communications. 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.c provided with the operating system source code) for a good example of spawning new tasks. /* unsigned long sJcbCode. you need to allocate or provide memory from your data segment for the stack that the new task will use. char *pJcbPD. In order to use the GetpJCB() call. operating system messaging (sharing an exchange). Prior to calling SpawnTask(). Other than that. The pointer is read-only. It may be simply keeping the time on the screen updated on a user interface no matter what else you are doing. Utilities to display information about all of the jobs running may also need to see this data. Reentrancy considerations include modifications to variables in your data segment (two tasks sharing variables). but you don’t want that either. printing. /* char *pJcbData. /* char *pJcbCode. char sbJobName[14]. /* unsigned long sJcbData. The GetpJCB() call will provide a pointer to allow you to read values from a JCB. You are responsible to ensure you don’t overrun this stack. This new function will be scheduled for execution independently of your main program. Header files included with the source code have already defined the following structure for you.0 Page 120 of 667 . Attempts to write directly to the JCB will cause your application to be terminated with a protection violation. They can even execute the same procedures and functions (share them). If you do. In these cases you can "spawn" a new task to handle the secondary functions while your main program goes about it’s business. /* /* lstring */ Linear add of Job’s PD */ Address of code segment */ Size of code segment */ Address of data segment */ Size of data segment */ MMURTL V1. Job Control Block Applications may sometimes need to see the data in a Job Control Block. and allocation or use of system resources such as memory.

/* Virtual Video Buffer */ long CrntX.” provides complete details on the keyboard implementation. dcbUserName): dError SetSysIn(pFileName.0 Page 121 of 667 . dcbJobName): dError SetExitJob(pFileName. /* Full screen pause */ long NextJCB. /* std input . /* Virtual Screen Size */ long nLines. /* 7 = WhiteOnBlack */ char fCursOn. The JCB structure is a total of 512 bytes. GetExitJob(pFileNameRet. /* Active video buffer */ char *pVirtVid. dcbPath): dError SetUserName(pUserName. /* Error Set by ExitJob */ char *pVidMem. /* Command Line . /* current path (prefix) */ char JcbExitRF[80]. /* OS Uses to allocate JCBs */ }. 1 = Block */ unsigned char ScrlCnt. /* Current cursor position */ long CrntY. /* User Name . To change data in the JCB. Only the active portions are shown in the preceding list. 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 VidMode.LString */ char sbPath[70]. The keyboard interface is a system service.LString */ char JcbSysOut[50]. /* Size of primary stack */ char sbUserName[30]. dcbFileName): dError SetPath(pPath. “Keyboard Service. with the second byte (offset 1) actually holding the first byte of data. pdcbPathRet): dError GetUserName(pUserNameRet. pPathRet. /* std output . A system service allows concurrent MMURTL V1. All of the names and filenames (strings) are stored with the first byte containing the length the active size of the string. They include: SetJobName(pJobName. long nCols.LString */ char JcbSysIn[50]. Chapter 13. pcbFileNameRet): dError GetCmdLine(pCmdLineRet. with unused portions padded. you must use the calls provided by the operating system.LString */ long ExitError.char *pJcbStack. /* 1 = Cursor is visible */ char fCursType. /* 0 = 80x25 VGA color text */ long NormVid. pdcbCmdLineRet): dError GetPath(dJobNum. /* Exit Run file (if any) */ char JcbCmdLine[80]. /* Count since last pause */ char fVidPause. /* 0=UL. pdcbUserNameRet): dError Basic Keyboard Internal support is provided for the standard IBM PC AT-compatible 101-key keyboard or equivalent. /* Address of primary stack */ unsigned long sJcbStack.

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. This is 4000 bytes (even though an entire page is allocated . y is the position down the screen and referenced as 0 to 24.3. Table 9. and cursor positioning. Foreground colors. For most applications.0 Page 122 of 667 . The keyboard has a built-in device driver that the keyboard service accesses and controls. This call returns a 32-bit value which contains the entire keyboard state (all shifted states such as ALT and CTRL are included). and 9. The high bit of each. and special keys. The colors for TTYOut(). Even though the attribute is passed in as a dword.access to system-wide resources for applications. PutChars() and PutAttrs() are made of 16 foreground colors.Foreground Colors (Low nibble) Normal Black Binary 0000 Hex 00h Intensity Bit Set Gray Binary 1000 Hex 08h MMURTL V1. character placement. The first of the two parameters to the ReadKbd() call asks you to point to where the 32-bit keycode value will be returned. These names are also defined in standard header files included with the operating-system code and sample programs. It provides access to all standard alphanumeric. There are tables in Chapter 13 to show all of the possible values that can be returned. while the second parameter is a flag asking if you want to wait for a keystroke if one isn’t available. Background colors describe the attributes.3 . The character set used is the standard IBM PC internal font loaded from ROM when the system is booted. 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. and low nibble is the foreground. x is the position across a row and referenced from 0 to 79. 8 background colors and 1 bit for blinking.4096 bytes). Tables 9.4. Positions on the screen are determined in x and y coordinates on a standard 80 X 25 matrix (80 across and 25 down). editing. the single procedural ReadKbd() operating-system call will suffice. In this byte. the high nibble is the background. is the intensity bit. MMURTL has a basic character device driver built-in which provides an array of calls to support screen color. only the least significant byte is used.

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. #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define BLACK BLUE GREEN CYAN RED WHITE GRAY LTBLUE LTGREEN LTCYAN LTRED BGBLACK BGBLUE BGGREEN BGCYAN 0x00 1x07 0x02 0x03 0x04 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x00 1x70 0x20 0x30 dError = PutVidChars(0. logically OR the Foreground. 0.Blue Green Cyan Red Magenta Brown White 0001 0010 0011 0100 0101 0110 0111 01h 02h 03h 04h 05h 06h 07h Light Blue Light Green Light Cyan Light Red Light Magenta Yellow Bright White 1001 1010 1011 1100 1101 1110 1111 09h 0Ah 0Bh 0Ch 0Dh 0Eh 0Fh Table 9. and Blink if desired to form a single value. MMURTL V1. 17.".4 .0 Page 123 of 667 . Look at the following coding example (Hex values are shown for the Attributes). "This is a test. BGBLUE|WHITE). Background.

Any task in your job may call ExitJob().Terminating Your Application The call ExitJob() is provided to end the execution of your program. This is done with the SetExitJob() call. exchanges. If no file is specified. All tasks in your job will be shutdown at that time and the application will be terminated. Remember. Another option to chaining is to tell the operating system what application to run when you exit. The exit() function in a high-level language library will call this function. all the resources are recovered from the job and it is terminated. You should also wait for responses to any requests you made. MMURTL has to hunt for them. You can pass information to the incoming application by using the SetCmdLine() function. This is done by calling the Chain() function.0 Page 124 of 667 . but an application knows what it allocated and it’s more efficient to do it yourself from an overall system standpoint. Some of the basic resources are saved for the new application. MMURTL will cleanup after you if you don’t. MMURTL V1. It is best to deallocate all system resources before calling ExitJob(). Replacing Your Application Your application has the option of "chaining" to another application. This terminates your application and loads another one that you specify. You will be terminated and sent to the "sidelines" if necessary. such as the job-control block and virtual video memory. This includes any memory. and other things allocated while you were running. or system calls. You can specify the name of a run file to execute when your job exits. MMURTL is more like a referee on the football field.

A program that performs a specific function or group of functions can be shared with two or more programs is a system service. the service does the processing and Responds. This is the basis behind the Request() and Respond() messaging primitives. If you want (or need) to build device drivers or system services. Initializing Your System Service The basic steps to initialize your service and start serving requests are listed below: 1.0 Page 125 of 667 . They should have a basic understanding of multitasking and messaging and be able to concentrate on the application itself. and Assembly language programmers. It describes writing message based system services and device drivers. MMURTL is a message-based operating system and is designed to support programs in a client/server environment. and memory management operations. or provide services to application programs. use messaging. if required. Systems programming deals with writing programs that work directly with the operating system and hardware. Pascal. Use of the associated operating system calls is described in a generic format that can be understood by C. 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. The file system and keyboard services are prime examples of message-based system services. Client programs make a Request. They contain theory and examples to help you understand how to access services. Application programmers shouldn’t have to worry about hardware and OS internal operations to accomplish their tasks. Many people associate the term client/server only with a separate computer system that is the server for client workstations. MMURTL V1. Systems programmers require a better understanding of the operating system. It can be compared to Remote Procedure Calls with a twist. such as: Additional memory. Initialize or allocate any resources required.Chapter 10. Systems Programming Introduction This section is aimed squarely at the MMURTL systems programmer. as well as for other services. you should make sure you understand the material covered in the architecture and Applications Programming chapters. Main Service exchange (clients send requests here). MMURTL takes this down to a single processor level.

char npRecv. /* /* /* /* A pointer to a Request Block */ The number to return */ Where we wait for Requests */ The Message with the Request */ void main(void) MMURTL V1. char *pData2. The service name is "NUMBERS " (note that the name is space-padded). long RQBRsvd3. A Simple System Service Example Listing 10.) 2. char npSend. Additional tasks if required. Wait for messages. if needed. long dData2. int ServiceCode.0 Page 126 of 667 . long dData1. }. service them. long RQBRsvd2. long cbData2. char *pData1.1 is the world’s simplest system service for the MMURTL operating system. /* Super Simple System Service */ struct RqBlk { /* 64 byte request block structure */ long ServiceExch. Listing 10. long RespExch. char *pRqHndlRet. It’s not very useful. 3. long dData0. NextNumber = 0. long ServiceRoute. It’s purpose it to hand out unique numbers to each request that comes in.1 . but it’s a good clean example showing the basic "guts" of a system service. #define ErcOK 0 #define ErcBadSvcCode 32 struct pRqBlk unsigned long unsigned long unsigned long *RqBlk. The service will get a pointer in the request block (pData1) that tells it where the user wants the number returned. Call RegisterSVC() with your name and Main Service Exchange. long RqOwnerJob.Additional exchanges. long cbData1. Service names are case-sensitive. Anything else you need to prepare for business (Initialize variables etc. then Respond. MainExch. long RQBRsvd1. Message[2].A System Service.

and it is your responsibility to document this information for application programmers. The complexity of your service. you define the data items you need. will be determined by how much information you need to carry out the request. The Request Block is in operating-system memory MMURTL V1. Items In the Request Block The Request Block contains certain items that the service needs to do it’s job. and the aliased pointer to the user’s memory is active and available only while the request block is being serviced. /* Exch & pointer */ if (!OSError) { pRqBlk = Message[0]. As a system service. OSError = RegisterSvc("NUMBERS ". else if (pRqBlk.ServiceCode == 1) { /* User Asking for Number */ *pRqBlk. This value is actually a pointer to the Request Block itself in unprotected operating system memory. and how much information a program must pass to you. installable services would find themselves terminated for the attempt. /* Unknown Service code! */ OSError = Respond(pRqBlk.pData1 = NextNumber++. /* get an exchange */ if (OSError) { /* look for a kernel error */ exit(OSError). The service must never attempt to write to the request block. /* Give them a number */ ErrorToUser = ErcOK. ErrorToUser): /* Respond to Request */ } } /* Loop while(1) */ } The Request Block As you can see. when you receive a Request.ServiceCode == 0) /* Abort request from OS */ ErrorToUser = ErcOK. MainExch).{ unsigned long OSError.0 Page 127 of 667 . How many items you need to use depends on the complexity of the service you’re writing. This gives the system service the opportunity to look at or use any parts of the Request Block it needs. In fact. As a system service writer. Wait returns and the two-dword message is filled in. if (OSError) { /* look for a system error */ exit(OSError). Message). /* First DWORD contains ptr to RqBlk */ if (pRqBlk. /* Respond with No error */ } else ErrorToUser = ErcBadSvcCode. The memory address of the Request Block. ErrorToUser. Programs and the operating system pass information to the service in various Request Block items. the system service interface using Request() and Respond() is not complicated. while (1) { /* WHILE forever */ OSError = WaitMsg(MainExch. Several items in the Request Block are important to the service. OSError = AllocExch(&MainExch). The first dword is the Request Block Handle.

and whether or not you hold requests from multiple callers before responding. 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. you should return the Error Bad Service Code which is defined in standard MMURTL header files. This is called the service code. As a service you must not attempt to access any data in the caller's space other than what is defined by pData1. In the simple example above. An installable service will operate at the user level of protection and access. because the operating system does not alias them as such. dData1. and dData2. These two request block items are specifically designed as pointers to a data area in the caller’s memory space. and another RenameFile. The size of the area is defined by the accompanying 32-bit values. How you handle this depends on how complex your service is. cbData1 and cbData2. 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. or any data movement back to the caller. the request block items pData1 and pData2 must be used. A prime example of their use is in the File System service. These items are dData0. For larger data movement to the service. and what service you provide. cbData1 and pData2.0 Page 128 of 667 . another CloseFile. and will alias the pointers so the caller's memory space is accessible to the service. For example. Also be aware that these pointers are only valid from the time you receive the request. the service only defines one service code that it handles. you must describe in your documentation what Request information you expect from the caller. in the File System. 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. where callers pass in a file handle using these data items. until you respond. MMURTL V1. You may not use or define service code zero to your callers in your documentation. When a service receives a Request with service code 0. This is the number 1 (one). cbData2. They may not contain pointers to the caller’s memory area. They are one-way data values from the caller to you. For all other values of the service code. If you receive a service code you don’t handle or haven’t defined. 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. one number represents OpenFile. The operating system is “aware” of this.which is read-only to user-level jobs.

but carries it one step farther. However. MMURTL uses this concept. This type of service is defined as asynchronous. before responding to any other requests you are holding. This type of service requires one or more additional exchanges to hold the outstanding requests. then respond before waiting for the next request. The kernel primitive MoveRequest() takes a request you have received using Wait. 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 abort request contains the Job number of the aborted program in dData0. If so. you return a number that indicates what you want to tell them. OpenFile() returns a 0 error code if everything went fine. receive a request. or maybe the File System couldn’t allocate an operating system resource it needed to MMURTL V1. You return a 0 (zero) if all went well.Asynchronous Services Most services will be synchronous. All operations are handled in a serial fashion. The file may already be opened by someone else in an incompatible mode. When you write system services. If an error occurred or you need to pass some kind of status back to the caller. callers can make a request to be notified of global key codes. While you are holding requests in an asynchronously. This type of service may even respond to the second request it receives before it responds to the first. This means the keyboard service must place the global key code requests on another exchange while they handle normal requests. the file may not exist. This is the easiest way to handle requests.0 Page 129 of 667 . You can then use WaitMsg() or CheckMsg() to get the requests from this second hold exchange when you know you can respond to them. These key codes may not occur for long periods of time. and many normal key strokes may occur in the meantime. you must respond without accessing its memory or acting on its request. you may receive an Abort Service code from the operating system (ServiceCode 0). An example of this is how the file system handles errors. it may return five or six different values for various problems that can occur during the OpenFile() process. System Service Error Handling When the service Responds. If so. the error (or status) to the user is passed back as the second dword parameter in the respond call. 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. You would look at each Request Block in the RqOwnerJob field to see if this request came from it. This is so the operating system can reclaim the request block. If this happens. and moves it to another exchange. The first is the request block handle. you must respond to it immediately. Synchronous services wait. you must be aware of conventions used in error passing and handling. A service may receive and hold more than one request before responding. The keyboard service is good example of an asynchronous service. In the keyboard service. which is a valuable system resource.

hard disk. In each of these cases. you would receive a non-zero number indicating the error. All device drivers use a Device Control Block (DCB). The device drivers are the closest layer to the OS. basic video. Device drivers are accessed via call gates transparent to the applications programmer which allow callers to easily and rapidly access code with a procedural interface from high-level languages (e. RAM disks. although they may be sequential too (implementation dependent). MMURTL has standardized entry points for all devices on the system. and communication (serial and parallel) Devices in MMURTL are classified as two different basic types: random. 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. The device driver defines and keeps the DCB in it’s memory space.and sequentialoriented. Disk drives. The interface is non-specific. The MMURTL standard Device Driver interface can handle both sequential and random devices with fixed or variable block and/orsector lengths.g. communications ports (serial and parallel). Each device driver must also maintain in it’s data area all hardware specific data (variables) needed to control and keep track of the state of the device. or by simply "pushing" parameters on the stack in assembler and making a call.0 Page 130 of 667 .) Most byteoriented devices tend to be sequential devices because they don’t have a fixed size block and are not randomly accessed. MMURTL provides several built-in device drivers including floppy disk. keyboard. which actually becomes the OS memory space when you load it. Sequential devices are things like communications ports. C and Pascal).. Some types of tape drives allow reading and writing to addresses (sectors) on the tape and therefore may also be random devices. MMURTL is built in layers. and Device Type are examples of the fields in a DCB. When a device driver initializes itself with the call InitDevDr().open your file. Things like Device name. Writing Device Drivers Device Drivers control or emulate hardware on the system. it will pass in a pointer to an array of DCBs. tape drives. keyboard. and sequential video access (ANSI drivers etc. and network frame handlers are all examples of devices (or pseudo devices) that require device drivers to control them. If a device driver controls more than one device. You should not confuse device drivers with message-based system services. as well as where the data is on the device. Network frame handlers may also be random because a link layer address is usually associated with the read and write functions. it passes in a pointer to it’s DCB. MMURTL V1. Block Size. You don’t have to access a global in your program or make another call to determine what went wrong.

while providing a fair amount of hardware independence. Building Device Drivers When the device driver is installed by the loader. In C this would be main() or in Pascal it’s the main program block. Those who have written device drivers on other systems will appreciate this concept. In assembler this would be the START entry point specified for the program. Other system calls may also have to be made to allocate or initialize other system resources it requires before it can handle its device. the file system is at user level (3).e. Device drivers must have access to the hardware they control. It has a main entry point which you specify when writing the program. the PICUs. network link layer devices). multitasking. The call to setup as a device driver is InitDevDr(). The DCB is shared by both the device driver and OS code. just like any other MMURTL V1.0 Page 131 of 667 . like any application in MMURTL. RAM DISK). you provided a pointer to your Device Control Blocks (DCB) after you have filled in the required information. 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. The device-driver builder must not access ports other than those required to control their hardware. which ensures complete harmony in MMURTL’s real-time. interrupt vectors. Device drivers must not attempt to directly control the system DMA.or request-based system services and application code (both at User level 3) cannot access ports at all. MMURTL provides a complete array of calls to handle all of the hardware. This code is immediately executed after loading the driver. For example. powerful. or network frame handlers (i. Only System code (protection level 0) is allowed processor port I/O access. to handling complex devices such as hard drives. SCSI ports. or a processor protection violation will occur.Device Driver Theory MMURTL is a protected-mode. It does not directly access hardware.. Device drivers can be doing anything from simple disk device emulation (e. and is programmed. The levels are 0 and 3 and are referred to as System (0) and User (3). device drivers and have even gone into substantial detail for beginners. multi-threaded environment. timer hardware. The device driver looks. paged memory OS that uses two of the four possible protection levels of the 386/486 processors. Additional system resources available to device drivers are discussed later in this section. With InitDevDr(). This means message. Device drivers have complete access to processor I/O ports for their devices without OS intervention. or is included in the OS code at build time. or any OS structures. MMURTL takes care of synchronization of system-level hardware access so device driver programmers don’t have to worry about it.g. I have made it rather painless to build fast. it has to make a system call to set itself up as a device driver in the system.

MMURTL V1. When programs call one of these PUBLIC calls. You can name them anything you want in your program. Enable (UnMaskIRQ()) any hardware interrupts you are servicing. This code will only execute once to allow you to initialize the driver. Check or set up the device’s. MMURTL does some housekeeping and then calls your specified function. 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 generic steps are described below. Allocate exchanges. 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 actual calls may vary depending on what system resources you need. Your functions may not have to do anything when called depending on the device you are controlling. unless this is done in one of the procedures (such as DeviceInit()) as a normal function of your driver. if needed. if needed. This will generally be a very small section of code (maybe 30 lines). Initialize or allocate any resources required: Allocate any additional memory. But they must at least accept the calls and return a status code of 0 (for no error) if they ignore it. You will have functions in your driver to handle each of these calls. Do anything else you need before being called. 3. The main entry point will never be called again. Your driver program must interface properly with the three public calls. 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. 1. Set up interrupt service routines.program. Enter the required data in the DCBs to pass to the InitDevDr() call.0 Page 132 of 667 . 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. if required. How Callers Reach Your Driver All device drivers are accessed via one of 3 public calls defined in the OS. 2. but they must have the same number and type of parameters (arguments) as the PUBLIC calls defined in the OS for all device drivers.

and should be called by you as the last thing your ISR does. In a multitasking environment it is possible that a device-driver function could be called while someone else’s code is already executing it. you have three device driver routines that are ready to be called by the OS. especially if the driver controls two devices. EndOfIRQ() sends and "End of Interrupt" signal to the Programmable Interrupt Controller Units (PICU). MMURTL has a complete array of support calls for such drivers. Interrupts Several calls are provided to handle initializing and servicing hardware interrupt service routines (ISRs) in your code. Many device drivers need to use DMA and timer facilities. 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. although it’s not a requirement.4. Most device drivers are NOT re-entrant so this will normally be set to FALSE (0). device drivers are very close to the hardware. See the section that deals specifically with ISRs for complete descriptions and examples of simple ISRs. and they must service interrupts from the devices they control. 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. InitDevDr() never returns to you. MaskIRQ() and UnMaskIRQ() are used to turn on and off your hardware interrupt by masking and unmasking it directly with the PICU. 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 layer of code performs several functions for you. System Resources For Device Drivers Because they control the hardware. the ISR usually sends the EOI sequence directly to the PICU. More detail about each of these calls can be found in the alphabetical listing of OS calls in Chapter 15.” MMURTL V1. ISRs should be written in assembly language for speed and complete code control. One very important function is blocking for non-re-entrant device drivers. This is often called an interrupt vector.0 Page 133 of 667 . One of the items you fill out in the DCB is a flag (fDevReent) to tell the OS if your driver is re-entrant. In other operating systems. Call InitDevDr(). At this point. They require special operating system resources to accomplish their missions. The floppy and hard-disk device drivers also provide good ISR examples. 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. specifically DOS. It will be the OS that calls them and not the users of the devices directly.”API Specification.

The Alarm() call will send a message to a specified exchange after a number of caller-specified 10-millisecond increments. DMA also has some quirks you should be aware of such as it’s inability to cross-segment physical boundaries (64Kb). IsendMsg() also does not re-enable interrupts before returning to the ISR that called it. This is accomplished with GetDMACount(). you will most likely need to use KillAlarm() which stops the alarm function if it hasn’t already fired off a message to you. Two calls provide these functions. A special call that may be required by some device drivers is IsendMsg(). direction. you can use SendMsg(). and size of a DMA move. See the OS Public Call descriptions for more information on DMASetUp() and how to use AllocDMAPage(). Program code can and will most likely be loaded into memory above the 16Mb limit if you have that much memory in your machine.0 Page 134 of 667 . See the call descriptions for more information on the Sleep(). If you use SendMsg(). The Sleep() call actually puts the calling process to sleep (makes it wait) for a specific number of 10-millisecond periods. This means all DMA transfers must be buffered into memory that was allocated using AllocDMAPage(). It doesn’t force a task switch (even if the destination exchange has a higher priority process waiting there). you will have to use the DMASetUp() call each time before instructing your device to make the programmed DMA transfer. IsendMsg() is used if an ISR must send more than one message from the interrupted condition. If you must send a message that guarantees a task switch to a higher priority task.Direct Memory Access Device (DMA) If your device uses DMA. 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. DMASetUp() lets you set a DMA channel for the type. and KillAlarm() functions. MMURTL uses paged memory. Timer Facilities Quite often. 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. or have the ability to "time-out" if an expected action doesn’t occur in a specific amount of time. If you use Alarm(). device drivers must delay (sleep) while waiting for an action to occur. it must be the final call in your ISR after the EndOfIRQ() call has been MMURTL V1. IsendMsg() is a special form of SendMsg() that can be used inside an ISR (with interrupts disabled). It is important that you understand that the memory addresses your program uses are linear addresses and do not equal physical addresses. or you only need to send one message from the ISR. Many users of DMA also need to be able to query the DMA count register to see if the DMA transfer was complete. mode. or how much was transferred. Alarm(). 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(). so it may be called from an ISR while interrupts are disabled. The Alarm() call can be used asynchronously to allow the device code to continue doing something while the alarm is counting down.

Device Control Block Setup and Use The DCB structure is shown in the following table with sizes and offsets specified. and how to implement them in your code. Table 10. Detailed Device Interface Specification The following sections describe the device control block (DCB). DO NOT MODIFY ALLOCATE and 0. this is job ALLOCATE and 0. 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. left justified Length of Device name in bytes 1 = RANDOM device. DO NOT MODIFY ALLOCATE and 0. DO NOT MODIFY ALLOCATE and 0.1-. Table 10. each of the three PUBLIC device calls. DO NOT MODIFY ALLOCATE and 0.0 Page 135 of 667 . You must ensure that there is no padding between the variables if you use a record in Pascal. The length of each field in the DCB is shown in bytes and all values are unsigned.) (0 for variable block size) Last error code from an operation Number of blocks in device (0 for sequential) pointer to device Oper.1 presents the information that needs to be included in the DCB. or assembler to construct the DCB. You can look at some of the included device driver code with MMURTL to help you. How the structure is implemented in each programming language is different. This will guarantee a task switch if the message is to a higher priority task.made and just before the IRETD() (Return From Interrupt). 2 = SEQUENTIAL Bytes Per Block (1 to 65535 max. If your driver controls more than one device you will need to have a DCB for each one it controls. but easy to figure out. DO NOT MODIFY MMURTL V1. Multiple DCBs must be contiguous in memory. The size of a DEVICE CONTROL BLOCK is 64 bytes. Device control block definition Name DevName sbDevName DevType NBPB Size 12 1 1 2 Offset 0 12 13 14 dLastDevErc ndDevBlocks pDevOp pDevInit pDevStat fDevReent fSingleUser wJob OSUseONLY1 OSUseONLY2 OSuseONLY3 OSuseONLY4 OSuseONLY5 OSuseONLY6 4 4 4 4 4 1 1 2 4 4 4 4 4 4 16 20 24 28 32 36 37 38 40 44 48 52 56 60 Description Device Name. DO NOT MODIFY ALLOCATE and 0. a structure in C.

pDevOp. Each sector is considered a block. An example of how this is accomplished is with the floppy device driver. head zero. but this is not much of a limitation.When a driver encounters an error. ndDevBlocks . on a floppy disk this would be the number of sectors x number of cylinders x number of heads on the drive. or it's a disk driver that is initialized for 1024-byte sectors then this would reflect the true block size of the device.This is the number of addressable blocks in your device. The Hard disk driver operates the same way. sbDevName . It numbers all sectors on the disk from 0 to nTotalSectors (nTotalSectors is nHeads * nSecPerTrack * nTracks). but a driver for non-standard floppy devices can name them anything (up to 12 characters). Disk drives are divided into sectors. The name is used so that callers don't need to know a device’s number in order to use it. Each device name must be unique.this indicates whether the device is addressable. For example.0 Page 136 of 667 . MMURTL expects device drivers to organize access to each random device by 0 to n logical addresses. it should set this variable with the last error code it received.The fields of the DCB must be filled in with the initial values for the device you control before calling InitDevDr(). 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. This reduces disk arm movement which. and the concept of an address for the data has no meaning (such as with a communications port). The head number takes precedence over track number when calculating the actual position on the disk. dLastDevErc . then it's considered a random device and this byte should contain a 1. Some names are standardized in MMURTL. In other words.a single byte containing the number of bytes in the device name. If it is another device. then this should contain a 2. Addressing with 32 bits limits address access to 4Gb on a singlebyte device and 4Gb x sector size on disk devices. and you read one sector past the end of that track.Each driver names the device it serves. If the device is sequential. but the following fields require detailed explanations: DevName .number of bytes per block. If data can be reached at a specific numbered address as specified in the DeviceOP() call parameter dLBA (Logical Block Address). pDevInit. If the device is mountable or allows multiple media sizes this value should be set initially to allow access to any media for identification. Most of the fields are explained satisfactorily in the descriptions next to the them. If the device has variable length blocks then this should be 0. floppy disk drives are usually named FD0 and FD1. znBPB . For instance. increases throughput. but this doesn’t prevent the driver from assigning a non-standard name. if you are on track zero. If the sectors are 512-byte sectors (MMURTL standard) then this would be 512. we then move to the next head. usually an unexpected one. and pDevStat are pointers to the calls in your driver for the following PUBLIC device functions: MMURTL V1. not the next track. DevType . this is device dependent. If it is a single-byte oriented device such as a communications port or sequential video then this would be 1. sector zero.

If the fSingleUser is true. and handle device conflicts internally. In C-32 (MMURTL’s standard C compiler). Note that your function should also remove these parameters from the stack. All functions in MMURTL return errors via the EAX register. They are: DeviceOp(dDevice. defining the function with no additional attributes or modifiers defaults it to a 32-bit near return. This is usually dictated by your compiler or assembler and how you define the function or procedure. wJob . A portion of the reserved DCB is used for this purpose. fSingleUser . pInitData. The queuing is accomplished with an exchange.2. this flag should be true (non-zero). The return instruction you use should be a 32-bit near return.0 Page 137 of 667 . the driver may have to tell MMURTL it’s re-entrant.Performs I/O operations in device DeviceInit . pData):dError DeviceInit(dDevice. On very complicated devices such as a SCSI driver. In assembler. RETN is the proper instruction. fDevReent . 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.5. this will be the job that is currently assigned to the device. dOpNum. If this is zero. They must be set to zero before the call to InitDevDr. If fSingleUser is false this field is ignored. sdInitdata):dError DeviceStat(dDevice. OSUseONLY1. pStatRet.Initializes or resets the device DeviceStats . For devices that are not re-entrant. ndBlocks.4.These are reserved for OS use and device drivers should not alter the values in them after the call to InitDevDr. all calls to device drivers are accessed through three pre-defined PUBLIC calls in MMURTL. This applies to devices like communications ports that are assigned and used for a session.If a device can only be assigned and used by one Job (user) at a time.This is a 1-byte flag (0 false. In a true multitasking environment more than one task can call a device driver.• • • DeviceOp . Most device drivers have a single set of variables that keep track of the current state of the device and the current operation. sdStatRetmax):dError Detailed information follows for each of these calls. where several devices are controlled though one access point. Your function implementations must MMURTL V1. MMURTL handles conflicts that occur when more than one task attempts to access a device. Standard Device Call Definitions As described previously. dLBA.3. MMURTL will queue up tasks that call a device driver currently in use. The procedural interfaces are shown with additional information such as the call frame offset.6 . the SendMsg().Returns device-specific status MMURTL uses indirect 32-bit near calls to your driver’s code for these three calls. non-zero true) that tells MMURTL if the device driver is re-entrant. no job is currently assigned the device. and WaitMsg() primitives.

A non-zero value indicates and error or status that the caller should act on. The rest of the dOp Numbers may be implemented for any device-specific operations so long as they conform to the call parameters described here. The dOpNum parameter tells the driver which operation is to be the same. Returning zero indicates successful completion of the function. The first 256 operations (dOp number) are pre-defined or reserved.pData).dOpNum. Device specific error codes should be included with the documentation for the driver. Standard device errors should be used when they adequately describe the error or status you wish to convey to the caller. The call frame offsets for assembly language programmers are these: dDevice dOpNum dLBA dnBlocks pData 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) 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 [EBP+24] [EBP+20] [EBP+16] [EBP+12] [EBP+08] MMURTL V1. and format. verify.dnBlocks. 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. write. An almost unlimited number of operations can be specified for the Device Operation call (2^32). The call is not device specific and allows any device to be interfaced through it.0 Page 138 of 667 . dError = DeviceOp(dDevice.dLBA. They equate to standard device operations such as read.

and so on. For sequential devices this parameter will be ignored. dnBlocks Number of contiguous Blocks for the operation specified. DError = DeviceStat(dDevice. this will simply be the number of bytes. you should return 0 to pdStatusRet and return the standard device error ErcNoStatus. MMURTL V1. For sequential devices. In cases where the function doesn’t or can’t return status.dStatusMax.256-n Driver Defined (driver specific) Dlba Logical Block Address for I/O operation. 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. Not all devices will return status on demand. The size of the initializing data and its contents are device specific and should be defined with the documentation for the specific device driver. An example of initialization would be a communications port for baud rate. The call frame offsets for assembly language programmers are: dDevice pStatRet dStatusMax pdStatusRet [EBP+20] [EBP+16] [EBP+12] [EBP+08] 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.pStatRet.0 Page 139 of 667 . parity.pdStatusRet).

It must equal the number of contiguous DCBs that the driver has filled out before the InitDevDr call is made. If more than one device is controlled. nDevices This is the number of devices that the driver controls. MMURTL V1. fReplace) dDevNum This is the device number that the driver is controlling. such as a disk or SCSI controller. This means the devices are number consecutively. and one DMA channel if applicable. then all devices controlled by the driver will be locked out when the driver is busy.0 Page 140 of 667 . usually handles multiple devices through a single set of hardware ports.dInitData). If this is not the case. The call frame offsets for assembly language programmers are: DDevice IInitData dInitData [EBP+16] [EBP+12]\ [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. and so on.pInitData. it should allocate all system resources it needs to operate and control its devices while providing service through the three standard entry points. and can’t handle more than one active transfer at a time. this is the first number of the devices. This is because one controller. nDevices. After the Device driver has been loaded. and the driver can handle two devices simultaneously the driver should be broken into two separate drivers. The DBCs must be contiguous in memory. The definition and parameters to InitDevDr() are as follows: InitDevDr(dDevNum. pDCBs This is a pointer to the DCB for the device.DError = DeviceInit(dDevice. This means the second DCB must be located at pDCBs + 64. If the driver controls more than one device. A 64-byte DCB must be filled out for each device the driver controls before this call is made. If the driver is flagged as not re-entrant. When a driver controls more than one device it must provide the Device Control Blocks for each device. the second at pDCBs + 128. pDCBs. this is the pointer to the first in an array of DCBs for the devices.

0 Page 141 of 667 . Please use the MMURTL API reference for a detailed description of their use. It also returns the physical address of the memory which is required for the DMASetUp call. AllocDMAMem() Allocates memory that will be compatible with DMA operations. If an error code already defined in MMURTL’s standard header file will adequately describe the error or status. They perform many of the tedious functions that would otherwise require assembly language. SetIRQVector() Sets up a vector to your ISR.fReplace If true. OS Functions for Device Drivers The following is a list of functions that were specifically designed for device drivers. A driver must specify at least as many devices as the original driver handled. 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. it only means the new driver will be called when the device is accessed. This does not mean that the existing driver will be replaced in memory. Your device drive will also return error codes for problems it has honoring the device call. the driver will be substituted for the existing driver functions already in place. EndOfIRQ() Resets the programmable interrupt controller unit (PICU) at the end of the ISR sequence. then use it. DMASetUp() Programs the DMA hardware channel specified to move data to or from your device. MMURTL V1. and a very good knowledge of the hardware. UnMaskIRQ() Allows interrupts to occur from a specified channel on the PICU. MaskIRQ() Masks one interrupt (prevents it from interrupting) by programming the PICU.

as well as the video display. Active Job (Video & keyboard) When multiple jobs are running in MMURTL. The Monitor Program Introduction The Monitor program is included as part of the operating system.Chapter 11. you will find a need for something similar to the monitor as an initial piece of code built into the operating system for testing. MMURTL V1. the keyboard. No spaces. or terminate. the job he is currently viewing.JOB file. the monitor attempts to open a text file in the system directory call INITIAL. Initial Jobs After all internal device drivers and services are loaded. tabs or other characters are allowed before the RUN file name. When the user presses these global keys. Listing 11. Many of the functions performed by the monitor are for testing the system. it continues to run because a job never actually knows if it is displaying data to the real screen or it’s own virtual screen. This is done by pressing the CTRL-ALT-PageDown keys to move forward through the active jobs until you see the one you want. Each line lists the full file specification of a RUN file. many of the functions now performed by the monitor will be moved to external programs. If you intend to write an operating system. The name must start in the first column of a line. The format is very simple. Even when a Job is not being displayed. a buffer in memory.1 shows a sample INITIAL. If it’s displaying video data.JOB. is assigned to the new job or is terminated. The monitor also serves as a context display controller by monitoring for global keys that indicate the user’s desire to change. it is still running unless it’s waiting for something such as keyboard input or some form of communication. only one job at a time can be displayed or accept keyboard input. A line that begins with a semicolon is ignored and considered a comment line. In future versions of MMURTL. This file contains the file names of one or more run files you want to execute automatically on boot up. The current job may also be terminated by pressing CTRL-ALT-Delete. The Monitor program enables you to select which job you are currently interacting with.0 Page 142 of 667 .

No parameters are passed . the monitor is reached with a JMP instruction.This is the initial jobs file. . Any spaces. C:\MSamples\Service\Service. Sample Initial.Job file.after each entry.1 describes the function of each assigned C:\MMSYS\CLI. MMURTL V1.End of file If the executable job listed is CLI. The file name must contain the FULL .Comment lines begin with a semi-colon . Monitor Function Keys The Monitor also provides other functions such as system resource spaces in front of the name and a proper end-of-line .Listing 11.this will be loaded on bootup . .You may list the jobs that you wanted executed upon .0 Page 143 of 667 . job display. .g. Table 11..1. One run file name per line.RUN <---.INITIAL.file name including path.Maximum line length is 80 characters total. .the run file name are ignored. The functions are provided with function keys which are labeled across the bottom of the display. the video and keyboard will be assigned to this job and taken away from the monitor. 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. (e.1.JOB . tabs or comments after .system boot in this file.RUN) . Drive:\DIR\NAME. Table the run file.RUN. and access to the built-in debugger.

This task also looks for the Ctrl-Alt-Delete key combination which terminates a job. Task switches total . but the count is fixed at OS build time. Tasks Ready to Run . Each job uses one. you will see the preemptive task switch count increase.1. The memory they occupy is dynamically allocated. any errors returned are displayed. The statistics provide a snap-shot of system resources every half second. This number does not directly relate the timer ticks because this can happen several times between the time interrupt interval. RS-232 serial communications.This is the number of tasks currently queued to run. It looks for Ctrl-Alt-PageDown and shifts the keyboard and video to the next job in numerical order.0 Page 144 of 667 .This is the total number of 4K memory pages available for allocation by the memory-management code. During this process. If this number is greater than zero. and the parallel port (LPT) driver.This is the number of times the operating system had nothing to do. These statistics include: Free 4K memory pages . They are also dynamically allocated with a fixed count at OS build time.This is the number of free job control blocks left. They are displayed as typed. The initialized services include the keyboard and the file system. MMURTL V1. Once the initialization is complete. CPU idle ticks (no work) . The first additional task provides the contextswitching capabilities by leaving a global key request with the keyboard service. There were no tasks ready to run.The first thing accomplished by the monitor is the initialization of all internal device drivers and system services.This is the total count of task switches since boot up. Free Task State Segments . Each new task uses one of these. The device drivers that are initialized include the hard and floppy disk. The initialization also includes starting two tasks inside the monitor code.These are task-management structures. The monitor echoes other keys to the screen to show you it is working properly. user interaction is provided via the functions keys discussed in table 11. The second task is used to recover resources when a job ends or is terminated for ill behavior (memory access or other protection violations). Free Job Control Blocks . Performance Monitoring Performance monitoring is provided by sampling statistic-gathering public variables the operating system maintains and updates. A status code of Zero indicates successful initialization. priority task.

Listing 11. the C library code can be used.h" #define ok 0 #define ErcNoDevice 504 static char rgStatLine[] = "mm/dd/yy 00:00:00 ".2.2.h" "MJob.A small structure used by all messages sent and received. These are also dynamically allocated with a fixed count at OS build time.0 Page 145 of 667 .h" "MMemory. #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. but dozens of messages may be sent between tasks.Response is made by the service.h" "MKbd. This number may seem a little high. I include no C library code in the operating system itself.h" "MVid. including requests.This is the number of free exchanges that can be allocated. These are static and the count is determined at OS build time.h" "MDevDrv. Monitor Source Listing Text-formatting functions are provided with the xprintf() function defined in the monitor. even in the same job. Free Link Blocks . In future versions when a lot of the monitor functionality is moved out of the operating system. Free Exchanges . These structures are also in dynamically allocated memory. See listing 11. the request block is returned for re-use. static char rgMonMenu2[] = " \xb3 \xb3 \xb3Reboot". but have a fixed count at OS build time.h" "MData. MMURTL V1. MMURTL Monitor Tick:0 static char rgMonMenu1[] = "LdCLI\xb3Jobs \xb3Stats \xb3 ". static char rgMonMenu3[] = " \xb3Debug \xb3 \xb3 ".h" "MTimer.Monitor program source listing. that don’t get picked up right away.h" "MFiles.

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

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

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. value.[EBP+8] CMP AL. EAX MOV ESI.0 JL isdigit0 . #asm XOR EAX. long *argptr) { char numstk[33]. chr. The number of chars written is returned (not incl \0).EAX . zero = ’ ’. **************************************************************/ static long _ffmt(char *outptr. 39h .No JMP SHORT isdigit2 isdigit1: MOV EAX. char *fmt. *ptr. MMURTL V1. JLE isdigit1 . total. -1 isdigit2: #endasm } static long strlen(char *cs) { . if(chr == ’-’) { /* left justify */ --justify.No CMP AL.Yes isdigit0: XOR EAX. minus. zero.[EBP+8] _strlen0: CMP BYTE PTR [ESI]. #asm MOV EAX. unsigned long width. *ptr = justify = minus = 0. width = value = i = 0. while(chr = *fmt++) { if(chr == ’%’) { /* format code */ chr = *fmt++. ptr = &numstk[32]. 30h .0 Page 148 of 667 .{ . justify. i. total = 0.

} 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. } if(chr == ’0’) /* leading zeros */ zero = ’0’. case ’o’ : i = 8. break. default: *--ptr = chr. switch(chr) { case ’d’ : if(value & 0x80000000) { value = -value. case ’s’ : ptr = value. case ’c’ : *--ptr = value.0 Page 149 of 667 .’0’). } value = *--argptr. break. /* output sign if any */ if(minus) { MMURTL V1. } case ’u’ : i = 10. break. break. case ’x’ : case ’X’ : i = 16. *--ptr = chr.chr = *fmt++. break. break. while(isdigit(chr)) { /* field width specifier */ width = (width * 10) + (chr . chr = *fmt++. ++argptr. case ’b’ : i = 2. ++minus. } while(value /= i). generate the ASCII string */ if((chr = (value % i) + ’0’) > ’9’) chr += 7.

1. while((*ptr) && (i <= value)) { *outptr++ = *ptr++. } /* move in data */ i = 0. ++i.0 Page 150 of 667 .*outptr++ = ’-’. } /************************************ Formatted print to screen *************************************/ long xprintf(char *fmt.) { */ */ MMURTL V1. i < width. } /* pad with ’zero’ value if right justify enabled if(width && !justify) { for(i = strlen(ptr). ++i. just move into string *outptr++ = chr. ++total. } } *outptr = 0. ++total. ++i) *outptr++ = zero. return total. value = width . if(width) --width. } /* pad with ’zero’ value if left justify enabled */ if(width && justify) { while(i < width) { *outptr++ = zero. ++total. } } } else { /* not format char. ++total.. ++total. ..

return. fmt. strlen(buffer). long total.0 Page 151 of 667 . /* set up ap pointer */ total = _ffmt(buffer. ..) { va_list ap. va_start(ap. char *fmt. total = _ffmt(s. ap).0. BLUE|BGWHITE). long total. 26. fmt).23. va_end(ap. PutVidChars(0. fmt. PutVidChars(27. if (iLine >= 23) { ScrollVid(0. /* set up ap pointer */ MMURTL V1. WHITE|BGBLUE). } /************************************ Formatted print to string s *************************************/ long xsprintf(char *s. SetXY(0. return total. *********************************************************/ static void InitScreen(void) { ClrScr(). iLine. xsprintf(&rgStatLine[70]. rgMonMenu1. fmt).1)..80. "%d".1. va_start(ap. PutVidChars(54. GetXY(&iCol. rgMonMenu2. fmt). fmt). 80. 26. ap).1). PutVidChars(0. tick). char buffer[S_SIZE]. 24.22). rgStatLine. } } /********************************************************* This is called to initialize the screen. return total. va_end(ap.va_list ap. BLUE|BGWHITE). rgMonMenu3. BLUE|BGWHITE). Color). TTYOut(buffer. 24. **********************************************/ void CheckScreen() { long iCol. &iLine). 24. } /********************************************** Checks to ensure we don’t scroll the function keys off the screen. SetXY(0. 25.

it has the job of looking for messages from the job code that indicate a job has terminated.. 80. 0x0f). WHITE|BGBLUE).5 second */ GetTimerTick(&tick).} /********************************************************* This is the status task for the Monitor. Msg[2]. (time & 0x0f). Besides displaying the top status line for the monitor.0. Sleep(50). 0x0f). rgStatLine[0] = ’0’ rgStatLine[1] = ’0’ rgStatLine[3] = ’0’ rgStatLine[4] = ’0’ rgStatLine[6] = ’0’ rgStatLine[7] = ’0’ + + + + + + ((time >> 20) & 0x0f). ((time >> 4) & 0x0f). /* sleep 0. KillMsg). "%d". /* sleep 0.0. TSSExch in it. ((time >> 8) & 0x0f). ((time >> 12) & 0x0f). */ erc = CheckMsg(KillExch. xsprintf(&rgStatLine[70]. rgStatLine.5 second */ /* Now we check for tasks that are Jobs that are killing themselves (either due to fatal errors or no exitjob). *pVid. /* year */ 0x0f). for(. U8 *pPD.) { GetCMOSTime(&time). /* month */ 0x0f). if (!erc) { /* someone’s there wanting to terminate. rgStatLine. The message has Error. Exch. /* seconds */ + + + + + + ((date ((date ((date ((date ((date ((date >> >> >> >> >> >> 20) 16) 12) 8) 28) 24) & & & & & & 0x0f).0 Page 152 of 667 . it recovers the last of the resources and then notifies the user on the screen. tick). rgStatLine[10] = ’0’ rgStatLine[11] = ’0’ rgStatLine[13] = ’0’ rgStatLine[14] = ’0’ rgStatLine[16] = ’0’ rgStatLine[17] = ’0’ GetCMOSDate(&date). i. WHITE|BGBLUE). tick). /* Day */ 0x0f)..*/ /* Get and save the error (KillMsg[0]) */ MMURTL V1. "%d". PutVidChars(0. When it gets one. *********************************************************/ static void StatTask(void) { unsigned long erc.. Sleep(50). ((time >> 16) & 0x0f). GetTimerTick(&tick). PutVidChars(0. xsprintf(&rgStatLine[70]. 80.

*/ DeAllocPage(pPD. DeAllocJCB(pJCB).KillError = KillMsg[0].) */ } } } /* for EVER */ } /********************************************************* This is the Manager task for the Monitor. KillJobNum. 2). Tone(440.. } /* Now we deallocate the JCB and the TSSExch which will free the TSS automatically! */ DeAllocExch(KillMsg[1]). 1. So we can just deallocate both pages in one shot. Error: %d\r\n". /* Call GetExchOwner which gives us pJCB */ erc = GetExchOwner(KillMsg[1]. if (!erc) { KillJobNum = pJCB->JobNum. DeAllocPage(pVid. pPD = pJCB->pJcbPD. 0. 0. It allows us to switch jobs with the MMURTL V1.0 Page 153 of 667 . 0. &i. xprintf("Job number %d terminated. erc = WaitMsg(Exch. erc = Request("KEYBOARD". 0.. KillError). Msg). CheckScreen(). /* We’re done (and so is he. 1). 0. Exch. &pJCB). pVid = pJCB->pVirtVid. /* No error returned */ /* When the JCB was created. the PD and it’s 1st PDE (PT) were allocated as two pages next to each other in linear memory. 0.50). fKilled = 1. /* Use our TSS exchange for Request */ SetVidOwner(1). Then we deallocate the single page for virtual video. /* Must change video to monitor if this guy owned it */ GetVidOwner(&i). if (i == KillJobNum) { GetTSSExch(&Exch). 0). 4.

&gcode. erc = GetpJCB(i. erc = Request("KEYBOARD". We don’t actually switch jobs. while (!fDone) { i++. MngrMsg). &MngrHndl. } } MMURTL V1. if (!erc) { if ((gcode & 0xff) == 0x0C) { /* Find next valid Job that is NOT the debugger and assign Vid and Keyboard to it. } if (i != j) { SetVidOwner(i). i. 0.0 Page 154 of 667 .. 0). 0. *********************************************************/ static void MngrTask(void) { long erc. 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. 0. &pJCB). erc = WaitMsg(MngrExch.. we just reassign video and keyboard to the next active job (except the Debugger). 0). 2. 0. fDone. MngrMsg). &MngrHndl. Also. /* Leave a Global Key Request outstanding with KBD service for the status task */ erc = Request("KEYBOARD". 4. fDone = 0. char *pJCB.. j.) { erc = WaitMsg(MngrExch. 0. */ erc = GetVidOwner(&j). 0. i = j. for(. if the debugger has the video. MngrExch.CTRL-ALT-PageDown key. else if (i>34) i = 1. 0. /* erc = 0 if valid JCB */ if ((!erc) || (i==j)) fDone = 1. 0. i. 0. 0. MngrExch. if (i==2) i = 3.

} } /* for EVER */ } /********************************************************* This simply does a software interrupt 03 (Debugger). } /********************************************************* This strobes keyboard data port 60h with 00 after sending 0D1 command to Command port.Test The Input Buffer Full Bit . } /* CTRL-ALT-DEL (Kill)*/ /* leave another global key request */ erc = Request("KEYBOARD".first we clear interrupts . MngrExch. MMURTL V1. 0. #asm INT 03 #endasm return. 0). & and the last job loaded. &MngrHndl.check port up to 64K times . *********************************************************/ static void GoDebug(void) { .Read Status Byte into AL .0 Page 155 of 667 . We have to loop reading the status bit of the command port to make sure it’s OK to send the command.else if ((gcode & 0xff) == 0x7F) { erc = GetVidOwner(&j).Strobe bit 0 of keyboard crtlr output /********************************************************* This reads a file called Initial. 0. 0.Job from the system directory and loads all jobs specified in the file. 0.64h TEST AL.02h LOOPNZ Reboot0 MOV AL. 4. 0.AL STI #endasm return. This resets the processor which then executes the boot ROM. *********************************************************/ static void Reboot(void) { . 0FFFFh Reboot0: IN AL. Video and keyboard are not assigned to any of these jobs unless it is cli. erc = KillJob(j).0FEh OUT 64h. #asm CLI MOV ECX. } . 2.

&fh). job. 1=B. unsigned char sdisk. j. while (!fdone) { i = 0. char fdone. if (!erc) { fdone = 0.*********************************************************/ void LoadJobFile(void) { long erc. 0. if ((!erc) && (i > 1)) { if (arunfile[0] == ’. } while ((!erc) && (arunfile[i-1] != 0x0A) && (i < 80)).’) continue.JOB\0". &ajobfile[1]. 0x0A) 0x0D) 0x20) 0x09) && && && && if (cbrunfile > 2) { arunfile[cbrunfile] = 0. sdisk += 0x41. job = 0. /* 0=A. 20). sjobfile.. GetSystemDisk(&sdisk). fh. &arunfile[i++]. while ((arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile] != (arunfile[cbrunfile])) cbrunfile++. char ajobfile[50]. xprintf("Loading: %s. MMURTL V1. sdisk &= 0x7F.. arunfile). 1. 7) == -1)) fcli = 1. fcli = 0. else fcli = 0.0 Page 156 of 667 .\r\n". sjobfile. /* a comment line */ cbrunfile =". i. /* null terminate for display */ if ((cbrunfile > 8) && (CompareNCS(&arunfile[cbrunfile-7]. cbrunfile. CopyData(":\\MMSYS\\INITIAL. &j). "cli. do { erc = ReadBytes(fh. sjobfile = strlen(ajobfile). fcli. char arunfile[80]. */ ajobfile[0] = sdisk. 2=C etc. 1. erc = OpenFile(ajobfile.

CheckScreen().0 Page 157 of 667 . job. /* if the last successfully loaded job was a cli. erc = 0. assign the keyboard and video to it. erc = LoadNewJob(arunfile. acli[40]. sdisk &= 0x7F. } } /********************************************************* This Loads the MMURTL Command Line Interpreter and switches video and keyboard to give the user access to it. 0. GP1Msg). Sleep(50). if (!erc) { xprintf("Successfully loaded as job %d\r\n". 0. job = 0. job). if (!erc) erc = WaitMsg(GP1Exch.CheckScreen(). sdisk += 0x41. cbrunfile. &job). erc). &GP1Hndl. 2=C etc. } } } else fdone = 1. erc = Request("KEYBOARD". CheckScreen(). unsigned char sdisk. } } else { xprintf("INITIAL. /* 0=A. Sleep(50).\r\n"). GetSystemDisk(&sdisk). GP1Exch. */ MMURTL V1. 0. 0. } else { xprintf("ERROR %d Loading job\r\n".JOB file not found in system directory. 0. CheckScreen(). 1=B. */ if ((job > 2) && (fcli)) { SetVidOwner(job). } CloseFile(fh). job. *********************************************************/ static long LoadCLI(void) { long erc. 4. 0). 0.

xprintf("MMURTL (tm) . Tone(1000. if (erc) xprintf("SpawnTask (StatTask) Error: %d\r\n". erc = LoadNewJob(acli. SetVidOwner(job). xprintf("Copyright (c) R. GP1Msg)..15). if (erc) xprintf("AllocExch (Kill Exch) Error: %d\r\n". 1 ). &acli[1]. if (erc) xprintf("AllocExch (Mngr Exch) Error: %d\r\n". if (!erc) erc = WaitMsg(GP1Exch. CheckScreen(). erc). Tone(250. ccode1. iCol. 0).RUN\0". Color = WHITE|BGBLACK. 16). iLine. unsigned char c. Real-Time kerneL\r\n").0 Page 158 of 667 . } /********************************************************* This is the main procedure called from the OS after all OS structures and memory are initialized. xprintf("Loading: %s. InitScreen(). /* Task 4 */ MMURTL V1.Message based.acli[0] = sdisk. erc = AllocExch(&KillExch). /* 250 Hz for 150ms */ /* 250 Hz for 330ms */ /* Allocate an exchange for the Manager task global keycode */ erc = AllocExch(&MngrExch). 0. c = ((BootDrive & 0x7f) + 0x41).. Color = YELLOW|BGBLACK. k. 24. job. CopyData(":\\MMSYS\\CLI. 0. 0. &job).". i. strlen(acli).33). *********************************************************/ void Monitor(void) { long erc. &GP1Hndl. } return erc. 0. Sleep(50).A. &StatStack[255]. j. job). 0. erc = Request("KEYBOARD".Burgess. if (!erc) { xprintf("New CLI Job Number is: %d\r\n". MUltitasking. unsigned long ccode. 0. erc = SpawnTask( &StatTask. 4. GP1Exch. erc). char text[70]. erc). 0. acli). 1991-1995 ALL RIGHTS RESERVED\r\n\r\n").

xprintf("%d\r\n".) { /* Loop forEVER looking for user desires */ /* Make a ReadKbd Key Request with KBD service. i). 1 ). /* Spawn manager task */ SpawnTask( &MngrTask. erc = InitKBDService(). Error: ")... xprintf("Init Serial Comms Device Driver Error: %d\r\n". i = (oMemMax+1)/1024. 0. erc = coms_setup(). /* Allocate general purpose exchanges to use in the monitor */ erc = AllocExch(&GPExch). erc).... &MngrStack[255]. erc).. /* Call LoadJobFile to read job file from system directory and execute and jobs listed there. xprintf("Init Parallel LPT Device Driver Error: %d\r\n". erc). c).0 Page 159 of 667 . if (erc) xprintf("AllocExch Error: %d\r\n". erc). erc = fdisk_setup(). Error: %d\r\n". */ LoadJobFile(). erc). xprintf("Initializing file system. 10. */ MMURTL V1. xprintf("Init hard disk device driver.\r\n"). Tell it to wait for a key. for (. xprintf("Init floppy device driver.. erc = InitFS(). erc). erc).xprintf("BootDrive: %c\r\n". erc = hdisk_setup(). if (erc) xprintf("AllocExch GP1 Error: %d\r\n". Error: ").. i = (nMemPages*4096)/1024. erc). xprintf("%d\r\n". xprintf("Init KBD Service Error: %d\r\n". erc = QueryPages(&nMemPages). xprintf("Total memory (Kb): %d\r\n". erc = AllocExch(&GP1Exch). erc = lpt_setup(). xprintf("Free memory (Kb): %d\r\n". i).. xprintf("File System.

xprintf("Free Task State Segments: %d\r\n". xprintf("Name: %s\r\n". text).0 Page 160 of 667 . xprintf("Free Job Control Blocks: %d\r\n". 0). xprintf("Task switches total: %d\r\n". &GPHndl. 0)) { /* ReadKbd no wait until no error */ SetXY(0. &ccode. erc = GetpJCB(i. &pJCB). 13). nTSSLeft). /* Col offset */ for (i=1. nMemPages). if (erc) xprintf("Kbd Svc Request KERNEL ERROR: %d\r\n". erc). erc). if (erc) xprintf("KERNEL Error from Wait msg: %d\r\n". pJCB->JobNum).. 0. i<nMaxJCBs. nHalts). xprintf("Any key to dismiss status. GPMsg).loops displaying status till key is hit */ InitScreen().j).. case 0x10: /* F2 Jobs */ InitScreen(). /* lop off everything but the key value */ switch (c) { case 0x0F: /* F1 Run */ erc = LoadCLI(). erc).erc = Request("KEYBOARD". 1. nSwitches). 1. nRQBLeft). text[pJCB->sbJobName[0]] = 0. } } break. xprintf("Free 4K memory pages: %d\r\n". case 0x11: /* F3 Stats . while (erc = ReadKbd(&ccode1. 0.j). nReady). CopyData(&pJCB->sbJobName[1]. nJCBLeft). SetXY(k+10. break. /* erc = 0 if valid JCB */ if (!erc) { SetXY(k. xprintf("Tasks Ready to Run: %d\r\n". /* Line */ k = 0. 0. erc = QueryPages(&nMemPages). xprintf("Preemptive task switches: %d\r\n". xprintf("CPU idle ticks (no work): %d\r\n". MMURTL V1. /* wait for the keycode to come back */ erc = WaitMsg(GPExch. i++) { if (j > 20) k = 40. 4. 0.1). GPExch. xprintf("Job: %d\r\n". j = 2. j++. if (erc) xprintf("Error from LoadCLI: %d\r\n". nSlices). \r\n"). c = ccode & 0xff. xprintf("Free Request Blocks: %d\r\n". text.

case 0x00: /* No Key */ Sleep(3). break.. break. Sleep(9). PutVidChars(29. 1. "\\". GREEN|BGBLACK). 1. "|". "\\". "/".0 Page 161 of 667 . 1. 1. SetXY(0. GREEN|BGBLACK). case 0x18: /* F10 Debug */ GoDebug().xprintf("Free Link Blocks: xprintf("Free Exchanges: SetXY(0. 1.. 1. break. 1.23. erc = ReadKbd(&ccode1. PutVidChars(29. Sleep(9). } } /* for EVER */ } MMURTL V1. GREEN|BGBLACK). "/". xprintf ("\r\n"). WHITE|BGBLACK). "|". any other key to cancel"). PutVidChars(29. GREEN|BGBLACK).1). xprintf(". Sleep(9). 1. } SetXY(0. 1. 2. /* Sleep for 30 ms */ break. GREEN|BGBLACK). Sleep(9). 1. WHITE|BGBLACK). %d\r\n". Sleep(12). GREEN|BGBLACK). nEXCHLeft).future use */ case 0x13: /* F5 */ case 0x14: /* F6 */ case 0x15: /* F7 */ case 0x17: /* F9 */ case 0x19: /* F11 */ case 0x1A: /* F12 */ break. PutVidChars(29. GREEN|BGBLACK). "-".1. 1. PutVidChars(29. } } GetXY(&iCol. PutVidChars(29. 1.1). 1). case 0x12: /* F4 . &iLine). case 0x16: /* F8 Reboot */ xprintf("\r\nF8 again to reboot. 1. 1. Sleep(12). Sleep(9). 1. GREEN|BGBLACK). Sleep(9). "-".80. 1.22). PutVidChars(29. if (iLine >= 23) { ScrollVid(0. GREEN|BGBLACK). if ((ccode1 & 0xff) == 0x16) Reboot().12). %d\r\n". " ". else TTYOut (&c. 1. nLBLeft). default: if (((c > 0x1F) && (c < 0x80)) || (c==0x0D) || (c==8)) { if (c==0x0D) TTYOut (CRLF. PutVidChars(29. 1. 1.Cancelled\r\n"). PutVidChars(29.

the debugger is entered. The most common exceptions are the General Protection Fault (0D hex) and the Page Fault (0E hex). It is not a separate run file. See “Debugger Theory” section for more information on INT 03 instruction usage. pressing Esc will restart the debugger at the offending address and you will re-enter the debugger again. non-symbolic debugger. The debugger may also start on its own if certain exceptions occur. Exiting the Debugger Pressing the Esc key will exit the debugger and begin execution at the next instruction. If the debugger was entered on a fault. along with the exception number that caused it. 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 it’s current incarnation. This usually provides enough information to correct the offending code.Chapter 12. See “Debugger Theory” for more information.0 Page 162 of 667 . Debugger Introduction The debugger is built into the operating system. the debugger is an assembly language. or by placing and INT 03 instruction anywhere in your application's assembly language file. Some exceptions are designed to take you directly into the debugger and display an error code to indicate the problem. Intimate knowledge of the Intel processor instruction set is required to properly use the debugger. When this occurs. and a red banner is displayed. Entering the Debugger The Debugger may be entered using the Debugger function key in the monitor. The debugger provides the following functions: • • • • • • • • Display of instructions (disassembled) Dumping of linear memory as Bytes or dwords Display of Exchanges. the Debug command in the command-line interpreter. and why. The registers can be examined to see where it happened. MMURTL V1.

without executing them. This becomes the active address. This requests a linear address. F10 . This returns to the application and executes one instruction. with the down arrow key.Set Breakpoint. You may also use F8 CrntAdd (Current Address) to select a new address to display before setting the breakpoint. This allows you to set the code address the debugger is displaying (disassembling). The address is displayed on the left. such as dumping data. function keys are across the bottom. F5 Exch . the address of the TSS.Dump Dwords. F3 ClrBP . This displays all exchanges and shows messages or tasks that may be waiting there.Display Exchanges. Only exchanges that actually have a message or task will be displayed. and all the data as well as the ASCII is shown to the right. after which it immediately returns to the debugger where the next active code address and its associated instruction are displayed. and the priority of the task are displayed with each one. The complete general register set is displayed along the right side of the screen.Clear Breakpoint. F9 DumpB . The task number. The format is the same as the dump command in the CLI. F2 SetBP . and then dumps the bytes located at that address. This sets the breakpoint at the currently displayed instruction. Debugger Display The debugger display is divided into 3 sections.Display Tasks. The registers and function keys will be redisplayed when the function is finished. F4 CS:EIP . and all the data as well as the ASCII is show to the right. This requests a linear address. The address is displayed on the left. the address of the task’s JCB. F8 CrntAdd .0 Page 163 of 667 . This clears the single breakpoint. The F4 CS:EIP function key can be used to return to the next address scheduled for execution.Single Step. and then dumps the dwords located at the that address. This does not change the address that is scheduled for execution. You may move down through the instructions. This displays all active tasks on the system.The debugger will also exit using the Single Step command (F1 function key). the associated Job number. and the left side is the instruction and data display area. and will be reentered immediately following the execution of the next complete instruction. Certain debugger display functions. F6 Tasks . Each byte of a dword is displayed is in the proper order (High • • • • • • • MMURTL V1. will clear the screen for display.Goto CS:EIP. Debugger Function Keys Each of the debugger function key actions are described below: • • F1 SStep .Change Current Address. This redisplays the current instruction pointer address.Dump Bytes.

The first two task state segments are static structures and are used for the operating system Monitor and debugger.Exchanges.Services Array.Global Descriptor Table.Link Blocks. Each TSS is 512 bytes. The request handle that you receive from the request primitive is actually a pointer. This is the address of the 32 queue structures where tasks that are ready to run are queued. These structures can also be very useful during program debugging. This can be dumped to view all of the GDT entries. .Address Information. as most of them are.• order. TSS1 . The GDT contains many items. MMURTL V1. This is useful for dumping structures that are dword oriented. JCBs . TSSs are numbered sequentially. The names of the services and the exchanges they service are contained here. low order). it is the index of that exchange in the array. This lists names and linear addresses of important structures used during OS development.0 Page 164 of 667 . This is the address of the first request block in the array of request blocks allocated during system initialization. next. This can be dumped to view interrupt vector types and addresses. 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. a linear address. Exch .Ready Queue. This the address of the array of Job Control Blocks. LBs . F12 AddInfo .Request Block Array. IDT . TSS3 . some of which include the TSS descriptor entries for you program. TSS descriptors point to your task state segment and also indicate the privilege level of the task. These are 16-byte structures used as links in chains of messages and tasks waiting at an exchange.Interrupt Descriptor Table.” SVCs . next. RQBs . When you receive an exchange number form the AllocExch() function. “Application Programming. The rest of the task state segments are in several pages of allocated memory and are initialized during system startup. The array of link blocks are allocated as a static array. This is an array of 512-byte structures. The Debugger TSS is 512 bytes after this address. with the name of the structure and what use it may be during debugging. GDT . RdyQ . This is the address of the Monitor TSS.Dynamic TSSs.First TSS. that will be somewhere in this array. Each exchange is 16 bytes. The abbreviations are given in the following list. This is the address of the array of exchanges in allocated memory.Job Control Blocks. These values are hard coded at system build. The JCB is discussed in detail in Chapter 9. which makes the first of the dynamic TSSs number 3.This is the address of the array of currently registered services.

START in an assembly language program. Stack parameters (arguments) are always referenced above the frame pointer. Debugger Theory The Intel 32-bit processors have very powerful debugging features. This is the address of the array of timer blocks. actually a trap. entering the debugger. such as [EBP+12]. listed in your ATF file. ESP . or at the beginning of any instruction.and displayed as an unsigned number. and placing an INT 03 instruction there. but for right now. They have internal debug registers that allow you to set code and data breakpoints. You would insert the INT 03 instruction wherever you feel would be useful. PUSH EBP MOV EBP.aTmr .Timer Blocks. Lots of code here MOV ESP. The following example shows typical entry and exit code found in a high-level procedure. and also how local variables are accessed. locking up. or for . With near calls like you would make inside your program. whenever any linear memory location is accessed. A single 4-byte integer is referenced as [EBP-4] or [EBP+FFFFFFFC]. You can start by searching for _main in your primary assembly language file generated by the C compiler.0 Page 165 of 667 . MMURTL V1. It may help you to be able to identify high-level entry and exit code sequences (begin and end of a C function). the last parameter is always [EBP+8]. you can set breakpoints in several locations of your program by editing the . The Sleep() and Alarm() functions each set up a timer block. its down and dirty troubleshooting. or whatever). There is a symbolic debugger in MMURTL’s near future.ASM files. EBP POP EBP RETN When local variables are accessed they are always referenced below the frame pointer in memory. Debugging Your Application If your application is "going south" on you (crashing. just knowing how far your program made it is enough to solve the problem. What this means is that you can set the processor to execute an interrupt. In some cases.

just like applications do. including allocated memory. and the interrupt procedure does a task switch to the debugger’s task. you must be able to access all of its memory. you initially enter an interrupt procedure that places the running task in a hold status. This effectively replaces the running task with the debugger task. These include copying the interrupted task’s Page Directory Entry into the debugger’s JCB and the debugger’s TSS.0 Page 166 of 667 . This is so the debugger is using the tasks exact linear memory image. MMURTL V1. and do the reverse without the kernel even begin aware of what has happened. MMURTL’s debugger is set up as a separate job. It can remove the currently running task and make itself run. A few housekeeping chores must be done before the task switch is made. The paging hardware translates the linear address to a physical address for its own use. If you are going to debug a task. The debugger must be able to do this to get its job done. When it occurs. every task that executes it will produce the interrupt and enter the debugger. The MMURTL debugger currently only uses one of these registers and allows only instruction breakpoints to be set. Remember that several tasks may be executing exactly the same piece of code. In MMURTL. The debugger becomes the active job. If you set a breakpoint using the INT 03 instruction. When the debug interrupt activates. When writing an operating system. like the older processors. this really doesn’t apply as much because they each have their own linear address space. it has special privileges that allow to it to operate outside of the direct control of the kernel. which means you can have four active breakpoints in your program if you only use the registers. The INT 03 interrupt is just like any other on the system. without having to fill in special registers. the current pRunTSS.The INT 03 instruction may also be placed in your code. No other task operates at a priority that high. The debugger is the highest priority task on the system (level 1). There are four debug address registers in the processor. is saved by the debugger. a pointer to the currently running TSS. Even though the debugger has its own JCB and looks like another application. entry 3 in the interrupt table is used to vector to a procedure or task. the debugger becomes one of the most important tools you can have. Hold status simply means that it is not placed on the ready queue as if it were a normal task switch. For applications. Instead. usually a blank screen and nothing else. it can mean hours of grueling debugging with very little indication of what’s going on. The debugger also has its own virtual screen. 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 is an interrupt procedure that will switch to the debugger task after some fix-ups have been completed. It has its own Job Control Block (JCB) and one task (its own TSS). Debugging in a multitasking operating system is complicated to say the least. If you have problems before the debugger is initialized.

It’s actually an orphan when it comes to being a program in its own right. When the debugger is entered. a page fault is fatal to a task. The exceptions that cause entry into the debugger include Faults. it uses the values in the task’s Task State Segment (TSS).0 Page 167 of 667 . Traps. This makes the debugger transparent. The debugger uses this information to help you find the problem. If you look through the 386/486 documentation.The debugger cannot debug itself. you will see that there are many interrupts.) 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. These procedures get the information off the stack before switching to the debugger. It doesn’t have its own Page Directory or Page Tables like other jobs (programs). These contain the address of the exception handler. It means it tried to access memory that didn’t belong to it. Until MMURTL uses demand page virtual memory. Some of these are used by the OS to indicate something needs to be done. while others should not happen and are considered fatal for the task that caused them. becomes a task in the job it interrupted. It almost.Processor exceptions. This also has the effect of starting the task back at the instruction after it was interrupted which is the next instruction to execute. All of the values are just the way they were when the exception occurred except the CS and EIP. The INT 03 interrupt is not the only way to enter MMURTL’s debugger.1. This return address is used by the debugger entry code. or faults. that can occur.1. MMURTL has a very small interrupt procedure for each of these exceptions. 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. Some of the exceptions even place an error code on the stack after the return address. To fix this so that you see the proper address of the code that was interrupted. which MMURTL V1. and Aborts (processor fatal) and are shown in table 12. Table 12. but not quite.. it displays the registers from the interrupted task.. 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. MMURTL’s debugger is also entered when other fatal processor exceptions occur. we must get the return address from the stack and place it back into the TSS. To do this. called exceptions.

The debugger exit code is just about the reverse of the code used to enter the debugger. This also allows you to restart the application after a breakpoint at the proper place.0 Page 168 of 667 . you should kill the offending job and restart. along with any error that may have been removed from the stack on a fault. The debugger removes himself as the running task and returns the interrupted task to the running state with the values in the TSS. then single step while watching the registers to see where the problem exactly what you want. While you are in the debugger. If the debugger was entered because of a fault. setting a breakpoint before the exception occurs. MMURTL V1. This also includes switching the video back to the rightful owner. all of the registers from the task that was interrupted are displayed.

It is a service because it is a shared resource just like the file system.Not used (0) */ dData2 . Several programs can have outstanding requests to the keyboard service at one time.0 Page 169 of 667 . Using the Request interface allows for asynchronous program operation. Keyboard Service Introduction The keyboard is handled with a system service. Only one program (job) will be assigned to receive keystrokes at any one time. 0. If you don't need asynchronous keyboard access. 0). It's easier to use.Not used (0) */ All message-based services use the same request interface.” system services are accessed with the Request primitive. As described in chapter 10. “system programming. 0 0. Each of the functions is identified by its Service Code number. MyExch. 1. /* /* /* /* /* /* /* /* /* /* /* /* Service Name */ wSvcCode for ReadKbd */ dRespExch */ pRqHndlRet */ npSend (no Send Ptrs) */ pData1 */ cbData1 (size of KeyCode)*/ pData2 . 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. The Service Name is KEYBOARD (uppercase as always). 1. Here is an example of a Keyboard Service request: erc = Request( "KEYBOARD". the public call ReadKbd() is provided which is a blocking call with only 2 parameters. MMURTL V1. this is true multitasking).fWait for Key */ dData1 .not used (0) */ Not used (0) */ dData0 . 0. but not as powerful. I’ll describe it after I discuss the following services. &MyRqhandle. The exception to this rule is the Global Keyboard request to receive CTRL-ALT keystrokes. &KeyCode 4.Chapter 13.

Not used dData1 dData2 MMURTL V1. Service Code 1 2 3 4 Function Read Keyboard Notify On Global Keys Cancel Notify on Global Keys Assign Keyboard Table 13.Not used = 0 . The key code is undefined if this occurs (so you must check the error). A value of 1 in dData0 will cause the service to hold your request until a key is available. The dData0 determines if the service will hold the request until a key is available. Request Parameters for Read Keyboard: wSvcCode npSend pData1 dcbData1 pData2 dcbData2 dData0 = = = = = = = 1 0 Ptr where the KeyCode will be returned 4 . Read Keyboard The Read Keyboard function (1) allows a program to request keyboard input from the service.1 shows the services provided by the service code. A value of 0 means the request will be sent back to you immediately.Available Services Table 13.Return with error if no key waiting 1 . In this case. even if a key is not available.Count of bytes in the code 0 .Wait for Key = 0 . The key codes are described in detail in tables later in this chapter. The error from the service is ErcNoKeyAvailable (700) if no key was available.Available Keyboard Services The four functions are described in the following section. The first request pointer points to an unsigned dword where the key code will be returned.Not used. the error should be 0. fWaitForKey (0 or 1) 0 .Not used 0 .0 Page 170 of 667 .1.

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 Cancel Notify On Global Keys The Cancel Notify On Global Keys function (3) cancels an outstanding Notify On Global keys request. This cancels all outstanding Notify requests from the same job number for your application. and you no longer want to receive Global key notifications. this cancels it. It also means that your application must have an outstanding Notify request with the keyboard service to receive these keys. This allows for "hot-key" operation. The Cancel Notify request will be returned also. Unlike regular keystrokes. If you have an outstanding Notify request. This means that users of the Read Keyboard function (1) will not see these keys. The Notify request will be sent back to the exchange with an error stating it was canceled.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. you should send a Cancel Notify request (service code 3). described in detail later.0 Page 171 of 667 . The Global key codes returned are identical to the regular key codes. keys that are pressed when the CTRL-ALT keys are depressed are not buffered. 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 MMURTL V1. If you have an outstanding Notify request.

(D) Shift State Byte (Second Byte) If you need to know the shift state of any of the shift keys (Ctrl. as shown in table 13.2.). The key will be properly shifted according to the state of the Shift and Lock keys.Assign Keyboard The Assign Keyboard Request assigns a new job to receive keys from the service. The upper 3 bytes provide the status of special keys (Shifts.3. while providing complete keyboard status to allow further translation for all ASCII control codes.2. Num. Locks. The second byte provides the shift state which is six bits for Shift. This will leave you with the 8-bit code for the key. shown in table 13. etc.0 Page 172 of 667 . as shown in table 13. To eliminate all the key status from the 4-byte key code to get the keystroke value itself. Alpha-Numeric Key Values The Key code returned by the keyboard service is a 32-bit (4 byte) value. Alt & Ctrl. Alt or Shift). logically AND the key code by 0FFh (0xff in C). This request should only be used by services such as the Monitor or a program that manages multiple jobs running under the MMURTL OS. The high-order byte is for the Numeric Pad indicator and is described later.4. shows how the bits are defined. and Scroll). All ASCII text and punctuation is supported directly. The low-order byte is the actual key code. you can use the second byte in the 32-bit word returned. The third byte is for lock states (3 bits for Caps. 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. Table 13. MMURTL V1.

3 shows how the bits are defined. Num or Scroll).2 . Table 13. CpLockMask EQU 00000100b MMURTL V1.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. 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. Table 13. you can use the third byte in the 32-bit word returned.0 Page 173 of 667 .Table 13..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.3 .

the Enter key on the numeric keypad might do something differently in your program than the typewriter Enter key.NmLockMask ScLockMask EQU 00000010b EQU 00000001b Numeric Pad Indicator Only one bit is used in the high-order byte of the key code.1h to 7Fh). The Caps Lock key does not affect the keys shown with an asterisk (*) in the Shift Code column. For example. Ctrl.Keys Values Base Key Esc 1 2 3 4 5 6 7 8 9 0 = BS HT a b c MMURTL V1. A description is given only if required.0 Desc. Alt. This is needed if you use certain keys from the numeric pad differently than their equivalent keys on the main keyboard. the same value is provided in all shifted states (Shift. All codes are 7-bit values (1 to 127 . or Locks). If a shift code is not shown.. Zero and values above 127 will not be returned. LSB) will be set if the keystroke came from the numeric key pad. Escape KeyCode 1Bh 31h 32h 33h 34h 35h 36h 37h 38h 39h 30h 2Dh 3Dh 08h 09h 61h 62h 63h Shifted Key ! @ # $ % ^ & * ( ) _ + Shift Code 21h 40h 23h 24h 25h 5Fh 26h 2Ah 28h 29h 5Fh 2Bh Desc minus equal backspace tab Exclamation At pound dollar percent carat ampersand asterisk open paren close paren underscore A B C Page 174 of 667 41h 42h 43h . This bit (Bit 0. The Shift Code column shows the value returned if the Shift key is active. Key Codes Table 13.4 . Table 13.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.

5 shows the key code value returned. comma period slash 64h 65h 66h 67h 68h 69h 6Ah 6Bh 6Ch 6Dh 6Eh 6Fh 70h 71h 72h 73h 74h 75h 76h 77h 78h 79h 7Ah 5Bh 5Ch 5Dh 60h ODh 3Bh 27h 2Ch 2Eh 2Fh 20h D E F G H I J K L M N O P Q R S T U V W X Y Z { | } ~ : " < > ? 44h 45h 46h 47h 48h 49h 4Ah 4Bh 4Ch 4Dh 4Eh 4Fh 50h 51h 52h 53h 54h 55h 56h 57h 58h 59h 5Ah 7Bh 7Ch 7Dh 7Eh 3Ah 22h 3Ch 3Eh 3Fh open brace vert.0 Page 175 of 667 . MMURTL V1. Table 13. ’ . / Space accent Enter semicolon apostr. . bar close brace tilde colon quote less greater question Function Strip Key Codes Shift and Lock states do not affect the function key values. Shift and Lock state bytes must be used to determine program functionality.d e f g h I j k l m n o p q r s t u v w x y z [ \ ] ‘ CR .

0 Page 176 of 667 .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 .6 are returned when the shift keys or Num Lock are active.6 . Table 13. Caps lock does not affect these keys.Table 13. (Period) MMURTL V1.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 Numeric Pad Key Codes The Shift values shown in Table 13.5 .

You may also want the keyboard more deeply embedded in your operating system code.”Keyboard Code. I also recommend that you concentrate on the documentation for your keyboard implementation.7.” contains the source code behind the system service described in this chapter. Pieces of it may be useful for your system. 13. Edit. as I have tried to do in this chapter.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 Your Keyboard Implementation You may not want all of the functionality in your system that I provided. and Special Pad Key Codes None of the keys in these additional key pads are affected by Shift or Lock states. The keyboard translation tables could be used to nationalize this system. Remember. such as international key translations. or you may want more. if you desire. The values returned as the key code are shown in table 13. when all else fails.7. Chapter 25.Cursor. MMURTL V1.0 Page 177 of 667 . the programmer will read the documentation.

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

No internal file buffers are allocated by the file system in Block mode. Multiple users may open and access a Read-mode file. or just reading alone. the default name NETWORK is used. this is not necessary with Network names because the file system fixes them. a number called a file handle is returned to you. File Open Modes A file may be opened for reading and writing.If the network name is omitted from the specification. This also means that network names are limited to eight characters. A file handle is a 32-bit unsigned number (dword). Even though service names must be capitalized and space padded. These two modes are called modify(Read and Write) and read(Read-only). The network name matches the name of the network service when it was installed. File Access Type A file may be opened in Block or Stream mode. This number is how you refer to that file until closed. The filename across a network may not be limited to the MS-DOS file naming conventions described above. in any mode or type. The file handle is used in a all subsequent file operations on that file. Only a single user may open a file in Modify mode. This user is granted exclusive access to the file while it is open. Block Mode Block mode operation is the fastest file access method because no internal buffering is required. When a file is opened in Read mode.0 Page 179 of 667 . it may not be opened in Modify mode by any user. When a network file is opened. the network service receives the request from the file system. and a node name is specified. Make no assumptions about this number. The file handle that is returned will be unique on the system that originated the request. hence the default system service name for a network routing service is network. The data is moved in whole blocks in the fastest means determined by the device driver. The only restriction is that MMURTL V1. This depends on the type of file system on the node being accessed. File Handles When a file is opened.

Stream Access files have internal buffers and maintain your Current LFA. no matter what Mode the file was opened in.0 Page 180 of 667 . LFA 0 is the beginning of the file. Logical File Address All files are logically stored as 1-n Bytes. This is called a file pointer in some systems. When a file is initially open for Stream access the file pointer is set to 0. The standard block size is 512 bytes for disk devices. you do not specify an LFA to read or write from. Additional functions are provided to allow you to find and set the current LFA. Block access files have no internal buffers and do not maintain a current LFA.whole blocks must be read or written. The current LFA for stream files is updated with each ReadBytes() and WriteBytes() function. or file pointer. MMURTL V1. The file size minus 1 is the last logical byte in the file. Block Access file system functions require to you specify a Logical File Address (LFA). This is a one page. This is the byte offset in the file to read or write from. 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. the file system allocates an internal buffer to use for all read and write operations. For Stream files. Block mode operation allows use of the following file system functions: ReadBlock() WriteBlock() CloseFile() GetFileSize() SetFileSize() DeleteFile() Stream Mode In Stream mode. The Logical File Address (LFA) is an unsigned dword (dLFA). 4096 byte buffer.

&MyRqhandle.returned handle */ cbData2 -. Table 14.1 . 1. Table 14.1 .not used */ Unused Request parameters must be set to 0.0 Page 181 of 667 . 0).File System Requests The file system is a message-based system service. Listing 14. Listing 14.may be needed */ nSendPtrs -. The file system actually runs as a separate task. You can make a request. The procedural interface for Request has 12 parameters. &FileHandle 4. &"AnyFile. 11. then go do something else before you come back to wait or check the function to see if it’s completed (true multitasking).File System Service Codes Function OpenFile CloseFile ReadBlock WriteBlock ReadBytes Service Code 1 2 3 4 5 MMURTL V1. /* /* /* /* /* /* /* /* /* /* /* /* ptr to name of service */ wSvcCode -.respond here */ pRqHndlRet -. MyExch. This means it can be accessed directly with the Request primitive. Each of the functions is identified by its Service Code number. The small amount of time for message routing will not make any measurable difference in the speed of it’s operation because most of the time is spent accessing hardware.Block Type Access */ dData2 -.ModeRead */ dData1 -. It also provides the shared access required for a true multitasking system.ptr to name */ cbData1 -. 1.doc".size of name */ pData2 -.1 is an example of a File System request in the C programming language.1 for OpenFile */ dRespExch -. The file system is an ideal candidate for a message-based service. 1.1 Send ptr */ pData1 -.Size of a handle */ dData0 -. 0. Using the Request interface allows for asynchronous program operation.Openfile request in C dError = Request( "FILESYS ". All message-based services use the same request-based interface. It meets all the requirements.1 shows the service codes the file system supports.

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

0 Page 183 of 667 . File System Functions in Detail The following pages detail each of the file system functions that are available. dAccessType. dOpenMode. MODIFY = 1 dAccessType . Request Parameters for OpenFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 1 1 pName dcbName pdHandleRet 4 (Size of a file handle) dOpenMode dAccessType Not used (0) MMURTL V1.READ = 0.Block = 0. pdHandleRet . KBD and VID devices only. it can not be opened in ModeModify by any other users. Opening in ModeModify excludes all others from opening the file. pdHandleRet): dError OpenFile() opens an existing file in the current path for Block or Stream operations. Stream = 1. dcbName . OpenFile Procedural interface: OpenFile (pName. Procedural parameters: pName .DWord with length of the filename dOpenMode .FD0 FD1 HD0 HD1 Floppy Disk Floppy Disk Hard disk Hard disk None None None None Device access is currently implemented for NUL. If a file is open in ModeRead. A full file specification may be provided to override the current job path. Multiple users can open a file in ModeRead.pointer to the filename or full file specification.pointer to a dword where the handle to the file will be returned to you. dcbname.

Procedural parameters: dhandle .DWord with a valid file handle (as returned from OpenFile. nBytes. The file must be opened for Block access or an error occurs.DWord with number of bytes to read. dLFA . dLFA. all buffers are flushed and deallocated. This must be a multiple of the block size for the 512-byte disk.pointer to a dword where the count of bytes successfully read will be returned. Procedural parameters: dHandle . If stream access type was specified. pDataRet. pdnBlkRet . nBytes . Request parameters for ReadBlock: wSvcCode = 3 MMURTL V1.a dword that was returned from OpenFile(). pDataRet .Logical File Address to read from.0 Page 184 of 667 . This MUST be a multiple of the block size. Request Parameters for CloseFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = 2 0 0 0 0 0 = dHandle = 0 = 0 ReadBlock Procedural interface: ReadBlock(dHandle.Pointer to a buffer large enough to hold the count of blocks you specify to read.CloseFile Procedural interface: CloseFile (dHandle): dError CloseFile() closes a file that was previously opened. This will always be a multiple of the block size (n * 512). pdnBytesRet): dError This reads one or more blocks from a file.

DWord with number of bytes to write. dLFA . SetFileSize() must be used to extend the file length if you intend to write beyond the current file size. One Block = 512 bytes.pointer to a dword where the number of bytes successfully written will be returned. Writing beyond the current file length is not allowed. pData. This must always be a multiple of 512.Pointer to the data to write nBytes . dLFA.DWord with a valid file handle as returned from OpenFile. pdnBytesRet): dError This writes one or more blocks to a file. Procedural parameters: dHandle . See the SetFilesize() section. nBytes. pdnBytesRet .Logical File Address to write to. pData . This must be a multiple of the block size. Request parameters for ReadBlock: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = 4 = 1 = pData = nBytes (512 = 1 Block) = pdnBytesRet = 4 (size of dnBytesRet) = dHandle = 0 = 0 ReadBytes Procedural interface: ReadBytes(dHandle.0 Page 185 of 667 . pDataRet. pdnBytesRet): dError MMURTL V1. The file must be opened for Block access in Modify mode or an error occurs. This will always return a multiple of 512.nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = 0 pDataRet nBytes (multiple of 512) pdnBytesRet 4 (size of dnBytesRet) dHandle 0 0 WriteBlock Procedural interface: WriteBlock(dHandle. nBytes.

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

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

Request parameters for SetFileLFA: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = 8 = 0 = 0 = 0 = 0 = 0 = dHandle = dLFA = 0 GetFileSize Procedural interface: GetFileSize(dHandle. as returned from OpenFile() dLFA . will be extended or truncated as necessary to accommodate the size you MMURTL V1.Dword with a valid file handle.Dword with a valid file handle.0 Page 188 of 667 .a pointer to a Dword where the current size of the file will be returned.a Dword with the LFA you want to set. pdSizeRet): dError This gets the current file size for files opened in any access mode.dhandle . and allocated space on the disk. 0FFFFFFFF hex will set the current LFA to End Of File. Procedural parameters: dhandle . The file length. as returned from OpenFile() pdSizeRet . dSize): dError This sets the current file size for Stream and Block Access files. Request parameters for GetFileLFA: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 9 0 pdSizeRet 4 0 0 dHandle 0 0 SetFileSize Procedural interface: SetFileSize(dHandle.

Request Parameters for SetFileSize: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = 10 = 0 = 0 = 0 = 0 = 0 = dHandle = dSize = 0 CreateFile Procedural interface: CreateFile (pName.a Dword with the new size of the file.specify. as returned from OpenFile() dSize . dAttributes): dError This creates a new. and ready to be opened and accessed. Procedural parameters: pName . The file is closed after creation.Dword with length of the filename dAttributes .0 Page 189 of 667 . empty file in the path specified.Dword with a valid file handle.A Dword with one of the following values: 0 = Normal File 2 = Hidden File 4 = System File 6 = Hidden. 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. dcbName.pointer to the filename or full file specification to create. dcbName . System file Request parameters for CreateFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 = = = = = = = = 11 1 pName dcbName 0 0 dAttributes 0 MMURTL V1. Procedural parameters: dHandle .

Procedural parameters: pName . dcbNewName . No further access is possible. dcbname. dcbName .pointer to the current filename or full file specification.pointer to the new filename or full file specification.Dword with length of the current filename pNewName . The file must be opened in Modify mode (which grants exclusive access).Dword that was returned from OpenFile. Request parameters for DeleteFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 = = = = = = = 13 0 0 0 0 0 dHandle MMURTL V1. and the filename is available for re-use. The file must not be opened by anyone in any mode. pNewName dcbNewName): dError This renames a file.0 Page 190 of 667 .Dword with length of the new filename Request parameters for RenameFile: wSvcCode nSend pData1 cbData1 pData2 cbData2 dData0 dData1 dData2 = = = = = = = = = 12 1 pName dcbName pNewname dcbNewname 0 0 0 DeleteFile Procedural interface: DeleteFile (dHandle): dError This deletes a file from the system.dData2 = 0 RenameFile Procedural interface: RenameFile (pName. Procedural parameters: dHandle .

The path must contain the new directory name as the last element. empty directory for the path specified.Dword with length of the path MMURTL V1. The path must contain the directory name as the last element.dData1 dData2 = 0 = 0 CreateDirectory Procedural interface: CreateDirectory (pPath. as opposed to a full path. dcbPath . Procedural parameters: pPath . the name is appended to the path from your job control block. dcbPath. If only the existing directory name is specified. fAllFiles): dError This deletes a directory for the path specified. dcbPath): dError This creates a new. All files and sub-directories will be deleted if fAllFiles is non-zero. the directory is created in your current path. If only the new directory name is specified. Procedural parameters: pPath . specified in the Job Control Block.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 DeleteDirectory Procedural interface: DeleteDirectory (pPath.pointer to the path.0 Page 191 of 667 .pointer to the path (directory name) to create. dcbPath . or directory name to delete. This means all subdirectories above the last directory specified must already exist. as opposed to a full path.

Each sector contain 16 directory entries. 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.a pointer to a 512-byte block where the directory sector will be returned dSectNum . You specify which logical directory sector you want to read (from 0 to nTotalSectors-1). If no path is specified. The directory must exist in the path specified. Procedural parameters: pPath . to read tj sector from dcbPath . all files in this directory. and files in subdirectories will be deleted.fAllFiles . the path in your JCB will be used. all subdirectories.Dword with length of the path pSectRet .0 Page 192 of 667 . pSectRet.a Dword with the logical sector number to return MMURTL V1. dSectNum): dError Directories on the disk are one or more sectors in size.pointer to the path. or directory name. 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. This call returns one sector from a directory. dcbPath.If set to non-zero.

Another structure called the File User Block (FUB). The file system allocates file system structures dynamically when it is initialized. the open mode.0 Page 193 of 667 . is what links the user of a file to the FCB. and other things that are required to manage the user’s link to the file.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. The file handle is actually a pointer to the FUB. There is one FUB for each instance of a user with an open file. Each opened file is allocated an FCB. The FUBs contain the file pointer. File User Blocks Because this is a multitasking operating system. buffer pointers. File Control Blocks All file system implementations have some structure that is used to manage an open file. Quite a few of them use this same name. Internal File System Structures The file system has several internal structures it uses to manage the logical disks and open files. The FCB actually contains a copy of the 32-byte directory entry from the disk. Each FCB is 128 bytes. Some structures are dynamically allocated. FCB. the FCB is not directly associated with the job that opened the file. The file system starts with an initial 4Kb (one memory page) of FCBs. If the file is opened in Stream mode. MMURTL V1. you allocate a one page buffer(4Kb) for reading and writing. while others are part of the operating system data segment. This is read into the FCB when the file is opened.

the actual contiguous read will usually be limited to the size of a cluster. The beginning of the FAT represents the beginning of the usable data area on the logical disk. which is cluster size. However. Assume the directory entry you read says the first FAT entry is number 4. 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. When it reaches 0. the FCB is deallocated for re-use. The disk controller (IDE/MFM) can read 256 contiguous sectors in one read operation. the fifth points to the sixth. X = not your file. On most hard disks. In this example. the location of every cluster of 1 or more sectors of a file is managed in a linked list of sorts. Lets look at an example of how you use the FAT to locate all parts of file on the disk. Just 1Kb of FAT space represents 2Mb of data space on the disk if clusters are 4Kb. or it found a beginning cluster with no corresponding directory entry pointing to it. Ever get lost clusters when you ran a Chkdsk in MS-DOS? Who hasn’t? It usually means it found some clusters pointing to other clusters that don’t end. unless the piece of the FAT you need is already in memory. because of the design of the FAT file system. L = Last entry in your file.Because it is a pointer. A bad pointer could do some real damage here if you let it. fifth and sixth cluster in the data area on the disk. you can usually do it quite rapidly. MMURTL V1. The FCB keeps track of the number of FUBs for the file. they use what is called a FAT16. with each cluster having an associated entry in the FAT. A more accurate name would be cluster allocation table because its actually allocating clusters of disk space to the files.0 Page 194 of 667 . The disk is allocated in clusters. 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). A badly fragmented disk can force as many as four or five accesses to different parts of the FAT just to read one file. FAT Buffers In the FAT file system. That’s a lot of FAT (or file). it’s not allocating files. or three entries. extra checking is done to ensure it is valid in every call. This means it’s important to attempt to keep as much FAT information in memory as you can and manage it wisely. The fourth entry points to the fifth. and we’ll assume the file size is 12Kb. and the sixth is marked as the last. That’s 128Kb. you’ll assume that each FAT entry is worth 4Kb on disk. From this example you can see that every file we access will require a minimum of two disk accesses to read the file. This means each FAT entry is a 16-bit word. This linked list is contained in a table called the File Allocation Table.

Look through the FCBs to see if someone already has it open in ModeRead. Load FAT buffer if required. The list could go on and on. This is in itself is time-consuming. then write the data. If opened in block mode. you issue the write command to the device driver to write out the sectors as indicated by the user. allocate an FUB and attach it. From this. the initial filling of buffers for a word processor (and the final save usually). Both copies must be updated. The first cluster of the file is listed in the directory entry. Run files being executed. and can only do this if 128Kb of the file is located in contiguous sectors. The hardware is limited to 128Kb reads in one shot. Let’s look what the file system has to go through to open. If the file is being extended. File Operations Reading a file is a multipart process. The file is open. MMURTL V1. return an error ErcFileInUse. inside of a 400 Mb file. but much overhead is involved. you simply issue the read to the device driver to read the sectors into the caller’s buffer (locating them using the FAT). On many disks there are also two copies of the FAT for integrity purposes. Locate the directory entry and read it into the FCB. Directories are stored as files. 3. attach them to the chain.A great number of files are read and written sequentially. It sounds simple. Write Writing to a file is a little more complicated. and you read it into the FAT buffer if it is not already in one. vector. you simply write over the existing data while you follow the cluster chain. and only a good level-4 mind reader knows where. If opened in Block mode. 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. read and close a file: 1. and raster image files. Usually. and depends on how the file was opened. If so. A simple LRU (least recently used) algorithm ensures the most accessed FAT sectors will remain in memory. This means that you have to go to the FAT to find where the whole directory is stored so you can look through it. MMURTL’s DOS FAT file system allocates a 1Kb FAT buffer based on file access patterns. you must find unallocated clusters. Large database files are the major random users. If the sectors have already been allocated in the FAT. If it’s open in ModeWrite. 2. Read What actually happens on a read depends on whether the file is opened in Block or Stream mode. source files for compilers. you calculate which disk sector in the FAT has this entry.0 Page 195 of 667 . the user will strike next.

Close This is the easy part. Then we deallocate the FUB and FCB.0 Page 196 of 667 . MMURTL V1. You flush buffers to disk if necessary for Write Mode.

or poorly described function call can cause serious agony and hair loss in a programmer. you’ll find the documentation to be almost 30 percent of the work – and it’s a very important part of the entire system. Parameters to Calls (Args) All stack parameters are described by their names.0 Page 197 of 667 . The prefixes indicate the size and type of the data that is expected. The offset should be 0. Chapter 14.1 lists the prefixes. Table 15. If you are going to write your own operating system. 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 .. They are listed by functional group first. They are defined as far and require a 48-bit call address. I might add.4 bytes unsigned) d is the default when b or w is not present.32 bit near) MMURTL V1.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. then an alphabetical list provides details for each call and possibly an example of its use. The selector is the call gate entry in the Global Descriptor Table (GDT). “File System Service”). The call address consists of a selector and an offset. API Specification Introduction This section describes all operating system public calls.g. Table 15. 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.Size and Type Prefixes Prefix b w d ib iw id p Description Byte (1 byte unsigned) Word (2 bytes unsigned) Double Word (dword .Chapter 15. An improperly. Public Calls Public Calls are those accessible through a call gate and can be reached from "outside" programs.

Dword that contains the Count of Bytes of a filename.accurately describes the provided or expected data types. The following are examples of parameter names using prefixes and suffixes.Additional Prefixes Prefix a c s o n Description Array (of) Count (of) Size (of) Offset (into) Number (of) Examples of compound prefixes are shown in table 15.3. pdDataRet . dcbFileName .3 . It is processor (hardware) enforced. Each compound set of prefixes .a Pointer to a Dword where data is returned. or should be.and optionally suffixes .2 .or 16-bit values) are pushed as dwords automatically. Table 15. Combine these prefixes with variable or parameter names and you have a complete description of the data. Table 15.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. Stack Conventions MMURTL keeps a dword (4 Byte) aligned stack. Table1 5. and Min means Minimum. These are known as compound descriptions. 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 V1. dcbDataRetMax .0 Page 198 of 667 . be returned. Max means Maximum.Dword with the maximum count of bytes that can.2 lists these additional prefix characters.Additional descriptive prefixes may be used to help describe the parameter. Bytes and words (8. Ret means Returned.

dExitError):dError ExitJob(dExitError): dError LoadNewJob(pFileName. pcbFileNameRet): dError SetCmdLine(pCmdLine. pJCBRet): dError GetJobNum(pdJobNumRet):dError SetJobName(pJobName. dMsg1. dMsg2): dError WaitMsg(dExch. dData1. dcbFileName. The operating-system calls are described in a generic fashion that does not directly correspond to a particular programming language such as C. indicates a dword error or status code is returned from the function. dcbUserName): dError GetUserName(pUserNameRet.pqMsgRet):dError Request( pSvcName. a colon with the returned value. This information is provided for the assembly language programmers. npSend. cbData2. MMURTL high-level languages follow this convention. pRqHndlRet. pdcbCmdLineRet): dError SetPath(pPath. wSvcCode.MMURTL will only use as much of it as is defined by the call. if a value is returned by the function. dcbPath): dError GetPath(dJobNum. dMsg2): dError CheckMsg(dExch. Messaging Respond(dRqHndl. The dError. or Assembler.0 Page 199 of 667 . the most common returned value shown. dData0. dcbFileName. pData1. dMsg1. dcbFileName): dError GetExitJob(pFileNameRet. and finally. The parameter (argument) names follow the naming convention described earlier. MMURTL procedures will only get the byte from the stack while the upper three bytes of the dword are ignored. dDestExch) : dError Job Management Chain(pFileName. The function name is followed by the parameters. Many of these calls will not be used by the applications programmer. pdcbPathRet): dError SetUserName(pUserName. pPathRet. Pascal. dRespExch. Calls Grouped by Functionality The following is a list of all currently implemented operating system public calls. pData2. pdJobNumRet):dError GetpJCB(dJobNum. cbData1. pdcbUserNameRet): dError MMURTL V1. grouped by type. dData2 ) : dError MoveRequest(dRqBlkHndl. dStatRet): dError AllocExch(pdExchRet): dError DeAllocExch(dExch): dError SendMsg (dExch. pMsgsRet) : dError ISendMsg (dExch. dcbCmdLine): dError GetCmdLine(pCmdLineRet. If a parameter is a byte. dcbJobName): dError SetExitJob(pFileName.

dLBA.0 Page 200 of 667 . dExch. bFill) CompareNCS(pS1. dBytes) CopyDataR(pSource. pDestination. dStatusRet):dError DeviceInit(dDevice. pS2. pPhyAddRet): dError QueryPages(pdnPagesRet): dError High Speed Data Movement FillData(pDest. dBytes) Timer and Timing Alarm(dExchRet. dnPages): dError AliasMem(pMem. pdPhyMemRet): dError AllocOSPage(nPages. dcbAliasBytes. JobNum): dError GetPhyAdd(dJobNum. dcbFileName): dError GetSysOut(pFileNameRet. cBytes.SetSysIn(pFileName. dcbMem. dSize): returned offset or -1 CopyData(pSource. pdcbFileNameRet): dError Task Management NewTask(dJobNum. pDCBs. dESP. dPriority. pDestination. dTicks): dError KillAlarm (dAlarmExch): dError Sleep(nTicks): dError MicroDelay (dn15us): dError MMURTL V1. ppMemRet. dcbFileName): dError GetSysIn(pFileNameRet. pStatRet. dEIP): dError SpawnTask(pEntry. ppMemRet): dError DeAllocPage(pMem. dInitData): dError InitDevDr(dDevNum. LinAdd. dExch): dError UnRegisterSvc(pSvcName): dError Memory Management AllocPage(nPages. dOpNum. dPriority. pdcbFileNameRet): dError SetSysOut(pFileName. fOSCode):dError SetPriority(bPriority): dError GetTSSExch(pdExchRet): dError System Service & Device Driver Management DeviceOp(dDevice. fReplace): dError RegisterSvc(pSvcName. pStack. wCodeSeg. dSize): returned offset or -1 Compare(pS1. ppAliasRet): dError DeAliasMem(pAliasMem. pData): dError DeviceStat(dDevice. dfDebug. dJobNum. pS2. pInitData. dnBlocks. fDebug. nDevices. dStatusMax. ppMemRet): dError AllocDMAPage(nPages.

nLines. dTicks10ms): dError TTYOut (pTextOut. sdMem.0 Page 201 of 667 . & Video SetVidOwner(dJobNum): dError GetVidOwner(pdJobNumRet): dError GetNormVid(pdNormVidRet): dError SetNormVid(dNormVidAttr): dError Beep(): dError ClrScr(): dError GetXY(pdXRet. dAttrib): dError GetVidChar(dCol. Speaker.dPort) OutDWord(DWord. dPort) OutWord(Word. pbCharRet. pDataOut. dNewY): dError PutVidChars(dCol. pwCountRet): dError InByte(dPort): Byte InWord(dPort): Word InDWord(dPort): DWord InWords(dPort. nCols. pdYRet): dError SetXY(dNewX. dAttr): dError ScrollVid(dULCol. pbAttrRet): dError PutVidAttrs(dCol. dBytes) ReadCMOS(bAddress):Byte Interrupt and Call Gate Management AddCallGate: dError AddIDTGate: dError SetIRQVector(dIRQnum.GetCMOSTime(pdTimeRet): dError GetCMOSDate(pdDateRet): dError GetTimerTick(pdTickRet): dError ISA Hardware Specific I/O DmaSetUp(dPhyMem. fdRead. dChannel. pVector) GetIRQVector (dIRQnum. dTextOut. dULline. nChars. dLine. dMode): dError GetDMACount(dChannel. dLine. dAttrib): dError MMURTL V1. pVectorRet): dError MaskIRQ (dIRQnum): dError UnMaskIQR (dIRQnum): dError EndOfIRQ (dIRQnum) Keyboard. dfWait): dError Tone(dHz. dPort) OutWords(dPort. pDataIn. dfUp): dError ReadKbd (pdKeyCodeRet. ddLine.dBytes) OutByte(Byte. nChars. pbChars.

This call doesn’t check to see if the IDT descriptor for the call is already defined. AddIDTGate ()is primarily for operating system use. Unlike most other operating MMURTL V1. or 0 if all went well.0 Page 202 of 667 . This call doesn’t check to see if the GDT descriptor for the call is already defined. exceptions. AddIDTGate() builds and adds an IDT entry for trap gates.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 . AddCallGate AddCallGate: dError AddCallGate() makes an entry in the Global Descriptor Table (GDT) for a publicly available operating-system function. but can be used by experienced systems programmers. pdLenRet. dEditAttr): dError Alphabetical Call Listing The remaining sections of this chapter describe each of the operating system calls available to programmers. Parameters: AX . AddCallGate() can only be executed by code running at supervisor level (0). AddCallGate() is primarily for operating system use. It assumes you know what you are doing and overwrites the descriptor if it is already there. Unlike most other operating system calls.Selector number for call gate in GDT (constants!) ESI . AddIDTGate AddIDTGate: dError AddIDTGate() makes an entry in the Interrupt Descriptor Table for traps. dMaxLen. pbExitChar. The selector number is checked to make sure you’re in range (40h through maximum call gate number).EditLine( pStr. It assumes you know what you are doing and overwrites one if already defined.Returns an error. the parameters are not stack-based. exception gates and interrupt gates. but can be used by experienced systems programmers. dCrntLen. and interrupts. They are passed in registers.Offset of entry point in segment of code to execute EAX .

EAX Returns Error. AliasMem AliasMem(pMem.Offset of entry point in operating system code to execute. AddIDTGate() can only be executed by code running at supervisor level (0). except it can be used asynchronously. the parameters are not stack based. the more precise the timing. each containing 0FFFFFFFF hex (-1). and don’t get placed in a waiting condition. ppAliasRet): dError MMURTL V1.Dword with the number of 10 millisecond periods to wait before sending a message the specified exchange. This must be zero (0) for task gates. and is the TSS descriptor number of the task for a task gate. dcbMem. The larger dnTicks is. then see which one gets their first by simply waiting at the exchange. you can set it to send a message to the same exchange you are expecting another message.system calls. even though it’s very close. else 0 if all went OK Alarm Alarm(dExchRet. dnTicks): dError Alarm() is public routine that will send a message to an exchange after a specified period of 10millisecond increments has elapsed. Parameters: AX .Word with Interrupt Number (00-FF) ESI . except you provide the exchange. The Selector of the call is Always 8 (operating-system code segment) for interrupt or trap. This would be for the time-out function described above. do some other processing. In fact. The message that is sent is two dwords. and then "CheckMsg()" on the exchange to see if it has gone off. Alarm() can be used for a number of things. The overhead included in entry. 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.0 Page 203 of 667 . Alarm() should not be called for extremely critical timing in applications. Alarm() is accomplished using the same timer blocks as Sleep(). This means you can set an alarm. exit and messaging code makes the alarm delay timing a little more than 10 milliseconds and is not precisely calculable. dJobNum. Parameters: dnTicks . Alarm() is similar to Sleep().Selector of gate (08 or TSS selector for task gates) CX .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 . They are passed in registers.

This procedure is used by the operating system to perform address conversions when passing data using the Request() and Respond() primitives.A pointer to the pointer you want to use to access the other application’s memory. If the caller continuously allocates pages and then deallocates them this could lead to fragmentation of the linear memory space. you will cause a fault and your application will be terminated. This is the count of 4Kb pages to allocate. AllocDMAPage AllocDMAPage(nPages. etc. Parameters: pMem . ppRet . pdPhyMemRet): dError AllocDMAPage() allocates one or more pages of Memory and adds the entry(s) to the callers page tables.0 Page 204 of 667 . Parameters: pdExchRet is a pointer to the dword where the exchange number is returned.The count of bytes pMem is pointing to.).a pointer to 4-byte area where the pointer to the memory is returned. dcbMem . No cleanup is done on the caller’s memory space. If you do. dJobNum . ppMemRet. (The address the aliased memory pointer you will use). MMURTL V1. Memory is guaranteed to be contiguous and will be within reach of the DMA hardware. Using it as a normal pointer will surely cause horrible results. Exchanges are required in order to use the operating system messaging system (SendMsg. You must not attempt to access more than this number of bytes from the other application’s space.A pointer to the memory address from the other application’s space that needs to be aliased. Even addresses of identical numeric values are not the same addresses when shared between applications. WaitMsg. This procedure allocates an exchange (message port) for the job to use.AliasMem() provides an address conversion between application addresses. ppAliasRet . pdPhyMemRet . The allocation routine uses a first-fit algorithm. This pointer is only valid when used with the DMA hardware. It will always begin on a page boundary.Dword (4 BYTES).Job number from the other application. Each application has its own linear address space. Parameters: nPages . AllocExch AllocExch(pdExchRet): dError The kernel Allocate Exchange primitive.a pointer to 4-byte area where the physical address of the memory is returned.

Parameters: None Chain Chain(pFileName. Memory is guaranteed to be contiguous and to always begin on a page boundary. Beep Beep: dError A Public routine that sounds a 250-millisecond tone at 800Hz.AllocOSPage AllocOSPage(nPages. The allocation routine uses a first fit-algorithm.a dword (4-bytes). This is the count of 4Kb pages to allocate. dExitError): dError Chain() is called by applications and services to replace themselves with another application (as specified by the filename). Parameters: nPages . Chain()terminates all tasks belonging to the calling job and frees system resources that will not be used in the new job. dcbFileName. or similar errors. Parameters: nPages . ErcBadRunFile. and running the new MMURTL V1. If the chain is too far along.0 Page 205 of 667 . may be returned to the application and the chain operation will be canceled. The allocation routine uses a first fit algorithm. No cleanup is done on the caller’s memory space. 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. Memory is guaranteed to be contiguous and will always begin on a page boundary. This is the count of 4Kb pages to allocate. AllocPage AllocPage(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.a pointer to 4-byte area where the pointer to the memory is returned. If chain cannot open the new job or the new run file is corrupted. ppRet .a dword (4-bytes).a pointer to 4-byte area where the pointer to the memory is returned. If the caller continuously allocates pages and then deallocates them this could lead to fragmentation of the linear memory space. 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. ppRet .

this points to the name of a valid executable run file. Checkmsg() returns with an error to the caller. then the two dwords of the message can represent any two values you like. the second dword is the status code from the service. dError . IMPORTANT: The value of the first dword is an agreed-upon convention. CheckMsg CheckMsg(dExch. will be loaded and run. If it was a response from a service. The first dwords value (dMsgHi) determines if it’s a non-specific message or a response from a service. including pointers to your own memory area. the message is returned to the caller immediately.returns 0 if a message was waiting.A Dword containing the length of the run file name. The chained application may or may not use this error value. If there is no exit job specified. The operating system follows this convention with the Alarm() function by sending 0FFFFFFFFh as the first and second dwords of the message. If you allocate an exchange that is only used for messaging between two of your own tasks within the same job. dcbRunFilename .is the exchange to check for a waiting message pMsgsRet . and has been placed in qMsg. Parameters: dExch .0 Page 206 of 667 . otherwise it is a non-specific message that was sent to this exchange by SendMsg() or IsendMsg(). The caller is never placed on an exchange and the Task Ready Queue is not evaluated.application fails. If you intend to use the exchange for both messages and responses to requests. if no message 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. In other words. This call is used to check for non-specific messages and responses from services. the Job will be terminated as described in the ExitJob() call. else ErcNoMsg is returned. If a message is available. dExitError . you should use the numbering convention. MMURTL V1. Parameters: pFilename .A Dword containing an error code to place in the ErrorExit field of the JCB. if the JCB contained a valid one. pMsgsRet) : dError The kernel CheckMsg() primitive allows a Task to receive information from another task without blocking the caller. If the value is less than 80000000h (a positive-signed long in C) it is a response from a service. the ExitJob.

dBytes .Pointers to the data strings to compare.Pointers to the data strings to compare. dSize) : returned offset or -1 This is a high-speed string compare function using the Intel string instructions.Count of bytes to move. DSize . Parameters: None Compare Compare(pS1.Pointer to where you want the data moved. or it returns -1 (0xffffffff) if all bytes match (ignoring case) out to dSize. DSize – Number of bytes to compare in pS1 and pS2. CopyDataR CopyDataR(pSource. pS2.Pointer to the data you want moved. It may or may not be the one you are viewing (active screen). dBytes) This is a high-speed string move function using the Intel string move intrinsic instructions.ClrScr ClrScr() This clears the virtual screen for the current Job. Parameters: pSource . This version is not case sensitive(NCS). pS2. CompareNCS CompareNCS(pS1. 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.Number of bytes to compare in pS1 and pS2. pDestination.0 Page 207 of 667 . dBytes) MMURTL V1. dSize) : returned offset or -1 This is a high-speed string compare function using the Intel string instructions. This version is ASCII case sensitive. pS1 and pS2 . It returns the offset of the first byte that doesn’t match between the pS1 and pS2. pDestination. Data is always moved as dwords if possible. pS1 and pS2 . CopyData CopyData(pSource. pDestination .

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

The size of the initializing data and it’s contents are device-specific and are defined with the documentation for the specific device driver. out of over 4 billion.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) MMURTL V1. Most drivers will implement dOpNums 2 and 3 (Read and Write). The dOpNum parameter tells the driver which operation is to be performed.Pointer to device-specific data for initialization. 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. This is documented for each device driver. Disk devices (DASD .0 Page 209 of 667 . dOpNum. These reserved numbers correspond to standard device operations such as read. DeviceInit DeviceInit(dDevice. will be deallocated.Direct Access Storage Devices) will usually implement the first eight or more. dnPages . parity.Total number of bytes in the initialization data. for previously allocated pages of memory. An example of initialization would be a Comms port. pInitData.Dword indicating device number pInitData .Dword with the count of pages to deallocate. Each device driver documents all device operation numbers it supports.Pointer which should contain a valid memory address. Parameters: dDevice . and so on. but as many pages as can be. or to reset them after a catastrophe. All drivers must support dOpNum 0 (Null operation). The first 256 operation numbers. It is used to verify the driver is installed. are predefined or reserved. rounded to a page boundary.the device number dOpNum . dLBA. dnBlocks. DeviceOp DeviceOp(dDevice. verify. for baud rate. only that number will be deallocated. dInitData) : dError Some devices may require a call to initialize them before use. If dnPages is greater than the existing span of pages allocated at this address an error is returned. If fewer pages are specified. Parameters: dDevice . write. dInitData . and format.Parameters: pMem .

DMASetUp DMASetUp(dPhyMem. fdRead.Number of continguous blocks for the operation specified. pStatRet.Pointer to buffer where status will be returned dStatusMax . Channels 5. Not all devices will return status on demand. 3.Pointer to dword where the number of bytes of status returned to is reported. sdMem . 6. COMMS). 2. dStatusRet):dError The DeviceStat() function returns device-specific status to the caller if needed. Standard Block size for disk devices is 512 bytes. or can’t. Parameters: dPhyMem . In cases where the driver doesn’t. for specified operation DeviceStat DeviceStat(dDevice. The status information will be in record or structure format that is defined by the specific device driver documentation. dnBlocks . For sequential devices this will probably be the number of bytes (e. pdStatusRet . or return buffer for reads. you must specify the length in bytes using physical memory address. MMURTL V1. which controls DMA through the DREQ/DACK lines. to actually control the data move. and though they move words. Device drivers can call the GetPhyAdd() routine to convert linear addresses to physical memory values are 0. ErcNoStatus will be returned. dStatusMax. Parameters: dDevice .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. then setup the device.. pData . sdMem. See the specific device driver documentation. dChannel. dMode): dError This is a routine used by device drivers to set up a DMA channel for a device read or write.This is the maximum number of bytes of status you will accept. and 7. 1. Block size is defined by the driver. 6 and 7 are for 16-bit devices. Typical use would be for a disk or comms device in which the driver would setup DMA for the move.Device number to status pStatBuf .0 Page 210 of 667 . 5.number of bytes to move dChannel . return status. For sequential devices this parameter will usually be ignored.Pointer to data.g.

0 is Demand Mode . pdLenRet. Any other key causes EditLine() to exit. 2 is Block mode. End of Interrupt commands are sent to the proper PICU(s) for the caller.the current length of the string (80 Max.a pointer to the character string to be edited. pbExitChar. see the call description for SetIRQVector(). dCrntLen .0 Page 211 of 667 . 1 is Single Cycle mode (Disk I/O uses single cycle).). Parameters: dIRQnum . For a description of hardware IRQs. etc. The display and keyboard are handled entirely inside EditLine(). Display and keyboard input are handled inside of EditLine(). which is for operating system use only. EndOfIRQ EndOfIRQ (dIRQnum) This call is made by an interrupt service routine to signify it has completed servicing the interrupt.a pointer to a dword where the length of the string after editing will be returned. dCrntLen. returning the contents and length of the string in it’s current condition 08 (Backspace) move cursor to left replacing char with 20h which is a destructive backspace Left-Arrow . dMaxlen . PbExitCharRet . The first character of the line is displayed at the current X and Y cursor coordinates for the caller’s video screen. EditLine EditLine( pStr. The line must be on a single row of the display with no character wrap across lines.the hardware interrupt request number for the IRQ that your ISR has finished serving. and 3 is Cascade a pointer to a Byte where the exit key from the edit operation is returned. ExitJob ExitJob(dExitError): dError MMURTL V1. The following keys are recognized and handled internally for editing features. dMode .any non zero value sets DMA to read (Read is a read from memory which sends data to a device).the maximum length of the string (80 Max. Parameters: pStr . pdLenRet .fdRead . dMaxLen. dEditAttr): dError This function displays a single line of text and allows it to be edited on screen.editing attribute.).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.). dEditAttr .

This field is not directly used or interpreted by the operating system. bFill) This is used to fill a block of memory with a repetitive byte value such as zeroing a block of memory. Applications can exit and replace themselves with another program of their choosing. before calling ExitJob(). A Command Line Interpreter. FillData FillData(pDest. but not limited to.this is a dword that indicates the completion state of the application. the video and keyboard are reassigned to the OS Monitor program. If this happens while video and keyboard are assigned to this job. This is the normal method to terminate your application program. including. or act on this value. MMURTL V1. The operating system does not interpret. specifying itself so it will run again when your program is finished. or the byte value to fill it with. Link Blocks. ExitJob() attempts to load the ExitJob() Run File as specified in the SetExitJob() call. GetCmdLine GetCmdLine(pCmdLineRet. This uses the Intel string intrinsic instructions. A highlevel language’s exit function would call ExitJob().0 Page 212 of 667 . in any way except to place it in the Job Control Block. the JCB.ExitJob() is called by applications and services to terminate all tasks belonging to the job. and specifying the Run filename. The CmdLine is preserved across ExitJobs. and to free system resources. and while Chaining to another application. and communications channels. BFill . Parameters: PDest . High-level language libraries may use this call to retrieve the command line and separate or expand parameters (arguments) for the applications main function. After freeing certain resources for the job. Parameters: dExitError . If no ExitJob is specified. Exchanges. 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. cBytes. Cbytes . This call retrieves the Command Line from the JCB. The complimentary call SetCmdLine() is provided to set the Command Line. TSSs. ExitJob() errors out with ErcNoExitJob and frees up all resources that were associated with that job. It is kept in the JCB. which runs your program from a command line would normally call SetExitJob().is the size of the block. by calling SetExitJob().is a pointer to the block of memory to fill.

pwCountRet): dError A routine used by device drivers to get the count of bytes for 8-bit channels. GetCMOSDate GetCMOSDate(pdDateRet): dError This retrieves the date from the on-board CMOS battery backed up clock.A pointer to a dword where the time is returned in the previously described format. Next byte is the hours (BCD 24 hour). Next byte is the minutes (BCD).A pointer to a dword where the date is returned in the previously described format. The time is returned from the CMOS clock as a dword as: Low order byte is the seconds (BCD).is a pointer to a dword where the length of the command line returned will be stored.points to an array of bytes where the command line string will be returned. Parameters: pdTimeRet . GetCMOSTime GetCMOSTime(pdTimeRet) : dError This retrieves the time from the on-board CMOS battery backed up clock. MMURTL V1. This array must be large enough to hold the largest CmdLine (79 characters). Next byte is the Day (BCD 1-31). Parameters: pdTimeRet . left in the Count register for a specific DMA channel.0 Page 213 of 667 . or words for 16-bit DMA channels. 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). High order byte is 0. High order byte is year (BCD 0-99). Next byte is the Month (BCD 1-12). pdcbCmdLineRet . GetDMACount GetDMACount(dChannel.Parameters: pabCmdLineRet .

legal values are 0. Calling SetExitJob() with a zero length value clears the exit job file name from the JCB. The complimentary call SetExitJob() is provided. 2. 1. 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. the count returned is always the exact value found in the count register for that channel.0 Page 214 of 667 . This array must be large enough to hold the largest ExitJob filename (79 characters). 15 indicates 16 words or bytes.Parameters: dChannel . GetExitJob GetExitJob(pabExitJobRet. 5. GetJobNum GetJobNum(pdJobNumRet):dError MMURTL V1. pdcbExitJobRet): dError Each Job Control Block has a field to store the ExitJob filename. If you program a 16bit DMA channel to move data from a device for 1024 words (2048 bytes) and you call GetDMACount() which returns 1 to a pointer to a dword where the length of the ExitJob filename returned will be stored.points to an array of bytes where the ExitJob filename string will be returned. 3. The ExitJob remains set until it is set again.A pointer where the address of the ISR will be returned. 6 and 7 are for 16-bit devices. This field is used by the operating system when an application exits. this means all but two words were moved. Parameters: pabExitJobRet . and 7 (Note: Channels 5. Note that even though you specify bytes on 16-bit DMA channel to the DMASetUp call. GetIRQVector GetIRQVector (dIRQnum. 6.This is the value found in the DMA count register for the channel specified. pVectorRet . to see if there is another application to load in the context of this job (JCB).) pwCountRet . Zero (0) indicates one word or byte. Parameters: dIRQnum . The values in the DMA count registers are always programmed with one less than the count of the data to move. Applications may use this call to see what the ExitJob filename. pdcbExitJobRet .the hardware interrupt request number for the vector you want returned. For a description of IRQs see the call description for SetIRQVector().

Parameters: pdNormVidAttrRet .this is the job number for the path string you want. virtual or real. See SetNormVid() for more information. GetPhyAdd GetPhyAdd(dJobNum. The attribute value is always a byte but is returned as a dword for this. and some other operating system video calls.this points to a dword where the value of the normal video attribute will be returned. pPathRet. pdcbPathRet . This call allows applications and the file system to retrieve the current path string. See Chapter 9. Parameters: dJobNum . MMURTL V1. This is used by device drivers for DMA operations on DSeg memory. GetNormVid GetNormVid(pdNormVidAttrRet): dError Each Job is assigned a video screen.a pointer to a dword where the job number is returned. The normal video attributes used to display characters on the screen can be different for each job. The normal video attributes are used with the ClrScr() call. The path is interpreted and used by the File System. This returns the normal video attribute for the screen of the caller. All tasks belong to a job.0 Page 215 of 667 .This returns the job number for the task that called it. This string must be long enough to contain the longest path (69 characters). pPathRet . pdcbPathRet): dError Each Job Control Block has a field to store and application’s current path. “Application Programming. pdPhyRet): dError This returns a physical address for given Linear address. dLinAdd. Parameters: pbJobNumRet . and are also used by stream output from high level language libraries.” for a table containing values for video attributes. and background colors may be changed by an application.this points to an array of bytes where the path will be returned. Refer to File System documentation. GetPath GetPath(dJobNum.this points to a dword where the length of the path name returned is stored. The default foreground color which is the color of the characters themselves.

this is a pointer that points to a pointer where the pJCB will be returned. pdcbFileNameRet): dError This returns the name of the standard output file or device from the application’s Job Control Block. pdPhyRet . This array must be at least 30 characters long. GetSysOut GetSysIn( the Linear address you want the physical address for. ppJCBRet .Parameters: dJobNum . MMURTL V1. Each job has a structure to control and track present job resources and attributes. pdcbFileNameRet): dError This returns the name of the standard output file.Pointer to an array where the name will be returned. GetSysIn GetSysIn(pFileNameRet. Parameters: pFileNameRet . Parameters: pFileNameRet .Pointer to a dword where the length of the name will be returned. pdcbFileNameRet .Pointer to an array where the name will be returned.Pointer to a dword where the length of the name will be returned.this is the job number for the Job Control Block you want a pointer to. The data in a JCB is read-only to applications and system services. Parameters: dJobNum . ppJCBRet): dError This returns a pointer to the Job Control Block for the specified job number. This array must be at least 30 characters long. from the application’s Job Control Block.points to the dword where the physical address is returned GetpJCB GetpJCB(dJobNum. not a pointer it. This is the address itself. or the job number of owner of d LinAdd . pdcbFileNameRet .0 Page 216 of 667 .

GetVidChar GetVidChar(dCol. This value will roll over every 16 months or so. The name is preserved across ExitJobs. such as Alarm() or Sleep(). pbAttrRet): dError This returns a single character and its attribute byte from the caller’s video screen. 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.0 Page 217 of 667 . MMURTL V1. GetUserName GetUserName( the length of the string to store. GetTSSExch GetTSSExch(pdExchRet): dError This returns the exchange that belongs to the task as assigned by the operating system during SpawnTask() or NewTask().a pointer to a dword where the TSS Exchange will be returned. It is an unsigned dword. pbCharRet. Parameters: pdExchRet . This field is not directly used or interpreted by the operating system. and while Chaining to another application. dcbUsername .points to an array of bytes where the user name will be returned. Parameters: pabUsernameRet . When MMURTL is first booted it begins counting 10-millisecond intervals forever. The complimentary call SetUserName() is provided to set the user name. Parameters: pdTickRet . pdcbUserNameRet): dError The Job Control Block for an application has a field to store the current user’s name of an application.A pointer to a dword where the current tick is returned. ddLine. This exchange is used by some direct calls while blocking a thread on entry to non-reentrant portions of the operating system.GetTimerTick GetTimerTick( pdTickRet) : dError This returns the operating system tick counter value. This call retrieves the user name from the JCB.

Parameters: MMURTL V1. GetVidOwner GetVidOwner(pdJobNumRet): dError This returns the job number for the owner of the real video screen. Parameters: PdXRet .Parameters: dCol . of the character you want returned. InByte InByte(dPort) : Byte This reads a single byte from a port address and returns it from the function. pbAttrRet .is a pointer where you want the current horizontal cursor position returned.0 Page 218 of 667 .pointer to a dword where the current video owner will be returned. of the character you want returned. the one you are viewing. or Y position. pbCharRet . or X position. dLine .a pointer to a byte where the video attribute for the character is returned. pdYRet): dError This returns the current X and Y position of the cursor in your virtual video screen.The address of the a pointer where you want the current vertical cursor position returned. Parameters: pdJobNumRet .The column.The line location. and returns it from the function. PdYRet . InDWord InDWord(dPort) : DWord This reads a single dword from a port address. GetXY GetXY(pdXRet.a pointer to a byte where the character value will be returned. Parameters: DPort .

dBytes is the total count of bytes to read (WORDS * 2). This call is specifically designed for device drivers.DPort . and one DMA channel if applicable. A 64-byte DCB must be filled out for each device the driver controls before this call is made.The address of the port. nDevices. dBytes) InWords reads one or more words from a port using the Intel string read function. and can’t handle more than one active transfer at a time. This is because one controller. InWord InWord(dPort) : Word This reads a single word from a port address. If the driver is flagged as not reentrant. and the driver can handle two devices simultaneously at different hardware ports.The address of the port. pDataIn. fReplace) : dError InitDevDr() is called from a device driver after it is first loaded to let the operating system integrate it into the system. Parameters: DPort . The data is read from dPort and returned to the address pDataIn. pDCBs.The total number of bytes to be read.The address where the data string will be returned. such as a disk or SCSI controller.The address of the port. and returns it from the function. Parameters: DPort . InWords InWords(dPort. The DBCs must be contiguous in memory.0 Page 219 of 667 . MMURTL V1. PDataIn . while providing the three standard entry points. When a driver controls more than one device it must provide the device control blocks for each device. If this is not the case. usually handles multiple devices through a single set of hardware ports. This number is the number of words you want to read from the port times two. After the Device driver has been loaded it should allocate all system resources it needs to operate and control it’s devices. DBytes . then it may be advantageous to make it two separate drivers. then all devices controlled by the driver will be locked out when the driver is busy. InitDevDr InitDevDr(dDevNum.

dMsgLo . This procedure should only be used by device drivers and interrupt service routine. this is the pointer to the first in an array of DCBs for the devices. It is the responsibility of the caller to set them if desired. Parameters: dDevNum .a dword (4 bytes) containing the exchange to where the message should be sent. If the driver controls more than one device. or CheckMsg() returns these to your intended receiver. fReplace . dMsgLo will be in the lowest array index (msg[0]).If true. The message is two double words that must be pushed onto the stack independently. nDevices . This does not mean that the existing driver will be replaced in memory. Parameters: dExch . this is the first number of the devices.” for more detailed information on writing device drivers. This procedure provides access to the operating system to allow an interrupt procedure to send information to another task via an exchange. If a task is waiting at the exchange. it only means the new driver will be called when the device is accessed. dMsgLo): dError This is the Interrupt Send primitive. the driver will be substituted for the existing driver functions already in place. IMPORTANT: Interrupts are cleared on entry in ISendMsg() and will not be set on exit. It must equal the number of contiguous DCBs that the driver has filled out before the InitDevDr() call is made. Interrupt tasks can use ISendMsg ()to send single or multiple messages to exchanges during their execution. dMsgHi. pDCBs . It will get a chance to run the next time the RdyQ is evaluated by the Kernel. This means the devices are numbered consecutively. the third at pDCBs + 128. unsigned long msg[2] in C. MMURTL V1.This is the device number that the driver is controlling. ISendMsg ISendMsg (dExch.This is a pointer to the DCB for the device.See Chapter 10.0 Page 220 of 667 . When WaitMsg(). This means the second DCB would be located at pDCBs + 64. If more than one device is controlled. dMsgHi .This is the number of devices that the driver controls. and dMsgHi will be in the highest memory address (msg[1]). A driver must specify and control at least as many devices as the original driver handled. This is the same as SendMsg() except no task switch is performed and interrupts remain cleared.the lower dword of the message. the message is associated with that task and it is moved to the Ready Queue. “System Programming.the upper dword of the message. etc. it will be into an array of dwords.

Parameters: dAlarmExch . This allocates all the system resources required for the new job including a Job Control Block. pdJobNumRet) : dError This loads and executes a run file. Parameters: pFilename .is the exchange you specified in a previous Alarm() operating system call. the CPU will not be interrupted by the particular piece of hardware even if interrupts are enabled. When an IRQ is masked in hardware. nothing will stop it and you should expect the Alarm() message at the exchange. Virtual Video. For instance. and the memory required to run code. stack. pdJobNumRet .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. and data pages. dcbFileName.pointer to a dword where the new job number will be returned. MaskIRQ MaskIRQ (dIRQnum) : dError This masks the hardware Interrupt Request specified by dIRQnum.0 Page 221 of 667 . For a description of IRQs see the call description for SetIRQVector(). Page Descriptor. initial Task State Segment.pointer to the filename to run.length of the run filename. and you didn’t need it anymore. If the alarm is already queued through the kernel. dcbFileName . 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. All alarms set to fire off to the exchange you specify are killed. LoadNewJob LoadNewJob(pFileName. Parameters: dIRQnum . MicroDelay MicroDelay(dn15us) : dError MMURTL V1.the hardware interrupt request number for the IRQ you want to mask. you would call KillAlarm().

DestExch) : dError The kernel primitive MoveRequest() allows a system 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 data pointers in the outstanding request block have been aliased for the service that the request was destined for.0 Page 222 of 667 . It would move it to a second exchange until it can honor the request. dPriority. it is made to run. and you call this often enough. MoveRequest MoveRequest(dRqBlkHndl. else it’s placed on the Ready Queue in priority order. but application speed or appearance may be affected if the priority of your task is high enough. so the time could be much longer. If the priority of this new task is greater then the running task. Parameters: dRqBlkHndl . but instead is actually consuming CPU time for this interval. dfDebug. dESP. Parameters: dn15us . Most applications will use SpawnTask() instead of NewTask() because it’s easier to use. although longer periods will work. The recommended maximum length is 20-milliseconds. then fills in the TSS from the parameters to this call and schedules it for execution. The timing is tied directly to the 15us RAM refresh hardware timer. It will delay further execution of your task by the number of 15-microsecond intervals you specify. MicroDelay guarantees the time to be no less than n-15us where n is the number of 15-microseconds you specify. An example of use is when a system receives a request it can’t answer immediately.A dword containing the number of 15-microsecond intervals to delay your task. NewTask NewTask(dJobNum.handle of the Request Block to forward DestExch . Interrupt latency of the system is not affected for greater delay values. precise. delays that may be required when interfacing with device hardware. but a task switch will probably not occur during the delay even though this is possible. Interrupts are not disabled. The operating system uses this to create the initial task for a newly loaded job. MMURTL V1.MicroDelay is used for very where the Request is to be sent. You should be aware that your task does not go into a wait state. dCodeSeg. dExch. dEIP) : dError This allocates a new Task State Segment (TSS).

dPort) This writes a single byte to a port address. DPort . pDataOut.The address of the port. DPort .The byte value to write to the port.The word value to write to the port.Which code segment.Initial instruction pointer (offset in CSeg) OutByte OutByte(Byte. Parameters: Word .The dword value to write to the port. Parameters: Byte .Job number the new task belongs to (JCB) CodeSeg . dBytes) MMURTL V1.The address of the port. OutWord OutWord(Word. Exch . Paramters: Dword .Stack pointer (offset in DSeg) EIP .Exchange for TSS. No error is returned.Non-zero if you want the enter the debugger immediately upon execution.0 Page 223 of 667 .Parameters: JobNum . OS=8 or User=18 Priority . dPort) This writes a single word from a port address and returns it from the function.The address of the port.0-31 Primary Application tasks use 25 fDebug . dPort) This writes a single dword from a port address and returns it from the function. DPort . ESP . OutWords OutWords(dPort. OutDWord OutDWord(DWord.

DBytes . PdataOut . dAttrib): dError This places characters with the specified attribute directly on the caller's video screen.number of characters pbChars is pointing to. Parameters: dCol . dAttr . dLine. The “Basic Video” section in Chapter 9 describes all of the attribute values.column to start on (0-79) dLine . “Application Programming.color/attribute to use during display.line (0-24) pbChars . PutVidChars PutVidChars(dCol. if not displayed. dLine.” describes all of the attribute values.The count of bytes to write to the port address.column to start on (0-79) dLine . nChars.0 Page 224 of 667 .A pointer to one or more dwords to send out the port. or virtual screen. It is independent of the current video stream which means X and Y cursor coordinates are not affected. Parameters: DPort .number of characters pbChars is pointing to dAtrib .The address of the port.line (0-24) dnChars .Dword with number of characters to apply the attribute to. MMURTL V1.This uses the processor string intrinsic function OUTSW to write the words pointed to by pDataOut to dPort. dnChars. This is done independently of the current video stream which means X and Y cursor coordinates are not affected. PutVidAttrs PutVidAttrs(dCol. Parameters: dCol . You specify the number of bytes total (Words * 2).pointer to the text to be displayed nChars . dAttr): dError This applies the video attribute specified to the characters at the column and line specified on the video screen. pbChars. This is the number of Words * 2. The Basic Video section in Chapter 9.

0 Page 225 of 667 . If no key was available. Parameters: pdKeyCodeRet . This is specific to the ISA/EISA PC architecture. Parameters: pdnPagesRet . fWait) : dError ReadKbd() is provided by the keyboard system service as the easy way for an application to read keystrokes.If true (non-zero). RegisterSvc RegisterSvc(pSvcName. Parameters: BAddress . error ErcNoKeyAvailable will be returned. the call will wait until a key is available before returning. ReadCMOS ReadCMOS(bAddress):Byte This reads a single byte value at the address specified in the CMOS RAM and returns it from the function. Access to all other functions of the keyboard service are only available through the Request and Wait primitives.A pointer to a dword where the keycode will be returned. This call blocks your task until completion. dExch) : dError MMURTL V1. 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.QueryPages QueryPages(pdnPagesRet): dError This returns the number of free physical memory pages left in the entire operating system managed memory space. If fWait is true (non-zero). Each page of memory is 4096 byte.The address of the byte you want from 0 to MaxCMOSSize ReadKbd ReadKbd (pdKeyCodeRet. fWait .a pointer to a dword where the current count of free pages will be returned. If fWait is false (0). the call will return the keycode to pdKeyCodeRet and return with no error.

The service must be registered with the operating system. if you made a Request() to QUEUEMGR and no service named QUEUEMGR was installed. Examples: "KEYBOARD" and "FILESYS " dExch . This will identify a system service name with a particular exchange. dData1.The exchange you want the response message to be sent to upon completion of the request. With all kernel primitives. dRespExch . dData2 ): dError This is the operating system Request primitive.This procedure registers a message-based system service with the operating system. Parameters: pSvcName . the value returned from the Request() call itself. dError. cbData2. and padded with spaces (20h). Service name examples are ’QUEUEMGR’ or ’FAXIN ’.A pointer to an 8-Byte left justified. Values for the Service Code are documented by each service. otherwise an operating system status code will be returned. dData0. pRqHndlRet. wSvcCode . Zero will be returned if the Request() primitive was properly routed to the service. Parameters: pSvcName . Function Codes 0. dRespExch.a pointer to an 8-byte string. 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. pData2.0 Page 226 of 667 .The service code is a 16-bit unsigned word that identifies the action the service will perform for you. An error from the service is returned to your exchange (dRespExch) in the response message. a dError indicating this problem would be returned (ErcNoSuchService).the exchange to be associated with the service name. 65534 (0FFFEh). Request Request( pSvcName. is an indication of whether or not the Request() primitive functioned normally. wSvcCode. if forwarded across a network. space padded. or on what machine the request is being serviced. The string must be left justified. This error is not the error returned by the service. service name. cbData1. See Respond(). MMURTL V1. For example. Service names are case sensitive. It must be installed or included in the operating system at build time. and 65535 (0FFFFh) are always reserved by the operating system. npSend. It allows a caller to send a "request for services" to a message-based system service. pData1.

pRqHndlRet - a pointer to a dword that will hold the request handle that the request call will fill in. 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. npSend - a number (0, 1 or 2) that indicates how many of the two pData pointers, 1 and 2, are sending data to the service. If pData1 and pData2 both point to data in your memory area that is going to the service, then npSend would be 2. What each service expects is documented by the service for each service code. The descriptions for each service code within a service will tell you the value of npSend. The service actually knows which pointers it’s reading or writing. 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. Always assume it will be network-routed. pData1 - a pointer to your memory area that the service can access. This may be data sent to the service, or a memory area where data will be returned from the service. Use of pData1 and pData2 are documented by the system service for each service code it handles. cbData1 - the size of the memory area in pData1 being sent to, or received from the service. This must be 0 if pData1 is not used. pData2 - same as pData1. This allows a service to access a second memory area in your space if needed. cbData2 - the size of the data being sent to the service. This must be 0 if pData2 is not used. dData0, dData1, dData2 - dData0, 1 and 2 are dwords used to pass data to a service. These are strictly for data and cannot contain pointers to data. The reason for this is that the operating system does not provide alias memory addresses to the service for them. The use of dData0, 1 and 2 are defined by each service code handled by a system service. Respond Respond(dRqHndl, dStatRet): dError Respond() is used by message-based system services to respond to the caller that made a request to it. A service must respond to all requests using Respond(). Do not use SendMsg or ISendMsg. With all kernel primitives, dError, the value returned from the Respond() call itself, is an indication of whether or not the respond primitive functioned normally. Zero will be returned if all was well, otherwise an operating-system status code will be returned. Parameters: dRqHndl - This is the Request handle that was received at WaitMsg or CheckMsg() by the service. It identifies a Request Block resource that was allocated by the operating system. dStatRet - This is the status or error code that will be placed in the second dword, dMsgLo, of the message sent back to the requester of the service. This is the error/status that the service passes back to you to let you know it is handled your request properly.


Page 227 of 667

ScrollVid ScrollVid(dULCol,dULline,dnCols,dnLines, dfUp) This scrolls the described square area on the screen either up or down dnLines. If dfUp is nonzero the scroll will be up. The line(s) left blank is filled with character 20h and the normal attribute stored in the JCB. Parameters: DULCol - The upper left column to start on (0-79) DULLine - The upper left line (0-24) DnCols - The number of columns to be scrolled. DnLines - The count of lines to be scrolled. DfUp - This non-zero to cause the scroll to be up instead of down. If you want to scroll the entire screen up one line, the parameters would be ScrollVid(0,0,80,25,1). In this case the top line is lost, and the bottom line would be blanked. SendMsg SendMsg (dExch, dMsgHi, dMsgLo): dError SendMsg() allows a task to send information to another task. The two-dword message is placed on the specified exchange. If there was a task waiting at that exchange, the message is associated, linked with that task, and it is moved to the Ready queue. Users of SendMsg() should be aware that they can receive responses from services and messages at the same exchange. 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, which is the first dword pushed. If it were a Response from a service, the first dword would be less than 80000000h which is a positive 32-bit signed value. This is an agreed-upon convention, and is not enforced by the operating system. If you never make a request that indicates exchange X is the response exchange, you will not receive a response at the X exchange unless the operating system has gone into an undefined state - a polite term for crashed. 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. in C this is unsigned long msg[2]. 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 where the message should be sent. dMsgHi - The upper dword of the message. dMsgLo - The lower dword of the message.


Page 228 of 667

SetCmdLine SetCmdLine(pCmdLine, 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. This field is not directly used or interpreted by the operating system. This call stores the command line in the JCB. The complimentary call GetCmdLine() is provided to retrieve the Command Line. The CmdLine is preserved across ExitJobs, and while Chaining to another application. 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. Parameters: pCmdLine - Points to an array of bytes, the new command line string. dcbCmdLine - The length of the command line string to store. The command line field in the JCB is 79 characters maximum. Longer values will cause ErcBadJobParam to be returned. SetExitJob SetExitJob(pabExitJobRet, pdcbExitJobRet): dError Each Job Control Block has a field to store the ExitJob filename. This call sets the ExitJob string in the JCB. 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 set the name of the next job they want to execute in this JCB. This is typically used by a command line interpreter. The complimentary call GetExitJob() 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. When a job exits, and its ExitJob is zero length, the job is terminated and all resources are reclaimed by the operating system. Parameters: pExitJob - Points to an array of bytes containing the new ExitJob filename. The filename must not longer than 79 characters. dcbExitJob - A dword with the length of the string the pExitJob points to. SetIRQVector SetIRQVector(dIRQnum, pVector) The Set Interrupt Vector call places the address pVector into the interrupt descriptor table and marks it as an interrupt procedure. 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


Page 229 of 667

the interrupt occurred in a user level task, which will happen most often. Initially, all unassigned hardware interrupts are masked at the Priority Interrupt Controller Units (PICUs). After the interrupt vector is set, the caller should then call UnMaskIRQ to allow the interrupts to occur. Parameters: dIRQnum - The hardware interrupt request number for the IRQ to be set. This will be 0-7 for interrupts on 8259 #1 and 8-15 for interrupts on 8259 #2. Table 15.4 shows the predetermined IRQ uses. Table 15.4 - Hardware IRQs IRQ 0 IRQ 1 IRQ 2 IRQ 3 IRQ 4 IRQ 5 IRQ 6 IRQ 7 IRQ 8 IRQ 9 IRQ 10 IRQ 11 IRQ 12 IRQ 13 IRQ 14 IRQ 15 8254 Timer Keyboard (8042) Cascade from PICU2 (handled internally) COMM 2 Serial port * COMM 1 Serial port * Line Printer 2 * Floppy disk controller * Line Printer 1 * CMOS Clock Not pre-defined Not pre-defined Not pre-defined Not pre-defined Math coprocessor * Hard disk controller * Not pre-defined

* - If installed, these should follow ISA hardware conventions. Built-in device drivers assume these values.

pVector - The 32-bit address in operating system protected address space, of the Interrupt Service Routine (ISR) that handles the hardware interrupt. SetJobName SetJobName(pJobName, dcbJobName): dError This sets the job name in the Job Control Block. This is a maximum of 13 characters. It is used only for the display of job names and serves no other function. If no job name is set by the application, The last 13 characters of the complete path for the run file that is executing is used. Parameters: pJobName - Pointer to the job name you want to store in the job control block. dcbJobName - Count of bytes in the pJobName parameter. The maximum length is 13 bytes. Longer values will be truncated. A zero length, will clear the job name from the JCB.


Page 230 of 667

SetNormVid SetNormVid(dNormVidAttr): dError Each Job is assigned a video screen either virtual or real. The normal video attributes used to display characters on the screen can be different for each job. The default foreground color, 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 also used by stream output from high level language libraries. If you SetNormVid() to Black On White, then call ClrScr(), the entire screen will become Black characters on a White background. Parameters: dNormVidAttr - This is a dword containing the value of the video attribute to become the normal or default. The attribute value is always a byte but passed in as a dword for this and some other operating system video calls. See chapter 9 for a table containing values for video attributes. SetPath SetPath(pPath, dcbPath): dError Each Job Control Block has a field to store the applications current path, a text string. The path is interpreted and used by the File System. Refer to the file System documentation). This call allows applications to set their path string in the JCB. Typically, a command line interpreter would use this call to set the current path. Parameters: pPath - This points to an array of bytes containing the new path string. dcbPath - This is a dword containing the length of the new Path for the current Job. The path is 79 characters maximum. SetPriority SetPriority(bPriority): dError This changes the priority of the task that is currently executing. Because the task that is running is the one that wants its priority changed, you can ensure it is rescheduled at the new priority by making any operating-system call that causes this task to be suspended. The Sleep() function with a value of 1 would have this effect. Parameters: bPriority - This is the new priority (0-31).


Page 231 of 667

SetSysIn SetSysIn(pFileName, dcbFileName): dError This sets the name of the standard input file stored in the job control block. The default name, if none is set by the application, is KBD. The keyboard system service bypasses this mechanism when you use the request/respond interface. If you desire to read data from a file to drive your application, you should read stream data from the file system using the procedural interface call ReadBytes(). Only ASCII data is returned from this call. If the SysIn name is set to KDB, this has the same effect as calling ReadKbd() with fWait set to true, and only receiving the low order byte which is the character code. Parameters: pFileName - A maximum of 30 characters with the file or device name. dcbFileName - The count of characters pFileName is pointing to. SetSysOut SetSysOut(pFileName, dcbFileName): dError This sets the name of the standard output file stored in the job control block. The default name, if none is set, is VID. The direct access video calls bypass this mechanism. If you desire to write data from your application to a file, you should write stream data using the file system procedural interface call, WriteBytes(). Only ASCII data is saved to the file. If the SysOut name is VID, writing to the file VID with WriteBytes() is the same as calling TTYOut(). Parameters: pFileName - A maximum of 30 characters with the file or device name. dcbFileName - The count of characters pFileName is pointing to. SetUserName

SetUserName(pUserName, dcbUserName): 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 used or interpreted by the operating system. The complimentary call GetUserName() is provided to retrieve it from the JCB. The name is preserved across ExitJobs, and Chaining to another application. Parameters: pUsername - points to a text string, which is the user’s name. dcbUsername - is the length of the string to store.


Page 232 of 667

SetVidOwner SetVidOwner(dJobNum): dError This selects which job screen will be displayed on the screen. This is used by the MMURTL Monitor and can be used by another user written program manager. Parameters: DJobNum - The job number for new owner of the active screen, which is the one to be displayed. SetXY SetXY(dNewX, dNewY): dError This positions the cursor for the caller’s screen to the X and Y position specified. This applies to the virtual screen, or the real video screen if currently assigned. Parameters: DNewX - The new horizontal cursor position column. DNewY - The new vertical cursor position row. 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. Sleep() should not be used for small critical timing in applications less than 20ms. The overhead included in entry and exit code makes the timing a little more than 10ms and is not precisely calculable. 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, which may consume valuable CPU time causing lower priority tasks to become starved. If your task is a high enough priority, a busy-wait condition may cause the CPU to appear locked up, when it’s actually doing exactly what it’s told to do. Sleep sends -1 (0FFFFFFFF hex) for both dwords of the message. Sleep is accomplished internally by setting up a timer block with the countdown value, and an exchange to send a message to when the countdown reaches zero. The timer interrupt sends the message, and clears the block when it reaches zero. The default exchange in the TSS is used so the caller doesn’t need to provide a separate exchange. Parameters: dnTicks - A dword with the number of 10-millisecond periods to delay execution of the task, or make it sleep.


Page 233 of 667

SpawnTask SpawnTask(pEntry, dPriority, fDebug, pStack, fOSCode):dError This creates a new task, or thread of execution with the instruction address pEntry. This allocates a new Task state segment (TSS), then fills in the TSS from the parameters you passed. Many of the TSS fields will be inherited from the Task that called this function. If the priority of this new task is greater then the running task, it is made to run; otherwise, it’s placed on the Ready queue in priority order. Parameters: pEntry - This is a pointer to the function, procedure or code section that will become an independent thread of execution from the task that spawned it. dPriority - This is a number from 0 to 31. Application programs should use priority 25. System services will generally be in the 12-24 region. Secondary tasks for device drivers will usually be lower than 12. Device drivers such as network frame handlers may even require priorities as high as 5, but testing will be required to ensure harmony. Print spoolers and other tasks that may consume a fair amount of CPU time, and may cause applications to appear sluggish, should use priorities above 25. Certain system services are higher priority, lower numbers, such as the file system and keyboard. fDebug - A non-zero value will cause the task to immediately enter the debugger upon execution. The instruction displayed in the debugger will be the first instruction that is to be executed for the task pStack - A memory area in the data segment that will become the stack for this task. 512 bytes is required for operating system operation. Add the number of bytes your task will require to this value. fOSCode - If you are spawning a task from a device driver or within the operating system code segment you should specify a non-zero value, 1 is accepted. Tone Tone(dHz, dTicks10ms): dError A Public routine that sounds a tone on the system hardware speaker of the specified frequency for the duration specified by dTicks. Each dTick10ms is 10-milliseconds. Parameters: dHz - Dword that is the frequency in Hertz, from 30 to 10000. dTicks10ms - The number of 10-millisecond increments to sound the tone. A value of 100 is one second.


Page 234 of 667

TTYOut TTYOut (pTextOut, dTextOut, 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 following characters in the stream are interpreted as follows: 0A Line Feed - The cursor, next active character placement, will be moved down one line. If this line is below the bottom of the screen, the entire screen will be scrolled up one line, and the bottom line will be blanked 0D Carriage Return - The Cursor will be moved to column zero on the current line. 08 backspace - The cursor will be moved one column to the left. If already at column 0, Backspace will have no effect. The backspace is non-destructive, no characters are changed. For character placement, if the cursor is in the last column of a line, it will be moved to the first column of the next line. 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. The cursor will then be placed in the first column on the last line. Parameters: PTextOut - A near Pointer to the text. DTextOut - The number of characters of text. DAttrib - The attribute or color you want. UnMaskIRQ UnMaskIQR (dIRQnum) : dError This unmasks the hardware interrupt request specified by dIRQnum. When an IRQ is masked in hardware, the CPU will not be interrupted by the particular piece of hardware even if interrupts are enabled. Parameters: dIRQnum - The hardware interrupt request number for the IRQ you want to unmask. For a description of IRQs see the call description for SetIRQVector(). UnRegisterSvc UnRegisterSvc(pSvcName) : dError This procedure removes the service name from the operating system name registry. The service with that name will no longer be available to callers. They will receive an error. Parameters: pSvcName - A pointer to an 8-byte string. The string must left justified, and padded with spaces (20h). Service names are case sensitive. Examples: "KEYBOARD" , "FILESYS "


Page 235 of 667

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


Page 236 of 667

Chapter 16, MMURTL Sample Software
While working with MMURTL, most of my efforts have been directed at the operating system itself. I wrote literally hundreds of little programs to test detailed aspects of MMURTL. Only in the last year or so have I made any real effort to produce even the simplest of applications or externally executable programs. One of the first was a simple command-line interpreter. It has been expanded, and it’s real purpose is to show certain job management aspects of MMURTL. However, it does work and it’s useful in its own right. It’s still missing some important pieces (at least important to me), but it’s a good start. Other test programs that may be useful to you have been included on the CD-ROM and are discussed in this chapter. Some of these programs have their source code listed and are discussed in this chapter. To see what has been included on the CD-ROM, see Appendix A, “What's On the CD-ROM.”

Command Line Interpreter (CLI)
The MMURTL Command-Line Interpreter (called CLI) is a program that accepts commands and executes them for you. The CLI screen is divided into two sections. The top line is the status line. It provides information such as date, time, CLI version, job number for this CLI, and your current path. The rest of the screen is an interactive display area that will contain your command prompt. The command prompt is a greater-than symbol (>) followed by a reverse video line. This line is where you enter commands to be executed by the CLI. The CLI contains some good examples using a wide variety of the job-management functions of the operating system. The source code to the CLI is listed in this chapter at the end of this section. The source code to the CLI is listed in this chapter and on the CD-ROM. Internal Commands The CLI has several built-in commands and can also execute external commands (load and execute .RUN files). The internal commands are: • Cls - Clear Screen • Copy - Copy a file


Page 237 of 667

• Dir - Directory listing • Debug - Enter Debugger • Del - Delete a file • Dump - Hex dump of a file • Exit - Exit and terminate CLI (return to Monitor) • Help - Display list of internal commands • Monitor - Return to Monitor (leave this CLI running) • MD - Make Directory • Path - Set file access path (New path e.g. D:\DIR\) • RD - Remove directory • Rename - Rename a file (Current name New name) • Run - Execute a run file (replacing this CLI) • Type - Type the contents of text file to the screen Each of these commands along with an example of its, use, is described in the following sections. 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. Copy (Copy a file) This makes a copy of a file with a different name in the same directory, or the same name in a different directory. If a file exists with the same name, you are prompted to confirm that you want to overwrite it.

Wildcards (pattern matching) and default names (leaving one parameter blank) are not supported in the first version of the CLI. Both names must be specified. The current path will be properly used if a full file specification is not used. Dir (Directory listing) This lists ALL the files for you current path or a path that you specify.
>Dir C:\SOURCE\ <Enter>

The listing fills a page and prompts you to press Enter for the next screen full. Pressing Esc will stop the listing. The listing is in multiple columns as:

The first-cluster entry is a hexadecimal number used for troubleshooting the file system. The TYPE is the attribute value taken directly from the MS-DOS directory structure. The values are as follows:
MMURTL V1.0 Page 238 of 667

• • • • • •

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. It means the file has been modified or newly created since the archive bit was last reset. Debug (Enter Debugger) This enters MMURTL’s built-in Debugger. To exit the Debugger press the Esc. For more information on the Debugger see chapter 12, “The Debugger.” Del (Delete a File) This deletes a single file from a disk device. Example:
>Del C:\TEST\FILE.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. A new line is displayed for each 16 bytes. The text from the line is displayed following the hexadecimal values. If the character is not ASCII text, a period is displayed instead. Dump pauses after each screen full of text.

Exit (Exit the CLI)
This exits this CLI and terminates this job. The Keyboard and video will be assigned to the monitor if this was the active job. Help (List internal commands) This opens the text file called Help.CLI (if it exists) and displays it on the screen. The file should located in the MMURTL system directory. This is an ASCII text file and you may edit it as needed. Monitor (Return to Monitor) If this is the active CLI (the one displayed), this will assign the keyboard and video back to the monitor. This CLI will still be executing.


Page 239 of 667

Unlike a single tasking operating system.) MMURTL V1. renaming. Do not confuse the term path for the MS-DOS version of the path command. listing. for example: >MD OLDSTUFF <Enter> >MD C:\SAMPLES\OLDSTUFF <Enter> The directory tree up to the point of the new directory must already exist. Path (Set file access path) This sets the file-access path for this Job. In MMURTL. The path may also be set programmatically (by a program or utility). The path you set in the CLI will remain the path for any job that is run. etc.. a job’s path is simply a prefix used by the file system to make complete file specifications from a filename. In other words. or external command that is executed for this CLI. 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. The path must end with the Backslash.MD (Make Directory) This creates an empty directory in the current path or the path specified on the command line. 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 tree will only be extend one level. The only entries allowed are the two default entries (. Each job control block maintains a context for it’s particular job. The current path is displayed on the status line in the CLI. and . Each CLI (Job) has it’s own path which is maintained by the operating system in the job control block. if you don’t specify a full file specification (including the drive). >RD OLDSTUFF <Enter> >RD C:\MMURTL\OLDSTUFF <Enter> The directory must be empty.).0 Page 240 of 667 . the file system appends your specification to the current path for this job. With any file-system operation (opening.

pausing after each page. Here’s an example: >Type C:\MMSYS\Commands.RUN). Examples: >Run C:\NewEdit\NewEdit. The Path remains the same as was set in the CLI. 3. No spaces or tabs are allowed before the command name.RUN file) This executes the named RUN file in place of this CLI (same job number).CLI must be located in the \MMSYS directory on your system disk.TXT <Enter> Run (Execute a .run <Enter> Any parameters specified after the RUN-file name are passed to the RUN file. The File COMMANDS.Rename (Rename a file) This changes the name of a file. Your current path (shown on the status line) 2. The MMSYS directory on the system disk. MMURTL looks for external commands in three places (in this order): 1. The contents of the file COMMANDS.TXT NEWNAME.0 Page 241 of 667 . Type (View a text file on the screen) This displays the contents of a text file to the screen one page at a time. The first item on the line is the command name. The file must not be open by any other job. It is a simple text file with one command per line.CLI File The file COMMANDS.Txt <Enter> >Run D:\MMDEV\Test.CLI <Enter> External Commands External commands are those not built into the New. The command format is as follows: >Rename OLDNAME. which is followed MMURTL V1. If the new filename exists you are prompted to either overwrite existing file or cancel the command.CLI Commands. These are RUN files (MMURTL executable files with the file suffix . The format of this ASCII text file is discussed below.CLI tells the CLI what RUN file to execute for a command name.

There must also be at least one space or tab between the RUN-file name and the arguments (if they exist). The CLI doesn’t recognize any Hot Keys.CLI file.End of Command. This key sequence is received and acted upon by the Monitor program. DASM D:\DASMM\DASM. You may also add parameters to the line in COMMANDS. CLI Source Listing The following is the source code for version Print C:\MSamples\Print\Print. Listing 16.cli At least one space or tab must be between the command name and the RUN file. Any parameters you specify on the command line in the CLI are passed to the run file that is executed. but other application might.1.0 Page 242 of 667 .CLI Source Code Listing Ver. 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. The Include files from the OS Source directory contain all the ANSI C prototype for the operating system functions.0 of the Command Line Interpreter. #define U32 unsigned long #define S32 long #define U16 unsigned int MMURTL V1. In this case.CLI follows: . Global hot keys are those that are pressed while the CTRL and ALT keys are held down.0.Terminates the currently displayed job.with the full file specification for the RUN file to execute. CTRL-ALT-PAGE DOWN . This key sequence is received and acted upon by the Monitor program. Additional spaces and tabs are ignored.Switches video and keyboard to next job or the . EDIT C:\MSAMPLES\EDITOR\Edit. An example of COMMANDS.Any line beginning with SEMI-COLON is a comment. the parameters you specified on the command line are appended to those you have in the CM32 C:\CM32M\CM32. CTRL-ALT-DELETE .

hlppath[60]. /* aStatLine is exactly 80 characters in length */ char aStatLine[] = "mm/dd/yy 00:00:00 ". char text[70]. char ExitChar.h" "\OSSOURCE\Status. /* /* /* /* holds system disk and system path */ Path to help file */ Path to CLI */ Path to command file */ CLI V1.0 Page 243 of 667 .h" "\OSSOURCE\MData.h" "\OSSOURCE\MJob. unsigned char Buffer[512]. unsigned char bigBuf[4096].h> #include <ctype. iLine.h> #include <string. long cbPath. long cbCmd = 0.h" "\OSSOURCE\MMemory.h" "\OSSOURCE\MTimer.h> /* Includes for OS public calls and structures */ #include #include #include #include #include #include #include #include #include "\OSSOURCE\MKernel. clipath[60]. char aPath[70].h" /******************** BEGIN DATA ********************/ /* Define the Foreground and Background colors */ #define EDVID BLACK|BGWHITE #define NORMVID WHITE|BGBLACK #define STATVID BLACK|BGCYAN long iCol. syspath[50]. char char char char sdisk.#define #define #define #define #define S16 int U8 unsigned char S8 char TRUE 1 FALSE 0 #include <stdio. /* Command line */ MMURTL V1. cmdpath[60]. char fUpdatePath.h" "\OSSOURCE\MKbd.0 Job Path: char aCmd[80].h" "\OSSOURCE\MFiles.h" "\OSSOURCE\MVid.

/* "MD". /* Messaging for Main */ unsigned long StatExch. /* "Monitor". #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 { 0 external */ 1 . date. /* "Dump". /* "Exit".unsigned long GPExch.not used */ 2 */ 3 */ 4 */ 5 */ 6 */ 7 */ 8 */ 9 */ 10 */ 11 */ 12 */ 13 */ 14 */ 15 */ 16 */ /* External Command */ long CmdNum = 0. /* }. unsigned long GPHndl. /* Param 0 is cmd name */ MMURTL V1. unsigned long GPMsg[2]. /* array of internal commands for parsing */ #define NCMDS 16 char paCmds[NCMDS+1][10] = "". /* "Cls".0 Page 244 of 667 . /* Messaging for status task */ long StatStack[256]. /* "RD". /* 1024 byte stack for stat task */ long time. /* "Path". /* "Ren". /* "Dir". /* "Run". /* "Copy". /* "Del". /* "Help". long JobNum. char *apParam[13]. /* "xxxxx". /* "Debug". /* "Type".

U8 Ext[3]. } dirent[16].long acbParam[13]. U16 StartClstr. U8 Attr. 0x0f). 16 records 32 bytes each */ struct dirstruct { U8 Name[8]. + + + + + + ((time >> 20) & 0x0f). ((time >> 4) & 0x0f). /* year */ 0x0f). /************************* BEGIN CODE ********************/ /********************************************************* This is the status task for the CLI. ((time >> 12) & 0x0f). CopyData(aPath. U16 Time. It runs as a separate task and is started each time the CLI is executed. ’ ’). } GetCMOSDate(&date). 0x0f). 30. aStatLine[0] = ’0’ + aStatLine[1] = ’0’ + aStatLine[3] = ’0’ + aStatLine[4] = ’0’ + aStatLine[6] = ’0’ + aStatLine[7] = ’0’ + GetCMOSTime(&time). #define nParamsMax 13 #define ErcBadParams 80 /* Directory Entry Records. *********************************************************/ void StatTask(void) { for(. U32 FileSize.. cbPath). U8 Rsvd[10]. (time & 0x0f). It updates the status line which has the time and path. MMURTL V1. 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). /* Day */ 0x0f). U16 Date. ((time >> 8) & 0x0f). ((time >> 16) & 0x0f).) { if (fUpdatePath) { if (cbPath > 30) cbPath = 30. FillData(&aStatLine[47]. &aStatLine[47]. /* month */ 0x0f). fUpdatePath = 0.0 Page 245 of 667 .

PutVidChars(0. break. #asm INT 03 #endasm return. case 223: pSt = "Can’t rename across directories". char st[40]. break. case 231: pSt = "Directory is full".0 Page 246 of 667 . break. case 226: pSt = "File Already Exists (duplicate name)". } /********************************************************* This simply does a software interrupt 03 (Debugger). break. STATVID). MMURTL V1. break. break. *********************************************************/ void CheckErc (unsigned long erc) { char *pSt. pSt). FillData(st. break. break. break. } printf("%s\r\n". break. break. default: sprintf(st. 0).0. case 204: pSt = "Directory doesn’t exist". case 228: pSt = "Root directory is full". pSt = st. break. case 230: pSt = "Disk is full (bummer)". 40. case 101: pSt = "Out of memory (need more for this)". case 205: pSt = "File is ReadOnly". aStatLine. break. *********************************************************/ void GoDebug(void) { . case 222: pSt = "Can’t rename across drives". break. case 203: pSt = "File doesn’t exist". case 1: pSt = "End of file". break. case 80: pSt = "Invalid parameters". erc). Sleep(100). break. case 4: pSt = "User cancelled". case 208: pSt = "File in use". break. "Error %05d on last command". 80. break. case 200: pSt = "Invalid filename (not correct format)". case 202: pSt = "The name is not a file (it’s a directory)". switch (erc) { case 0: return. /* sleep 1 second */ } /* forEVER */ } /********************************************************* This displays error code text string if listed and NON zero. case 201: pSt = "No such drive".

&iLine). buff[i] = *pb++. } buff[i+1] = 0. i++) { printf("%02x ". i. unsigned char buff[17]. ClrScr(). MMURTL V1. long cb. KeyCode.23). i<j. iLine = 2. printf("%s\r\n". erc = 0. STATVID). unsigned long addr) { U32 erc. fNew is used to determine is we do or not.0 Page 247 of 667 . ++iLine. if (buff[i] < 0x20) buff[i] = 0x2E. &buff[0]). j. if (fNew) { iCol = 0. if (cb > 15) j=16. &iLine). If we are returning from and external command we do not reset the cursor position. while (cb) { printf("%08x ".} /********************************************************* This is called to initialize the screen. for (i=0. PutVidChars(0. *********************************************************/ void InitScreen(int fNew) { SetNormVid(NORMVID). GetXY(&iCol. 80. iLine).0. } SetXY(iCol. else j = cb. aStatLine. if (iLine >= 22) { SetXY(0. if (buff[i] > 0x7F) buff[i] = 0x2E. GetXY(&iCol. return.*pb). } /********************************************* This does a hex dump to the screen of a memory area passed in by "pb" **********************************************/ U32 Dump(unsigned char *pb. addr).

SetXY(0. &fh). wait */ if ((KeyCode & 0xff) == 0x1b) { return 4. else cb=0. } erc = OpenFile(apParam[1]..1). erc. acbParam[1]. ModeRead. /* file lfa */ GetFileSize(fh.0 Page 248 of 667 . } InitScreen(TRUE). if (k-j > 512) l = 512. 512. This expects to find the filename in param[1].. addr+=j. if ((apParam[1]) && (acbParam[1])) { if (iLine >= 23) { ScrollVid(0. &dret). erc = ReadBytes (fh. } CloseFile (fh). while ((j<k) && (!erc)) { FillData(Buffer. any other key to continue. SetXY(0. 0). *********************************************************/ long DoDump(void) { unsigned long j. erc = 0. /* ReadKbd. if (!erc) { j=0. else l=k-j. } } else printf("Filename not given\r\n").23). Buffer. return erc."). dret. 1). k. fh.printf("ESC to cancel. } if (cb > 15) cb-=16. iLine =1.23. 1. &k).1.1). } return erc. j). if (erc < 2) erc = Dump(Buffer. } /********************************************************* This sets up to call the dump routine for each page. 512. j+=512.80. dret. l. ReadKbd(&KeyCode. MMURTL V1.

"). dret = 1. dret.. SetXY(0.1). lfa. 1).1). fh. SetFileLFA(fh. Buffer. erc.80. i = 1.0 Page 249 of 667 . iLine. 1. } for (j=0. &lfa). SetXY(0. j.23.1. 0. wait */ if ((KeyCode & 0xff) == 0x1b) return(4). erc = 0. any other key to continue. cbName. iLine++. &dret).23). *********************************************************/ long DoType(char *pName. /* ReadKbd. 512. } SetXY(0. } erc = OpenFile(pName. KeyCode. Buffer. iLine =1. while ((erc<2) && (dret)) { GetFileLFA(fh. while ((Buffer[i-1] != 0x0A) && (i < dret)) { i++. NORMVID).. erc = ReadBytes (fh. if (iLine >= 22) { SetXY(0. if (!erc) { FillData(Buffer. printf("ESC to cancel. lfa+i). ReadKbd(&KeyCode.iLine). &fh). } if (dret) { PutVidChars(0. InitScreen(TRUE). j++) { if ((Buffer[j] == 0x09) || (Buffer[j] == 0x0d) || (Buffer[j] == 0x0a)) Buffer[j] = 0x20. 78.23). } MMURTL V1.} /********************************************************* This types a text file to the screen. j<=i. if ((pName) && (cbName)) { if (iLine >= 23) { ScrollVid(0. long cbName) { long i. 0). i.

pDateRet. cbFrom. } /******************************************************* This converts DOS FAT date & time and converts it into strings with the format MM/DD/YY HH/MM/SS. da = date & 0x001F.da. yr = ((date & 0xFE00) >> 9) + 1980 . fhFrom. 0). 0.0 Page 250 of 667 . erc. } /******************************************************* Copy a single file with overwrite U32 cbFrom.yr). "%02d:%02d:%02d". char st[15]. bytesWant. ********************************************************/ U32 CopyFile(char *pFrom. dLFA. mi = (time & 0x07E0) >> 5. 8). &fhFrom). CloseFile (fh). THis could be made a good deal faster by allocating a large chunk of memory and using it instead of the 4K static buffer. sprintf( erc).mo. return erc. U32 cbTo) { U32 fhTo. CopyData(st. char *pDateRet) { U32 yr. CopyData(st. hr = (time & 0xF800) >> 11. "%02d-%02d-%02d". This uses a conservative 4K buffer. This is used by the directory listing. bytesLeft. char *pTimeRet.} printf("\r\nError: %d\r\n". Use block mode for speed.da. sprintf(st. ********************************************************/ void CnvrtFATTime(U16 time. char *pTo. if (!erc) { /* Check to see if it exists already */ erc = CreateFile(pTo. size. if (yr > 99) yr -=100. se = (time & 0x001F) * 2. 8). bytesGot. pTimeRet. erc = OpenFile(pFrom. U16 date. MMURTL V1. } } else printf("Filename not given\r\n"). mo = (date & 0x01E0) >> 5. cbTo.

if (!erc) { /* both files are open in block mode */ erc = GetFileSize(fhFrom. cbTo. size). } dLFA += bytesGot. dLFA = 0. ModeModify. 1). else bytesWant = bytesLeft. if (bytesGot) { erc = WriteBlock(fhTo. else bytesLeft-=bytesOut. bigBuf. &size). bytesWant. wait */ if (((KeyCode & 0xff)==’Y’) || ((KeyCode & 0xff)==’y’)) { erc = 0. bytesLeft = size. if (bytesWant & 511) /* handle last sector */ bytesWant += 512. &bytesGot). } } } CloseFile(fhFrom). Overwrite? (Y/N)\r\n"). &fhTo). if (!erc) erc = SetFileSize(fhTo. bytesWant = (bytesWant/512) * 512. } return(erc). &bytesOut). bytesGot. *********************************************************/ MMURTL V1. 0. bigBuf. erc = ReadBlock(fhFrom. } CloseFile(fhTo). while ((!erc) && (bytesLeft)) { if (bytesLeft >= 4096) bytesWant = 4096. ReadKbd(&KeyCode.if ((!erc) || (erc==ErcDupName)) { if (erc == ErcDupName) { printf("File already exists. /* ReadKbd. } } if (!erc) { erc = OpenFile(pTo. dLFA. dLFA. if (bytesLeft < bytesOut) bytesLeft = 0.0 Page 251 of 667 . } /********************************************************* Does a directory listing for param 1 or current path.

Date. SectNum).23). KeyCode. &st[24]).0 Page 252 of 667 . &dirent[0]. printf("ESC to cancel. dirent[i]. fDone = TRUE.1). "%8s %3s %8d xx/xx/xx xx/xx/xx %2x %04x\r\n".StartClstr). char st[78].1). fDone = 1. SetXY(0. any other key to continue.23. dirent[i].Name[0]) && (dirent[i]. while (!fDone) { erc = GetDirSector(apParam[1]. dirent[i]. /* ReadKbd. i<16. 512.FileSize.Attr.U32 DoDir(void) { U8 fDone. if (!erc) { for (i=0. printf("%s". } InitScreen(TRUE). erc. if (iLine >= 22) { SetXY(0."). U32 SectNum. i++) { if (!dirent[i]. iLine =1. dirent[i].Time. dirent[i]. st). i. &st[33]. ReadKbd(&KeyCode. } if ((dirent[i].. } fDone = 0.80.Name. SetXY(0. CnvrtFATTime(dirent[i].. acbParam[1].Ext. 1).Name[0]) { erc = 1.Name[0] != 0xE5)) { sprintf(st. SectNum = 0. dirent[i]. } } } } else MMURTL V1.1. iLine++. wait */ if ((KeyCode & 0xff) == 0x1b) { erc = 4.23). if (iLine >= 23) { ScrollVid(0.

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

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

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

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

cbPath = strlen(syspath). while (1) { FillData(aCmd. TTYOut (">". 79. InitScreen(TRUE). break. EDVID). iLine). TTYOut ("\r\n". &ExitChar. &iLine). SetXY(0. /* set up all params */ if ((acbParam[0]) && (apParam[0])) { /* It’s a command! */ i = 1. ’ ’). 79. strlen(clipath)). MMURTL V1. SetPath(syspath. } erc = 0. NORMVID). InitScreen(FALSE). 1. &iLine). if (ExitChar == 0x0d) ParseCmdLine().{ GetXY(&iCol. cbCmd. GetXY(&iCol. cbCmd = 0. switch (CmdNum) { case EXTCMD: /* external command */ FindRunFile(). aCmd[79] = 0. } i++. &cbCmd. EditLine(aCmd. if ((acbParam[0] == j) && (CompareNCS(apParam[0]. SetXY(0. } fUpdatePath = 1.0 Page 257 of 667 . j) == -1)) { CmdNum = i. SetExitJob(clipath. while (i <= NCMDS) { j = strlen(paCmds[i]). acbParam[0] = 0. } else { strcpy (aPath. strlen(syspath)). syspath). iLine). 2. apParam[0] = 0. NORMVID). if (iLine == 0) /* under status line */ iLine = 1. CmdNum = 0. paCmds[i].

0. acbParam[1]). ExitJob(0). break. 0. &cbPath). apParam[2]. case COPYCMD: if ((acbParam[1]) && (acbParam[2])) erc = CopyFile(apParam[1]. &GPHndl. 0. break. 0.0 Page 258 of 667 . SetVidOwner(1). break. case DUMPCMD: erc = DoDump(). break. 0. 4. &fh). 0. break. acbParam[1]. break. if (!erc) printf("Done. 0). case DBGCMD: GoDebug(). case EXITCMD: SetExitJob("". case REMDIR: erc = DeleteDir(apParam[1]. aPath. case PATHCMD: erc = SetPath(apParam[1]. break. case HELPCMD: erc = DoType(hlppath. break. else erc = ErcBadParams. GPMsg). erc = WaitMsg(GPExch. acbParam[1]). 0.break. fUpdatePath = 1. break. acbParam[2]). if (!erc) erc = DeleteFile(fh).\r\n"). case DELCMD: erc = OpenFile(apParam[1]. case RENCMD: if ((acbParam[1]) && (acbParam[2])) erc = RenameFile(apParam[1]. 0). acbParam[2]). acbParam[1]). if (!erc) erc = GetPath(JobNum. case MAKEDIR: erc = CreateDir(apParam[1]. break. else erc = ErcBadParams. case MONCMD: erc = Request("KEYBOARD". MMURTL V1. acbParam[1]. GPExch. break. strlen(hlppath)). break. 1. case CLSCMD: InitScreen(TRUE). acbParam[1]. break. case DIRCMD: erc = DoDir(). apParam[2]. 1.

default: break. The editor always wordwraps lines that extend beyond 79 columns. strlen(clipath)). Editor Screen The editor screen is divided into three sections. such as the position of the cursor in the file (column and line). acbParam[1]. The top line provides continuous status information.23. What’s On the CD-ROM. status and error display. MMURTL V1. The next 23 lines are the text editing area. case TYPECMD: erc = DoType(apParam[1]. &iLine). current filename.1).23). erc = Chain(apParam[1]. The source code is listed below and also included on the accompanying CD-ROM. SetExitJob(clipath. The editor contains a good example of extensive use of the keyboard service (translating KeyCodes).1. break. cbCmd-i).80. } GetXY(&iCol. if (iLine >= 23) { ScrollVid(0. The largest file that can be edited is 128Kb. and typing mode (Insert or Overtype). The last line is used for data RUNCMD: if (acbParam[1]) { i = 2. acbParam[1]). SetXY(0. SetCmdLine(&aCmd[i]. } } } A Simple Editor The editor included with MMURTL is a very simple in-memory editor. } CheckErc(erc). 0). Editor Commands The following four tables describe all the editor commands. } break. See Appendix A.0 Page 259 of 667 . while (aCmd[i] != ’ ’) i++.

0 Page 260 of 667 .Cursor and Screen Management Commands Keystroke Page Down Page Up Up Down Left Right ALT-B ALT-E ALT-UP Arrow ALT-Down Arrow ALT-Left Arrow ALT Right Arrow Home End SHIFT Right SHIFT Left 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) 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.1 .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 MMURTL V1.Table 16.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.2 .

Listing 16.h" "\OSSOURCE\MMemory.h" "\OSSOURCE\MJob.h" "\OSSOURCE\MData.0 Page 261 of 667 . /* 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.h> #include <string.h" "\OSSOURCE\MFiles.4 .h" EDVID NORMVID MARKVID STATVID BRITEWHITE|BGBLUE WHITE|BGBLACK WHITE|BGRED BLACK|BGCYAN #define EMPTY 99999 #define NLINESMAX 26 struct EditRecType { U8 *pBuf.Editor Source Code.h> /* Includes for OS public calls and structures */ #include #include #include #include #include #include #include #include #define #define #define #define "\OSSOURCE\MKernel. File system calls are also used in place of equivalent C library functions.Table 16. Listing 16.2 is the editor source code.h> #include <ctype.2.h" "\OSSOURCE\MTimer.h" "\OSSOURCE\MVid.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. which provides good examples of checking for file system errors.h" "\OSSOURCE\MKbd. MMURTL V1.

/* cursor.. 0. char fModified. void clearbuf(void). *pBufWork. #define nParamsMax 13 char Filename[60]. char aStat1[80]. /* Offset in buf for 1st char in line */ iBufMax. iTabNorm. iAttrNorm. unsigned char b. *pBuf2. iAttrMark.U8 U32 U32 U32 U32 U32 U32 U32 U8 U8 U32 U32 U32 U32 U32 U32 U32 U32 U32 U32 }. unsigned char filler[100].cLines-1 */ oBufInsert. /* prototype for forward usage */ /********************************************************* Displays errors if they occur for certain file opeations. /* cursor. long i. char fOvertype. switch (call) { case 1: MMURTL V1. char aStat[80].. char aCmd[80]. iRowMax. long erc. 0. oBufBound.sLine-1 */ iLine. /* For copy and move */ Line[NLINESMAX]. /* sBuf . iColMax. /* Get our command line */ long cbCmd = 0. 40. /* offset+1 of last char */ oBufMark. char *apParam[13]. char *pBuf1.0 Page 262 of 667 . Beep(). bSpace. /* offset of next char in */ oBufLast. sLine. /* Param 0 is cmd name */ long acbParam[13]. b1. fh. 0).1 */ iColMin. /* Screen coords */ iRowMin. *********************************************************/ long CheckErc(long call. fVisible. struct EditRecType EdRec. oBufLine0 /* oBufLine0 */ iCol. struct EditRecType *pEdit. if (erc) { FillData(st. long erc) { char st[40]. long cbFilename.

MMURTL V1. erc). case 5: sprintf(st. "Error %05d occured on CreateFile". "Error %05d occured on ReadBytes". case 4: sprintf(st. break. i<40. PutVidChars (40. break. int fClose) { U32 i. If fPrompt is true. } /********************************************************* Clears the status line with 80 blank chars.0 Page 263 of 667 . st. if (!st[i]) st[i] = "Error %05d occured on OpenFile". break. break. unsigned char *pBuff. st. 24. keycode. "Error %05d occured on ReadKbd". "Error %05d occured on SetFileLFA". break. } /************************************************************* Saves a file you are editing. FillData(st. "Error %05d occured on SetFileSize". erc). If fClose. pBuff = pEdit->pBuf. break. erc). } return (erc). 24. PutVidChars (0. erc). case 2: sprintf(st. case 3: sprintf(st. erc). 80. this will prompt you to save. 80. the file will be closed and the buffer will be closed. case 6: sprintf(st. NORMVID). i++) ’ ’. erc). STATVID). break. break. erc). default: sprintf(st. } for (i=0. case 7: sprintf(st. fYes. "Error %05d occured on last command". *********************************************************/ void ClearStatus(void) { char st[80]. 0). erc). 39. **************************************************************/ void SaveFile(int fPrompt.sprintf(st. "Error %05d occured on WriteBytes".

pEdit->oBufLast)). i <=pEdit->iBufMax. cbFilename = 0. if (!erc) erc = CheckErc(3. Sleep(150). 24). BLACK|BGCYAN). ReadKbd(&keycode. pEdit->fVisible = FALSE. ClearStatus(). } } /************************************************************* This prompts for a filename to open and opens it if it exists. PutVidChars (0. ". 24. 43. pBuf1. i++) if (pBuff[i] == 0x07) pBuff[i] = 0x20. SetXY(0. SetFileSize(fh. 1).. "DONE. WriteBytes (fh. **************************************************************/ void OpenAFile(char *name) MMURTL V1. } } if (fh && fClose) { CloseFile(fh). ClearStatus(). fModified = 0. &i)). If not. clearbuf(). 0)).0 Page 264 of 667 . if (((keycode & 0xff) == ’N’) || ((keycode & 0xff) == ’n’)) { fYes = 0. SAVE IT? (Y/N)". } fYes = 1. } } if (fYes) { erc = CheckErc(6. it will prompts to create. fh = 0. if (!erc) erc = CheckErc(5. pEdit->oBufLast. ClearStatus(). 10. if (fPrompt) { ClearStatus(). SetFileLFA(fh..if ((fh) && (fModified)) { if (pEdit->fVisible) { /* fix visible characters */ for (i=0. STATVID). TTYOut("This file has been modified.

cbFilename = strlen(Filename). BLACK|BGCYAN).{ U32 filesize. if (filesize < 131000) /* Buf is 131071 */ { erc = ReadBytes (fh. BLACK|BGCYAN). Beep(). 29.". &b1. if (erc > 1) erc = CheckErc(2. 1). TTYOut("Doesn’t exist. 10. SetXY(10. &fh). /* the SUN */ } else { CloseFile(fh). 0)). 60. else erc = 0. BLACK|BGCYAN). fh = 0. filesize. PutVidChars (0. OpenFile(Filename. cbFilename. ReadKbd(&keycode. TTYOut("File is too large to edit. CreateFile(Filename. 1. ModeModify. pEdit->oBufLast = dret. &filesize). } if ((b1==0x0d) && (cbFilename)) { erc = OpenFile(Filename. if (erc) { fh = 0. SetXY(0.0). ModeModify. EditLine(Filename. SetXY(50. 0. if (!name) { SetXY(0. Create?? (Y/N)". } else { b1=0x0d. cbFilename. 1). cbFilename. /* offset+1 of last char */ pBuf1[pEdit->oBufLast] = 0x0F. if (((keycode & 0xff) == ’Y’) || ((keycode & 0xff) == ’y’)) { erc = CheckErc(4. &cbFilename. cbFilename = 0. erc = 0. SetXY(50.24).24.24). &dret). 24). 26. 13).0 Page 265 of 667 . name. erc). BLACK|BGWHITE). if (!erc) erc = CheckErc(1. } } else if (erc == 203) { /* no such file */ Beep(). 24). pBuf1. keycode. dret. "Filename: ". cbFilename = 0. strncpy(Filename. 1. ReadKbd(&keycode. MMURTL V1. if (!erc) { GetFileSize(fh. &fh)).

} if (!erc) ClearStatus().0 Page 266 of 667 . } } else CheckErc(1. pBuff = pEdit->pBuf. return(nEols). iBuf points to the beginning point in the buffer to find the end of line for. even though we word wrap always. nEols = 0. ABSOLUTE means LFs were found. ClearStatus(). *pBuff. } /************************************************************ This returns the index to the the last character in a line upto a maximum of sLine-1. /* Calculate the most it could be */ MMURTL V1. unsigned char *pBuff. i.} } else { cbFilename = 0. *************************************************************/ unsigned long findEol (unsigned long iBuf) { unsigned long unsigned char iEol. iEolMax. i = 0. } /************************************************************ 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 = pEdit->pBuf. *************************************************************/ unsigned long CountEols (void) { unsigned long nEols. while (i < pEdit->oBufLine0) /* count LFs */ if (pBuff[i++] == 0x0A) nEols++. erc).

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

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

} else /*buf col*/ PutVidAttrs (pEdit->iColMin. char fOK. *pBuff. iColFinish . CopyData (pEdit->pBufWork.iColFinish. &pBuff[pEdit->oBufInsert+1]. iLn. if ((fOvertype) && (!fSpecInsert)) { pBuff[pEdit->oBufInsert] = bPutIn. } else { cb = pEdit->oBufLast . pEdit->iAttrNorm). } MMURTL V1. pEdit->oBufInsert++. pEdit->oBufLast++.0 Page 269 of 667 . iLn. >iAttrMark). fModified = 1. *************************************************************/ char putInBuf( unsigned char bPutIn. pEdit->iColMax . char fOvertype. if (pEdit->oBufInsert-1 <= pEdit->oBufBound) pEdit->oBufBound++. char fSpecInsert) { unsigned long cb. } } /************************************************************ This inserts data into the main editing buffer.pEdit->oBufInsert + 1. pEdit->iAttrNorm). if ((pEdit->oBufInsert < pEdit->iBufMax) && ((pEdit->oBufLast < pEdit->iBufMax) || ((fOvertype) && (!fSpecInsert)))) { fOK = 1. pEdit->oBufInsert++. pBuff[pEdit->oBufInsert] = bPutIn. iLn. cb). cb). pEdit->sLine. CopyData (&pBuff[pEdit->oBufInsert]. pEdit->pBufWork. if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufInsert-1 < pEdit->oBufMark) pEdit->oBufMark++. pEdit- if (iColFinish < pEdit->iColMax) PutVidAttrs (iColFinish+1. pBuff = pEdit->pBuf.PutVidAttrs (iColStart. if (pEdit->oBufLast == pEdit->oBufInsert) pEdit->oBufLast++.iColStart +1.

} MMURTL V1.1. i++) pBuff[iMk+i] = pBuffWork[iBd+1+i]. i<=iBd-iMk. } /************************************************************ This executes the MOVE command which moves a marked block to the cursor’s current location in the file.1. i<=iMk . if (pEdit->oBufMark < EMPTY) { fModified = 1. pEdit->oBufInsert = pEdit->oBufInsert . i++) /* Move mk/bd */ pBuff[pEdit->oBufInsert+i] = pBuffWork[iMk+i]. iMk = pEdit->oBufInsert.iMk. i <=iBd-iMk. *************************************************************/ void moveData (void) { unsigned long i. Beep(). } else { iBd = pEdit->oBufMark. *pBuffWork. char *pBuff. i++) pBuffWork[i] = pBuff[i].0 Page 270 of 667 . iBd = pEdit->oBufBound.pEdit->oBufInsert . pEdit->oBufMark = iMk. for (i=0. i++) /* Shift overwritten ahead */ pBuff[pEdit->oBufInsert+iBd-iMk+1+i] = pBuffWork[pEdit->oBufInsert+i]. } iBd = pEdit->oBufInsert + iBd . if (pEdit->oBufBound > pEdit->oBufMark) { pEdit->oBufBound = iBd. pBuff = pEdit->pBuf. pBuffWork = pEdit->pBufWork. pEdit->oBufInsert . if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark. } if ((pEdit->oBufInsert < iMk) || (pEdit->oBufInsert > iBd)) { for (i=0.iBd .iBd + iMk . erc = 40400.1.} } else { fOK = 0. i++) pBuff[pEdit->oBufInsert+i] = pBuffWork[iMk+i]. i <= pEdit->oBufLast. iMk. } return (fOK). for (i=0. iBd. if (pEdit->oBufInsert < iMk) { for (i=0. iMk = pEdit->oBufBound. } if (pEdit->oBufInsert > iBd) { for (i=0.

} } } else Beep(). pBuff = pEdit->pBuf. } /************************************************************ MMURTL V1. if (pEdit->oBufMark < EMPTY) { fModified = 1. } if (pEdit->oBufLast+iBd-iMk+1 < pEdit->iBufMax) { CopyData(pBuff. *pBuffWork. if (pEdit->oBufBound > pEdit->oBufMark) { pEdit->oBufBound = iBd. if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark. iMk = pEdit->oBufInsert.else { pEdit->oBufMark = iBd. if (pEdit->oBufLast >= pEdit->oBufInsert) CopyData(&pBuffWork[pEdit->oBufInsert]. char *pBuff. pEdit->oBufBound = iMk. } /************************************************************ This executes the COPY command which copies a marked block to the cursor’s current location in the file. pEdit->oBufLast = pEdit->oBufLast + iBd .0 Page 271 of 667 . *************************************************************/ void CopyIt (void) { unsigned long iMk. } else { iBd = pEdit->oBufMark. iMk = pEdit->oBufBound.iMk + 1. iBd = pEdit->oBufInsert + iBd . } else { pEdit->oBufMark = iBd. pBuffWork. &pBuff[pEdit->oBufInsert]. pBuffWork = pEdit->pBufWork. pEdit->oBufLast . pEdit->oBufLast+1).iMk + 1. pEdit->oBufInsert = pEdit->oBufInsert + iBd . iBd-iMk+1).pEdit->oBufInsert+1). iBd = pEdit->oBufBound. CopyData(&pBuffWork[iMk]. iBd. } } } else Beep(). &pBuff[pEdit->oBufInsert+iBd-iMk+1]. pEdit->oBufBound = iMk. pEdit->oBufMark = iMk.iMk.

pBuff = pEdit->pBuf. *************************************************************/ void nullMarkBound (void) { pEdit->oBufMark = EMPTY. CopyData(&pBuff[iBd+1]. *************************************************************/ void normAttr (void) { unsigned long i. *************************************************************/ void deleteData (void) { unsigned long i. pEdit->iAttrNorm). iMk. (hides it). i++) PutVidAttrs (pEdit->iColMin. char fProb. } if ((pEdit->oBufLine0 >= iMk) && (pEdit->oBufLine0 <= iBd)) fProb = TRUE. if (pEdit->oBufInsert > pEdit->oBufLast) pEdit->oBufInsert = pEdit->oBufLast. if (pEdit->oBufMark < EMPTY) { fModified = 1. i. else if ((pEdit->oBufInsert > iMk) && (pEdit->oBufInsert <= iBd)) pEdit->oBufInsert = iMk. } else { iBd = pEdit->oBufMark.iBd + iMk . else fProb = FALSE.iBd + iMk. pEdit->sLine. i <= pEdit->iRowMax. &pBuff[iMk]. pEdit->oBufLast = pEdit->oBufLast . char *pBuff. normAttr ().0 Page 272 of 667 .This executes the COPY command which copies a marked block to the cursor’s current location in the file. *pBuffWork. if (pEdit->oBufInsert > iBd) pEdit->oBufInsert = pEdit->oBufInsert . if (pEdit->oBufMark <= pEdit->oBufBound) { iMk = pEdit->oBufMark.1. pBuffWork = pEdit->pBufWork. pEdit->oBufBound = EMPTY. if (fProb) { MMURTL V1. iBd. } /************************************************************ This DELETES a selected block. } /************************************************************ This unmarks a selected block. iBd = pEdit->oBufBound. for (i = pEdit->iRowMin. pEdit->oBufLast-iBd). iMk = pEdit->oBufBound.

1. } nullMarkBound (). if ((pEdit->oBufInsert >= pEdit->oBufLine0) && (pEdit->oBufInsert < pEdit->Line[i])) { /* if bogus line. } } /************************************************************ After screen movement (such as scrolling). i.might be off screen . i = pEdit->iRowMin. while ((i <= pEdit->iRowMax) && (pEdit->oBufInsert >= pEdit->Line[i])) i++.if it is.pEdit->Line[j] + pEdit->iColMin. pEdit->iCol = pEdit->oBufInsert . j = pEdit->iLine. this finds the proper location of the cursor in relationship to the portion of the file currently displayed. i = pEdit->iRowMax+1. j. pEdit->oBufLine0 = i. if ((pEdit->Line[j+1] < EMPTY) && (pEdit->oBufInsert >= pEdit->Line[j+1])) pEdit->iLine = pEdit->iLine + 1. j = pEdit->iLine. guarantee end of good */ i = pEdit->iLine.0 Page 273 of 667 . if (pEdit->Line[i] == EMPTY) pEdit->iCol = pEdit->iColMax. } /************************************************************ This readjusts iCol & iLine to get them back in sync with oBufInsert if oBufInsert is on screen. this will adjust screen */ unsigned long i. if (pEdit->iLine < pEdit->iRowMin) pEdit->iLine = pEdit->iRowMin. if (pEdit->iLine > pEdit->iRowMax + 1) pEdit->iLine = pEdit->iRowMax. this makes it so it is! *************************************************************/ void coordCursor_oBuf (void) { unsigned long oBuf. *************************************************************/ void findCursor (void) { /* locates cursor based on oBufInsert . pEdit->iLine = i . If oBufInsert is not onscreen. MMURTL V1.i = findPrevLine (pEdit->oBufInsert).

/* if bogus line. pEdit->Line[k] = pEdit->oBufLine0. pEdit->oBufInsert = pEdit->Line[i] + pEdit->iCol . i <= pEdit->iRowMax. is not if prev = CR */ if (pEdit->oBufInsert == oBuf) /* if at EOL */ if (pEdit->pBuf[oBuf-1] == 0x0A) pEdit->oBufInsert--. i = pEdit->iLine. /* Set Line[iRowMin] to match oBufLine0 */ k = pEdit->iRowMin. /* Set all subsequent Line[s] by calling findEol for each. k. find last good line */ while ((pEdit->Line[i] == EMPTY) && (i > pEdit->iRowMin)) { pEdit-> if (pEdit->oBufInsert > pEdit->oBufLast) pEdit->oBufInsert = pEdit->oBufLast.pEdit->iColMin. i++) { if (pEdit->Line[i] < EMPTY) { j = findEol (pEdit->Line[i]). pEdit->iCol = pEdit->oBufInsert + pEdit->iColMin pEdit->Line[i]. if prev char <> CR. oBuf = pEdit->Line[i+1]. j.0 Page 274 of 667 . MMURTL V1. */ for (i = k. This also sets all of the Line array values (Line[n]) *************************************************************/ void makeOnScreen (void) { unsigned long i. /* 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). /* get to potential insert . } } /************************************************************ Adjusts oBufLine0 to make sure that oBufInsert is on the screen. if (pEdit->oBufInsert > oBuf) pEdit->oBufInsert = oBuf. } i = pEdit->iLine. if (j < pEdit->oBufLast) /* j = offset of last char of line */ pEdit->Line[i+1] = j + 1.

pEdit->oBufLine0 = pEdit->Line[j].0 Page 275 of 667 . i<=k. k = pEdit->iRowMax. not offset */ if ((!pEdit->fVisible) && (pBuff[i] == 0x0A) && (cb)) cb--. else pEdit->Line[k+1] = EMPTY. while (pEdit->oBufInsert >= pEdit->Line[k+1]) { for (i=j. j<NLINESMAX. else i = pEdit->oBufLast. iLn++) { /* i = offset in buf of last char on line */ /* cb = nchars in line */ cb = 0. j++) pEdit->Line[j] = EMPTY. /* oBufInsert on screen . *************************************************************/ void showScreen (char *pFiller) { unsigned long i. /* Make size. i++) pEdit->Line[i] = pEdit->Line[i+1]. if (oBuf < EMPTY) { if (pEdit->Line[iLn+1] < EMPTY) i = pEdit->Line[iLn+1] . iLn.1. makeOnScreen (). } } /* If the InsertPoint (your cursor position) is past the last line then do this junk to fix it */ j = pEdit->iRowMin.Line correct */ /* EOL of iRowMax */ /* i = offset of last char of line for (iLn = pEdit->iRowMin. oBuf. } MMURTL V1. char *pBuff.oBuf + 1. cb. i = findEol (pEdit->Line[k]). iLn <= pEdit->iRowMax.else for (j=i+1. } } /************************************************************ Redisplay all data on the screen. if (i < pEdit->oBufLast) */ pEdit->Line[k+1] = i + 1. cb = i . oBuf = pEdit->Line[iLn]. pBuff = pEdit->pBuf.

80. cb. pEdit->iCol = pEdit->iColMin. key. doMark (iLn). char *pBuff. char *pBuff. FillData(filler. pEdit->oBufInsert = 0. j. *pBuffWork. normAttr (). pEdit->bSpace = 0x20. i++) pEdit->Line[i] = EMPTY. fModified = 0. iLn. *************************************************************/ void Editor(char *pbExitRet) { unsigned long i. pEdit->iLine = pEdit->iRowMin. pEdit->iAttrNorm). *************************************************************/ void clearbuf (void) { unsigned long i. pBuff = pEdit->pBuf. pEdit->iAttrNorm).if ((cb) && (oBuf < EMPTY)) PutVidChars (pEdit->iColMin. pBuff+oBuf. i<NLINESMAX. for (i=0. pBuff[pEdit->oBufLast] = 0x0F. i = pEdit->iRowMin. /* TRUE = display entire screen */ char fDone. pEdit->fVisible = FALSE. Called before use and after closure.0 Page 276 of 667 . pEdit->sLine-cb. pFiller. char fSpecInsert. k. fOvertype = FALSE. It is a HUGE while loop which reads keystrokes and processes them. if (cb < pEdit->sLine) PutVidChars (pEdit->iColMin+cb. pEdit->Line[i] = 0. iLn. pEdit->oBufLast = 0. pEdit->oBufLine0 = 0. nullMarkBound(). /* TRUE = insert no matter what fOvertype is */ char fScreen. 0x20). unsigned char b. } /************************************************************ This is the main editing function. /* the SUN */ MMURTL V1. } } /************************************************************ Resets all variables for the editor and clears the buffers.

80. pBuff[pEdit->oBufLast] = 0x0F. /* we know oBufInsert on screen */ findCursor (). FillData(aStat. fScreen = TRUE. i <=pEdit->oBufLast. pBuffWork = pEdit->pBufWork. if (key & 0x3000) { switch (b) { /* ALT key is down */ case 0x42: /* ALT-B -. erc = AllocExch(&exch)."C: %02d L: %05d nChars: %05d". 3). } else pEdit->bSpace = 0x20.long exch. MMURTL V1. 80. fSpecInsert = FALSE. pEdit->iLine). if (fOvertype) CopyData("OVR". i. normAttr (). 3). fDone = FALSE. 1)). PutVidChars (0. aStat. pEdit->oBufLast). if (cbFilename) CopyData(Filename. sprintf(aStat.Beginning of Text */ case 0x62: /* ALT-b */ pEdit->oBufLine0 = 0. /* True if char should be inserted even if fOver */ /* Wait for char */ /* the SUN */ CheckErc(7. fScreen = FALSE. while (!fDone) { if (fScreen) { showScreen (filler).0 Page 277 of 667 . STATVID). } SetXY (pEdit->iCol. else CopyData("INS". pEdit->iCol. b = key & 0xff. 0. i++) if (pBuff[i] == 0x20) pBuff[i] = pEdit->bSpace. &aStat[77]. &aStat[40]. ReadKbd (&key. 0x20). cbFilename). i= CountEols() + pEdit->iLine. if (pEdit->fVisible) { pEdit->bSpace = 0x07. pBuff = pEdit->pBuf. &aStat[77]. for (i=0.

break.0 Page 278 of 667 . case 0x56: */ case 0x76: /* ALT-v */ if (pEdit->fVisible) { for (i=0. i <= pEdit->oBufLast. if (pEdit->Line[i+1] < EMPTY) { pEdit->iCol = pEdit->iColMin + pEdit->Line[i+1] pEdit->Line[i]. break. if ((pBuff[pEdit->Line[i]-1] == 0x0A) && (pEdit->iCol > pEdit->iColMin)) pEdit->iCol--.CloseFile */ case 0x63: /* ALT-c */ SaveFile(TRUE. coordCursor_oBuf (). case 0x04: /* ALT-Right Arrow . } else pEdit->iCol = pEdit->iColMin + pEdit->oBufLast . i++) if (pBuff[i] == 0x07) /* ALT-V Make nontext chars Visible/Invisible MMURTL V1.Cursor to bottom of screen */ pEdit->iLine = pEdit->iRowMin. case 0x01: /* ALT-UP Arrow . fScreen = TRUE. while ((pEdit->Line[i+1] < EMPTY) && (i < pEdit->iRowMax)) { pEdit->iLine++. pEdit->iCol = pEdit->iColMin. pEdit->iCol = pEdit->iColMin.Cursor to EOL */ i = pEdit->iLine. } pEdit->iCol = pEdit->iColMax. break. fScreen = TRUE. break. i = pEdit->iLine+1. case 0x03: /* ALT-Left Arrow . i = pEdit->iLine. TRUE).pEdit->oBufInsert = 0. break. break. case 0x43: /* ALT-C -. pEdit->iLine = pEdit->iRowMin. coordCursor_oBuf (). fScreen = TRUE.End of Text */ case 0x65: /* ALT-e */ pEdit->oBufInsert = pEdit->oBufLast.Cursor to BOL */ pEdit->iCol = pEdit->iColMin. break. case 0x45: /* ALT-E -.Cursor to top of screen */ pEdit->iLine = pEdit->iRowMin. case 0x02: /* ALT Down Arrow .pEdit->Line[i]. i = pEdit->iLine.

fScreen = TRUE.pBuff[i] = 0x20. fScreen = TRUE. FALSE). pEdit->fVisible = FALSE. i++) if (pBuff[i] == 0x20) pBuff[i] = 0x07. case 0x51: /* ALT Q . } else { for (i=0. OpenAFile(0). if (b == 0x20) b = pEdit->bSpace. } fScreen = TRUE. /* Don’t overwrite CR */ if (pBuff[pEdit->oBufInsert] == 0x0A) fSpecInsert = TRUE. pEdit->bSpace = 0x07.0 Page 279 of 667 . } break. case 0x7F: /* ALT Delete (Delete Marked Block) if (pEdit->oBufMark < EMPTY) { deleteData (). pEdit->bSpace = 0x20.SaveFile */ case 0x73: /* ALT s */ SaveFile(FALSE. default: break. fDone = TRUE. fScreen = TRUE. break. } break.Quit */ case 0x71: SaveFile(TRUE. TRUE). } } else if (key & 0x0300) { /* CTRL key is down */ */ } /* Standard editing keys including LF */ else if (((b >= 0x20) && (b <= 0x7E)) || (b == 0x0D)) { coordCursor_oBuf (). break. case 0x53: /* ALT S . if (b == 0x0D) b = 0x0A. pEdit->fVisible = TRUE. i<=pEdit->oBufLast. case 0x4F: /* ALT-O OpenFile */ case 0x6F: /* ALT-o */ if (!fh) { clearbuf(). break. MMURTL V1.

0 Page 280 of 667 . pEdit->oBufLast+1). fSpecInsert))) Beep().1. MMURTL V1. if ((pEdit->oBufMark == pEdit->oBufBound) && (pEdit->oBufMark == pEdit->oBufInsert)) nullMarkBound (). } else if (key & 0x0C00) { /* SHIFT key is down & NOT letter keys */ switch (b) { case 0x04: /* SHIFT Right */ if (pEdit->iCol < pEdit->iColMax .1. pEdit->oBufLast = pEdit->oBufLast . findCursor (). } } } if (pEdit->oBufInsert < pEdit->oBufLine0) pEdit->oBufLine0 = findPrevLine (pEdit->oBufLine0). if (pEdit->oBufInsert) { pEdit->oBufInsert = pEdit->oBufInsert . case 0x03: /* SHIFT Left */ if (pEdit->iCol > pEdit->iColMin + 5) pEdit->iCol -= 5. if (!fOvertype) { CopyData(pBuff. else if (pEdit->iCol > pEdit->iColMin) pEdit->iCol--. fOvertype. if (pEdit->oBufInsert <= pEdit->oBufBound) pEdit->oBufBound--. } } else { /* Unshifted editing keys */ switch (b) { case 0x08: /* Backspace */ if (pEdit->oBufLast) { coordCursor_oBuf (). default: break.if (!(putInBuf (b. fScreen = TRUE. CopyData(&pBuffWork[pEdit->oBufInsert+1]. pEdit->oBufLast-pEdit->oBufInsert). else if (pEdit->iCol < pEdit->iColMax) pEdit->iCol++. pBuffWork. break. pBuff[pEdit->oBufLast] = 0. if (pEdit->oBufMark < EMPTY) { if (pEdit->oBufInsert <= pEdit->oBufMark) pEdit->oBufMark--.5) pEdit->iCol += 5. break. &pBuff[pEdit->oBufInsert].

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

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

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

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

/* Set Overtype OFF */ if (argc > 1) { OpenAFile(argv[1]).cLines-1 offset of next char in offset+1 of last char */ */ */ */ */ FillData(filler. MARKVID. 0. = = = = = = = = = = = = = = = 1.h" "\OSSOURCE\MJob. 0. Listing 16. } Editor(&b). 0x20. fOvertype = FALSE. /* iColMax-iColMin+1 */ /* /* /* /* /* /* /* /* Rev Vid*/ Rev Vid Half Bright Tabs every 4th column oBufLine0 */ cursor. i<NLINESMAX. i = pEdit->iRowMin. It’s included here to demonstrate the interface to the RS-232 communications device driver.0 Page 285 of 667 . FALSE. 23. EMPTY.. 4.h" "\OSSOURCE\MKbd. 0. 0. pEdit->Line[i] = 0.h> <string. ExitJob(0). EMPTY. 80.DumbTerm source code. #include #include #include #include #include #include <stdio. 0. for (i=0. fModified = 0. 0.pEdit->iRowMin pEdit->iRowMax pEdit->sLine pEdit->bSpace pEdit->fVisible pEdit->iAttrMark pEdit->iAttrNorm pEdit->iTabNorm pEdit->oBufLine0 pEdit->iCol pEdit->iLine pEdit->oBufInsert pEdit->oBufLast pEdit->oBufMark pEdit->oBufBound SetNormVid(NORMVID).h" MMURTL V1. } DumbTerm DumbTerm is possibly the "dumbest" communications terminal program in existence. i++) pEdit->Line[i] = EMPTY.sLine-1 */ cursor. 0x20).. 0.h> "\OSSOURCE\MDevDrv. EDVID.3 . 80.h> <ctype.

%d\r\n". /* Get the 64 byte device status block which is specific to the RS-232 device driver. but should already be defaulted with standard values when driver was initialized. com. } /* set the params in the block */ com. %d\r\n".databits com. ClrScr().h" #define NORMVID BRITEWHITE|BGBLUE #define CLIVID WHITE|BGBLACK unsigned long key. Dumb Terminal Program\r\n"). /* View other params which we could set.h */ erc = DeviceStat(6. = 1. if (erc) { MMURTL V1. ExitJob(erc). &i). ClrScr(). i. /*****************************************************/ void main(void) { int erc. char fOK.Baudrate com.RBufSize). The structure is defined in commdrv.IRQNum). if (erc) { SetNormVid(CLIVID).RTimeOut).h" #include "\OSSOURCE\RS232.XBufSize). 64. com.stopbits = 9600. = 8. com.h" #include "\OSSOURCE\MVid.XTimeOut). %d\r\n".#include "\OSSOURCE\MTimer. SetNormVid(NORMVID).parity = com. lastb. unsigned char b. */ printf("IRQNum: printf("IOBase: printf("sXBuf: printf("sRBuf: printf("RTimeO: printf("XTimeO: %d\r\n". &com. %d\r\n".0 Page 286 of 667 . (MMURTL Comms Device Driver demo) \r\n"). NO_PAR. com. &com. /* Set the params we changed with a DeviceInit */ erc = DeviceInit(6. printf(" printf(" Terminally DUMB. erc). 64). com. %d\r\n". struct statRecC com. com. printf("Error on Device Stat: %d\r\n".IOBase).

erc). pData */ erc = DeviceOp(6. dnBlocks. } /* If device init went OK. erc).SetNormVid(CLIVID). dOpNum. 0.\r\n"). break. CmdOpenC. /* no wait */ if (key & 0x3000) { /* ALT key is down */ switch (toupper(b)) { case ’Q’ : /* device. SetNormVid(CLIVID). ClrScr(). */ while (fOK) { if (!ReadKbd(&key. CmdWriteB. 0)) { b = key & 0x7f. pData */ erc = DeviceOp(6. ClrScr(). if (erc) printf("WriteByteCError: %d \r\n".. erc).. } } else { /* device. &b). dLBA. printf("OpenCommC ERROR: %d \r\n". if (erc) { SetNormVid(CLIVID). 0. fOK = 1. pData */ erc = DeviceOp(6. dLBA. dOpNum. ExitJob(erc). 0. 0. } printf("Communications Port Initialized. ExitJob(erc). dnBlocks. dOpNum. ExitJob(erc). &i). dLBA. /* This is it. else { if (b == 0x0D) { MMURTL V1. CmdCloseC.0 Page 287 of 667 . dnBlocks. 0. printf("Error on Device Init: %d\r\n". 0. we open the comms port */ /* device. default: break. ClrScr(). &i).

pData */ erc = DeviceOp(6. 1. } } } Print The print program is an example of using the device-driver interface for the parallel LPT device driver. 1. Listing 16.Binary print. NORMVID). Print also converts single-line feeds to CR/LF. 3 "LPT") */ #include #include #include #include <stdio. CmdReadB.Print source code.0 Page 288 of 667 .Expand tabs to n space columns (n = 1.Suppress the Form Feed sent by the Print command \B .Display the file while printing \F .4. (Compare DumbTerm and this program). The differences handling different devices will be the values in the status record and also and commands that are specific to a driver.h> MMURTL V1.. 0. In listing 16. &b).4 or 8) \D . */ if ((lastb == 0x0D) && (b != 0x0A)) TTYOut ("\n". notice the device-driver interface is almost identical for each device. dLBA.2. if (!erc) { TTYOut (&b. dOpNum. NORMVID). /* A simple program that prints a single file directly using the Parallel Device Driver in MMURTL (Device No. 0. } } } } CmdWriteB. That’s the intention of the design. lastb = b. &b). dnBlocks. /* add a LF if it’s not there after a CR. erc = DeviceOp(6.. The Print program formats a text file and sends it to the LPT device.h> <string. It recognizes the follow command-line switches: \n .4. /* device. 0.h> <stdio.b = 0x0A. 0. Send with no translations.h> <ctype.

h" "\OSSource\MJob.#include <stdlib. cl. i < argc. NoFF = 0. ++i) /* start at arg 1 */ { ptr = argv[i]. erck. col = 0.h> #include #include #include #include #include #include #define #define #define #define "\OSSource\MDevDrv. break. char fdone. long long long long long tabstops = 4. switch(*ptr) { case ’1’ : /* Tab Translation Width */ case ’2’ : case ’4’ : case ’8’ : tabstops = *ptr .0 Page 289 of 667 . case ’F’ : /* No FF at end of file */ case ’f’ : NoFF = 1. MMURTL V1. if (*ptr == ’/’) { ptr++. 8).h" "\OSSource\Parallel. fBinary = 0. for(i=1.h" "\OSSource\MTimer. name[0] = 0. unsigned char b.0x30.h" "\OSSource\MVid. SetJobName("Printing". /*****************************************************/ void main(long argc. char name[80]. break. unsigned char *argv[]) { long erc.h" "\OSSource\MKbd. FILE *f. fDisplay = 0. struct statRecL lpt.h" FF LF CR TAB 0x0C 0x0A 0x0D 0x09 unsigned long key. *ptr. lastb. i.

we open the printer port */ /* device.\r\n". argv[i].0\r\n"). break. erc). no FF\r\n\n"). exit(1). MMURTL V1. printf("/B Binary print.. break. break. pData */ erc = DeviceOp(3. erc). } /* If device status went OK. } /* Get the 64 byte device status block which is specific to the parallel device driver.Tab stop translation value\r\n"). printf("/F no FormFeed at end of file\r\n"). if (erc) { printf("OpenLPT ERROR: %d \r\n". &i). } printf("Printing %s .0 Page 290 of 667 . 0. Version 1. dnBlocks. & ’D’ : /* Display while printing */ case ’d’ : fDisplay = 1. 64. case ’B’ : /* BINARY . printf("/D Display file while printing\r\n"). printf("Error: Source filename required\r\n"). */ erc = DeviceStat(3. printf("/1 /2 /4 /8 . if (erc) { printf("Error getting LPT Device Status: %d\r\n". name). NO translation. The structure is defined in parallel. &i). 79).No translation at all! */ case ’b’ : fBinary = 1.h We do this just to see if it’s a valid device. printf("Usage: Filename /1 /2 /4 /8 /F /D /B\r\n").. ExitJob(erc). } if (!name[0]) { /* Input file not explicitly named errors out */ printf("Print File. dLBA. exit(1). ExitJob(erc). dOpNum. CmdOpenL. } } else if(!name[0]) strncpy (name. default: printf("Invalid switch"). 0.

*/ f = fopen(name..0 Page 291 of 667 . CmdCloseLU. } else { switch (b) { /* print/translate the char */ case CR: erc = DeviceOp(3. 1. CmdWriteB. cl = fgetc(f). while ((!fdone) && (!erc)) { i++. dnBlocks. ExitJob(erc). dOpNum. &b). 0. b = (cl & 0xff). fdone = 0. /* reset */ break. &lastb). "r"). dLBA./* This is it. CmdWriteB. CmdWriteB. i = 0. 1. b = 0. 0. 0. } erc = DeviceOp(3. case TAB: do { erc = DeviceOp(3. break. name). if (!f) { /* device. case LF: if (lastb != CR) { lastb = CR. 0. 1. lastb). } else if (fBinary) { erc = DeviceOp(3.. 1. 0. 0. CmdWriteB. if (fDisplay) printf("\r\n". 1. &i). col++. &b). if (fDisplay) MMURTL V1. col = 0. printf("Can’t open: %s\r\n". if (cl == EOF) { fdone = 1. pData */ erc = DeviceOp(3. CmdWriteB. 0. lastb = b. &lastb). " "). } col = 0. erc = DeviceOp(3.

0). &i). and also allows deinstallation with proper termination and recovery of operating-system resources. “Systems Programming. 0. erc). b). "\f"). if (erc) printf("Error Writing Byte: %d\r\n". MMURTL V1. } } } } if ((!fBinary) && (!NoFF)) { erc = DeviceOp(3. Error: %d\r\n". col++. The program is similar to the example shown in chapter 10. break.printf(" "). } fclose(f). CmdCloseL. printf("Done\r\n").0 Page 292 of 667 . CmdWriteB. 0. System Service Example Installable system services are the easiest way to coordinate resource usage and provide additional functionality in MMURTL. 0. default: if (fDisplay) printf("%c". dLBA. ExitJob(erc). dnBlocks. 0. 1. } 1. The following two listings show a simple system service and a client of that service. &b). erc = DeviceOp(3. erc = 4.” It has been expanded some. CmdWriteB. /* device. pData */ erc = DeviceOp(3. } } if (i%100==0) /* every 100 chars see if they want to abort */ { erck = ReadKbd(&key. if (erc) printf("Can’t close LPT. /* no wait */ if (!erck) { if (key & 0xff == 0x1b) { fdone = 1. dOpNum. erc). } while (col % tabstops). break.

This is expanded from the sample in the System Programmer chapter to show how to properly deinstall a system service.RUN program to exercise the service.0 Page 293 of 667 . 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. keycode.h" "\OSSource\MJob.h" ErcOK ErcOpCancel ErcNoSuchSvc ErcBadSvcCode 0 4 30 32 struct RqBlkType *pRqBlk.h> "\OSSource\MKernel. Listing 16. /* get an exchange */ /* look for a kernel error */ OSError = RegisterSvc("NUMBERS ". While this program is running. /* look for a system error */ MMURTL V1. if (OSError) ExitJob(OSError). It becomes a system service while keeping it’s virtual video screen.Service. /* A pointer to a Reqeust Block */ unsigned long NextNumber = 0. run the TestSvc. /* Super Simple System Service.Simple Service Source Code. Pressing any key will terminate the program properly.h" "\OSSource\MVid. if (OSError) ExitJob(OSError). /* The number to return */ unsigned long MainExch. ErrorToUser. long *pDataRet. SetNormVid(WHITE|BGBLACK). OSError = AllocExch(&MainExch).5 . /* Where we wait for Requests */ unsigned long Message[2]. ClrScr(). MainExch). /* Used for keyboard request */ void main(void) { unsigned long OSError.C Listing Execute this from a command-line interpreter. /* The Message with the Request */ long rqHndl.

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

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

All source files are located in one directory. One of my goals was to keep the source code organized in an easy-to-understand fashion. use the stack just as a compiler would.). and how to build the operating system using the tools included on the CDROM. By including the comments. It helps me think. The caller cleans the stack in this convention.. In fact. There are also header files for the C source. but the number of variable arguments is passed in the EDI register. Quite a few of them. A brief description of each of the main source files follows. Two conventions are used throughout the operating system. and INCLUDE files for the assembler that tie publics. Organization The MMURTL source code is organized in a modular fashion. Comments In the Code You'll find out that I make a lot of comments in my source code. I can go back a year or two later and figure out what I did. There are 29 main source files (excluding header and include files). sometimes I write the comments describing a small block of code before I actually code it. Many of the lower-lever procedures use registers to pass data back and forth. and structures together. The other is a mangled version of what you'd find in most C compilers for functions with an ellipse (. I hope I’ve met this goal. the human becomes a compiler of sorts.0 Page 296 of 667 . MMURTL V1. I just expend the time up front instead of later. as you look through the code. common variables. You won’t have to go searching through hundreds of files to find a three-line function. It's not that I couldn't actually figure it out without them. Introduction to the Source Code This chapter discusses each of the important source files. Calling Conventions I bring this topic up here because. Each logical section is contained in one or more files that are either C or assembler source code. When a human writes this much assembler (and it's to be interfaced with assembler generated by a compiler). some of the things that tie all of the source files together.Chapter 17. but most of the dependent functions will be contained in the file you are viewing or reading. You may find some of the source files rather large.. with the called procedure cleaning the stack. I call it mangled because the arguments are still pushed left to right. you will see that much of the assembler looks almost like it was generated by a compiler. The most prominent and the preferred method is pushing parameters left to right. however.

ASM .ASM .This defines the Global Descriptor Table (GDT).ASM . On the other hand. MMURTL V1. Several selector entries are predefined so you can go directly into protected mode. UASM.ASM . it ends up at physical and linear address 0. You have to see what you’re doing.MOSIDT. This is also a required table for the Intel processors when they run in protected mode. It’s not very pretty. This file should be left in the order I have it in. and used by the processor. I haven’t tried playing "musical source files" either.This defines the basic Interrupt Descriptor Table (IDT).The keyboard source goes against my own rule of not combining a device driver with a system service. but it is not used by the processor. Video. Debugging is never fun when you have to do it at the assembler level. The position of this file is also critical because it is identified to.ASM . It is a table-driven disassembler used to display instructions in the debugger.Video code and data was also one of the first pieces I wrote. Debugging a debugger is even less fun. someday). The position of this file in relationship to the others is critical because the offset to the IDT must be identified to the processor.ASM was one of the first files written. undoubtedly. and it works rather well. The ordering for the remainder of the modules is not as important. MAIN.This file is the debugger disassembler. even in the earliest stages of a project.This file contains the bulk of the debugger logic. Keyboard. Debugger. This is the first file in the source. This provides the re-entrancy protection and also the proper vectoring (indirect calling) of devicedriver functions. DevDrvr. The GDT is 6K in size. MOSGDT. System services in assembly language are not fun to write or maintain. This file should also remain in it’s current order because we have hard-coded it’s address in some source modules.The device-driver interface code and data are contained in this file. In fact. The IDT is a required table for the Intel processors.This is also a data definition file that sets up the operating system’s Page Directory and first page table. This is really just a data position holder in the data segment. MPublics. Also some data is defined here.C . because the entries in the IDT are done dynamically. This ends up at address 800h physical and linear. Chapter 26 goes into detail on this files contents.ASM .ASM . MOSPDR.ASM . which means it’s data will be at the lowest address. Chapter 25 explains all the mysteries behind this table-driven piece of work.This is the first assembler file that contains code.This is also a table. Keyboard.0 Page 297 of 667 . but it’s effective and accurate. This defines the selector entries for all MMURTL public calls.ASM . This must be the second source file in the assembler template file. The operating system entry point is located here (the first OS instruction executed). so I am reluctant to begin rewriting it (Although I will. Leaving them in their current order would probably be a good idea.

ASM – Lower-level job functions and supporting code for functions in JobC.System service management code such as RegisterSvc() is in this file.This file contains the RS-232 UART driver. IntCode.C . I was surprised. It supports the 16550. It think it still works with MFM drives.ASM .This is the file that collected all the little things that you need. NumCnvrt. SVCCode.This is an MS-DOS FAT-compatible file system.ASM . address aliasing and management of physical memory can be found in this file. It provides stream access as well as block access to files. It has only the necessary calls to get the job done.The program loader and most of the job-related functions are handled in this file.C . The reasoning behind controlling the drive motors separately from the rest of the floppy electronics still has me baffled.ASM . but I don’t have any more to test it.C .The code memory allocation. It drives two channels. as well as port I/O support. HardIDE.Floppy disk device drivers are much harder to write than IDE hard disk drivers. Parallel.Floppy.ASM . when a device driver writers need DMA. MMURTL V1.ASM .along with all of the timer functions such as Sleep() . RQBCode.C .The IDE hard disk device driver was pretty easy to write. FSys.The simple parallel port driver is contained here. RS232. but can be modified for four. JobCode. Device driver writers will appreciate this. how much to move. This is one complicated driver.0 Page 298 of 667 .Request block management code is in this file. High-speed string manipulation and comparison.ASM .Interrupt Service Routine (ISR) handling code can be found here.ASM . It seems to me that hardware manufacturers could be a little more compassionate towards software people. you’re on your own.C . and the mode. If you have MFM drives.are in this file. MemCode.The interrupt service routine for the primary system timer . JobC. DMACode. You will still see remnants of MFM drive commands in here because that’s what I started with. MiscCode.In many systems. but you never know where to put. TmrCode.C . You only need to know the channel. is in this file.ASM . they must handle it themselves.This is some code used internally that converts numbers to text and viceversa. but only to enable the input buffer (which helps). if needed.C are contained in this file. This file has routines that handle DMA for you.

specify /E on the command line after the name. It provides a lot of the OS functionality. Most of them cause you to go directly into the Debugger. DASM produces the operating system RUN file. but it really isn’t part of the operating system.ATF It's truly that simple. Building MMURTL MMURTL requires the CM32 compiler (included on the CD-ROM) for the C source files. All of the assembly language source files are then assembled with DASM. Except. The RUN file is in the standard MMURTL Run file format which is described completely in the Chapter 28.ASM .ASM .C .0 Page 299 of 667 . This file is required to assemble complex programs with DASM. but you don't need to read all the DASM documentation to build MMURTL. Each of the C source files is turned into an assembler file (which is what the CM32 compiler produces). InitCode. If you want an error file. “DASM:A 32-Bit Intel-Based Assembler.” The only two MS-DOS commands you need are CM32 and DASM. If you want a complete listing of all processor instructions and addresses. This takes about 1.The monitor program is contained in this file.BAT) will build the entire operating system.This file has all the helper routines that initialize most of the of the dynamic operating-system functions.ASM .C After all the C source files are compiled.Kernel.5 minutes on a 486/33. The following is the Assembler Template File (ATF). and the DASM assembler (also included). Monitor. You could replace quite easily. Chapter 18 discusses the kernel and presents the most important pieces of its code along with discussions of the code. MMURTL V1. you use the DASM command as follows: C:\MMURTL> DASM MMURTL.All of the kernel calls and most of the kernel support code is in this file.Exception handlers such as those required for processor faults are in this file. One MS-DOS batch file (MakeALL. /L on the command line after the name. The assembler is discussed in detail in chapter 28. An example of compiling one of the source files is: C:\MMURTL> CM32 Monitor.

Indirect calling address table .0 Page 300 of 667 .code and data.INCLUDE MOSGDT.INCLUDE DevDrvr.Listing 17.INCLUDE Debugger.INCLUDE TmrCode. Timer Code .INCLUDE Except. a C source file and must be compiled first with .ASM .INCLUDE HardIDE. Code & Data for Debugger .INCLUDE MAIN.ASM .ASM .* Loader/Job handling Code & Data . Ordering for the remainder of the modules is not as important. Code and Data for Device Driver Interface . Request Block Code .ASM .ASM . Kernel code .ASM .* File System Service .ASM . The order of the assembler files in the ATF file determines the order of the data and code.ASM .ASM . MMURTL V1.INCLUDE MemCode. Defines the Interrupt Descriptor Table .INCLUDE Kernel.INCLUDE RQBCode.INCLUDE IntCode. Misc.array.INCLUDE JobCode. Additional Job management . . Exception handlers .INCLUDE UASM.INCLUDE Video.INCLUDE SVCCode. Initialization support code .These first 5 INCLUDES MUST be in this order!!!! . OS Page Directory & 1st Table (8k) .ASM .INCLUDE DMACode.* Serial Comms Device Driver .INCLUDE MOSPDR. ISR handling code .ASM .ASM .DATA .INCLUDE NumCnvrt.INCLUDE InitCode. Main OS code and some data & START address .ASM .1 – Assembler Template File .ASM .INCLUDE RS232.ASM . The same is true for the data as it is encountered in the files. As each piece of code is assembled. 6K) .* Floppy Device Driver .INCLUDE FSys. Keyboard ISR and Service .ASM .ASM .* IDE Disk Device Driver .ASM .END If you’ve read all of the chapters that precede this one.ASM .INCLUDE Monitor.ASM .ASM .INCLUDE MOSIDT. CM32 (no switches needed).* The monitor code & data . An asterisk (*) by the include file indicates it originates as . Maybe Move to DLL later or can it .* Code & Data for Debugger Disassembler . 6K for GDT @ 00000800h Physical (1.ASM .ASM .INCLUDE Parallel. Video code and data .INCLUDE MPublics. .ASM .INCLUDE MiscCode.ASM :* Parallel Comms Device Driver (RAB) . DMA Handling Code .ASM .INCLUDE Keyboard.Assembler Template File for MMURTL V1. Memory management code .ASM . System Service management code .5 Pages.0 . it is placed into the code segment in that order.INCLUDE Floppy. you’ll understand that all MMURTL programs are comprised of only two segments .ASM . code (string. .ASM .INCLUDE JOBC.I/O support) .

no doubt. you can calculate the correct offsets and fix the EQU statements for access where a C compiler would put them on the stack. You will. the code can be compiled with any ANSI C compiler. but you would have no problem setting them up for TASM or MASM.0 Page 301 of 667 . use another compiler for your project. you can take the file system code (FSYS. This is because DASM doesn’t support structures. In fact. CM32 packs all variables with no additional padding. 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. Otherwise. you can declare them as Pascal functions (some even use the old PLM type modifier). Even though I use CM32 to compile the C source files. The same basic warning about the alignment of structures applies. and they will work fine. You’ll find that many items in MMURTL depend on the memory alignment of some of the structures. MMURTL V1. 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. 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. The assembler INCLUDE files that define all the structures actually define offsets from a memory location. If your C compiler allows it. I built each section of code in modular sections and tried to keep it that way. For instance.Using Pieces of MMURTL in Your OS You’ll find that most of the source code is modular.C) and it can be turned into a free-standing program with little effort. This is one thing you should watch. CM32 packs all structure fields completely.

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

link blocks.INC DD 0 . we must ensure that interrupts are disabled prior to the allocation or deallocation of all kernel data segment resources. Most of these functions manipulate all the linked lists that maintain things like the ready queue. Because certain kernel functions may be called from ISRs.Listing 18.DATA . I also had to really keep good track of which registers I used.Used as temp in Service Abort function TimerTick DD SwitchTick DD dfHalted DD _nSwitches DD _nHalts DD _nReady DD Local Kernel Helper Functions This section has all of the little functions that help the kernel primitives and scheduler code.INCLUDE dJunk EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN MOSEDF.Kernel data segment source. This is important. along with enQueueRdy and deQueueRdy. which means interrupts must be cleared.1. You may notice that there is a deQueueTSS but no enQueueTSS. The following lines begin the code segment for the kernel: . from an exchange but not yet linked to a TSS and placed on the ready queue). The functions described have a certain symmetry that you’ll notice. it’s that they manipulate common data and therefor are not reentrant. The concept itself should be very important to you if you intend to write your own multitasking operating system. is coded in-line.0 Page 303 of 667 . It’s not that the routines are called so often.INCLUDE .INCLUDE . for example.INCLUDE . and therefore. .INC TSS. and exchanges.INC RQB.INC JOB. and because portions of other kernel functions may be interrupted by a task change that happens because of an action that an ISR takes. This especially applies when a message is "in transit" (taken. The interface to these calls are exclusively through registers.CODE EXTRN LinToPhy NEAR MMURTL V1. such as enQueueMsg and deQueueMsg. This is strictly a speed issue. This is because the enQueueTSS functionality is only needed in two places.

pointed to by the ESI register.ESI . MOV DWORD PTR [EAX+fEMsg]. it returns NIL (0) in the EAX register. .ESI.ESI .ESI . In MMURTL.enQueueMsg The enQueueMsg places a link block containing a message on an exchange.MsgTail^.ESI .ESI . pLBin^.Next <= NIL. This routine will place the link block pointed to by EAX onto the exchange .Queueing for messages at an exchange. Interrupts will already be disabled when this routine is called. and if a task is waiting there. an exchange is a place where either messages or tasks wait (link blocks that contain the message actually wait there). . 1 .MsgTail <= pLBin. pLBin => ESI CMP DWORD PTR [EAX+EHead]. . MMURTL V1. pExch => EAX. Put pExch Back in ESI RETN .2. else eqMNotNIL: MOV EDX..MsgHead <= pLBin.EAX .0 Page 304 of 667 . . .EDX.MsgHead = NIL JNE eqMNotNIL .MsgTail <= pLBin.ESI . MOV [EAX+ETail].FLAGS . MOV DWORD PTR [EAX+NextLB]. then MOV [EAX+EHead]. 0 . A flag tells you whether it’s a task on a message. Flag it as a Msg (vice a task) XCHG EAX. OR EAX. . MOV DWORD PTR [EAX+fEMsg]. if . INPUT : ESI. REGISTERS : EAX. . See listing 18. Listing 18. OUTPUT : NONE . For this reason we share the HEAD and TAIL link pointers of an exchange for tasks and messages. 1 . Put pExch Back in ESI eqMsgDone: RETN .. If EAX is NIL then the routine returns. MOV [EDX+NextLB].. MODIFIES : EDX .. enQueueMsg: .EAX . the task is immediately associated with the message and placed on the ready queue in priority order. XCHG ESI. Flag it as a Msg (vice a task) XCHG EAX. The message can be a pointer to a Request or an 8-byte generic message. If not. There can never be tasks and messages at an exchange at the same time (unless the kernel is broken!). 0 .2.EAX . if pLBin = NIL THEN Return.[EAX+ETail] . MOV [EAX+ETail]. When a message is sent to an exchange. deQueueMsg The deQueueMsg removes a link block from an exchange if one exists there. JZ eqMsgDone ..NextLB <= pLBin.

4. REGISTERS : EAX. ESI register and place the pointer to the link block dequeued into EAX.MsgHead. if pLBout = NIL then Return.head and EBX . . . exchanges can hold messages or tasks.3.. No! (return 0) MOV EAX. See listing 18. Set up to return nothing MOV EBX.MsgHead^. MOV EAX. Listing 18. If not.[ESI+fEMsg] ..EBX. This routine will dequeue a link block on the exchange pointed to by the . but not both.[ESI+EHead] . . INPUT : ESI . deQueueTSS: .ESI.EBX.EAX . register and place the pointer to the TSS dequeued into EAX. . REGISTERS : EAX. .As stated before.. OUTPUT : EAX .De-queueing a message from an exchange. OR EAX. . MODIFIES : *prgExch[ESI].Next. OUTPUT : EAX . This routine will dequeue a TSS on the exchange pointed to by the ESI . you return NIL (0).EBX . MOV [ESI+EHead]. XOR EAX. If it’s a message you remove it from the linked list and return it. Is it a Msg? JZ deMsgDone .FLAGS . MODIFIES : EAX.EBX . You check the flag in the exchange structure to see which is there. Listing 18.FLAGS . EAX .[ESI+fEMsg] . EAX . deQueueTSS The deQueueTSS removes a pointer to a TSS from an exchange if one exists there. Get Msg Flag OR EAX. INPUT : ESI . See listing 18. JZ deMsgDone . deMsgDone: RETN . If not. MOV EBX. EAX return NIL if no TSS is waiting at Exch ESI . deQueueMsg: .msg.[EAX+NextLB] .ESI.4.3. it returns NIL (0) in the EAX register.De-queueing a task from an exchange. . Msg flag (is it a Msg) MMURTL V1.MsgHead <= . pLBout <= .0 Page 305 of 667 .

It’s a Msg (return leaving EAX 0) pTSSout <= .Tail <= pTSSin. See listing 18.0 Page 306 of 667 . 0 . .NextTSS <= pTSSin. This links the TSS to rgQueue[nPRI]. . RETN . This routine will place a TSS pointed to by EAX onto the ReadyQueue. . .Next <= NIL. . . .5.EBX .. This .EBX . .Next. pTSSin^. INPUT : EAX . Add offset of RdyQ => EAX ADD EAX.EDX . OR EAX. . get the priority MOV BL. then MOV [EAX+Head]. MODIFIES : EAX. else eqRNotNIL: MOV EDX.EBX. if Head = NIL JNE eqRNotNIL .Tail^. MOV DWORD PTR [EAX+NextTSS]. head & tail per QUEUE).[EAX+NextTSS] MOV [ESI+EHead].Head <= pTSSin. . 0 .EBX. . JZ eqRdyDone .EBX deTSSDone: RETN . MMURTL V1.EDX.TSSHead^. in EBX XCHG EAX. one for each of the possible task priorities in MMURTL.[ESI+EHead] OR EAX..OR EBX. Times 8 (size of QUEUE) LEA EDX.[EAX+Priority] . ..EBX . . algorithm chooses the proper priority queue based on the TSS priority.Tail <= pTSSin. pTSSin => EBX SHL EAX. .EBX .TSSHead <= . XOR EBX.EBX . EAX JZ deTSSDone MOV EBX. if pTSS = NIL then return.TSSHead. REGISTERS : EAX.FLAGS . enQueueRdy The enQueueRdy places a task (actually a TSS) on the ready queue.Adding a task to the ready queue.RdyQ .EDX .. You dereference the TSS priority and place the TSS on the proper linked list. if pTSSout = NIL then Return.EAX . Priority => EAX.. The Rdy Queue is an array of QUEUES (2 pointers. OUTPUT : NONE . 3 . EBX JNZ deTSSDone MOV EAX. .EBX MOV [EAX+Tail]. Listing 18.[EAX+Tail] MOV [EDX+NextTSS]. MOV [EAX+Tail]. The ready queue is a structure of 32 linked lists. ..5. . .. PUBLIC enQueueRdy: . INC _nReady . EAX pts to proper Rdy Queue CMP DWORD PTR [EAX+Head].

EAX JNZ deRdyFound ADD EBX. The pointer to this TSS is also removed from the list.De-queueing the highest priority task. IF pTSSout is NIL Then there are No TSSs on the RdyQ. . See listing 18. that 0 is the highest priority.6. .ECX deRdyDone: RETN . and 31 is the lowest.FLAGS .6. This routine provides that functionality for the piece of code in the timer interrupt routine MMURTL V1. Listing 18. this returns NIL (0). If none is found. Set up the number of times to loop LEA EBX. This routine will return a pointer in EAX to the highest priority task . OUTPUT : EAX . . without actually removing it from the queue. . . MOV ECX. Keep in mind. EAX JZ deRdyDone DEC _nReady MOV ECX. . deQueue the process And return with the pointer in EAX . . EAX is returned as NIL.eqRdyDone: RETN . . MODIFIES : RdyQ . If there was no task queued. Point to the next Priority Queue DEC ECX and LOOP IF NOT ZERO . INPUT : NONE .[EBX] OR EAX. .sQUEUE LOOP deRdyLoop deRdyFound: OR EAX.EBX.0 Page 307 of 667 . 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. Get base address of RdyQ in EBX deRdyLoop: MOV EAX.ECX.[EAX+NextTSS] MOV [EBX]. PUBLIC deQueueRdy: . Get pTSSout in EAX IF pTSSout is NIL Then go and check the next priority. Then the routine will "pop" the TSS from the RdyQ. 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. queued on the RdyQ. RETURN Otherwise. REGISTERS : EAX. .RdyQ . .nPRI . .

(in TimerCode. This why it’s specified as PUBLIC. If there was no task queued. See listing 18." See Listing 18. . It WILL NOT remove it from the Queue. Listing 18. INPUT : NONE . Get base address of RdyQ in EBX ChkRdyLoop: MOV EAX. but someone’s got to it.FLAGS . . MOV ECX.Finding the highest priority task PUBLIC ChkRdyQ: .7. OUTPUT : EAX . This routine will return a pointer to the highest priority TSS that . either by its own choosing or is killed off due to some unforgivable protection violation.ASM because they work closely with kernel structures.RdyQ . Get pTSSout in EAX . . the RemoveRdyJob recovers the task state segments (TSSs) that belonged to that job.ECX.ASM) that provides preemptive task switching. (NEAR) MMURTL V1. MODIFIES : RdyQ . IF pTSSout is NIL Then go and . Set up the number of times to loop LEA EBX. They are located in Kernel. . EAX is returned as NIL. "It’s a nasty job. . They provide things like resource garbage collection and exchange owner manipulation. check the next priority. RemoveRdyJob . . RemoveRdyJob When a job terminates.0 Page 308 of 667 . EAX JNZ ChkRdyDone ADD EBX. REGISTERS : EAX. is queued to run.8. 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.[EBX] OR EAX. Listing 18. DEC ECX and LOOP IF NOT ZERO .nPRI .EBX. Point to the next Priority Queue .7.Removing a terminated task from the ready queue.sQUEUE LOOP ChkRdyLoop ChkRdyDone: RETN .8.

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

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

. . . [EDX+Owner] OR EAX.9.prgExch ADD EDX. .EAX MOV EAX. GetExchOwner (NEAR) This routine returns the owner of the exchange specified. .ESP MOV EAX. . . A pointer to the JCB of the owner is returned. ErcNotAlloc JMP SHORT GEOEnd GEO02: MOV ESI. . .sEXCH MUL EDX MOV EDX. . EAX GEOEnd: MOV ESP.Listing 18.Where to return pJCB of Exchange . Get Resp Exchange in EDX Is the exchange out of range? No. ErcOutofRange is returned is the exchange number is invalid (too high) Procedureal Interface : GetExchOwner(long Exch. No. . char *pJCBRet): dErrror Exch is the exchange number. Valid Exch (Allocated) . not allocated . . continue Yes. .EBP POP EBP RETN 8 .ErcOutOfRange JMP GEOEnd GEO01: MOV EDX.0 Page 311 of 667 . . . Error in EAX register PUBLIC _GetExchOwner: PUSH EBP MOV EBP. . [EBP+8] MOV [ESI]. . pJCBRet is a pointer to the JCB that the tasks to kill belong to. . EAX JNZ GEO02 MOV EAX. [EBP+12] CMP EAX. . . . MMURTL V1.Finding the owner of an exchange. . . Exch pJCBRet EQU DWORD PTR [EBP+12] EQU DWORD PTR [EBP+8] . . ErcNotAlloc is returned if the exchange isn’t allocated. . . Compute offset of Exch in rgExch sExch * Exch number Add offset of rgExch => EAX EDX -> Exch .nExch JB GEO01 MOV EAX. . . EAX XOR EAX. .

. . EBX XOR EAX. . telling them that a particular job has died.ESP MOV EAX. EAX POP EBP RETN 8 Exchange Number Compute offset of Exch in rgExch sExch * Exch number Add offset of rgExch => EAX EAX -> oExch + prgExch . then exited or was killed off because of nasty behavior. The error it returns is of no consequence. . [EBP+12] MOV EDX.EDX MOV EBX. . . . . If a program sent a request. Listing 18. System services may hold requests from many jobs at a time until they can service the requests (known as asynchronous servicing). If a service is holding a request from the job that died. it should respond to it as soon as it gets the abort message.11. . . . . mentioned previously.prgExch ADD EAX.0 Page 312 of 667 . .SetExchOwner This is the complimentary call to GetExchOwner(). . . No error checking is done as the job code does it upfront! Procedureal Interface : SetExchOwner(long Exch. . . Exch pNewJCB EQU DWORD PTR [EBP+12] EQU DWORD PTR [EBP+8] . . SetExchOwner (NEAR) This routine sets the owner of the exchange specified to the pJCB specified. the SendAbort function is called to send a message to all active services. other services. 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). . . char *pNewJCB): dErrror Exch is the exchange number. This call sets the exchange owner to the owner of the job control block pointed by the second parameter of the call. See listing 18. PUBLIC _SetExchOwner: PUSH EBP MOV EBP. . . or the operating system itself.sEXCH MUL EDX MOV EDX. . [EBP+8] MOV [EAX+Owner].10. pNewJCB is a pointer to the JCB of the new owner. MMURTL V1. The kernel knows it’s already dead and will reclaim the request block and exchanges that were used. It should not process any data for the program (because it’s dead). SendAbort System services are programs that respond to requests from applications. See listing 18. .10.Changing the owner of an exchange.

nSVC SAB01: CMP DWORD PTR [ESI]. Procedureal Interface : SendAbort(long JobNum. .11. .Listing 18. .cbData1 MOV EAX.cbData0 PUSH 0 .OFFSET rgSVC MOV ECX.pData0 PUSH 0 . Get the number of Service Descriptors PUBLIC _SendAbort: PUSH EBP MOV EBP. . sSVC . Get the address of rgSVC . Valid name? . . . . .Exchange PUSH EAX PUSH OFFSET dJunk . NO.Notifying services of a job’s demise. .JobNum PUSH EAX .dData0 PUSH 0 . .dData1 PUSH 0 .0 Page 313 of 667 . .Get count and ptr to SVC names back from stack POP ECX POP ESI SAB05: ADD ESI.npSend PUSH 0 .pHandleRet PUSH 0 . . . We ignore the kernel errors.ESP MOV ESI. [EBP+8] .Push all the params to make the request PUSH ESI . . 0 JE SAB05 .Save count and pointer to SVC name PUSH ECX .pData1 PUSH 0 . If we receive a kernel error on Request it may be becuase it is a service that is aborting itself. . . SendAbort (NEAR) This routine sends one abort message to each valid service with the jobnum of the aborting job.Next Service name MMURTL V1. next service PUSH ESI .Abort Service Code MOV EAX. . [EBP+12] . .pName PUSH 0 . 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] . .dData2 CALL FWORD PTR _Request .

Request( pSvcName [EBP+56] .12. EAX MOV ESP.asm. The function that searches the array is GetExchange(). . and code for it is in the file SVCCode. except this function requires several more parameters. pData1 [EBP+36] . .EBP POP EBP RETN 8 . The procedural interface to Request looks like this: .ESP . pRqHndlRet [EBP+44] . dRespExch [EBP+48] . wSvcCode [EBP+52] . A request block is the basic structure used for client/server communications.0 Page 314 of 667 . These are functions such as GetPriority(). . dData1 [EBP+16] .12. Request() The kernel Request primitive sends a message like the Send() primitive. Set up New FramePtr . Some of the functions are auxiliary functions for outside callers and not actually part of the kernel code that defines the tasking model. 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. Listing 18.LOOP SAB01 XOR EAX. See listing 18. .pServiceName MMURTL V1. [EBP+56] .Request kernel primitive code. dcbData2 [EBP+24] . . Public Kernel Functions The remainder of the code defines the kernel public functions that are called through call gates. dcbData1 [EBP+32] . dData2 [EBP+12] ) : dError PUBLIC __Request: PUSH EBP MOV EBP. The offsets to the stack parameters are defined in the comments for each of calls. A system structure called a request block is allocated and some of these parameters are placed in it. These are called with 48-bit (FAR) pointers that include the selector of the call gate.Validate service name from registry and get exchange MOV EAX. dnpSend [EBP+40] . dData0 [EBP+20] . Save the Previous FramePtr . They’re in this file because they work closely with the kernel. pData2 [EBP+28] .

Get dData0 .Leaves Service Exch in ESI if no errors .Get Svc Code . EAX MOV EAX.Get pData2 .Get pData1 . .EAX JZ SHORT Req02 JMP ReqEnd Req02: .EAX has pRqBlk (Handle) MOV EBX.put in RqBlk . [EBP+28] MOV [EBX+pData2].ESI still has the exchange for the service .Put Resp Exch into RqBlk .Get dData1 . EAX MOV EAX. [EBP+52] MOV [EBX+ServiceCode].nExch JB Req03 MOV EAX. EDX CALL GetCrntJobNum MOV [EBX+RqOwnerJob].Did we get one? (NIL (0) means we didn’t) JNZ Req04 . [EBP+20] MOV [EBX+dData0]. EAX MOV EAX.Must be 2 or less .No.Validate exchange MOV EDX.put in RqBlk .Put nSend PbCbs into RqBlk .Yes.Get cbData2 . JMP ReqEnd . EAX . EAX MOV EAX. ESI MOV EAX. EAX ptr to new RqBlk MOV EAX. MMURTL V1. AX MOV [EBX+RespExch]. Error in EAX register . [EBP+48] CMP EDX. EAX MOV EAX.Caculate nRecv (2-nSend) .put in RqBlk .put in RqBlk .Put Svc Exch into RqBlk . EAX MOV EAX. [EBP+40] CMP EAX. EAX MOV EAX. EAX MOV [EBX+ServiceExch].Number of Send PbCbs .CALL GetExchange OR EAX. [EBP+24] MOV [EBX+cbData2]. ErcNoMoreRqBlks .Get dData2 .Any errors? . EAX MOV EAX. Req04: .Yes.ercOutOfRange JMP ReqEnd Req03: .Get cbData1 . [EBP+16] MOV [EBX+dData1]. 2 Req06: MOV [EBX+npSend]. Sorry. AL MOV CL.. [EBP+32] MOV [EBX+cbData1].put in RqBlk .EAX has ptr to new RqBlk (or 0 if none) STI OR EAX.Get them a request block CLI CALL NewRQB . return error .put in RqBlk . 3 JB Req06 MOV EAX. continue Yes.put in RqBlk . .No .put in RqBlk .EDX still has the response exchange . . [EBP+36] MOV [EBX+pData1]. Get Resp Exchange in EDX Is the exchange out of range? No.Get crnt JCB (Job Num of owner) .0 Page 315 of 667 . [EBP+20] MOV [EBX+dData2]..EBX now pts to RqBlk . 2 . .Put Svc Code into RqBlk .

Exch => EAX Compute offset of Exch in rgExch Add offset of rgExch => EAX MAKE ESI <= pExch . . . . . EBX . give up the message No. Get the pLB just saved into EBX . [EBP+44] MOV [EDI].The ptr to the exch is required for deQueueTSS so we get it. . and put it in the TSS . DeQueue a TSS on that Exch Did we get one? Yes. .pFreeLB OR EAX. .Now we will return the RqBlkHandle to the user.EBX . AL MOV [EBX+npRecv]. . .EAX JMP SHORT ReqEnd Req10: POP EBX MOV [EAX+pLBRet].Now we allocate a Link block to use MOV EAX.ESI still has the exchange Number for the service. .EBX DEC _nLBLeft . EBX MOV EDX.ESI EDX. free up RqBlk Move error in the EAX register Go home with bad news MOV DWORD PTR [EAX+LBType]. .Next .EDX ESI.prgExch EAX.EAX JNZ Req08 CALL DisposeRQB MOV as one anyway (so no problem) MOV EDI.0 Page 316 of 667 . EAX <= pFreeLB.Data PUSH EAX .REQLB . .Give it to them . 0 ..Leave in CL . pLB^. .At this point the RqBlk is all filled in. . Get the pLB just saved EnQueue the Message on Exch No Error And get out! MMURTL V1. Store zero in upper half of pLB^.Put npRecv in RqBlk . CL . MOV [EAX+DataLo].EAX . MOV MOV MUL MOV ADD MOV EAX. . .ercNoMoreLBs JMP ReqEnd Req08: MOV EBX. . No interruptions from here on .ESI now points to the exchange CALL deQueueTSS OR EAX. . This is a Request Link Block MOV DWORD PTR [EAX+NextLB]. Is pFreeLB NIL? (out of LBs) NO.. sEXCH EDX EDX.Next <= NIL. . RqHandle into Lower 1/2 of Msg MOV DWORD PTR [EAX+DataHi].Ptr to return handle to . .EAX JNZ Req10 POP EAX CALL enQueueMsg XOR EAX. Save pLB on the stack .Save RqBlk in EDX CLI . 0 . .EDX . pFreeLB <= pFreeLB^.SUB CL.The handle is actually a ptr to the RqBlk but they can’t use .[EAX+NextLB] MOV pFreeLB.

pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX.EAX BX.ESP MOV EAX. .pRunTSS JNE Req12 XOR EAX. . .EAX . See listing 18. This is very similar to Send() except it de-aliases addresses in the request block and then deallocates it. . Make the TSS in EAX the Running TSS . .BX _nSwitches EAX. . . . . TimerTick SwitchTick. . . . dRqHndl MOV ESI. . . Keep track of how many swtiches for stats .Save time of this switch for scheduler .0 Page 317 of 667 . . Rtn to Caller & Remove Params from stack Respond() The Respond primitive is used by system services to respond to a Request() received at their service exchange. dStatRet): dError . Respond(dRqHndl.13.Respond kernel primitive code.nExch JNAE Resp02 MOV EAX. Return to Caller with erc ok. Listing 18.ercOutOfRange JMP RespEnd . . The exchange to respond to is located inside the request block.EAX JMP SHORT ReqEnd Req12: MOV MOV MOV INC MOV MOV JMP XOR ReqEnd: STI MOV ESP. . . Get out MMURTL V1. Put it in the JumpAddr for Task Swtich . .EBP POP EBP RETF 48 pRunTSS. [EAX+RespExch] CMP ESI.[EAX+Tid] TSS_Sel. . . continue Error into the EAX register. Get the task Id (TR) . JMP TSS (This is the task swtich) . Save Callers Frame Setup Local Frame pRqBlk into EAX Response Exchange into ESI Is the exchange out of range? No.13.CALL enQueueRdy MOV EAX. The request block handle must be supplied along with the error/status code to be returned to the caller. 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. . EAX FWORD PTR [TSS] EAX. dRqHndl EQU DWORD PTR [EBP+16] dStatRet EQU DWORD PTR [EBP+12] PUBLIC __Respond: PUSH EBP MOV EBP. .

Same job . PUSH EAX CALL FWORD PTR _DeAliasMem . RespEnd . dRqHndl CLI CALL DisposeRQB .Save pExch across call PUSH EDX .sEXCH .Null pointer! PUSH ESI .pMem PUSH EBX . Get Request handle into EBX (pRqBlk) . .no DeAlias needed MOV EAX.EDX .Null pointer! PUSH ESI .ercNotAlloc .No need to dealias (zero bytes) EAX. in the EAX register. Exch => EAX EDX. Get Request handle into EBX (pRqBlk) MOV EBX. Get Request handle into EBX (pRqBlk) MOV EBX.ESI . Get Request handle into EBX (pRqBlk) . [EAX+RqOwnerJob] CALL GetCrntJobNum CMP EAX.pMem PUSH EBX .DO it and ignore errors POP ESI .get pExch back Resp05: MOV EAX. 0 .get pExch back Resp06: MOV EAX. No interruptions . . [EAX+pData2] OR EDX.DO it and ignore errors POP ESI . EBX JE Resp06 . dRqHndl .EAX . EBX JZ Resp06 . EDX JZ Resp05 . dRqHndl MOV EBX. EDX JZ Resp06 . If the exchange is not allocated Resp04 . Return Rqb to pool. Not needed anymore MMURTL V1.No need to dealias (zero bytes) MOV EDX. [EAX+cbData1] OR EBX. dRqHndl . ESI.prgExch . EDX.Save pExch across call PUSH EDX . Add offset of rgExch => EAX EAX. OR EBX. Compute offset of Exch in rgExch EDX . [EAX+pData1] OR EDX.0 Page 318 of 667 . MAKE ESI <= pExch DWORD PTR [EAX+Owner]. EBX JZ Resp05 MOV EDX. [EAX+cbData2] . return to the caller with error EAX.Resp02: MOV MOV MUL MOV ADD MOV CMP JNE MOV JMP Resp04: MOV EAX.cbMem CALL GetCrntJobNum PUSH EAX CALL FWORD PTR _DeAliasMem .cbMem CALL GetCrntJobNum .

Next <= NIL. MOV EBX.EBP POP EBP RETF 8 .EAX JNZ Resp07 MOV EAX.pRunTSS CALL enQueueRdy CALL deQueueRdy CMP JNE XOR JMP Resp10: MOV MOV MOV INC MOV MOV JMP XOR RespEnd: STI MOV ESP. . MMURTL V1. .. Get the pLB just saved CALL enQueueMsg . IF pFreeLB=NIL THEN No LBs. . JMP TSS . If the high priority TSS is the . dStatRet . Allocate a link block MOV EAX. Return to Caller with erc ok. This is a Response Link Block MOV DWORD PTR [EAX+NextLB]. Make the TSS in EAX the Running TSS .pRunTSS Resp10 EAX.Next MOV pFreeLB. pLB^. .EAX BX. 0 . . Return to Caller with erc ok.EAX . EAX FWORD PTR [TSS] EAX.[EAX+Tid] TSS_Sel. pFreeLB <= pFreeLB^. . Rtn to Caller & Remove Params pRunTSS. same as the Running TSS then return .EBX CALL enQueueRdy MOV EAX. DeQueue a TSS on that Exch OR EAX. .0 Page 319 of 667 . .Save time of this switch for scheduler . EnQueue the Message on Exch XOR EAX. DEC _nLBLeft . Save pLB on the stack CALL deQueueTSS . . Did we get one? JNZ Resp08 . caller with error in the EAX register MOV EBX. Store in upper half of pLB^. 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 . . RESPLB .pFreeLB OR EAX. EAX .[EAX+NextLB] . Put it in the JumpAddr . Store in lower half of pLB^. NewLB <= pFreeLB. Get Request handle into EBX MOV [EAX+DataLo]. give up the message POP EAX .BX _nSwitches EAX.EBX . Yes. And get out! Resp08: POP EBX MOV [EAX+pLBRet]. .Data MOV EBX. No Error JMP SHORT RespEnd . Get the task Id (TR) .EAX SHORT RespEnd . dRqHndl . TimerTick SwitchTick. .EBX . Update stats MOV DWORD PTR [EAX+LBType]. EAX.EAX . Get Status/Error into EBX MOV [EAX+DataHi].ercNoMoreLBs JMP RespEnd Resp07: .EBX .Data PUSH EAX .

. . No interruptions from here on .ercNoMoreLBs JMP MReqEnd . 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. MoveRequest(dRqBlkHndl. This allows a service to move a request to another exchange it owns.sEXCH MUL EDX MOV EDX.0 Page 320 of 667 . Get Exchange Parameter in ESI Is the exchange is out of range No. .dRqBlkHndl EQU [EBP+16] . IF pFreeLB=NIL THEN No LBs. caller with error in the EAX register Go home with bad news . .14. .14. . . . MoveRequest kernel primitive code. .EAX MOV EDX.prgExch ADD EAX. Procedural Interface : . Allocate a link block MOV EAX. . because the pointers in the request block are not properly aliased. . NewLB <= pFreeLB.DestExch EQU [EBP+12] PUBLIC __MoveRequest: PUSH EBP MOV EBP.EDX MOV ESI. . DestExch):ercType .ESP MOV ESI. . . . .ercOutOfRange JMP MReqEnd MReq02: MOV EAX. . Listing 18. .nExch JNAE MReq02 MOV EAX. See listing 18. . . . ErcNotOwner JMP MReqEnd MReq04: CLI . . EAX JE MReq04 MOV EAX. Get out MMURTL V1. DestExch the exchange to where the Request should be sent.pFreeLB OR EAX. . . continue in the EAX register. . . . [EAX+Owner] CALL GetpCrntJCB CMP EDX. . This cannot be used to forward a request to another service or job. dqMsg is the handle of the RqBlk to forward. [EBP+12] CMP ESI. .ESI MOV EDX. It is very similar to SendMsg() except it checks to ensure the destination exchange is owned by the sender.EAX JNZ MReq08 MOV EAX. Get out .MoveRequest() This is the kernel MoveRequest primitive.

Next . SendMsg() This is the kernel SendMsg primitive. .Save time of this switch for scheduler . MOV EBX. MMURTL V1.EBX CALL enQueueRdy MOV EAX. . MOV DWORD PTR [EAX+NextLB].EBP POP EBP RETF 8 .EAX JNZ MReq10 POP EAX CALL enQueueMsg JMP SHORT MReqEnd MReq10: POP EBX MOV [EAX+pLBRet]. .EBX MOV DWORD PTR [EAX+DataHi]. . .Next <= NIL. 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. . . .0 Page 321 of 667 . This sends a non-specific message from a running task to an exchange. . . Put it in the JumpAddr for Task Swtich . Update stats REQLB 0 . [EBP+16] MOV [EAX+DataLo]. This is a Request Link Block . .Data PUSH EAX CALL deQueueTSS OR EAX. Save pLB on the stack DeQueue a TSS on that Exch Did we get one? Yes. pLB^. . .EAX JMP SHORT MReqEnd MReq12: MOV MOV MOV INC MOV MOV JMP XOR MReqEnd: STI MOV ESP. Store zero in upper half of MOV DWORD PTR [EAX+LBType]. RqHandle into Lower 1/2 of Msg .EAX 0 . . .EAX BX.15. pRunTSS. TimerTick SwitchTick. EAX FWORD PTR [TSS] EAX.MReq08: MOV EBX. . give up the message Get the pLB just saved EnQueue the Message on Exch And get out! .pRunTSS JNE MReq12 XOR EAX. . .BX _nSwitches EAX. pFreeLB <= pFreeLB^. See listing 18. RqHandle . . . pLB^.EBX DEC _nLBLeft . .[EAX+Tid] TSS_Sel. JMP TSS (This is the task swtich) . if a task is waiting at the exchange and it is of equal or higher priority than the task which sent the message.[EAX+NextLB] MOV pFreeLB. Make the TSS in EAX the Running TSS .pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX. Get the task Id (TR) . Keep track of how many swtiches for stats . This may cause a task switch. Return to Caller with erc ok.

.EAX . pFreeLB <= pFreeLB^. .prgExch .Next .15. SendEnd .ercOutOfRange JMP SendEnd Send00: MOV MOV MUL MOV ADD MOV CMP JNE MOV JMP Send01: CLI . caller with error in the EAX register Go home with bad news EAX.ESP MOV ESI. .ESI . exch is a DWORD (4 BYTES) containing the exchange to where the . Update stats . pLB^.SendMsg kernel primitive code. . EDX. .ercNoMoreLBs JMP SHORT MReqEnd Send02: MOV EBX. . .nExch JNAE Send00 MOV EAX. MAKE ESI <= pExch DWORD PTR [EAX+Owner].EDX . Allocate a link block MOV EAX. in the EAX register. message should be sent.SendExchange CMP ESI.EBX DEC _nLBLeft . . MMURTL V1. SendExchange EQU [EBP+14h] MessageHi EQU DWORD PTR [EBP+10h] MessageLo EQU DWORD PTR [EBP+0Ch] PUBLIC __SendMsg: PUSH EBP MOV EBP. . dMsg1. Procedural Interface : . ESI. MOV DWORD PTR [EAX+LBType]. Compute offset of Exch in rgExch EDX . NewLB <= pFreeLB. 0 . If the exchange is not allocated Send01 . IF pFreeLB=NIL THEN No LBs. Exch => EAX EDX.EAX JNZ SHORT Send02 MOV EAX.Listing 18. .sEXCH . return to the caller with error EAX.0 Page 322 of 667 . SendMsg(exch. dMsg2):ercType . Add offset of rgExch => EAX EAX. only by the sending and receiving tasks. . 0 .Next <= NIL. . DATALB . This is a Data Link Block MOV DWORD PTR [EAX+NextLB]. . . No interrupts . .pFreeLB OR EAX. . . .[EAX+NextLB] MOV pFreeLB. . Get Exchange Parameter in ESI If the exchange is out of range the return to caller with error in the EAX register.ercNotAlloc . dMsg1 & dMsg2 are DWord values defined and understood .

JMP TSS . This is the same as SendMsg() except no task switch is performed. Make the TSS in EAX the Running TSS . EAX FWORD PTR [TSS] .EBX CALL enQueueRdy MOV EAX. DeQueue a TSS on that Exch . .pRunTSS JNE Send03 JMP SHORT Send04 Send03: MOV MOV MOV INC MOV MOV JMP Send04: XOR EAX. Did we get one? Yes. .Data PUSH EAX CLI CALL deQueueTSS STI OR EAX. . TimerTick SwitchTick. .EAX BX. MMURTL V1. . which will probably be caused by the timer interrupt slicer.EAX SendEnd: STI MOV ESP. . . . Put it in the JumpAddr . .0 Page 323 of 667 .MOV MOV MOV MOV EBX. It will get a chance to run the next time the RdyQ is evaluated by the kernel.Data of Msg in EBX half of pLB^. . Save pLB on the stack . Get the task Id (TR) .EBX . If a task is waiting at the exchange. the message is associated (linked) with the task and then moved to the RdyQ.EBP POP EBP RETF 12 pRunTSS. . This procedure allows an ISR to send a message to an exchange. . give up the message Get the pLB just saved No interrupts EnQueue the Message on Exch And get out (Erc 0)! . Get lower half Store in lower Get upper half Store in upper of Msg in EBX half of pLB^.Save time of this switch for scheduler . . No interrupts . . .BX _nSwitches EAX. . ISendMsg() This is the kernel ISendMsg primitive (Interrupt Send).EAX JNZ Send25 POP EAX CLI CALL enQueueMsg JMP Send04 Send25: POP EBX CLI MOV [EAX+pLBRet].pRunTSS CALL enQueueRdy CALL deQueueRdy CMP EAX. .MessageLo [EAX+DataLo]. . . Get the pLB just saved into EBX 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 .MessageHi [EAX+DataHi]. . Return to Caller with erc ok. .[EAX+Tid] TSS_Sel.EBX EBX. .

message should be sent.sEXCH . . Add offset of rgExch => EAX ADD EAX.ercOutOfRange MOV ESP. MOV ESI. .prgExch . OR EAX.0 Page 324 of 667 .EAX . . You may notice that jump instructions are avoided and code is placed in-line. Listing 18. exch is a DWORD (4 BYTES) containing the exchange to where the .ercNotAlloc . MAKE ESI <= pExch CMP DWORD PTR [EAX+Owner]. . . MOV ESP. ISendMsg(exch. return to the caller with error MOV EAX. If the exchange is not allocated JNE ISend01 .EBP POP EBP RETF 12 ISend00: MOV EAX.16.SendExchange CMP ESI. MMURTL V1. if desired. RETF 12 . dMsg1. Parameters on stack are the same as _SendMsg. POP EBP .Interrupt tasks can use ISendMsg() to send single or multiple messages to exchanges during their execution. . IF pFreeLB=NIL THEN No LBs. .INTS ALWAYS CLEARED AND LEFT THAT WAY! . Allocate a link block MOV EAX.IsendMsg kernel primitive code. Exch => EAX MOV EDX.pFreeLB . See listing 18. 0 . dMsg2):ercType . . ISend01: . dMsg1 and dMsg2 are DWORD messages. PUBLIC __ISendMsg: CLI PUSH EBP MOV EBP.ESI . Get Exchange Parameter in ESI .EAX . in the EAX register. . in the EAX register.16. Procedural Interface : . NewLB <= pFreeLB.EBP . . If the exchange is out of range . ISendMsg is intended only to be used by ISRs in device drivers. for speed. JNZ SHORT ISend02 . Compute offset of Exch in rgExch MUL EDX . then return to caller with error . . where possible. . MOV EDX. .ESP MOV ESI.nExch JNAE ISend00 MOV EAX. Interrupts are cleared on entry and will not be set on exit! It is the responsibility of the caller to set them.EDX . .

EBX . And get out! ISend03: POP EBX MOV [EAX+pLBRet]. This is because they may be operating in completely different memory contexts.Data PUSH EAX .EAX . pFreeLB <= pFreeLB^. Yes.MessageLo . MMURTL V1. which means they have different page directories. If no message is at the exchange. Get the pLB just saved into EBX .DATALB .0 . pLB^. If you halted.Next . MOV DWORD PTR [EAX+LBType]. EnQueue the Message on Exch JMP ISend04 . DeQueue a TSS on that Exch OR EAX.[EAX+NextLB] MOV pFreeLB. . and put it in the TSS . MOV EBX. EnQueue the TSS on the RdyQ WaitMsg() This is the kernel WaitMsg primitive. give up the message POP EAX . . Get lower half of Msg in EBX MOV [EAX+DataLo].EBX CALL enQueueRdy ISend04: XOR EAX. I simply halt the processor with interrupts enabled.17.EBX DEC _nLBLeft . . Return to Caller with erc ok. The WaitMsg() and CheckMsg() primitives also have the job of aliasing memory addresses inside of request blocks for system services. .Next <= NIL. and the ready queue is reevaluated to make the next highest-priority task run. . Get upper half of Msg in EBX MOV [EAX+DataHi].0 Page 325 of 667 . Store in lower half of pLB^.EBP POP EBP RETF 12 .EBP POP EBP RETF 12 ISend02: MOV EBX.ercNoMoreLBs MOV ESP. Get the pLB just saved CALL enQueueMsg .MOV EAX. . You will notice that if there is no task ready to run. This procedure allows a task to receive information from another task via an exchange.MessageHi . the task is placed on the exchange. there wasn’t one to begin with.EAX MOV ESP. . Store in upper half of pLB^. caller with error in the EAX register . This is a very key piece to task scheduling. Save pLB on the stack CALL deQueueTSS . See listing 18. No. Did we get one? JNZ ISend03 . This is a Data Link Block MOV DWORD PTR [EAX+NextLB].Data MOV EBX. 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.EBX . .

where the message should be sent. the return to caller with error MOV EAX.pRunTSS . EAX <= pLB from pExch (ESI) If no message (pLB = NIL) Then Wait for Message Else Get Message and Return . PUBLIC __WaitMsg: .EDX . dExch is a DWORD (4 BYTES) containing the exchange to . POP EBP .17. Get pRunTSS in EAX to Wait . . pMessage is a pointer to an 8 byte area where the .WaitMsg kernel primitive code. . Wait(dExch. Procedural Interface : . in the EAX register. Wait01: CLI CALL deQueueMsg OR EAX. MOV ESP. . MOV ESP. Put Exch in to ESI CMP DWORD PTR [EAX+Owner]. .ESP .This next section of code Queues up the TSS pointed to . WaitExchange EQU [EBP+10h] pMessage EQU [EBP+0Ch] . RETF 8 .EAX . RETF 8 . message is stored.WaitExchange .ESI . ExchId => EAX MOV EBX.ercNotAlloc .nExch . . 0 .EBP . Wait00: MOV EAX. MOV ESI. MOV EBP.pdqMsgRet):ercType . Compute offset of ExchId in rgExch MUL EBX . return to the caller with error MOV EAX. . . . A result code is returned in the EAX EAX on the exchange pointed to by ESI MMURTL V1. MOV EDX. POP EBP .0 Page 326 of 667 .EAX JZ Wait02 JMP Wait05 Wait02: MOV EAX. PUSH EBP . . . . . Get Exchange Parameter in ESI CMP ESI.EBP . . If the exchange is not allocated JNE Wait01 . in the EAX register. If the exchange is out of range JNAE Wait00 .prgExch . MOV ESI.ercOutOfRange .Listing 18.sEXCH . Add offset of rgExch => EAX ADD EAX. .

. 0 . . (i.ESI . MOV [EAX+ETail]. if .. 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 MMURTL V1. we make the current task "wait") MOV DWORD PTR [EAX+NextTSS]. pTSSin^. MOV DWORD PTR [EAX+fEMsg].Next <= NIL. MOV MOV MOV INC MOV MOV JMP . .EAX BX. . Get highest priority TSS off the RdyQ . pExch => EAX..TSSHead = NIL JNE Wait025 .ESI . else Wait025: MOV EDX. pTSSin => ESI CMP DWORD PTR [EAX+EHead].We just placed the current TSS on an exchange. . Check for a task to switch to . 1 dfHalted. 0 .BX _nSwitches EAX.ESI . You bet! NO SWITCH!!! EDI. MOV [EDX+NextTSS]. . Make ESI <= pExch Again . No. An interrupt has occured. . . MOV [EAX+ETail]. Make ESI <= pExch Again JMP SHORT Wait03 .Now we switch tasks by placing the address of the .now we get the next TSS to run (if there is one) Wait03: CALL deQueueRdy OR EAX.0 Page 327 of 667 .. Anyone ready to run? .TSSTail <= pTSSin. Halt CPU and wait for interrupt . then HLT CPU until ready .a hardware task switch. MOV DWORD PTR [EAX+fEMsg]. XCHG ESI.EAX . 0 . . EDI Wait03 . EAX JNZ Wait035 MOV MOV INC STI HLT CLI XOR MOV JMP Wait035: CMP EAX. Clear Interrupts TSS in pRunTSS and jumping to it.EAX . .. EAX FWORD PTR [TSS] ..[EAX+Tid] TSS_Sel.[EAX+ETail] . .e. then MOV [EAX+EHead]. 0 . Yes (jump to check pTSS) . This forces . pRunTSS. Flag it as a TSS (vice a Msg) XCHG ESI.TSSTail^.. Same one as before??? .TSSTail <= pTSSin.Save time of this switch for scheduler .NextTSS <= pTSSin. .TSSHead <= pTSSin. EDI _nHalts .ESI .EAX . . TimerTick SwitchTick. Make high priority TSS Run.EDI dfHalted.pRunTSS JE Wait04 . Flag it as a TSS (vice a Msg) XCHG ESI.

Linear Address of pData1 .. ppAliasRet): dError MOV ECX. . continue . No.EBP POP EBP RETF 8 Wait051: POP EAX PUSH EAX MMURTL V1.Error?? . dcbMem.pRunTSS MOV cbData1 0? . Get the pLB in EAX STI CMP DWORD PTR [EAX+LBType].cbMem .Return Error. Get pRqBlk into EBX . .REQLB Block? JNE Wait06 block . .dJobNum . the message is already deQueued from the exch and the critical part of WaitMsg is over. [EBX+RqOwnerJob] PUSH EAX ADD EBX. . EAX JZ Wait051 POP EBX MOV ESP. pData1 PUSH EBX CALL FWORD PTR _AliasMem OR EAX. Is the link block a Req Link .Make stack right .pMem . dJobNum.pLB.DataLo is RqHandle (pRqBlk) PUSH EAX MOV EBX. we have either switched tasks and we are delivering a message (or Req) to the new task. If we got here. Either way. We can start interrupts again except when we have to return the Link Block to the pool (free it up) . EAX JZ Wait051 . Treat it as a data link . .0 Page 328 of 667 . . Save ptr to Link Block ..Set up params for AliasMem .[EDX+pLBRet] Wait05: . . .Yes . WE CAN RESTART INTERRUPTS HERE . .No.Offset to pData1 in RqBlk .Restore ptr to Link Block . (Alias the 2 Pointers in the RqBlk) . ._AliasMem( pData1 NULL? .Yes . . ECX JZ Wait051 MOV EAX. or the there was a message waiting at the exch of the first caller and we are delivering it. Put the TSS in EAX into EDX . [EBX+pData1] OR EAX.Save again PUSH EAX PUSH ECX MOV EAX. If it is an OS service we alias in OS memory! Wait04: MOV EDX.Now we set up to alias the memory for the service . [EBX+cbData1] OR ECX.Second Pointer (pData2) .[EAX+DataLo] .. .

ECX . .Error?? .0 Page 329 of 667 .. if no message is available CheckMsg() returns to the caller.EBP .Data in specified memory space (EDX) . [EBX+cbData2] OR ECX.Make stack right . . INC _nLBLeft . EAX JZ Wait052 POP EBX MOV ESP. EAX JZ Wait052 PUSH EAX PUSH ECX MOV EAX.EBX [EDX+4]. .pMessage [EDX]. pFreeLB <= pLBin. . Get pRqBlk into EBX .EAX . .[EAX+DataLo] MOV ECX.Yes . [EBX+pData2] OR EAX. MMURTL V1. pData2 PUSH EBX CALL FWORD PTR _AliasMem OR EAX. . In other words.EAX .pFreeLB .EBX . CheckMsg() This is the kernel CheckMsg primitive.No. MOV pFreeLB.cbMem .Set up params for AliasMem .Return the LB to the pool CLI MOV EBX.Yes . MOV [EAX+NextLB]. STI . ECX JZ Wait052 MOV EAX. [EBX+RqOwnerJob] PUSH EAX ADD cbData2 0? . continue .MOV EBX.Restore ptr to Link Block Get pLB^.EBP POP EBP RETF 8 Wait052: POP EAX Wait06: MOV MOV MOV MOV MOV EBX. XOR EAX. .Return Error. pLBin^. RETF 8 .pMem .[EAX+DataLo] ECX.dJobNum .. .Linear Address of PData1 . POP EBP .is pData2 NULL? . ErcOK! (0) MOV ESP.Next <= pFreeLB. .Offset to pData2 in RqBlk . If a message is available it is returned to the caller immediately. This procedure allows a task to receive information from another task without blocking.[EAX+DataHi] EDX. .Data into ECX:EBX Get Storage Addr in EDX Put pLB^.

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

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

. NewTask() will allocate operating system stack space separately from the stack memory the caller provides as a parameter. .Data into ECX:EBX Get Storage Addr in EDX Put pLB^.Return the LB to the pool CLI MOV EBX.ECX .. . This is used primarily to create a task for a job other than the one you are in. The operating system job-management code uses this to create the initial task for a newly loaded job. pLBin^.NewTaskkernel primitive code. and any other access violations caused with result in a general protection violation.x. . .EBP POP EBP RETF 8 . continue . or page fault.Return Error. NewTask() This is the kernel NewTask() primitive. . pData2 PUSH EBX CALL FWORD PTR _AliasMem OR EAX.19. . .0 Page 332 of 667 . See listing 18.pCkMessage [EDX].Error?? . stack protection is not provided for transitions through the call gates to the OS code. The change will be transparent to applications when it occurs. EAX JZ Chk032 POP EBX MOV ESP.Offset to pData2 in RqBlk . The protect in this version of MMURTL is limited to page protection. .Make stack right . This creates a new task and schedules it for execution. STI .Restore Ptr to Link Block Get pLB^.[EAX+DataHi] EDX.EBP POP EBP RETF 8 Chk032: POP EAX Chk04: MOV MOV MOV MOV MOV EBX. pFreeLB <= pLBin. MOV [EAX+NextLB].EBX ..EAX . .ADD EBX. In future versions. .Data in specified memory space (EDX) .19. For MMURTL version 1. Procedural interface: MMURTL V1.Linear Address of PData1 .EAX MOV ESP.[EAX+DataLo] ECX. MOV pFreeLB. . XOR EAX. INC _nLBLeft . Listing 18.ErcOK! (0) . .pFreeLB .No.EBX [EDX+4]. .Next <= pFreeLB.

.EAX JNZ NT0002 MOV EAX. . . . Priority.TRUE for DEBUGing NTS_Exch EQU [EBP+20] . NTS_CS .8 for OS.ercNoMoreTSSs JMP NTEnd NT0002: MOV EBX. ECX . pFreeTSS <= pFreeTSS^.0 Page 333 of 667 . 18h for user task NTS_Pri EQU [EBP+28] . fDebug. ESP. NTS_EIP .mov ESP into TSS [EAX+TSS_ESP].DL . NTS_Exch ECX... . .ercOutOfRange NTEnd .EBX DEC _nTSSLeft STI .Initial stack pointer NTS_EIP EQU [EBP+12] .Priority of this task NTS_fDbg EQU [EBP+24] .0202h . .EBX .EBX [EAX+TSS_ESP0]. NewTask(JobNum.Exch in range? EDX. Exch. CodeSeg. ECX. . NTS_Job EQU [EBP+36] .EBX EBX.pFreeTSS OR EAX. . NTS_Pri EDX.Task start address PUBLIC __NewTask: PUSH EBP MOV EBP. EIP): dErcRet .Save pNewTSS .Priority OK? PUSH EAX MMURTL V1.EAX now has pNewTSS MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV [EAX+Priority].we can’t be interrupted .ESP MOV CMP JBE MOV JMP NT0000: MOV CMP JBE MOV JMP NT0001: CLI MOV EAX.No.ercBadPriority NTEnd .mov CS into TSS [EAX+TSS_CS]. nExch NT0001 EAX.CX .put Priority into TSS DWORD PTR [EAX+TSS_EFlags].[EAX+NextTSS] MOV pFreeTSS. . NTS_ESP . IF pFreeTSS=NIL THEN EIP into TSS (Start Address) [EAX+TSS_EIP]. ECX. NewTSS <= pFreeTSS.Exchange for TSS NTS_ESP EQU [EBP+16] .Load the Flags Register [EAX+TSS_Exch].Put new Exch in TSS (ECX is free) EBX.Job Num for this task NTS_CS EQU [EBP+32] . .Next . nPRI-1 NT0000 EAX.

Put Physical Add for PD into TSS_CR3 CMP DWORD PTR NTS_fDbg.Now we get pJCB from JobNum they passed in so we can .pNewTSS into EAX MOV [EAX+TSS_CR3]. .0 Page 334 of 667 .BX INC _nSwitches MOV EAX.Debug on entry? JE NT0004 . [ECX+JcbPD] PUSH EAX . EAX POP EAX .get the PD from the JCB MOV EAX.Set up to call GetpJCB .EAX now has pJCB . NTS_Job CALL GetpJCB MOV ECX.Move new TSS into pRunTSS .Just put new guy on the ReadyQue (EAX) XOR EAX. .[EAX+Tid] MOV TSS_Sel.EAX NTEnd: STI MOV ESP.Save New TSS . NTS_Job .Get Physical Address for PD into EAX MOV EBX.Get who’s running CMP BYTE PTR [EDX+Priority].EDX PUSH EDX CALL enQueueRdy POP EAX MOV pRunTSS.We can’t be interrupted MOV EDX.CrntTSS -> EAX.EBP POP EBP RETF 28 .New guy does (lowest num) CALL enQueueRdy . NTS_Pri .Set up to call LinToPhy .Who got the highest Pri? JA NT0005 . . .ercOk JMP NTEnd .Save time of this switch for scheduler .Yes NT0004: MOV EBX. EAX JMP FWORD PTR [TSS] XOR EAX. TimerTick MOV SwitchTick.ErcOk MMURTL V1.pRunTSS .. .Save pNewTSS again MOV EAX. 0 .EBX .Get priority of new task CLI . New TSS -> EDX .Put pJCB into TSS .ECX now has pJCB . .BL .Jump to new TSS . 1 .EAX .ECX MOV EBX.New TSS -> EAX .Put Selector/Offset in "TSS" .Return to caller NT0005: XCHG EAX. .Restore pNewTSS to EAX . CALL LinToPhy .No MOV WORD PTR [EAX+TSS_TrapBit].EAX MOV BX. EAX POP EAX MOV [EAX+TSS_pJCB].

bad news .No. NewExchST PUSH EAX CALL FWORD PTR _AllocExch OR EAX.20.Dealloc Exch if we didn’t get a TSS PUSH NewExchST CALL FWORD PTR _DeAllocExch MOV EAX. 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] . As with NewTask(). fOSCode). 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.. pStack. IF pFreeTSS=NIL THEN Return.pFreeTSS OR EAX.20.we can’t be interrupted .Priority OK? PUBLIC __SpawnTask: PUSH EBP MOV EBP. . .. . .Yup.ESP SUB ESP. . .ercBadPriority JMP STEnd ST0001: LEA EAX. .two local DWORD vars . It’s the easy way to create additional tasks for existing jobs.EAX JNZ ST0002 STI .0 Page 335 of 667 .Allocate exchange . SpawnTask(pEntry.see if we got an error . .Allocate a new TSS CLI MOV EAX. dPriority.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.SpawnTask kernel primitive code . nPRI-1 JBE ST0001 MOV EAX. EAX JNZ STEnd . fDebug. Listing 18.ercNoMoreTSSs . . NewTSS <= pFreeTSS. JMP NTEnd MMURTL V1. Procedural Interface: . 8 CMP dPriST.

TimerTick MOV SwitchTick. .ErcOk .0 Page 336 of 667 . 0 ST0003 WORD PTR [EAX+TSS_CS].EBX MOV [EAX+TSS_ESP0].[EAX+NextTSS] MOV priority into BL .Debug on entry? JE ST0004 .ST0002: MOV EBX.New TSS -> Stack .mov exch into TSS [EAX+TSS_Exch]. JobCodeSel .EAX STEnd: .mov ESP into TSS MOV [EAX+TSS_ESP]. EAX JMP FWORD PTR [TSS] XOR EAX. New TSS -> EDX . [EBX+TSS_pJCB] .[EAX+Tid] MOV TSS_Sel.Get CR3 from crnt task MOV [EAX+TSS_CR3]. . EAX .EBX DEC _nTSSLeft STI MOV MOV MOV MOV CMP JNE MOV ST0003: MOV EBX.EAX MOV BX.0202h .put in TSS .EDX PUSH EDX CALL enQueueRdy POP EAX MOV pRunTSS.EDX MOV EDX.No MOV WORD PTR [EAX+TSS_TrapBit].Who got the highest Pri? . .pEntryST . pStackST .EAX JMP STEnd ST0005: XCHG EAX. OSCodeSel .Load the Flags Register CMP fDebugST.EDX . 0 . NewTSSST.EBX MOV EBX. pRunTSS MOV EDX.Get pJCB from Crnt Task MOV [EAX+TSS_pJCB].Return to caller . New guy does (lowest num) .EBX WORD PTR [EAX+TSS_CS]. move into new TSS MOV DWORD PTR [EAX+TSS_EFlags].CrntTSS -> EAX.BL JA ST0005 CALL enQueueRdy XOR EAX.Jump to new TSS .Defaults to OS code selector fOSCodeST. [EBX+TSS_CR3] .BL CLI MOV EDX.Put Selector/Offset in "TSS" . NewExchST . 1 .Old guy does.we can’t be interrupted .Save time of this switch for scheduler .Yes ST0004: MOV EBX.Place crnt TSS on Q .Get who’s running . .EBX MOV EBX. just put new guy on Q. dPriST MOV [EAX+Priority].Move new TSS into pRunTSS .BX INC _nSwitches MOV EAX.pRunTSS CMP [EDX+Priority].Save new TSS EBX.Next . pFreeTSS <= pFreeTSS^.Make OS code selector MMURTL V1.New TSS -> EAX .ercOk .If crnt >.mov EIP into TSS MOV [EAX+TSS_EIP].

. JUMP ADD EBX. returned.EAX . Make the msg/TSS queue NIL DWORD PTR [EBX+ETail]. Listing 18.ercOK (0) ESP.[EDX+TSS_pJCB] [EBX+Owner].ercNoMoreExch .STI MOV ESP. Get the pExchRet in EDX . . Get number of exchanges in ECX MMURTL V1. Make the Exch owner the Job . RETF 4 .ESI EDX. POP EBP . Zero the Exch Index . _nEXCHLeft . . 0 . . It is property of a Job (not a task). AllocExch() This is an auxiliary function to allocate an exchange.ESI MOV EBX.0 .ESP XOR ESI. Point to the next Exchange INC ESI .[EBP+0CH] [EDX].21.21. Increment the Exchange Index LOOP AE000 .EBP . .EBP . Is this exchange free to use JE AE001 . Get the pJCB in EAX . .pRunTSS EAX. Get pRunTSS in EDX . DWORD PTR [EBX+EHead]. . EBX <= ADR rgExch . AllocExch(pExchRet):dError . Keep looping until we are done STI .nExch AE000: CLI . The exchange is a "message port" where you send and receive messages. . The Exchange Handle is a DWORD (4 BYTES). Put Index of Exch at pExchRet . . . AE001: MOV MOV MOV MOV MOV STI MOV MOV DEC XOR MOV EDX. MOV EAX.Allocate exchange code . pExchRet is a pointer to where you want the Exchange Handle . See listing 18. Procedural Interface : . CMP DWORD PTR [EBX+Owner]. Stats EAX.EBP POP EBP RETF 20 .prgExch MOV ECX.EAX . If we found a Free Exch. PUBLIC __AllocExch: PUSH EBP MOV EBP. .0 Page 337 of 667 .sEXCH . There are no instances of the MOV ESP.0 .

[EBP+0CH] EAX. . Messages are deQueued. . and request blocks are freed up as necessary.EBP POP EBP RETF 4 DE000: CLI . .22.sEXCH EDX EDX. . Listing 18. . . . and link blocks. Those link blocks may also contain requests. . .prgExch EAX.0 . ECX . it may have left over link blocks hanging on it.Deallocate exchange code . . PUBLIC __DeAllocExch: PUSH EBP MOV EBP. TSSs. however. DeAllocExch(Exch):ercType .EAX . . . Get the message off of the Exchange OR EAX.[EDX+TSS_pJCB] MOV EDX. . OFFSET MonJCB JE DE000 MOV EAX. is this the OS??? yes MOV EDX. DeAllocExch() This is the compliment of AllocExch(). When an exchange is returned to the free pool of exchanges. See if a message may be queued JE DE001 . ESI must point to Exch for deQueue CALL deQueueMsg . . .22.POP EBP RETF 4 .ercNotOwner MOV ESP. This function has a lot more to do. .ESI EDX. Exch is the Exchange Handle the process is asking to be released.ESP MOV MOV MOV MUL MOV ADD MOV ESI. . . yes if not owner. . Go check for Task (TSS) MOV ESI.EDX JE DE000 CMP EBX. Procedural Interface : . . . . No. This means that DeAllocExch() must also be a garbage collector. 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. See listing 18. Yes.0 Page 338 of 667 .pRunTSS MOV EBX.EDX ECX. . CMP DWORD PTR [ECX+fEMsg]. . EAX MMURTL V1.[EAX+Owner] CMP EBX.

Check to See if TSS is queued JE DE002 .Return the LB to the pool MOV EBX. pFreeLB <= pLBin. MOV pFreeLB.Next <= pFreeLB.EAX .EBX . .only happen if a system service writer doesn’t follow . NIL = Empty. Go free the Exch. MOV [EAX+NextTSS].GetTSS exchange code.Next <= pFreeTSS. Get the TSS off of the Exchange .23.0 Page 339 of 667 .23.pFreeTSS . ECX . MMURTL V1. . ESI must point to Exch for deQueue CALL deQueueTSS . Listing 18. This is primarily provided for system services that provide direct access blocking calls for customers.EBP . XOR EAX. If we find an RqBlk on the exchange we must respond . Nothing there. Make TSS invalid MOV pFreeTSS.EBX . GetTSSExch(pExchRet):dError . Go And Check for more.ercOK (0) MOV ESP. INC _nEXCHLeft . MOV DWORD PTR [ECX+fEMsg]. JMP DE001 . 0 . . DE002: MOV DWORD PTR [ECX+Owner]. 0 . GetTSSExch() This is an auxiliary function that returns the exchange of the current TSS to the caller. See listing 18. Go And Check for more. INC _nLBLeft . Stats STI . . MOV DWORD PTR [EAX+TSS_pJCB].with ErcInvalidExch before we continue! This will . 0 . These services use the default exchange in the TSS to make a request for the caller of a procedural interface.Free up the TSS (add it to the free list) MOV EBX. Procedural Interface : . pFreeTSS <= pTSSin.EAX .instructions or a service crashes! .EAX . Reset msg Flag. RETF 4 . JMP DE000 . pLBin^. POP EBP . MOV [EAX+NextLB].JZ DE002 . Free up the exchange.pFreeLB . . DE001: CMP DWORD PTR [ECX+EHead]. 0 . INC _nTSSLeft . JUMP MOV ESI. pTSSin^.

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

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

Page Allocation Map . Listing 19.1 is the data segment portion of the memory management code.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 . MEMALIAS MEMUSERD MEMUSERC MEMSYS ALIASBIT PRSNTBIT .INCLUDE MOSEDF.Deafult is 32 (1Mb) .Semaphore exchange for Mem Mgmt .INC .1 bit/4Kbytes . The variable naming conventions should help you follow the code.Number of free physical pages left . Memory Management Code Introduction The memory management code is contained in the file MemCode.User Readable (Code) . Memory Management Data Three important structures that you modify and deal with are the page allocation map (PAM).INC . The following definitions are used to set memory pages for specific access rights: .Memory management constants and data .Chapter 19.INCLUDE TSS. It begins with data definitions that are used by all the private (local) and public functions.Max is 2048 (64Mb) . The task state segments are also used extensively.Default to 1 MB .0 Page 342 of 667 .INCLUDE JOB. page directories (PDs) and page tables (PTs).We alloc the "next" page table .DATA .User Writable (Data) . Listing advance (MANDATORY) It was easier for me to define masks in assembly language.2048 bytes = 64Mb .User Writable .ASM. EQU EQU EQU EQU EQU EQU 20 Bit Address AVL00DA00UWP 00000000000000000000100000000111b 00000000000000000000000000000111b 00000000000000000000000000000101b 00000000000000000000000000000101b 00000000000000000000100000000000b 00000000000000000000000000000001b .1.System Read/Write MMURTL V1.

As with all assembler files for DASM.3 ADD EAX.EAX SUB EAX. It places the highest addressable offset in global oMemMax.CODE command.EBX JMP MemLoop MemLoopEnd: . Listing 19.ECX JNE MemLoopEnd ADD EAX.3 MOV _oMemMax. which was just loaded. you have. and the Video RAM and Boot ROM.OUT: .EBX MOV ECX. It sets nPagesFree after finding out just how much you have. Now you must fill in bits used by OS.Increase PAM by another meg .NO! .DWORD PTR [EAX] CMP EBX. 256 ADD sPAM.Are we above 64 megs . .2. the code section must start with the . oMemMax must be last byte . See listing 19. InitMemMgmt InitMemMgmt finds out how much memory.Yes! . in megabytes.Move in test string .USED: .See if we got it back OK .Next Meg . 256 MOV EAX. We assume 1MB to start.Yes.Memory management initialization code . 32 CMP EAX. The code in listing 19.ECX MOV EBX.Zero out for next meg test The Page Allocation Map is now sized and Zeroed.1FFFFCh XOR EBX.3 also fills out each of the page table entries (PTEs) for the initial OS code and MMURTL V1.0h MOV DWORD PTR [EAX].100000h ADD _nPagesFree. InitMemMgmt: MOV _nPagesFree.Make it the last DWord again .06D72746CH MEMLoop: MOV DWORD PTR [EAX].3FFFFFCh JAE MemLoopEnd XOR of 2 megs (for DWORD) .1 Mb of pages = 256 . neither of which you consider free. PUBLIC Nothing Nothing (except that you can use memory management routines now!) ALL REGISTERS ARE USED.Another megs worth of pages .Read test string into EBX . by writing to the highest dword in each megabyte until it fails a read-back test. which means we start at the top of 2Mb (1FFFFCh).Set oMemMax . The first section comprises all of the internal routines that are used by the public functions users access.Internal Code The code is divided into two basic sections.0 Page 343 of 667 . I will describe the purpose of each of these functions just prior to showing you the code.Set it to zero intially . You also calculate the number of pages of physical memory this is and store it in the GLOBAL variable nPagesFree. I call these internal support code.’mrtl’ test string value for memory .IN: .2.

100000h . Right now you just mark everything from A0000 to FFFFF as used.1Mb yet? JAE IMM004 .Make Page Table Entry AND DWORD PTR [EDX]. This first part MARKS the OS code and data pages as used . SHL 2) .Marks page in PAM ADD EDX. .Continuation of memory management init. EDX.Mark it used in the PAM ADD EDX.4. and makes PTEs. allow you to set ROM areas as usable RAM. 4096 .Points to 128K Video & 256K ROM area . This covers A0000 through 0FFFFFh. 4 . MOV EDX. 0FFFFF000h . EBX.4 could be expanded to search through the ROM pages of ISA memory. IMM002: MOV MOV SHR MOV ADD IMM003: MOV [EDX].Supervisor.Leave upper 20 Bits OR DWORD PTR [EDX]. but I can't be sure everyone can do it. Present MOV for MarkPage call CALL MarkPage .No. OFFSET pTbl1 . 30000h .EDX pts to Page Table MMURTL V1. Its the law! Listing 19. Several chip sets on the market. finding the inaccessible ones and marking them as allocated in the PAM. EBX.Point to 1st physical/linear page (0) IMM001: MOV [EDX]. . 0101b . Listing 19. which is the upper 384K of the first megabyte. nor can I provide instructions to everyone. 0A0000h EAX 10 OFFSET pTbl1 EBX . Note that linear addresses match physical addresses for the initial OS data and code.Make it index (SHR 12. C0000h –FFFFFh.Next table entry ADD EAX. EAX .Reserve 192K for OS (for now) JAE SHORT IMM002 JMP SHORT IMM001 . EDX.Leave upper 20 Bits OR DWORD PTR [EDX]. 4 . such as the 82C30 C&T. EAX CALL MarkPage .Make Page Table Entry AND DWORD PTR [EDX].Next page please CMP EAX. 0001h . EAX . EAX .Next PTE entry ADD EAX.3.Go for more Now you fill in PAM and PTEs for Video and ROM slots. . The routine in listing 19.0 Page 344 of 667 .Continuation of memory management init. go back for more EAX.EDX points to OS Page Table 1 XOR EAX. EAX .Yes JMP SHORT IMM003 .Mark it "User" "ReadOnly" & "Present" MOV EBX. 0FFFFF000h . 4096 CMP EAX.

Get ’em! MMURTL V1. OFFSET PDir1 . PUSH 1 . Now we can go into paged memory mode. 1 page for Next Page Table MOV EAX. OFFSET pNextPT . I’m afraid.6.6.Clear prefetch queue 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.5. PUSH EAX CALL FWORD PTR _AllocOSPage .Physical address of OS page directory CR3.Finishing memory management initi.Get Control Reg 0 EAX. The initial page directory and the page table are static. CR0 . Listing 19.7.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. EAX . See listing 19. Listing 19. LEA EAX.Turning on paged memory management IMM004: MOV MOV MOV OR MOV JMP IM0005: .Store in Control Reg 3 EAX.. ANDing it with 8000000h.Store Control Reg 0 IM0005 . 80000000h . Listing 19. and then writing it again.Allocation of memory management exchange. See listing 19.7. See listing 19. EAX.Set paging bit ON CR0.5. EAX .0 Page 345 of 667 . This is done by loading CR3 with the physical address of the page directory.Alloc Semaphore Exch for Memory calls PUSH EAX CALL FWORD PTR _AllocExch PUSH PUSH PUSH CALL MemExch . MemExch . . 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. After the MOV CR0 you must issue a JMP instruction to clear the prefetch queue of any bogus physical addresses. then reading CR0.

It also marks the page as used. .No .8.OUT : . 0 FHPn EAX SHORT FHP1 MMURTL V1.Test bits . . 12 _nPagesFree EDX ECX .. or 0 if error EBX. 0 FHPn EBX FHP3 BYTE PTR [ECX+EAX].Where we are in PAM . Listing 19. this means if you call FindHiPage and don’t use it you must call UnMarkPage to release it.RETN . . OFFSET rgPAM MOV EAX. EBX MOV BYTE PTR [ECX+EAX].Done initializing memory managment FindHiPage FindHiPage finds the first unused physical page in memory from the top down and returns the physical address of it to the caller.FOUND ONE! (goto found) .Set the bit indexed by EBX . Flags PUSH EAX PUSH ECX PUSH EDX MOV ECX.EAX+ECX will be offset into PAM .USED: Nothing EBX is the physical address of the new page. This reduces nPagesFree by one. ..Multiply time 8 (page number base in EDX. EDX MOV DL. EAX EBX.Set page in use . 7 XOR EDX. Of course.0 Page 346 of 667 . See listing 19. BYTE PTR [ECX+EAX] FHP3: BT JNC CMP JE DEC JMP FHPf: BTS EDX.Another Byte lower .Find highest physical page code..One less available .Next bit FHP1: CMP JNE CMP JE DEC JMP FHP2: MOV EBX. DL SHL EAX. . 3 byte) ADD SHL DEC POP POP EBX.Now EBX = Physical Page Addr (EBX*4096) .0FFh FHP2 EAX. sPAM DEC EAX .Page Allocation Map . assuming that you will allocate it.All 8 pages used? .Are we at Bottom of PAM? .Add page number in byte .Get the byte with a whole in it.8.At the bottom of the Byte? . .Back for next byte . EBX FHPf memory left.Error (BAD CPU???) .IN : ..

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

0FFFFF000h .USED: EBX is the physical address of the page to mark Nothing EBX.10.One less available . EBX . Flags PUSH EAX PUSH ECX PUSH EDX MOV EAX. This function is used with the routines that initialize all memory management function.0 Page 348 of 667 . MarkPage finds the bit in the PAM associated with it and sets it to show the physical page in use. .IN : .Now EBX = Physical Page Addr (EBX*4096) . OFFSET rgPAM . [EAX+ECX] .ADD EBX.BitSet nstruction with Bit Offset MOV [EAX+ECX].10. See listing 19. 12 .One less available POP EDX POP ECX POP EAX RETN MMURTL V1.Set to zero for error MarkPage Given a physical memory address.Page Allocation Map AND EBX. 15 .Code to mark a physical page in use. EBX POP EDX POP ECX POP EAX RETN .Save the new PAM byte DEC _nPagesFree .Get the byte into DL BTS EDX.ECX is now byte offset into PAM SHR EBX. EBX SHR ECX. Listing 19.Add page number in byte . . DL .EBX is now bit offset into byte of PAM MOV DL. This reduces nPagesFree by one. 07h . EAX SHL EBX. 12 DEC _nPagesFree POP EDX POP ECX POP EAX RETN FLPn: XOR EBX.Get Bit offset into PAM AND EBX.Round down to page modulo 4096 MOV ECX.OUT : .

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

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

.If we got here it means that ECX has made it to zero and . . 22 SHL EAX.Return 0 cause we didn’t find it! JMP SHORT FREXIT .If . [ESI+EDX*4] . [EAX+JcbPD] ADD ESI.EDX is 10 MSBs of Linear Address . 1024 FR3 EDX SHORT FR0 . EBX FREXIT: POP EDI POP ESI . next PTE please ECX. EAX FR2: CMP JB INC JMP FR3: CMP DWORD PTR [EDI+EAX*4].Not empty.Is the address NON-ZERO (valid)? JNZ FR1 .In use . 2048 FR0: MOV EDI.0 Page 351 of 667 .Next PT! .Are we past last PTE of this PT? .This is the linear address we ended at .Zero means it’s . 12 SUB EAX. FR1: XOR EAX. .EAX is next 10 bits of LA . keep testing .empty (available) .From current linear address in tables we got here we must reset ECX for full count and back and start looking again EAX . SHL EDX.Yes.EBX still has count of pages.Lin address of next page table into EDI OR EDI.Keeps count of how many free found so far . EAX .Times size of page (* 4096) .we have a linear run large enough to satisy the request.EDX was index into PD. .Not done yet.Move to shadow addresses MOV ECX.One more empty one! . Save in EBX . . EAX CALL GetpCrntJCB MOV ESI. . EDX DEC EBX SHL EBX. EDI . .Address of PD saved here .Leaves pCrntJCB in EAX .No.go INC MOV JMP FROK: . EBX MOV EDX.Index into shadow addresses from EAX .We found enough entries goto OK . Next PTE Please. EAX.PTE we found minus ((npages -1) * 4096) .We kept original count in EBX FR2 FR4 ECX FROK EAX SHORT FR2 . go for it XOR EAX. while EAX was index into PT. 0 JNE DEC JZ INC JMP FR4: .PUSH PUSH PUSH PUSH ECX EDX ESI EDI .One less page (0 offset) .Copy number of pages to ECX.The starting linear address is equal to number of the last .ESI now has ptr to PD . 12 OR EAX.EAX indexes into PT (to compare PTEs) MMURTL V1. EBX .Index into PD for PT we are working on .

. EBX use).Restore linear address . EDX MOV EDX. Above 1Gb is user which we will set to user level protection.Leaves pCrntJCB in EAX .LinAdd into EDX again .14. USED: . EAX SHR EDX. 003FF000h SHR EDX. [EAX+JcbPD] POP EAX ADD ESI. Listing 19. AddRun: EAX Linear address of first page EBX Number of Pages to add Nothing EAX. or tables.Copy number of pages to ECX (EBX free to . . . IN : . and the count of pages should be in EBX. . . MOV EDX. If it is less than 1GB it means operating-system memory space that you will set to system. This is the way FindRun left them. .Save EAX thru GetpCrntJCB call .0 Page 352 of 667 . EAX AND EDX. EFlags PUSH PUSH PUSH PUSH PUSH EBX ECX EDX ESI EDI .POP EDX POP ECX POP EBX RETN . 2 PUSH EAX CALL GetpCrntJCB MOV ESI.Index into PD for PT (10 vice 12 -> DWORDS) . See listing 19.ESI now points to initial PT (EDX now free) .Offset to shadow address of PD . The linear address of the run should be in EAX. 2048 ADD ESI.14. AddRun AddRun adds one or more page table entries (PTEs) to a page table. OUT: . The address determines the protection level of the PTE’s you add.ESI now has ptr to PD! . [ESI] MMURTL V1. if the run spans two or more tables. Many functions that pass data in registers are designed to compliment each other’s register usage for speed.(save for caller) .Linear address of next page table into EDI MOV ECX.get rid of upper 10 bits & lower 12 . .Get index into PD for first PT . 22 SHL EDX. . 10 AR0: MOV EDI.Make it index to DWORDS . .LinAdd to EDX .Adding a run of linear memory.

. ARDone: POP EDI POP ESI POP EDX POP ECX POP EBX RETN . .At this point. EBX .Yes. 4 .Now we must call FindPage to get a physical address into EBX.15. Listing 19. . The ESI register has the linear address you are aliasing and the EDX register has the job number. THEN store it in PT. . OUT: EAX EBX ESI EDX Linear address of first page of new alias entries (from find run) Number of Pages to alias Linear Address of pages to Alias (from other job) Job Number of Job we are aliasing Nothing MMURTL V1.Are we past last PTE of this PT? JB AR1 . AR1: CALL FindHiPage OR EBX.See if it’s a user page . if the run spans two or more tables .and OR in the appropriate control bits.EBX has Phys Pg (only EBX affected) . See listing 19.Sets User/Writable bits of PTE AddAliasRun AddAliasRun adds one or more PTEs to a page table . 4096 . . and the count of pages should be in EBX. go do next PTE ADD ESI.Next PTE please.then check the original linear address to see if SYSTEM or USER . . . CMP EDX.Start at the entry 0 of next PT JMP SHORT AR0 . . . Once again. .Set PTE to present. . MEMSYS CMP EAX.0 Page 353 of 667 . User ReadOnly .Are we done?? JZ ARDone ADD EDX. IN : .or tables.15. EDI is pointing the next PT.No. 40000000h JB AR2 OR EBX. next PDE (to get next PT) XOR EDX. Aliased runs are always at user protection levels even if they are in the operating-system address span.Adding an aliased run of linear memory.. .EDX is index to exact entry DEC ECX . .EDX .adding PTEs from another job’s PTs marking them as alias entries. MEMUSERD AR2: MOV DWORD PTR [EDI+EDX]. The new linear address of the run should be in EAX. . this is the way FindRun left them.SO EDI+EDX will point to the next PTE to do. 4 . .

EAX now has physical address for this page MMURTL V1.LinAdd to EDX . . USED: EAX.This is the Physical address to store in the new PTE.Set up for next loop (post increment) . . 10 ALR0: MOV EDI. [EAX+JcbPD] POP EAX ADD ESI. . ESI MOV AliasJob.Save for next loop (used by LinToPhy) .Restore linear address . MOV EDX. EAX AND EDX.mark it MEMALIAS before adding it to PT.Save EAX thru GetpCrntJCB call .. 003FF000h SHR EDX. [ESI] . .Offset to shadow addresses in PD .get rid of upper 10 bits & lower 12 .Job we are aliasing . to get a physical address into EAX.Address we are aliasing . AliasLin ADD AliasLin.SO then EDI+EDX will point to the next PTE to do.ESI now has linear address of PD . EDI is pointing to the PT we are in. 2048 ADD ESI. EDX MOV EDX. 22 SHL EDX.Copy number of pages to ECX (EBX free to .Linear address of crnt page table into EDI .Make it index to DWORDS . EFlags . . . . 4096 CALL LinToPhy We must .At this point. . .ESP SUB ESP. .we have to move the other guy’s physical pages into MOV ECX. ALR1: PUSH ESI MOV EAX.Leaves pCrntJCB in EAX .0 Page 354 of 667 .This first section sets to make [ESI] point to first PT that . EAX SHR EDX. AliasJob MOV EBX. EDX PUSH PUSH PUSH PUSH PUSH EBX ECX EDX ESI EDI .(save for caller) . EBX use).Get index into PD for first PT .Index into PD for PT (10 vice 12 -> DWORDS) .LinAdd into EDX again . 2 PUSH EAX CALL GetpCrntJCB MOV ESI. AliasLin EQU DWORD PTR [EBP-4] AliasJob EQU DWORD PTR [EBP-8] AddAliasRun: PUSH EBP MOV EBP.Now we must call LinToPhy with Linear Add & JobNum . .ESI now points to first PT of interest . 8 MOV AliasLin.

. Individual PTEs will be set read-only for code.Now store it in new PTE MOV DWORD PTR [EDI+EDX]. as shown in the following. EFlags . Listing 19.No. initializes it. EAX DEC ECX JZ ALRDone ADD EDX. This sets the protection on the PT to user-Read-and-Write.EBP POP EBP RETN AddUserPT AddUserPT creates a new user page table. and sticks it in the user’s page directory.Adding a page table for user memory ..Yes. OUT: 0 if OK or Error (ErcNoMem . MEMALIAS POP ESI . USED: EAX. . .16.Are we past last PTE of this PT? . This is easier than AddOSPT. . See listing 19.EDX is index to exact entry . AddUserPT: PUSH EBX .(save for caller) PUSH ECX .Start at the entry 0 of next PT . PUSH ESI . This will be in user address space above 1GB. next PDE (to get next PT) . go do next PTE . AND EAX.16.Are we done?? .EDX JMP SHORT ALR0 ALRDone: POP POP POP POP POP EDI ESI EDX ECX EBX . IN : Nothing .no free phy pages!) . MMURTL V1. .Set system bits as ALIAS . .Restore ESI (LinToPhy used it) .cut off system bits of PTE . 4 CMP EDX.0 Page 355 of 667 . . MOV ESP. 4 XOR EDX. . PUSH EDX .Next PTE please. 4096 JB ALR1 ADD ESI. because there is no need to update anyone else’s PDs. . 0FFFFF000h OR EAX.

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

ErcNoMem JMP AOPTDone AOPT01: MOV EAX. PUSH ESI . AddOSPT: PUSH EBX . ESI EAX Get and . [ESI] OR EBX.Set present bit OR EAX. .no free phy pages!) . Next possible empty entry .Adding an operating system page table.See if have enuf physical memory . . EBX LOOPNZ AOPT02 .17. pNextPT CALL LinToPhy . . EFlags . OUT: 0 if OK or Error (ErcNoMem . MOV EAX.(save for caller) PUSH ECX . it won’t happen except once or twice while the operating system is running. out of physical mem .17. PUSH EDI .POP EBX RETN . PRSNTBIT MMURTL V1. 1 MOV EBX. No! (Try again) . .Pre allocated Page (Linear Address) . ESI points to OS Pdir . _nPagesFree OR EAX. OFFSET PDir1 MOV ECX.Sorry.0 Page 357 of 667 . 4 MOV EBX. EAX JNZ AOPT01 MOV EAX. in many cases. Count of PDEs to check now points to empty PDir Slot still has Physical Address of new table Physical Address back into EBX put them into PDir . USED: EAX. Adding an operating-system page table doesn’t happen often. IN : Nothing . Find first empty slot MOV ESI. Listing 19. 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. PUSH EDX . AddOSPT AddOSPT is more complicated than AddUserPT because a reference to each new operatingsystem page table must be placed in all user page directories. .EAX will have Physical address . Is it empty? . . Put it in the OS PD (and linear in shadow). 511 AOPT02: ADD ESI.OS Job Number (Monitor) . See listing 19.

EAX . EAX ESI. OFFSET PDir1 PUSH EDX PUSH EAX PUSH EBX PUSH PUSH PUSH CALL POP POP EBX . 1 XOR EAX.Move to shadow PUSH EBX ADD EAX.Lower half of PD (Physical Adds) FWORD PTR _CopyData EBX EAX .We now need another PreAllocated Page Table for . pNextPT [ESI].No.At this point the new table is valid to ALL jobs! .Get values from stack ADD EBX.Source of Copy .Move to shadow .0 Page 358 of 667 .Destination 1024 . 2 JA AOPT03 .Save nJCB we are on . Get back JCB number .next time.Move to shadow PUSH EAX PUSH 1024 .See if PD id zero (Inactive JCB) .Source EAX .MOV ADD MOV MOV . 2048 EBX. [ESI].Save values on stack MOV EDX.PD shadow offset needed by FindRun (0) . 2048 .Next JCB .Is it a valid Job? (0 if not) . . 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) . EAX CALL FindRun OR EAX.Upper half of PD (Linear Adds) CALL FWORD PTR _CopyData POP EDX AOPT04: DEC EDX CMP EDX. Not a valid JCB (unused) . . # of dynamic JCBs .Jobs 1 & 2 use PDir1 (Mon & Debugger) .was there an error (0 means no mem) MMURTL V1. [EAX+JcbPD] OR ECX.Linear add back into EBX .size of request .Physical address in lower half . 2048 .EAX NOW points to PD of JCB . EDX CALL GetpJCB MOV ECX. JcbPD MOV EBX. . nJCBs AOPT03: MOV EAX. EBX .Put in Linear address of PT (upper half) Update ALL PDs from PDir1 !! This doesn’t happen often if it happens at all. ECX JZ AOPT04 ADD EAX.EAX now has pointer to a job’s PD . . Get a run of 1 for next new page table MOV EBX.

Not all of the calls are present here because some are very pNextPT (the linear address) . Listing 19. DPL entry of 2 CC0x (Not used in MMURTL) . OUT: EAX Returns Errors. else 0 if all’s well . ECX. DPL entry of 1 AC0x (Not used in MMURTL) .Word with Call Gate ID type as follows: . . DPL entry of 3 EC0x (most likely) . . IN: AX . The selector number is checked to make sure you’re in range. . This is 40h through the highest allowed call gate number. . (x = count of DWord params 0-F) . ESI Offset of entry point in segment of code to execute . This call doesn’t check to see if the GDT descriptor for the call is already defined. CX Selector number for call gate in GDT (constants!) . . . The calling conventions all follow the Pascal-style definition. AddGDTCallGate AddGDTCallGate builds and adds a GDT entry for a call gate. USES: EAX. EFLAGS PUBLIC __AddCallGate: MMURTL V1.JNZ AOPT05 MOV EAX. EAX AOPTDone: POP EDI POP ESI POP EDX POP ECX POP EBX RETN . ESI. See listing 19. .18. .Adding a call gate to the GDT. DPL entry of 0 8C0x (OS call ONLY) . . EBX. EAX CALL AddRun XOR EAX. 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. ErcNoMem JMP SHORT AOPTDone AOPT05: MOV pNextPT.Set ErcOK (0) . allowing access to OS procedures.AddRun .0 Page 359 of 667 . . .18.

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

Yes . but not close enough to easily combine their code. _nPagesFree .Yes! Serious problem.More than 0? . If the current PT doesn’t have enough contiguous entries. AX MOV WORD PTR [EDX+2].Kernel Error?? . 2.Wait at the MemExch for Msg . Ensure you have physical memory by checking nPagesFree. Allocate each physical page.n4KPages EAX.Allocating a page of linear memory. Procedureal Interface : . BX RETF . A result code is returned in the EAX register.These equates are also used by AllocPage ppMemRet EQU [EBP+0Ch] . .ercBadMemReq ALOSPExit . 16 MOV WORD PTR [EDX+6]. PUBLIC __AllocOSPage: PUSH EBP MOV EBP.SHR EAX.ESP PUSH MemExch MOV EAX. TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX. .Can’t be zero! . The steps are: 1. pRunTSS ADD EAX. MMURTL V1.Put in the selector 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. . we add another page table to the operating-system page directory.ppMemRet): dError . See listing 19. . Find a contiguous run of linear pages to allocate. n4KPages EQU [EBP+10h] . .0h JNE SHORT ALOSPExit MOV OR JNZ MOV JMP ALOSP00: CMP EAX. 3.Put Offset 31:16 into gate .20. The steps involved depend on the internal routines described above.20. .Put Msg in callers TSS Message Area .0 Page 361 of 667 . .size of request . AllocOSPage(dn4KPages. This allows runs to span table entries.See if have enuf physical memory EAX. Listing 19.EAX ALOSP00 EAX. . 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. AllocPage and AllocDMAPage are not shown here because they are almost identical to AllocOSPage.

.Give em new LinAdd .Go back & try again . This allows system services to access a caller memory for messaging without having to move data around. See if they are available. ppMemRet MOV [EBX]. if it crosses page boundaries. . EAX XOR EAX. No alias is needed. . . The pages are created at user protection level even if they are in operating memory space.0 Page 362 of 667 .Send a Semaphore msg (so next guy can get .Sorry boss. .Only an entry in a table.PD shadow offset needed by FindRun (0) . we’re maxed out . you need two pages. The step involved in the algorithm are: 1. .Add a new page table (we need it!) .EAX still has new linear address . This wastes no physical memory. EAX JZ SHORT ALOSP01 JMP SHORT ALOSPExit ALOSP02: . EAX ALOSPExit: PUSH EAX PUSH MemExch in) PUSH 0FFFFFFF1h PUSH 0FFFFFFF1h CALL FWORD PTR _SendMsg POP EAX MOV ESP. ErcNoMem JMP SHORT ALOSPExit ALOSP01: MOV EBX.ERROR!! CALL AddRun MOV EBX.size of request . See if the current PD equals specified Job PD.(0 = No Runs big enuf) . Even if the address is only two bytes. EAX CALL FindRun OR EAX. MMURTL V1.No Error! . 4. EAX JNZ SHORT ALOSP02 .EBX still has count of pages .Get original error back (ignore kernel erc) .EBP POP EBP RETF 8 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.JBE ALOSP01 MOV EAX.EAX now has linear address . Calculate how many entries (pages) will be needed. 2.Does not return error .Save last error .See if it’s 0 (0 = NO Error) .Get address of caller’s pointer . .If we didn’t find a run big enuf we add a page table CALL AddOSPT OR EAX.No error . If so. Make PTE entries and return alias address to caller.Yes . This is for the benefit of system services installed in operatingsystem memory.n4KPages XOR EAX. however . Exit. 3.

dcbMem. dJobNum. . 0 for OS . .Are they the same Page Directory?? .Save nPages in ECX . .Put Msg in callers TSS Message Area .pMem into EAX 0FFFh .Wait at the MemExch for Msg .Yes.Get Job number for pMem . PUSH MemExch MOV EAX. . . EBX. . pMem is the address to alias. EAX. EAX JMP ALSPDone .EAX is nPages-1 . .dcbMem EBX .MODULO 4096 (remainder) [EBP+20] .JobNum EQU [EBP+16] .No.Add the remainder of address DWORD PTR [EBP+20] . ppAliasRet): dError .Exit. . Procedureal Interface : . . . . [EBP+16] CMP EAX. . alias it .EBX is number of pages for run MMURTL V1. .EAX is 256 for user space. . PUBLIC __AliasMem PUSH EBP MOV EBP.0 Page 363 of 667 .pMem EQU [EBP+24] . . AliasMem(pMem. .Puts Current Job Num in EAX . No Error . we’re done ALSPBegin: . . JobNum is the job number that pMem belongs to. . . EAX. TSS_Msg PUSH EAX CALL FWORD PTR _WaitMsg CMP EAX.Add to dcbMem 12 . .Yes! Serious problem.ppAliasRet EQU [EBP+12] . EBX JNE ALSPBegin XOR EAX.ESP CALL GetCrntJobNum MOV EBX. .Listing 19. [EBP+24] .21.Now wait our turn with memory management .Kernel Error?? . We’re IN! ALSP00: MOV AND MOV ADD ADD SHR INC MOV EBX.Code to alias memory. . . EAX. EAX ECX.dcbMem EQU [EBP+20] . EAX.Now we find out whos memory we are in to make alias . dcbMem is the number of bytes needed for alias access.0h JNE ALSPDone .EAX is now nPages we need! EAX . ppAliasRet is the address to return the alias address to. pRunTSS ADD EAX.

EAX has 0 or 256 .pAliasRet .original pMem .No! Add a new USER page table MOV EDI.Was there enough PTEs? .OS Job? .OS Job? .Address to alias .Set up for OS memory space . 0FFFh EDI.Save last error . EAX ALSP01: MOV EBX. EAX MOV ESI.Send a Semaphore msg (so next guy can get .No! Add a new OS page table .No.Get remaining bits . 256 JMP SHORT ALSP01 ALSP011: XOR EAX. [EBP+16] CALL AddAliasRun .Now. EDI EAX.EAX is now linear address or 0 if no run is large enough .Set to 0 (no error) . take new alias mem and add trailing bits to address . User memory .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 . 1 JE SHORT ALSP011 MOV EAX. [EBP+24] MOV EDX.Save alias page address base . EAX JNZ ALSP04 CALL GetCrntJobNum CMP EAX.EBX still has count of pages OR EAX.Yes . PUSH 0FFFFFFF1h .We are done ALSPExit: .Set . PUSH EAX .ERROR!! . EAX ESI.EBX .Number of pages we need into EBX .Go back & try again .0 = NO Error .CALL GetCrntJobNum CMP EAX.EAX .Job number . EAX JZ SHORT ALSP00 JMP SHORT ALSPExit ALSP04: .0 Page 364 of 667 .Yes . MMURTL V1.See if it is OS based service . .Returned address to caller! .Yes . [EBP+12] [ESI]. ECX CALL FindRun .See if it is OS based service .and return to caller so he knows address (EDI is lin add) MOV AND ADD MOV MOV XOR EAX. 1 JE SHORT ALSP03 CALL AddUserPT JMP SHORT ALSP03 ALSP02: CALL AddOSPT ALSP03: OR EAX. EAX . [EBP+24] EAX. PUSH MemExch in) .

0 Page 365 of 667 ..Add to dcbMem EAX. [EBP+16] .dcbMem EAX.pMem into EAX EBX. [EBP+20] .AliasJobNum EQU [EBP+12] PUBLIC __DeAliasMem PUSH EBP MOV EBP.Calculate the number of pages to DeAlias MOV AND MOV ADD ADD SHR INC MOV MOV MOV DALM01: MOV EBX.EBP POP EBP RETF 16 . . DWORD PTR [EBP+20] . CALL FWORD PTR _SendMsg .22. . 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.Add the remainder of address EAX.EAX is now nPages we need! ECX. This would not interfere with any of the memory allocation routines. .dcbAliasBytes EQU [EBP+16] .Job num into EAX for LinToPhy . EDX MOV EAX. [EBP+20] . PUSH 0FFFFFFF1h . . .pAliasMem EQU [EBP+20] . [EBP+12] CALL LinToPhy . . DeAliasMem(pAliasMem.MODULO 4096 (remainder) EAX. . EBX .EAX is nPages-1 EAX . Listing 19.ESP . dcbAliasBytes. . 0FFFh .Linear Mem to DeAlias . dcbAliasBytes is the size of the original memory aliased .Now we have Physical Address in EAX (we don’t really need it) MMURTL V1. DeAliasMem DeAliasMem zeros out the page entries that were made during the AliasMem call. pAliasMem is the address to "DeAlias" .22. .Call this to get address of PTE into ESI EBX.ECX . EAX . Procedureal Interface : . See listing 19. . POP EAX ALSPDone: MOV ESP.Get original error back (ignore kernel erc) . 12 .Address of next page to deallocate .Code to remove alias PTEs.Save also in EDI (for compare) EDX. . .Number of pages into EDI & ECX EDI. JobNum):ercType .

and pointer to PTE in ESI (We NEEDED THIS).Is page present (valid)??? .DO NOT deallocate the physical page MOV EBX. .Is page an ALIAS? . . . ALIASBIT JZ DALM03 . MMURTL V1.We did some.None at all! .0 Page 366 of 667 . .23. XOR EAX. .Next linear page .so we zero out the page.23. See listing 19. 4096 LOOP DALM01 DALMExit: MOV ESP.If we fall out EAX = ErcOK already . if so just ZERO PTE. it’s page is present . EDI DALM011 EAX.Deallocating linear memory.We dealiased what we could.Get PTE into EBX . PRSNTBIT JNZ DALM02 CMP JNE MOV JMP DALM011: MOV EAX. this will deallocate or deAlias as much as it can before reaching an invalid page.ZERO PTE entry DeAllocPage This frees up linear memory and also physical memory that was acquired with any of the allocation calls.NO . .Yes.. providing it is valid.If we got here the page is presnt and IS an alias . . It will always free linear memory. . This will only free physical pages if the page is not marked as an alias. Procedureal Interface : .DO not zero it! .NO! (did we do any at all) . ErcBadLinAdd SHORT DALMExit . EAX DALM03: ADD EDX. Even if you specify more pages than are valid. ErcBadAlias JMP SHORT DALMExit DALM02: TEST EBX. .but less than you asked for! ECX.EBP POP EBP RETF 12 . EAX MOV [ESI]. [ESI] TEST EBX. Listing 19. .See if PTE is an alias.

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

Code to find number of free pages . . Listing 19.AND EBX.ESP MOV ESI.24. QueryMemPages(pdnPagesRet):ercType . pMemLeft MOV EAX. . . over memory error . left available returned .24. You can do this because you can give them any physical page in the system.If we fall out EAX = ErcOK already .Next linear page . . MMURTL V1. pdnPagesRet is a pointer where you want the count of pages . 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 . Their 1Gb linear space is sure to hold what we have left. . EAX MOV [ESI]. 0FFFFF000h CALL UnMarkPage DAP03: XOR EAX. .save Memory error . EAX .EBP POP EBP RETF 8 . Procedureal Interface : . See listing 19.0 Page 368 of 667 .get Memory error back .Kernel error has priority . .get rid of OS bits . . 0 JNE DAMExit1 POP EAX DAMExit1: MOV ESP. EAX ADD EDX. so next guy can get in . pMemleft EQU [EBP+0Ch] PUBLIC __QueryPages: PUSH EBP MOV EBP. _nPagesFree MOV [ESI]. QueryMemPages This gives callers the number of physical pages left that can be allocated.ZERO PTE entry . .

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

memory wait-states. because the timer interrupt sends messages for the Sleep() and Alarm() functions. a slow machine. I ended up with 10ms.6 percent for the worst case. See listing 20. along with a piece of the kernel. In a 10-ms period we have 200. as well as the fastest one possible. A single communications channel running at 19. The average is was far less. and anything less than 20-MHz all but disappearing. MMURTL is no exception. The numbers before each instruction are the count of clocks to execute the instruction on a 20-MHz-386. Even more important than total bandwidth. This doesn’t take into account nonaligned memory operands. But even with these calculations. preemptive multitasking.0 Page 370 of 667 . which works out to about 1. I added up all the clock cycles in the ISR for a maximum time-consuming interrupt. Somewhere between these two was the average. As you well know. It was 0. The average really depends on program loading.200 BPS will interrupt every 520us. I experimented with several different intervals. my worries were unfounded.1. I began with 50ms but determined I couldn’t get the resolution required for timing certain events. MMURTL V1. divided by the total clocks for the same period. The most timecritical interrupts are non-buffered serial communications. Back when I started this project. which is very conservative. I did some time-consuming calculations to figure what percentage of CPU bandwidth I was using with my timer ISR. so I simply added 10 percent for the overhead. especially synchronous. My calculated worst case was approximately 150us. certain problems with memory access such as noncached un-decoded instructions. or 3000 clocks. however. It’s at the end of the timer ISR. I was consuming less than 0.2 percent of available bandwidth. The timer interrupt service routine (ISR) is also documented here.000 clocks on a 20-MHz machine.Chapter 20. actually the scheduler. etc. 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.2 percent. Timer Management Source Code Introduction The timer code is the heart of all timing functions for the operating system. testing is the only real way to be sure it will function properly. with 100-MHz machines around. Your bandwidth is the percentage of clocks executed for a given period.. was the effect on interrupt latency. 20-MHz machines were "screamers" and I was even worried about consuming bandwidth on machines that were slower than that. and it also wasn’t fast enough to provide smooth. 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. Two of them will come in at 260us. This file contains all of the code that uses or deals with timing functions in MMURTL.

and finally with test programs. including the 10 percent overhead. The only additional time consumer is if we tack on a task switch at the end if we must preempt someone. This doesn’t cause a problem at all. -1 PUSH EAX PUSH EAX CALL FWORD PTR _ISendMsg POP ECX POP EAX MOV DWORD PTR [EAX+fInUse]. and 250 on a 486 or a Pentium. Multiply the total (255) times the maximum number of timer blocks (64) and it totals 16. Timer Data The following section is the data and constants defined for the timer interrupt and associated functions. that would be 860us just for this worst-case interrupt.FALSE DEC nTmrBlksUsed JMP IntTmr03 IntTmr02: DEC DWORD PTR [EAX+CountDown] IntTmr03: ADD EAX. The odds of this are truly astronomical. or 20us.1. and I tried a hundred calculations with a spread sheet.320 clocks.0 Page 371 of 667 . This I could live with. 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. But. 400 clocks on a 20-MHz CPU is 50 nanoseconds times 400. SwitchTick and dfHalted are flags for the task-scheduling portion of the timer interrupt.0h JNE IntTmr02 PUSH EAX PUSH ECX PUSH DWORD PTR [EAX+TmrRespExch] MOV EAX. See listing 20. A task switch made by jumping to a TSS takes approximately 400 clocks on a 386.Loop in timer ISR 6 3 6 3 2 2 5 2 2 2 40 2 2 6 5 7 6 2 11 IntTmr01: CMP DWORD PTR [EAX+fInUse]. plus 130 clocks for the execution of ISendMsg() in each loop.Listing 20. MMURTL V1.sTmrBlk LOOP IntTmr01 This works out to 125 clocks for each loop. The real worst case would probably be more like 150us. That’s a bunch! On a 20-MHz machine.2.FALSE JE IntTmr03 CMP DWORD PTR [EAX+CountDown].

3. or one that has requested an alarm. The timer interrupt fires off every 10 milliseconds. TIMER INTERRUPT COUNTER and TimerBlocks . Each TimerBlock is 12 Bytes long .ASM.INC . EXTRN SwitchTick DD EXTRN dfHalted DD PUBLIC TimerTick DD 0 . the first things defined in this code segment. See listing 20.ASM.0 Page 372 of 667 . The three external near calls. .INCLUDE MOSEDF. The Sleep() and Alarm() functions set these blocks as callers require. Listing 20. the exchange number of a task that is sleeping. The Timer Interrupt The timer interrupt checks the timer blocks for values to decrement. allow the timer interrupt to access kernel helper functions from Kernel. The Timer Block is a structure that contains .INCLUDE TSS. .INC .Listing 20.3 . sTmrBlk EQU 12 fInUse EQU 0 .Incremented every 10ms (0 on bootup).ALIGN DWORD .DATA .DD 00000000h . The timer interrupt is part of the scheduling mechanism.Data and constants for timer code .DD 00000000h TmrRespExch EQU 4 . These are the offsets into the structure . PUBLIC nTmrBlksUsed DD 0 .External functions for timer code . MMURTL V1.DD 00000000h CountDown EQU 8 .CODE EXTRN ChkRdyQ NEAR EXTRN enQueueRdy NEAR EXTRN deQueueRdy NEAR .2 .Number of timer blocks in use PUBLIC rgTmrBlks DB (sTmrBlk * nTmrBlks) DUP (0) Timer Code The timer interrupt and timing related function are all defined in the file TmrCode.

yet very important part. or higher.Yes! . only one task is actually executing.tell him his times up! POP ECX . 0 JE SHORT TaskCheck IntTmr00: LEA EAX.Yes . You couldn’t reschedule tasks this way if you were using interrupt tasks because this would nest hardware task switches. If so.Correct count of used blocks JMP IntTmr03 .Anyone sleeping or have an alarm set? . you switch to it.EAX has addr of Timer Blocks .ISend Message MOV ptr to rgTmpBlk PUSH ECX .Timer Tick.Free up the timer block DEC nTmrBlksUsed . It is a small.4.ECX has count of Timer Blocks . It’s really the only part of task scheduling that isn’t cooperative. PUBLIC IntTimer: PUSHAD INC TimerTick CMP nTmrBlksUsed. You have now interrupted that task..0 Page 373 of 667 .skip decrement .FFFFFFFFh PUSH EAX .goto decrement it PUSH EAX . .bogus msg CALL FWORD PTR _ISendMsg .is count at zero? JNE IntTmr02 .get ptr back MOV DWORD PTR [EAX+fInUse].Timer Block found? JE IntTmr03 . Listing 20. INT 20 .goto next block CMP DWORD PTR [EAX+CountDown].bogus msg PUSH EAX .10ms more gone.FALSE .FALSE .Move forward thru the blocks IntTmr01: CMP DWORD PTR [EAX+fInUse].. See listing 20. MMURTL V1.0h . -1 ..INTS are disabled automatically . Other tasks maybe waiting at the ready queue to run.empty blk IntTmr02: DEC DWORD PTR [EAX+CountDown] IntTmr03: . The task you interrupted is placed back on the ready queue in exactly the state you interrupted it. we call ChkRdyQ and check the priority of that task.4 .No.. and they may be of an current count PUSH DWORD PTR [EAX+TmrRespExch] .No .The timer interrupt code also performs the important function of keeping tabs on CPU hogs.get count back POP EAX .nTmrBlks CLD . Keep this in mind if you use interrupt tasks instead of interrupt procedures like I do. which is the one you interrupted. If it is the same or higher.No . priority than the task that is now running.rgTmrBlks MOV ECX.The timer interrupt service routine .Yes . 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. At all times on the system. They may have been placed there by other ISRs.

Is this from a HALTed state? . pRunTSS MOV DL. no need to check tasks .We will now check to see if this guy has been running . EAX MOV EAX.ADD EAX. IRETD .If current is higher(lower num). JMP FWORD PTR [TSS] . . If the Queued Pri is LOWER .DL is pri of current .0 Page 374 of 667 . Get high priority TSS off the RdyQ . EAX JZ TaskCheckDone .. EAX JNZ TaskCheckDone MOV EAX.[EDI+Tid] . Put it in the JumpAddr for Task Swtich INC _nSwitches . Save time of this switch for scheduler MOV SwitchTick. we will .if we got a CARRY on the subtract.Change this to change the "time slice" .. and save in EDI . Make the TSS in EDI the Running TSS MOV on the RdyQ TaskCheck: MOV EAX. JMP TSS (This is the task swtich) POPAD . [ESI+Priority] CMP DL. his . Put current one in EAX . Keep track of # of preemptive switches MOV EAX. Get the task ID MOV TSS_Sel.Is there one at all?? . EAX . TimerTick . dfHalted OR EAX. That means .Must do this before we switch! CALL FWORD PTR _EndOfIRQ . CMP EAX.Compare current pri to highest queued . JL SHORT TimerTick SwitchTick EBX 3 TaskCheckDone . and on the RdyQ MOV pRunTSS.EDI . MOV ESI. [EAX+Priority] JC TaskCheckDone CALL deQueueRdy MOV EDI. MMURTL V1.Hasn’t been 30ms yet for this guy! CALL ChkRdyQ OR EAX.number was higher and we DON’T .Get next highest Pri in EAX (Leave Queued) . ESI CALL enQueueRdy .BX .switch him out if someone with an equal or higher pri .sTmrBlk LOOP IntTmr01 .. SUB EAX.or Equal.keep going .next block please! .No.YES. PUSH 0 . If so.Must do this before we switch! .current pri.for more then 30ms (3 ticks).The CMP subtracts Pri of Queued from .unless were done . Keep track of how many switches for stats INC _nSlices . we want to Switch. IRETD . TaskCheckDone: PUSH 0 CALL FWORD PTR _EndOfIRQ POPAD . MOV EBX.

Sorry.Offset of msg area PUSH ECX CALL FWORD PTR _WaitMsg .EBX . MOV DWORD PTR [EAX+fInUse].Get delay count MOV [EAX+CountDown]. PUBLIC __Sleep: PUSH EBP .0 Page 375 of 667 .Empty block? JNE Delay02 . 0 . out of timer blocks Delay03: MOV ESP. This requires an exchange. The timer interrupt sends the message and clears the block when it reaches zero.Get TSS_Exch for our use MOV EBX.EAX points to timer blocks MOV ECX.sTmrBlk LOOP Delay01 . MOV EAX.ErcOk .5. POP EBP .Pass exchange (for WaitMsg) ADD ECX.EBX .FALSE .5 .Count of timer blocks CLD .See if there’s no delay JE Delay03 LEA EAX.all is well JMP Delay03 Delay02: STI ADD EAX. See listing 20.[ECX+TSS_Exch] .Use the Timer Block INC nTmrBlksUsed .goto next block MOV EBX. MOV [EAX+TmrRespExch].Code for the Sleep function DelayCnt EQU [EBP+0Ch] .FAR return from publics MMURTL V1.pRunTSS .nTmrBlks .unless were done MOV EAX. MOV EBP.put it in timer block! STI PUSH EBX . Listing 20.EBP .TRUE . DelayCnt CMP EAX. The exchange used is the TSS_Exch in the TSS for the current task.ErcNoMoreTBs .clear direction flag Delay01: CLI .TSS_Msg .DelayCnt .can’t let others interfere CMP DWORD PTR [EAX+fInUse].and Wait for it to come back MOV EAX.ESP .rgTmrBlks .No .Sleep() 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. RETF 4 .Up the blocksInUse count MOV ECX.

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

which means the message has already been sent.sTmrBlk LOOP KAlarm01 KAlarm03: XOR EAX.KillAlarm() KillAlarm searches the timer blocks looking for any block that is destined for the specified Alarm exchange.EAX points to timer blocks .It’s OK to interrupt now . See listing 20. MicroDelay() Microdelay() provides small-value timing delays for applications and device drivers. . so we get out! .Code for the KillAlarm function .Get exchange for killing alarms to . . All alarms set to fire off to that exchange are killed.No .ALl done -.nTmrBlks CLD KAlarm01: CLI CMP JNE CMP JNE MOV DEC . nothing will stop it.KAlarmExch LEA EAX. KillAlarm(nAlarmExch):dErc .7 .7. KAlarmExch EQU [EBP+0Ch] .unless were done .EBP POP EBP RETF 4 . 0 JE KAlarm03 MOV EBX. .FALSE .Block in use? KAlarm02 .goto next block [EAX+TmrRespExch].TRUE .can’t let others interfere DWORD PTR [EAX+fInUse].ErcOk .rgTmrBlks MOV ECX.Does this match the Exchange? KAlarm02 DWORD PTR [EAX+fInUse].Make blocksInUse correct KAlarm02: STI ADD EAX. PUBLIC __KillAlarm: PUSH EBP MOV EBP.EAX MOV ESP. . The timing for this delay is based on the toggle of the refresh bit from the MMURTL V1. Procedural Interface: .0 Page 377 of 667 .EBX .Make Empty nTmrBlksUsed .ESP CMP nTmrBlksUsed. If the alarm is already queued through the kernel. The count is in 15us increments.Count of timer blocks .No blocks in use . Listing 20. .clear direction flag .

and the high order byte is 0. GetCMOSTime() This reads the time from the CMOS clock on the PC-ISA machines. AL JE MDL00 MOV AH.Check toggle of bit .get out if they came in with 0! .No toggle yet .system status port.8.Code for the MicrDelay function .Get system status port .EBP POP EBP RETF 4 . The call can also be inaccurate due to interrupts if they are not disabled. See listing 20. The time is returned from the CMOS clock as a dword. . The low order byte is the seconds in Binary Coded Decimal (BCD). Listing 20. The task is actually not suspended at all. The refresh bit is based on the system’s quartz crystal oscillator. the next byte is the minutes in BCD. 0 JE MDL01 MDL00: IN AL. . 10h CMP AH. AL LOOP MDL00 MDL01: XOR EAX. This forms an instruction loop checking the toggled value of an I/O port. the next byte is the Hours in BCD.Get delay count . MMURTL doesn’t keep the time internally.check refrest bit .ESP MOV ECX.Toggle! Move to AH for next compare . But it’s still very much needed. EAX MOV ESP. See listing 20. . This call will not be very accurate for values less than 3 or 4 (45 to 60 microseconds).0 Page 378 of 667 . Procedural Interface MicroDelay(dDelay):derror PUBLIC __MicroDelay: PUSH EBP MOV EBP. MMURTL V1. . [EBP+0Ch] CMP ECX. which drives the processor clock.9. 61h AND AL.8 . .

the next byte is the day (BCD 1-31). Procedural Interface: GetCMOSDate(pdTimeRet):derror MMURTL V1.Code to read the CMOS date .Hours .71h MOV BL.AL IN AL. pCMOSTimeRet MOV [ESI].9. . Procedural Interface: GetCMOSTime(pdTimeRet):derror pCMOSTimeRet EQU [EBP+12] PUBLIC __GetCMOSTime: PUSH EBP MOV EBP.ESP XOR EBX. .AL MOV ESI. . EBX MOV EAX. See listing 20. Listing 20. AL SHL EBX. 8 MOV EAX.Minutes . 0-6 0=Sunday). No Error . 8 MOV EAX.Seconds . the next byte is the month (BCD 1-12).10 .Give ’em the time .AL IN AL.Clear time return . .Listing 20.Code to read the CMOS time .20. . EBX XOR EAX.00h OUT 70h. GetCMOSDate() The Date is returned from the CMOS clock as a dword. The low-order byte is the day of the week (BCD. EAX MOV ESP.0 Page 379 of 667 . .71h MOV BL.AL IN AL.04h OUT 70h.71h MOV BL.AL SHL EBX.EBP POP EBP RETF 4 . . and the high order byte is year (BCD 0-99).02h OUT 70h. .

.AL SHL EBX.Clear date return .Year . Because it is a dword. With a 10ms interval.71h MOV BL.Month .AL IN AL. .pCMOSDateRet EQU [EBP+12] PUBLIC __GetCMOSDate: PUSH EBP MOV EBP. 8 MOV EAX.09h OUT 70h.11. there are over 4 billion ticks before roll-over occurs.07h OUT 70h. EBX XOR EAX. . .EBP POP EBP RETF 4 .Give ’em the time . EAX MOV ESP.AL IN AL.08h OUT 70h. this amounts to 262.0 Page 380 of 667 .000 ticks per month.AL IN AL. . . No Error .ESP XOR EBX.800. MMURTL maintains a double word counter that begins at zero and is incremented until the machine is reset or powered down.11. 8 MOV EAX.71h MOV BL. pCMOSDateRet MOV [ESI]. GetTimerTick(pdTickRet):derror MMURTL V1.AL MOV ESI.AL IN AL.71h MOV BL.06h OUT 70h. 8 MOV EAX. . This means the system tick counter will roll-over approximately every 16 months. EBX MOV EAX. AL SHL EBX.Day of month . The procedural interface: .Day of week . Listing 20.Code to return the timer tick . See listing 20. GetTimerTick() This returns the ever-increasing timer tick to the caller. AL SHL EBX.71h MOV BL.

To find the divisor of the clock. Hardware timer number 2. The length of time the tone is on is controlled by the Sleep() function which is driven by the system tick counter. 00000011b PUSH EAX POP EAX OUT 61h. CALL FWORD PTR _Sleep . 1193182 . . EAX POP EBP RETF 4 .The clock freq to Timer 2 is 1.Helper function for Beep() and Tone() . The Current Timer Tick is returned (it’s a DWord). MMURTL V1. . LSB.12.ESP MOV ESI. . Beep_Work() Beep_Work() is an internal function (a helper) used by the public calls Tone() and Beep(). 10110110b . . AH NOP NOP NOP NOP OUT 42h.EBX needs the desired tone frequency in HERTZ . See listing 20.12. AL IN AL. .No Error . is used to generate tones.. EAX XOR EAX.193182Mhz DIV EBX . AL XOR EDX. pTickRet MOV EAX. pTickRet EQU [EBP+12] PUBLIC __GetTimerTick: PUSH EBP MOV EBP.193182 Mhz . Binary OUT 43h. divide 1.This does all work for BEEP and TONE .0 Page 381 of 667 . The system clock drives the timer so the formula to find the proper frequency is a function of the clocks frequency.1. MSB. TimerTick MOV [ESI].193182Mhz by Desired Freq. . AL PUSH ECX .ECX is TIME ON in 50ms incs.DIVISOR is in EBX (Freq) OUT 42h. Listing 20. EDX MOV EAX.Send quotient (left in AX) MOV AL. which is connected to the system internal speaker.ECX needs length of tone ON-TIME in 10ms increments BEEP_Work: MOV AL. 61h OR AL. AL .Timer 2.

Freq . 61h NOP NOP NOP NOP AND AL.13. (Sorry. .14. .) This provides a fixed tone from the system’s internal speaker as a public call for applications to make noise at the user. . Procedural Interface: Beep() PUBLIC __Beep: PUSH EBP MOV EBP. It uses the helper function Beep_Work described earlier. . . Listing 20.ESP MOV EBX. AL RETN Beep() This function has nothing to with the Road Runner.350ms . . Tone() Tone allows the caller to specify a frequency and duration of a tone to be generated by the system’s internal speaker.Code to produce a beep. . I couldn’t resist. 800 MOV ECX.14 . . if it had.0 Page 382 of 667 . This call uses the helper function Beep_Work described earlier. .IN AL.Code to produce a tone . dTickseRet):derror dFreq is a DWord with the FREQUENCY in HERTZ dTicks is a DWord with the duration of the tone in 10ms increments MMURTL V1. Listing 20. See listing 20.13. I would have called it Beep_Beep(). . See listing 20. 11111100b OUT 61h. 35 CALL Beep_Work POP EBP RETF . Procedural Interface: Tone(dFreq. .

MMURTL V1.ESP MOV EBX.ToneFreq EQU [EBP+10h] ToneTime EQU [EBP+0Ch] . PUBLIC __Tone: PUSH EBP MOV EBP.0 Page 383 of 667 . . ToneFreq MOV ECX. ToneTime CALL Beep_Work POP EBP RETF 8 . .

0 Page 384 of 667 . Initial OS Page Directory (PD). Following these tables is the data segment portion from Main. Global Descriptor table (GDT).ALIGN command. This is because many things have to be started before you can allocate memory where the dynamic allocation of resources can begin. OS Global Data Listing 21. access to the data is faster if it is. a workhorse for Main. The order of the first 5 files included in the assembler template file for MMURTL are critical. and I don't worry about the rest of it. Even though the Intel processors can operate on data that is not aligned by it's size. I generally try to align data that is accessed often.ASM. the first instruction executed after boot up.ASM.ASM. Everything is packed unless you tell it otherwise with the .1 begins the data segment after the static tables that were in previous include files.ALIGN DWORD command issued to the assembler. The assembler does no alignment on its own. The code contains comments noting which of these items are temporary. and Public Call Gate table. It defines the selectors for public call gates so the rest of the OS code can call into the OS. It is the entry point in the operating system. Initialization Code Introduction This chapter contains code from two separate files. Periodically you will see the . The first file is Main. Certain data structures and variables are allocated that may only be used once during the initialization process. Initial OS Page Table (PT).Chapter 21.” I covered the basics of a generic sequence that would be required for initialization of an operating system. The public call gate table is not a processor-defined table as the others are. “OS Initialization. In chapter 7. The second file is InitCode. The tables prior to this portion of the data segment are: • • • • • Interrupt Descriptor Table (IDT).ASM which is presented later in this chapter. MMURTL V1.

INC .Interrupt Descriptor Table Limit . For stats (less static TSSs) .by the Monitor TSS. .0 Page 385 of 667 .INC for structure details.======================================================= PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC MonTSS DbgTSS pFreeTSS pDynTSSs _nTSSLeft DB DB DD DD DD sTSS dup (0) .1 . Debugger JCB .See MOSEDF. In order to use certain kernel . pool of LBs PUBLIC pFreeLB DD NIL .the first three dynamic exchanges. we need exchanges before they are allocated dynamically.-----------------. Kernel Structures . .base DB sJCB dup (0) DB sJCB dup (0) . we copy these into .We have an array of 3 exchanges set aside for this purpose.-----------------.DATA . Two static Job Control Blocks (JCBs) to get us kick-started. Initial TSS for Debugger NIL .the dynamic array is allocated an initialized. Setup an array of Service .ALIGN DWORD .INC .-----------------PUBLIC rgSVC DB (sSVC*nSVC) dup (0) Descriptors . MMURTL V1. Ptr to Free List of Link Block PUBLIC _nLBLeft DD nLB . and memory Management. The rest of them are in allocated memory PUBLIC MonJCB PUBLIC DbgJCB . Pointer to Free List of Task State Segs 0 . ptr to alloced mem for dynamic TSSs nTSS-2 .Listing 21.INCLUDE JOB.Global Descriptor Table Limit . For Monitor stats . .Main Operating System Data .-----------------PUBLIC rgLBs DB (nLB*sLINKBLOCK) dup (0) . Initial TSS for OS/Monitor sTSS dup (0) . Priority Based Ready Queue . Monitor JCB .-----------------. These static exchanges are used .primitives.INCLUDE TSS.-----------------PUBLIC PUBLIC PUBLIC PUBLIC GDTLimit GDTBase IDTLimit IDTBase DW DD DW DD 0000h 00000000h 0000h 00000000h .======================================================= . Debugger TSS.Exchanges take up a fair amount of memory.INCLUDE MOSEDF.base .INC . After . The RUN Queue for "Ready to Run" tasks PUBLIC RdyQ DB (sQUEUE*nPRI) dup (0) .

Pointer to dynamic array Exchanges. .Scheduling management variables PUBLIC TSS PUBLIC TSS_Sel . Setup three static temporary Exchanges . as soon as it’s allocated.------------------.Size of Job Name . This will be changed after allocation . .Name for Job . Used for jumping to next task .1K Debugger Stack DD 00000000h EQU Stack1Top-Stack1 . of dynamic exchanges.Placed in first JCB . OFFSET rgExchTmp .Source drive of the boot MMURTL V1.THESE ARE THE INITIAL STACKS FOR THE OS Monitor AND Debugger OSStack DD 0FFh DUP (00000000h) . nDynEXCH . for Monitor and Debugger TSSs . These are moved to a dynamic Exch Array .0 Page 386 of 667 . 0 . . Pointer to the Running TSS .----------------rgNewJob cbNewJob rgOSJob cbOSJob rgDbgJob cbDbgJob _BootDrive DB ’New Job’ EQU 7 DB ’MMOS Monitor’ EQU 12 DB ’Debugger DD 12 DD 00 ’ . For Monitor stats .1K OS Monitor Stack OSStackTop DD 00000000h OSStackSize EQU OSStackTop-OSStack Stack1 Stack1Top Stack1Size DD 0FFh DUP (00000000h) .Placed in New JCBs . Pointer to current array of Exchanges.PUBLIC nExch rgExchTmp DD 3 DB (sEXCH * PUBLIC PUBLIC PUBLIC prgExch DD pExchTmp DD _nEXCHLeft DD . 3) dup (0) . Tick of last task switch . " " .ALIGN DWORD PUBLIC pRunTSS PUBLIC SwitchTick PUBLIC dfHalted PUBLIC PUBLIC PUBLIC PUBLIC _nSwitches _nSlices _nReady _nHalts DD NIL DD 0 DD 0 DD DD DD DD 0 0 0 0 . nonzero if processor was halted . # # # # of of of of switches for statistics sliced switches for stats task Ready to Run for stats times CPU halted for stats DD 00000000h DW 0000h .

VIRTUAL command allows you to tell the assembler where this code will execute. initialization routines are so interdependent. "Will Robinson. Look for the . This is the first instruction executed after the operating system is booted. and calls procedures that initialize dynamic structures too.START assembler command. . BEWARE ON INITIALIZATION. The kernel structures and their .2. close attention before you change the order of ANYTHING. See listing 21. The .that this is the address where we execute .OS Initialization Code and Entry Point .0 Page 387 of 667 . It is similar to the MS-DOS assembler ORIGIN command. with one major difference: DASM doesn’t try to fill the segment prior to the virtual address with dead space. WARNING!! Dr.CODE . . .ASM is the entry point to the operating system.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.2 . (Anything before we jump to the monitor code that is) EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN EXTRN InitCallGates NEAR InitOSPublics NEAR _Monitor NEAR InitIDT NEAR InitFreeLB NEAR InitDMA NEAR Set8259 NEAR InitKBD NEAR MMURTL V1. The . the first byte of the second 64K in physical memory.VIRTUAL 10000h . Smith is approaching!!!" . . I had to make many warnings to myself just to keep from "housekeeping" the code into a nonworking state.Operating System Entry Point The first instruction in Main. you must pay . Listing 21. and all subsequent address calculations by the assembler are offset by this value. This code is used to initialize the permanent OS structures . WARNING. . You’ll see many warnings about the order of certain initialization routines. the start address is 10000h. This begins the OS Code Segment . . For MMURTL.64K boundry. I felt it was important to leave all of these comments in for you. BEGIN OS INITIALIZATION CODE . This lets the assembler know . 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.

0 Page 388 of 667 .========================================================================== CALL InitCallGates ..START PUBLIC OSInitBegin: LEA EAX. . . . Init the array of Link Blocks . THIS IS RIGHT AFTER WE GET A STACK. Initialize the Kbd hardware .========================================================================== .========================================================================== . Setup Operating System Structures and motherboard hardware . except AddCallGate which must be made valid first! .Sets up default Interrupt table . This is the first code we execute after the loader . count of Link Blocks . Tell em to work CALL InitOSPublics CALL InitIDT MOV ECX. It’s a FAR jump . code throws us into protected mode.========================================================================== .we know where they are! .EXTRN AddTSSDesc NEAR EXTRN InitMemMgmt NEAR EXTRN InitNewJCB NEAR EXTRN InitVideo NEAR EXTRN InitFreeTSS NEAR EXTRN InitDynamicJCBs NEAR EXTRN InitDynamicRQBs NEAR EXTRN DbgTask NEAR . FIRST THING IN OS CODE.========================================================================== . THE SECOND THING IN OS .========================================================================== SGDT FWORD PTR GDTLimit SIDT FWORD PTR IDTLimit .OSStackTop MOV ESP. .NOTE: This uses CallGates! . Sets up all call gates as DUMMYs .sLinkBlock CALL InitFreeLB CALL InitDMA CALL Set8259 CALL InitKBD PUSH 0 CALL FWORD PTR _EndOfIRQ MMURTL V1. Set up the initial Stack Pointer (SS = DS already).nLB MOV EDX. Left there from BootSector Code . Setup initial OS Stack . Set up OS Common Public for IDT and GDT Base and Limits . EDX is size of a Link Block .EAX MOV _BootDrive. . Sets up OS PUBLIC call gates . EDX .A formality . YOU CAN’T ALLOCATE ANY OS RESOURCES UNTIL THIS CODE EXECUTES!!! . Set up 8259s for ints (before KBD) . Sets up DMA with defaults .. Highest IRQ number (all IRQs) . from that code.

I/O Permission [EBX+Tid].AV(0). We also have to manaully make it’s entry in the GDT.AL AL. OS goes into paged memory mode. Store TSS Selector in TSS (Task ID) WORD PTR [EBX+Tid] . . . a descriptor entry for it in the GDT and we do the same for the debugger. .or 1 every 10 ms. . MOV MOV MOV SUB MOV MOV LTR MOV EBX. First JCB IS job 1! MMURTL V1.AL .========================================================================== . .Phys address of PDir1 MOV WORD PTR [EBX+TSSNum].this IS our task now!!! EAX. This TSS is a valid TSS after the first task switch. .Set up Job Control Block for Monitor (always Job 1) . We are ready to GO (for now) . we set up the TSS itself . .Allocate Exch and InitMemMgmt calls depend on pRunTSS being valid.Get ptr to initial TSS in EBX pRunTSS.Set time counter divisor to 11938 . . 1 . OFFSET rgTSSDesc CALL AddTSSDesc . . OFFSET MonTSS . sTSS MOV EBX. 0FFh.The following code section builds a descriptor entry for .ptr to initial TSS descriptor EAX. 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. The following code finishes the initialization procedures BEFORE the . . OFFSET MonTSS MOV EDI. Make the default TSS for the CPU to switch from a valid one.the initial TSS (for Montitor program) and places it into the GDT MOV EAX.193182 Mhz/11932 = 100 per second . (2E9Ch = 11932 decimal) MOV OUT MOV OUT STI AL.AX .and Load Task Register with the descriptor (selector) .Sub offset of GDT Base to get Sel of TSS WORD PTR [EBX+TSS_IOBitBase].LIM(0). IMPORTANT . Makes the timer Tick (lo) 10 ms apart by setting clock divisior to (hi byte) 11.0 Page 389 of 667 . creating and loading . Setup the Task Register BYTE PTR [EBX+Priority]. .========================================================================== . It will never be free. OFFSET PDir1 .9Ch 40h.EBX .02Eh 40h.because they will be filled by the processor on the first .P(1). 25 . OFFSET GDT .Priority 25 (monitor is another APP) MOV DWORD PTR [EBX+TSS_CR3]. We set up an initial Task by filling a static TSS.task switch.10ms ticks .. 0089h MOV EDX. . OFFSET rgTSSDesc .Now that we have valid Descriptor. .Number of first TSS (Duh) . .Note that none of the TSS register values need to be filled in .932 . . Limit of TSS (TSS + SOFTSTATE) G(0).B(0) Address of TSS Address of GDT entry to fill .DPL(0).JOB 0 is not allowed. .Freq in is 1.

NO pVirtVid yet (set up later in init) CALL InitNewJCB . MMURTL V1.MOV EAX.DataSel . I/O Permission [EBX+Tid]. ptr to second TSS descriptor EAX.EDX WORD PTR [EBX+TSS_CS]. cbOSJob .OSCodeSel . . . OFFSET DbgTask [EBX+TSS_EIP]. 0089h MOV EDX.the initial TSS (for Debugger) and places it into the GDT MOV EAX.The following code section builds a descriptor entry for . sTSS MOV EBX. OFFSET PDir1 . . EDX .LIM(0). we set up the TSS itself MOV SUB MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV EAX. .============================================================================ = . TSS_Exch PUSH EAX CALL FWORD PTR _AllocExch . 0FFh.0 Page 390 of 667 . THIS IS THE FIRST POINT AllocExh is valid MOV EAX.Must put ptr to JCB in TSS MOV [EBX+TSS_pJCB]. Set up DEBUGGER Task and Job . Put OSCodeSel in the TSS WORD PTR [EBX+TSS_DS].DataSel . OFFSET PDir1 . We can’t use NewTask because the debugger operates independent of .Size of Name XOR EDX. OFFSET MonJCB . OFFSET rgTSSDesc+8 CALL AddTSSDesc . . OFFSET GDT .Number the JCB MOV EBX. Debugger is HIGH Priority DWORD PTR [EBX+TSS_CR3]. Sub offset of GDT Base to get Sel of TSS EBX.Alloc exch for initial (first) TSS .DataSel . Store TSS Selector in TSS (Task ID) BYTE PTR [EBX+Priority]. The debugger must not be called (Int03) until this code executes. OFFSET MonTSS . . OFFSET rgTSSDesc+8 . WORD PTR [EBX+TSS_GS].B(0) Address of Debugger TSS Address of GDT entry to fill in . AND the video is initialized.P(1). IMPORTANT . . 1 . Get ptr to initial TSS in EBX WORD PTR [EBX+TSS_IOBitBase].DPL(0).AX . OFFSET rgOSJob . MOV DWORD PTR [EAX+JobNum]. The debugger is set up as another job with its one task. EAX .Page Directory MOV ESI. Limit of TSS (TSS + SOFTSTATE) G(0).Physical address of PDir1 EDX.AV(0).pJCB into MonTSS MOV EBX. OFFSET DbgTSS . WORD PTR [EBX+TSS_FS].Job Name MOV ECX.You can’t call AllocExch before pRunTSS is VALID! . OFFSET DbgTSS MOV EDI. pRunTSS ADD EAX. Put DataSel in the TSS WORD PTR [EBX+TSS_ES].DataSel . the kernel until it is called (INT 03 or Exception) . . 1 .Now that we have valid Descriptor.

DataSel . . CALL InitMemMgmt . A 1K Stack in the Dbg TSS DWORD PTR [EBX+TSS_ESP0].EBX still points to DbgTSS . OFFSET DbgTSS ADD EAX. cbDbgJob MOV EDX.Number the JCB .Name . 1 CALL InitNewJCB 2 .0 Page 391 of 667 . WORD PTR [EBX+TSS_SS0].Debugger gets video 1 . will not necessarily match Linear addresses beyond this point.Set up Job Control Block for Debugger . EAX.DataSel . OFFSET Stack1Top DWORD PTR [EBX+TSS_ESP]. Load the Flags Register WORD PTR [EBX+TSSNum].================================================================ .It also calls SEND! MMURTL V1. Not even the debugger. DWORD PTR [EBX+TSS_EFlags]. This was necessary because memory management uses messaging. debugger is always 2 MOV EAX.EAX . OFFSET PDir1 MOV ESI. Move one incorrectly and whamo – the code doesn't work. EAX MOV EBX. The memory-management initialization routines were presented in chapter 19.3.00000202h . Number of Dubegger TSS . You had to set up all the necessary items for kernel messaging so we could initialize memory management. ALSO NOTE: The InitMemMgmt call enables PAGING! Physical addresses . TSS_Exch PUSH EAX CALL FWORD PTR _AllocExch .” See listing 21.JOB 0 is not allowed.MOV MOV MOV MOV MOV MOV MOV WORD PTR [EBX+TSS_SS]. 2 . “Memory Management Code.Continuation of OS initialization. This really leaves a bad taste in your mouth after about 30 hours of debugging with no debugger. . all of the static data that needs to be initialized is done.size of name . Listing 21. MOV [EBX+TSS_pJCB]. There are many "chickens and eggs" here.3.Now allocate the default exchange for Debugger MOV EAX.InitMemMgmt allocates an exch so .this the first point it can be called. Pay attention! .EAX .Alloc exch for Debugger TSS At this point. OFFSET rgDbgJob MOV ECX.Page Directory (OS PD to start) . OFFSET DbgJCB MOV DWORD PTR [EAX+JobNum]. First JCB IS job 1. .

VGATextBase PUSH 1 MOV EAX.BB on CYAN . PUSH EAX CALL FWORD PTR _AllocOSPage .0 Page 392 of 667 . 1 page MOV EAX. Listing 21. [EAX+pVirtVid] MOV [EAX+pVidMem]. OFFSET MonJCB . Video NOT active for Debugger .Set Text Screen 0 .================================================================ . Offset in JCB to pVirtVid PUSH EAX CALL FWORD PTR _AllocOSPage .================================================================ .Now we will allocate two virtual video screens (1 Page each) . OFFSET DbgJCB MOV EBX.30423042h . . OFFSET pDynTSSs .EAX . Then call InitFreeTSS (which fills them all in with default values) PUSH 8 . . . OFFSET MonJCB . EBX CALL InitVideo . Make VirtVid Active for Monitor MOV DWORD PTR [EAX+pVidMem]. FIRST POINT video calls are valid . Get ’em! MMURTL V1. and video buffers.Continuation of OS initialization .Also we set pVidMem for Monitor to VGATextBase address . pVirtVid . FIRST POINT Debugger is working (only because it needs the video) .So we know we are here! MOV DS:VGATextBase+00h. we can allocate space for all of the dynamic arrays and structures such as task state segments.for the Monitor and Debugger and place them in the JCBs .cause it has the active video by default PUSH 1 . exchanges.Once we have the memory allocation calls working.=============================================================== . FIRST POINT memory management calls are valid . See listing 21. . . initialize them.=============================================================== . At this point we can allocate pages for dynamic structures and .4. MOV EAX. 8 pages for 64 TSSs (32768 bytes) MOV EAX. 1 page . Ptr to Monitor JCB ADD EAX. Get ’em! MOV EAX. pVirtVid PUSH EAX CALL FWORD PTR _AllocOSPage MOV EAX. Get ’em! . Allocate 8 pages (32768 bytes) for 64 Task State Segments (structures).4. OFFSET DbgJCB ADD EAX.

EAX nExch. Clear allocated memory for TSSs (512 * 64 = 32768 = 8192 * 4) where to store 0s Do it MOV EAX. prgExch EDI. You actually have a working operating system.================================================================ . This is 60 bytes for 3 . count of dynamic TSSs (. . 12 MOVSD EAX. . . PUSH 1 MOV EAX.into the dynamic array. . Get it! . . HLT .5. Returns ptr to allocated mem in pJCBs . nDynEXCH .OS & Dbgr TSS) .End of Initialization Code CALL _Monitor .exchanges. pExchTmp prgExch. . pExchTmp STOSD . EAX ECX. you never know (I AM human) MMURTL V1. Init the array of Process Control . Allocate 1 page (4096 bytes) for 256 Exchanges (16*256=4096). 1024 EDI. . loader tasks. MOV MOV MOV REP MOV MOV MOV ESI. From here. . . . . . you go to code that will finish off the higher-level initialization functions such as device drivers.0 Page 393 of 667 . the effect of initializing them because all fields in an Exch .) . Exchanges are 16 bytes each. OFFSET pExchTmp PUSH EAX CALL FWORD PTR _AllocOSPage XOR MOV MOV REP EAX. .. See listing 21. 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 . pDynTSSs STOSD . nTSS-2 CALL InitFreeTSS Blocks CALL InitDynamicJCBs CALL InitDynamicRQBs . Then zero the memory which has . 8192 EDI. 1 pages for 256 Exchs (4096 bytes) .Well. Listing 21.Head for the Monitor!! . EAX ECX.The Monitor call never comes back (it better not. pExchTmp ECX. .XOR MOV MOV REP EAX.5 . pDynTSSs MOV ECX. are zero if not allocated. . 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.

REGISTERS : EAX. OUTPUT : NONE .FLAGS . See listing 21. .Initialization Subroutines . Listing 21.=========================================================================== .CODE .EBX.The following code initializes structures just after bootup . 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. INPUT : ECX.6. and interrupt vectors.ECX. PUBLIC InitFreeLB: MMURTL V1. Initialization Helpers The file InitCode. The pFreeLB pointer is set to address the first element in rgLBs. last element of rgLBs is set to point to nothing (NIL). This is noted in the comments. each EDX bytes long and pointer to a list of free link blocks (pFreeLB). 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 data used in this algorithm are an array of ECX link blocks (rgLBs).. .ASM provides much of the support "grunt work" that is required to get the operating system up and running. element of rgLBs is set to point to the next element of rgLBs.0 Page 394 of 667 . Each . . This routine will initialize a free pool of link blocks. .EDX . MODIFIES : pFreeLB.6 .rgLBs . and it initializes some of the free dynamically allocated structures such as TSS’s and link blocks. The . The procedure InitOSPublics() sets up all the default values for call gates.

If you . anding and oring to get .EAX .EAX . Limit 15 . (Limit is size of TSS-1) SHL EBX.Decriptor type (default for OS TSS is 0089h) . EDI .LIM(0000).(1)) .8 .. Store lo double word MOV [EDI+4].(010).0 Page 395 of 667 .the TSS and places it into the GDT PUSH EAX PUSH EBX PUSH EDX PUSH EDI DEC EAX . docs for a complete description of the placement of the bits. OR into high order word MOV [EDI].Address of TSS . (0089h .16 .DL .The following code section builds a descriptor entry for .16 . AddTSSDesc . .000F0000h .. pFreeLB <= ^rgLBs. 24 MOV BH.======================================================================= . ^rgLBs[I+1]. EDX . . the descriptor the way the processor expects it. Chinese puzzle rotate ROL EDX.rgLBs MOV pFreeLB.EAX .DH . See the Intel . 0 with Base 15 .LEA EAX.EDX . 16 OR EBX.AX .DPL(00).. MOV DWORD PTR [EBX+NextLB]. Rotate to Final Alignment MOV DX. not the code . Note: The granularity bit represents the TSS itself. 0 .Address of Desc in GDT . rgFree[1023]. Base 31 . information are scattered through the descriptor entry. IN: .EBX . USED: .EAX LB_Loop: .EDX . Store hi double word POP EDI POP EDX POP EBX POP EAX RETN MMURTL V1. we have to do some shifting.Next <= NIL. EAX .AV(0).. moving. Mask Limit 19 . . EBX . . rgLBs[I].B(0). check the intel documentation the bits of data that hold this .Next <= MOV [EBX+NextLB]. Builds a descriptor for a task and places it in the GDT. EFlags (all other registers are saved) PUBLIC AddTSSDesc: . . that will run under it! .P(1). Base 23 .G(0). GDT is updated with descriptor .Size of TSS .. OUT: . 0 AND EAX. RETN . MOV EBX. . LOOP LB_Loop . for I = 0 TO ECX ADD EAX. Exchange hi & lo words of Base Addr MOV BL. 16 ROR EBX. so .

USED : ALL General registers. Sub offset of GDT Base to get Sel of TSS MOV WORD PTR [ESI+Tid].LIM(0). we add the TSS descriptors at OS . for I = 0 TO ECX ADD EAX. . Point to Next GDT Slot (for next one) . .Next MOV [ESI+NextTSS]. INPUT : EAX.EDI .Set up for Data Selectors MOV WORD PTR [ESI+TSS_ES]. DataSel MOV WORD PTR [ESI+TSS_FS]. EAX points to the TSSs to initialize (allocated memory). . This routine initializes the free pool of Task State Segments. last TSS is set to point to nothing (NIL). Size of TSS (TSS + SOFTSTATE) . 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. . TSS Number MOV WORD PTR [ESI+TSS_DS].Save pTSS MOV EAX.EDX . . protection level.8 . . .EDX MOV EDX. Get offset of Curr TssDesc in EAX SUB EAX.EAX . MODIFIES : pFreeTSS (and the dynamic array of TSSs) . The pFreeTSS pointer is set to address the first TSS. Store TSS Selector in TSS (later use) PUSH EBX PUSH EDX MOV EAX. sTSS MOV EBX. EAX <= rgTSSs[I].. OFFSET GDT .AV(0). calling this routine. The IOBitBase field is .EAX MOV EDI.0 Page 396 of 667 .========================================================================= . G(0). MOV WORD PTR [ESI+TSS_IOBitBase]. ^rgTSSs[I+1]. InitFreeTSS . NOTE: The allocated memory area for the TSS MUST BE ZEROED before .AX .B(0) MMURTL V1. On entry: . 0FFFFh . The . . FLAGS . DataSel PUSH EAX . ECX has the count of TSSs to initialize.P(1). The NextTSS . If we spawn or add a User level TSS we must . BX . 3 . . OUTPUT : NONE . OR the DPL bits with 3! PUBLIC InitFreeTSS: MOV pFreeTSS. DataSel . . DataSel MOV WORD PTR [ESI+TSS_SS0].ESI MOV EBX. DataSel MOV WORD PTR [ESI+TSS_SS].DPL(0). Address of TSS .EAX . DataSel MOV WORD PTR [ESI+TSS_GS]. OFFSET rgTSSDesc ADD EDI. The size of the TSS is taken from the constant sTSS. ECX . . 16 MOV EDX. IOBitBase MOV [ESI+TSSNum]. also set to FFFFh for NULL I/O permissions in each TSS.0089h CALL AddTSSDesc ADD EDI. field in each TSS is set to point to the next free TSS. By deafult.

Used : ALL registers and flags .POP EDX POP EBX POP EAX INC EBX LOOP TSS_Loop MOV DWORD PTR [ESI+NextTSS]. IN: Nothing .Next <= NIL. I have left all of these in so that you can see which are interrupt gates and which are interrupt traps.========================================================================= . . running on old MMURTLs or systems where special call gates don’t exist . 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. Out : Nothing .7.First we set up all call gates to point to a . 0 RETN . each of the over 100 calls are placed in the GDT. not the count MMURTL V1. As I mentioned before. See listing 21.Save nCallGates . ercBadCallGate RETF Besides setting up all the call gates with a dummy procedure. This means it can’t add itself. There is room reserved for over 600.========================================================================= DummyCall: MOV EAX.Initialize call gates amd the IDT . InitCallGate() takes care of another problem. . without crashing too horribly. DUMMY CALL for uninitialized GDT call gate slots . PUBLIC InitCallGates: . handler that returns ErcNotInstalled when called. . . InitCallGates inits the array of call gates with an entry to a generic . TSS Number .dummy procedure MOV ECX. This code manually adds the call for AddCallGate() so you can add the rest of them. Listing 21. nCallGates InitCG01: PUSH ECX DEC ECX .0 Page 397 of 667 . rgFree[LastOne]. The processor handles each of these a little differently.========================================================================= .make it an index. InitIDT. This prevents new code .Number of callgates to init . This problem is that the call to AddCallGate() is a public function called through a Call Gate. .7 . a little further down.

make index vice selector MOVZX EBX.0 Page 398 of 667 .we have to add it as a callgate. IN : Nothing .sub call gate base selector SHR EBX. 16 . SI .to call the FAR PUBLIC "AddCallGate" though a callgate. 3 . 255 . GDTBase . OSCodeSel ESI.sub call gate base selector SHR EBX. 40h . CX SUB EBX. In order to be able . CX SUB EBX. CX .0 DWord Params DPL 0 MOV ECX.Extend selector into EBX ADD EBX. InitIDT .DPL 3. MOV EAX. ECX.ignore error. Used : ALL registers and flags . 3 .call DPL & ndParams POP ECX LOOP InitCG01 . Out : Nothing . 08C00h .move upper 16 of offset into SI MOV [EBX+06].Put Code Seg selector into Call gate MOV [EBX].0:15 of call offset SHR ESI. inits the IDT with 256 entries to a generic . SI .. 0 Params DX. OFFSET __AddCallGate . PUBLIC InitIDT: MOV ECX.NOW a true offset in GDT MOV WORD PTR [EBX+02].SHL ADD MOV MOV MOV ECX.NOW a true offset in GDT MOV WORD PTR [EBX+02]. OFFSET DummyCall . . 8 . 8 . adds each of the basic IDT entries for included . OSCodeSel MOV ESI.Now ecx is selector number EAX.This decrements ECX till 0 .0:15 of call offset SHR ESI.Another chicken and egg here.Last IDT Entry InitID01: MMURTL V1. .========================================================================= . 0EC00h . 40 .16:31 of call offset MOV [EBX+04]. SI .move upper 16 of offset into SI MOV [EBX+06].call DPL & ndParams RETN .. ... .. 40 . 16 . Second.. AX . AX ..Same code as in PUBLIC AddCallGate MOVZX EBX.16:31 of call offset MOV [EBX+04]. software and hardware interrupt handlers. .AddCallGate -. handler that does nothing (except IRETD) when called. .Extend selector into EBX ADD EBX. First.Same code as in PUBLIC AddCallGate MOVZX EBX. SI . GDTBase ..Put Code Seg selector into Call gate MOV [EBX]. 0C8h MOV DX. CX .make index vice selector MOVZX EBX. ok. 3 . ISRs loaded with device drivers must use SetIRQVector.

08F00h MOV EBX. OSCodeSel . OFFSET IntOpCode CALL FWORD PTR _AddIDTGate MOV ECX. 08F00h . 08F00h . Trap gate MOV EBX. 08F00h . TRAP GATE MOV EBX. OFFSET IntDivBy0 CALL FWORD PTR _AddIDTGate MOV ECX.Double Exception MOV EAX. TRAP GATE MOV EBX. MMURTL V1. TRAP GATE .DPL 3. 08F00h . OSCodeSel .DPL 3.DPL 3. MOV ESI. 0 .Trying 8E00 (Int gate vice trap gate which leave Ints disabled) MOV ECX. OFFSET IntOverFlow CALL FWORD PTR _AddIDTGate MOV ECX.DPL 3. OSCodeSel . 08F00h .DPL 3.DPL 3. MOV ESI. OSCodeSel . OSCodeSel MOV ESI. OSCodeSel . 3 .Now we add each of the known interrupts MOV ECX. OSCodeSel .Seg Not Present . OFFSET IntQ CALL FWORD PTR _AddIDTGate POP ECX LOOP InitID01 . Offset IntDbgSS CALL FWORD PTR _AddIDTGate .Divide By Zero MOV EAX. 08F00h . 08E00h .DPL 3.Overflow MOV EAX.This will be filled in with TSS of Dbgr later MOV ESI. OSCodeSel .Invalid TSS MOV EAX. 8 . OFFSET IntDebug CALL FWORD PTR _AddIDTGate MOV ECX.DPL 3.Single Step MOV EAX. TRAP GATE MOV EBX. Trap Gate (for Debugger) WAS 8F00 MOV EBX. OSCodeSel . 1 . 0Bh MOV EAX. TRAP GATE MOV EBX. MOV ESI. MOV ESI.Invalid OPcode MOV EAX.DPL 3. MOV ESI.PUSH ECX MOV EAX. TRAP GATE MOV EBX. 0Ah . OFFSET IntInvTSS CALL FWORD PTR _AddIDTGate MOV ECX. OFFSET IntDblExc CALL FWORD PTR _AddIDTGate MOV ECX. TRAP GATE MOV EBX.Breakpoint MOV EAX. 08F00h . 4 .0 Page 399 of 667 . MOV ESI. 6 .



Used : ALL registers and flags . DPL 3 MOV ECX. 0EC01h . DPL 3 MOV ECX. 0EC03h . IF YOU ADD AN OS PUBLIC MAKE SURE IT GETS PUT HERE!!!!! . Out : Nothing .SendMsg -.2 DWord Params. before initcallgates. 08C03h .3 DWord params. OFFSET __SendMsg CALL FWORD PTR _AddCallGate MOV EAX.1Dword param.========================================================================== . OFFSET __ISendMsg CALL FWORD PTR _AddCallGate MOV EAX. 40h MOV DX. OFFSET __SetPriority CALL FWORD PTR _AddCallGate . OSCodeSel MOV ESI. RETN MMURTL V1. InitOSPublics adds all OS primitives to the array of call gates. PUBLIC InitOSPublics: MOV EAX. . OSCodeSel MOV ESI.MOV ESI.WaitMsg -. DPL 3 MOV ECX. 50h MOV DX.ISendMsg -.0 Page 402 of 667 . but MUST be called before the first far call to any . This can’t . 0EC02h . OFFSET __WaitMsg CALL FWORD PTR _AddCallGate MOV EAX. OSCodeSel MOV ESI. 58h MOV DX.Set Priority . OFFSET IntQ CALL FWORD PTR _AddIDTGate RETN .3 DWord Params. 48h MOV DX. OSCodeSel MOV ESI. IN : Nothing . DPL 0 MOV ECX.The rest of the call gate set ups go here. OS primitive thorugh a call gate!!! .

INCLUDE MOSEDF. the JCB.1 Listing 22. link blocks. was the death of a job and the reclamation of its valuable resources. Pieces of related code in the kernel. and finally the operating system memory. many of them public functions. and other places provide assistance with resource reclamation.1 . pictures of the complications of writing a loader floated in my head for quite a while.INC PUBLIC pFreeJCB PUBLIC pJCBs PUBLIC _nJCBLeft DD 0 DD 0 DD nJCBs . 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. TSSs.INCLUDE JOB. the monitor. request blocks. When things don’t work right.0 Page 403 of 667 .Chapter 22. These calls.DATA . Before I wrote this code. 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 resources you reclaim include all of the memory. exchanges. I have tried to break it down logically and keep it all in a high-level language.INC .INCLUDE TSS.asm MMURTL V1. this is the where you’ll end up. The program loader is in this chapter. Reclamation of Resources Equally as difficult as the loader. Monitor JCB reference EXTRN _BootDrive DD . The process of allocating all the resources. and finally starting the new task. making sure that each was correctly set and filled out.ASM provides many small helper functions for job management. can be a complicated scenario. See listing 22. JCBs are in allocated memory . it is usually quite a mess. In the functions ExitJob() and Chain(). It was every bit as difficult as I imagined. From Main.Job Management Subroutines . Ptr to free Job Control Blocks . are used by almost every module in the operating system. Job Management Helpers The file JobCode.INC . For Monitor stats EXTRN MonJCB DB .

Now to JobName MOV BYTE PTR [EDI]. USED : EAX. MOV EAX.Ptr to JCB that is to be filled in .size is filled in INC EDI . ESI -. INPUT : EAX -.Video number is in JCB MOV DWORD PTR [EAX+nCols]. ESI. . sbJobName . MOV [EAX+JcbPD]. Each element of rgJCBs is set to point to the next element of rgJCBs..======= End (Monitor & Debugger) . while . .pbJobName . OUTPUT : JOB Number in EAX .Linear Ptr to Page Directory for Job .EDI points to JCB ADD EDI. 80 . pJCBs . . This routine will initialize the free pool of Job Control Blocks (JCBs).Address of JCBs to be initialized . . 7 . This fills in a JCB with new information. MOV DWORD PTR [EAX+nLines]. CL . MODIFIES: pFreeJCB. . . EBX -.============================================================================ = .cbJobName .EBX . . MMURTL V1.Put Ptr to PD into JCB MOV EDI. ECX . EDX . ECX -. This is used to initilaize . USED: EAX.================================================================ . . EDX is size of each JCB. EDX -.EBX.Move it in MOV [EAX+pVirtVid].Pointer to Job Virtual Video Buffer (all jobs have one!) . OUTPUT : NONE . EDX.CODE . ECX is count of JCBs. The JCBs are also sequentially numbered.EDX. The last element of rgJCBs is set to point to nothing (NIL).ESI EFLAGS . The pFreeJCB pointer is set to address the first element in rgJCBs. . others (the ones we are initializing now) are dynamicly allocated.Begin Code ================== . in the array because some JCBs are static (Mon and Debugger). EDX . ECX. . a new JCB during OS init and when a new Job is loaded and run. We can’t use it’s position . EAX . . EFlags .Count of JCBs . InitFreeJCB .InitNewJCB is used initially by the OS to fill in the first two . INPUT : EAX . PUBLIC InitNewJCB: . 25 . . MOV DWORD PTR [EAX+NormAttr].0 Page 404 of 667 .first byte of name REP MOVSB . EBX.Size of JCBs .ECX. [EAX+JobNum] RETN . EDI. MODIFIES : JCB pointed to in EBX . EAX points to the first JCB.

Get OS pointer to JCBs CMP EAX. OUTPUT : EAX . Then call InitFreeJCB PUBLIC InitDynamicJCBs: PUSH 4 MOV EAX. update the value of pFreeJCB to point to the next "unused" JCB in .Get pointer to next free one MOV pFreeJCB. This routine will return in EAX register the address of the next free jcb. 0 . . Clear allocated memory for JCBs (4*4096=16384 . This routine will return to the caller a pointer to the next free jcb.Set up OS pointer to list MOV pJCBs.pFreeJCB . . then EAX will contain NIL (0).EAX points to next one MOV [ESI+NextJCB].[EAX+NextJCB] . JE NewJCBDone . pJCBs MOV ECX.Make current point to next MOV [ESI+JobNum]. 4 pages for 32 JCBs (16384 bytes) .0 . Allocate 4 pages (16384 bytes) for 32 Job Control Blocks (structures). . MOV EAX..IF pFreeJCB=NIL THEN Return.EAX .EBX. EBX .0 Page 405 of 667 .EAX .EAX . This routine will also .Number it INC EBX LOOP JCB_Loop . . 4096 EDI. . PUBLIC InitFreeJCB: MOV pFreeJCB. Get ’em! . .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.Make last one NIL RETN . . . sJCB CALL InitFreeJCB RETN . INPUT : NONE . 3 . EAX ECX. If none exists. . . .Go back till done MOV DWORD PTR [ESI+NextJCB].EBX has pointer to current one ADD EAX. EAX . the free pool. MOV EBX.Put it in OS pointer MMURTL V1. REGISTERS : EAX.================================================================ . . .EBX . OFFSET pJCBs PUSH EAX CALL FWORD PTR _AllocOSPage XOR MOV MOV REP EAX. Returns ptr to allocated mem in pJCBs . The data used in this algorithm is the free jcb pointer (pFreeJCB). nJCBs MOV EDX.1st number for Dynamic JCBs JCB_Loop: MOV ESI.Set up global ptr to first JCB MOV EBX.FLAGS . MODIFIES : pFreeJCB . pJCBs STOSD . . .EDX .============================================================================ = PUBLIC NewJCB: .

This routine will place the jcb pointed to by EAX back into the free . . PUBLIC GetpCrntJCB: MOV EAX. If pJCBin = NIL THEN Return. 0 .============================================================ . All TSSs are .Move it into newly freed JCB MOV pFreeJCB.DEC DWORD PTR _nJCBLeft NewJCBDone: RETN . USED: EAX. USED: EAX. .EAX . GetCrntJobNum .FLAGS . . This invalidates the JCB by placing 0 in JcbPD. . 0 .Move ptr to newly frred JCB to OS INC DWORD PTR _nJCBLeft . PUBLIC GetCrntJobNum: MMURTL V1. Returns the Job number for the currently executing task. MODIFIES : pFreeJCB .Pointer to JCB RETN .Linear Address of current JCB . The Job number . OUTPUT: EAX -. GetpCrntJCB .Current Job Number . INPUT : EAX . All TSSs are .pFreeJCB . . EFlags . This is based on which Task is executing. OUTPUT: EAX -. assigned to a Job! A Job may have more than one Task. . INPUT: Nothing . MOV DWORD PTR [EAX+JcbPD].EBX has OS ptr to free list MOV [EAX+NextJCB]. . Many OS functions deal with the Job number. pool of JCBs pointed to by (pFreeJCB) if EAX is not NIL. REGISTERS : EBX. DispJCBDone: RETN . This is based on which Task is executing. INPUT: Nothing . . . . assigned to a Job. A Job may have more than one Task.Invalidate JCB MOV EBX.EBX . . is a field in the JCB structure. . [EAX+TSS_pJCB] .Current Task State Segment MOV EAX. Returns a pointer to the current Job Control Block in EAX. .============================================================ . EFlags . .============================================================================ = PUBLIC DisposeJCB: . OUTPUT : NONE . . JE DispJCBDone . pRunTSS .0 Page 406 of 667 . CMP EAX.

. PUBLIC GetJobNum: MOV EAX.CALL GetpCrntJCB MOV EAX.Current JCB RETN . GetpJCB . OFFSET DbgJCB POP EDX RETN GetpJCB2: CMP EAX. . .Current Job Number . .Job Number of desired pJCB .Times size of JCB ADD EAX.Take off static JCBs+1 (make it an offset) MOV EDX.Now points to desired JCB POP EDX RETN . . USED: EAX. Returns the Job number for the pJCB in EAX in EAX. GetJobNum . OUTPUT: EAX -.============================================================ MMURTL V1. .============================================================ . PUBLIC GetpJCB: PUSH EDX CMP EAX.0 Page 407 of 667 . INPUT: EAX -. pJCBs . . Returns a pointer to a Job Control Block identified by number . [EAX+JobNum] RETN .============================================================ . sJCB MUL EDX . The Job number . [EAX+JobNum] . OFFSET MonJCB POP EDX RETN GetpJCB1: CMP EAX. .Within range of JCBs XOR EAX. Many OS functions deal with the Job number. is a field in the JCB structure. USED: EAX. 2 JNE GetpJCB2 MOV EAX. 1 JNE GetpJCB1 MOV EAX. OUTPUT: EAX -. EFlags . nJCBs+2 .Current JCB . EAX POP EDX RETN GetpJCB3: SUB EAX. . 3 .Linear Address of the JCB or 0 for invalid number . in EAX. EFlags .Add in two static JCBs JLE GetpJCB3 . INPUT: EAX pJCB we want job number from. All TSSs are assigned to a Job.

AllocJCB (NEAR) . [EBP+12] CALL GetJobNum MOV [ESI].. out of them! . pJCBRet EQU [EBP+8] PUBLIC _AllocJCB: PUSH EBP MOV EBP. . . ErcNoMoreJCBs will be returned if no more JCBs are avaialble. This allocates a new JCB (from the pool). . call to support the public job management calls in high level . EAX JNZ SHORT AJCB01 MOV EAX. We got one! . . . . This is a NEAR . pdJobNumRet is the number of the new JCB.pJCBRet . . pJCBRet is a pointer where you want the pointer to the new JCB is returned. . languages in the OS code. . Procedureal Interface : . . . . . [EBP+8] MOV [ESI].============================================================ . . DeAllocJCB(pJCB):ercType . Get a new JCB . . call to support the public job management calls in high level . This is a NEAR . .EBP POP EBP RETN 8 AJCB01: MOV ESI. . . . . . Procedureal Interface : . languages. . . . ErcNoMoreJCBs MOV ESP.No error . . pdJobNum EQU [EBP+12] . .Job Num . pJCB is a pointer the JCB to be deallocated. EAX MOV ESP. ppJCBRet):ercType .EBP POP EBP RETN 8 . . . DeAllocJCB (NEAR) . EAX XOR EAX. AllocJCB(pdJobNumRet. MMURTL V1. . .ESP CALL NewJCB OR EAX. ErcNoMoreJCBs will be returned if no more JCBs are avaialble. EAX MOV ESI. Sorry.0 Page 408 of 667 . This Deallocates a JCB (returns it to the pool).

GetpJCB . PUBLIC _DeAllocJCB: PUSH EBP MOV EBP. . . . ..EBP POP EBP RETN 4 . [EBP+8] CALL DisposeJCB XOR EAX. . . POP EBP . ErcBadJobNum . pJCBRet):ercType .0 is invalid CMP EAX.EBP . . pJCBRet EQU [EBP+12] PUBLIC __GetpJCB: PUSH EBP MOV EBP. ErcBadJobNum will be returned if dJobNum is out of range . . Get a new JCB . pJCB . EAX MOV ESP. MOV EAX. or 0 will be returned with the data.puts address of JCB in EAX MOV ESI. . dJobNum EQU [EBP+16] .pJCBRet MOV [ESI]. . .ESP .Dynamic + 2 static JBE GetpJcbOK GetpJcbBad: MOV EAX. [EBP+12] . pJCBRet is a pointer where you want the JCB returned. nJCBs + 2. GetpJcbOk: CALL GetpJCB . 0 . RETF 8 .Is this a valid JCB JNE GetpJCBOk1 MOV EAX. [EBP+16] . MOV ESP. EAX CMP DWORD PTR [EAX+JcbPD]. .===== BEGIN PUBLIC FAR JOB CALLS =========================== . . Procedureal Interface : . . GetpJCB(dJobNum. .============================================================ .ESP MOV EAX. . ErcInvalidJCB .JCB we are pointing to is unused MMURTL V1. dJobNum is the number of the JCB you want. This PUBLIC returns a pointer to the JCB for the JobNum . ErcBadJobNum will be returned if dJobNum is invalid . . pJCB EQU [EBP+8] .============================================================ . .Job Num OR EAX. you specifiy. . EAX JZ GetpJcbBad .0 Page 409 of 667 . .

No Error . variable _BootDrive defined in Main. This is . . pJCBRet is a pointer where you want the JCB returned.EBP POP EBP RETF 8 . GetSystemDisk(pSysDiskRet):ercType . . This PUBLIC returns a single byte which represents the . . . . pSysDiskRet is a pointer to a byte where . . . . This is from the public . . .MOV ESP. . This PUBLIC returns the number for the current Job. _BootDrive .pJobNumRet . . [EBP+12] MOV [ESI]. .Leave jobnum in EAX .0 Page 410 of 667 . EAX XOR EAX. Procedureal Interface : . . GetJobNum(pJobNumRet):ercType . It’s really not a filesystem function either. And will . pSysDiskRet EQU [EBP+12] PUBLIC __GetSystemDisk PUSH EBP MOV EBP. EAX POP EBP RETF 4 . GetJobNum . . disk that the system booted from. the job that the task that called this belongs to. .asm. It return 0-n (which corresponds to A-x) . . .============================================================ . . MMURTL V1. still be needed if a loadable filesystem is installed. .============================================================ . EAX MOV ESP. . . . This code is here for lack of a better place. . .ESP CALL GetCrntJobNum MOV ESI.ESP MOV EAX.EBP POP EBP RETF 8 GetpJcbOk1: XOR EAX. . . . GetSystemDisk . the number representing the system disk is returned. pJobNumRet EQU [EBP+12] PUBLIC __GetJobNum: PUSH EBP MOV EBP. . . Procedureal Interface : .

AL .c Source code /* This file contains functions and data used to support loading or terminating jobs (or services). EAX .AND EAX. and public functions for working with the job control block are contained in this module.0 Page 411 of 667 . ending jobs.h" MMURTL V1. 7Fh . Listing 22.No Error POP EBP .pJobNumRet MOV [ESI]. Functions for starting new jobs. XOR EAX. [EBP+12] . RETF 4 . .get rid of high bits MOV ESI. contains all of the major high-level functions for job management.c. Jobc.2 – Jobc.================= MODULE END ================================= Job Management Listing This file. It contains the public functions: Chain() LoadNewJob() ExitJob() GetExitJob() SetExitJob() SetCmdLine() GetCmdLine() GetPath() Setpath() SetUserName() GetUserName() */ Loads new job run file in current JCB & PD Loads a new job into a new JCB & PD Exits current job. 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 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 U32 unsigned long S32 long U16 unsigned int S16 int U8 unsigned char S8 char TRUE 1 FALSE 1 #include "MKernel.

h" "MKbd. char *pJCBRet). ercE. char *pNewJCB). ExchE. static struct JCBRec *pTmpJCB. *pPDE. /* Temporary NEAR externals from the monitor program for debugging */ extern long xprintf(char *fmt.). MMURTL V1. SendAbort(long JobNum. */ static long TmpStack[128]. static struct JCBRec *pCrntJCB. char *ppJCBRet).0 Page 412 of 667 . extern U32 Dump(unsigned char *pb. /* We switch to this stack when we clean out a user PD before we rebuild it. long Exch). SetExchOwner(long Exch.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. /* 512 byte temporary stack */ /* Used for allocating/filling in new JCB */ static struct JCBRec *pNewJCB. iE. .h" "MTimer.h" "MVid. BogusMsg[2]. static long cbFileE.h" "MFiles. GetExchOwner(long Exch..h" "MData. /* Used to access a JCB */ static char aFileE[80].#include #include #include #include #include #include #include "MMemory. RemoveRdyJob(char *pJCB). /* For ExitJob and Chain cause They can’t have local vars! */ static static static static static static long long long long char long JobNumE. long cb).. job_fhE. *pExchJCBE KeyCodeE.ASM */ extern extern extern extern extern long long long long long AllocJCB(long *pdJobNumRet.h" #include "runfile. static long JobNum.h" "MJob.

extern unsigned long KillExch; /* Run file data */

/* From the Monitor */

static char *pCode, *pData, *pStack; static long sCode, sData, sStack; static unsigned long oCode, oData; static long offCode = 0, Segs */ offData = 0; static unsigned long oCDFIX nCCFIX oCCFIX nDDFIX oDDFIX nDCFIX oDCFIX nCDFIX = 0, 0, 0, 0, 0, 0, 0, 0;

/* Ptrs in User mem to load to */ /* Size of segments */ /* Offset in file to Code & Data */ /* Virtual Offset for Code & Data

= = = = = = =

static char *pStart, filetype; static struct tagtype tag;

/************* INTERNAL SUPPORT CALLS ******************/ /****************************************************** This deallocates all user memory in a PD. This is used when we ExitJob to free up all the memory. We get the pointer to the PD and "walk" through all the User PTs deallocating linear memory. When all PTs have been cleaned, we eliminate the user PDEs. This leaves a blank PD for reuse if needed. 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, j, k, erc; unsigned long *pPT; char *pMem; for (i=768; i<1024; i++) { if (pPD[i]) { pPT = pPD[i] & 0xFFFFF000; for (j=0; j<1024; j++) { /* Look at each shadow PDE */ /* If it’s a linear address (non 0)*/ /* Point to Page Table */

/* Get Beginning address for each run to deallocate */ k = 0; /* nPages to deallocate */ pMem = ((i-512) * 0x400000) + (j * 4096); while ((pPT[j++]) && (j<1024)) k++; /* one more page */ if (k)


Page 413 of 667

{ erc = } } } } }

/* we have pages (one or more) */ DeAllocPage(pMem, k);

/********************************************************* This opens, reads and validates the run file. If all is well, it leaves it open and returns the file handle, else it closes the run file and returns the error to the caller. The caller is responsible for closing the file!!! *********************************************************/ static long GetRunFile(char *pFileName, long cbFileName, long *pfhRet) { long erc, i, fh, dret; char fDone, junk; offCode = 0; offData = 0; nCDFIX = 0; oCDFIX = 0; nCCFIX = 0; oCCFIX = 0; nDDFIX = 0; oDDFIX = 0; nDCFIX = 0; oDCFIX = 0; *pfhRet = 0; /* default to 0 */

/* Mode Read, Stream type */ erc = OpenFile(pFileName, cbFileName, 0, 1, &fh); if (!erc) { /* File opened OK */ fDone = 0; while ((!erc) && (!fDone)) { = 0; erc = ReadBytes (fh, &tag, 5, &dret); switch ( { case IDTAG: erc = ReadBytes (fh, &filetype, 1, &dret); if ((filetype < 1) || (filetype > 3)) erc = ErcBadRunFile; break; case SEGTAG: erc = ReadBytes (fh, &sStack, 4, &dret); if (!erc) erc = ReadBytes (fh, &sCode, 4, &dret); if (!erc) erc = ReadBytes (fh, &sData, 4, &dret); break; case DOFFTAG: erc = ReadBytes (fh, &offData, 4, &dret);


Page 414 of 667

break; case COFFTAG: erc = ReadBytes (fh, &offCode, 4, &dret); break; case STRTTAG: erc = ReadBytes (fh, &pStart, 4, &dret); break; case CODETAG: erc = GetFileLFA(fh, &oCode); if (!erc) erc = SetFileLFA(fh, oCode+tag.len); /* skip it break; case DATATAG: erc = GetFileLFA(fh, &oData); if (!erc) erc = SetFileLFA(fh, oData+tag.len); /* skip it break; case CDFIXTAG: erc = GetFileLFA(fh, &oCDFIX); nCDFIX = tag.len/4; if (!erc) erc = SetFileLFA(fh, oCDFIX+tag.len); /* skip it break; case CCFIXTAG: erc = GetFileLFA(fh, &oCCFIX); nCCFIX = tag.len/4; if (!erc) erc = SetFileLFA(fh, oCCFIX+tag.len); /* skip it break; case DDFIXTAG: erc = GetFileLFA(fh, &oDDFIX); nDDFIX = tag.len/4; if (!erc) erc = SetFileLFA(fh, oDDFIX+tag.len); /* skip it break; case DCFIXTAG: erc = GetFileLFA(fh, &oDCFIX); nDCFIX = tag.len/4; if (!erc) erc = SetFileLFA(fh, oDCFIX+tag.len); /* skip it break; case ENDTAG: fDone = TRUE; break; default: erc = GetFileLFA(fh, &i); if (tag.len > 1024) erc = ErcBadRunFile; if (!erc) erc = SetFileLFA(fh, i+tag.len); /* skip it */ if (erc) erc = ErcBadRunFile; break; } } if (erc)








Page 415 of 667

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

/* Get pJCB to current Job */


Page 416 of 667

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

/**************************************************/ long far _SetUserName(char *pUser, long dcbUser) {


Page 417 of 667

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


Page 418 of 667

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


Page 419 of 667

U32 PhyAdd;

erc =

GetRunFile(pFileName, cbFileName, &fh);

if (!erc) { /* 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; pNewJCB = 0; pPD = 0; pPT = 0; pVid = 0; erc = AllocJCB(&JobNum, &pNewJCB); /* Alloc OS memory pages required for new job */ if (!erc) erc = AllocOSPage(3, &pPD); pPT = pPD + 4096; pVid = pPD + 8192; if (!erc) { FillData(pPT, 4096, 0); FillData(pVid, 4096, 0); GetpJCB(1, &pTmpJCB); pOSPD = pTmpJCB->pJcbPD;

/* Job’s PD, PT * pVirtVid */

/* 1 page later */ /* 2 pages later */

/* Zero user PT */ /* Zero user video */ /* Get OS pJCB */ /* Pointer to OS PD */

GetPhyAdd(1, pPT, &PhyAdd); /* Get Phy Add for new PT */ PhyAdd |= MEMUSERD; pOSPD[256] = PhyAdd; pOSPD[768] = pPT; /* xprintf("pOSPD : %08x\r\n", xprintf("pUserPD: %08x\r\n", xprintf("pUserPT: %08x\r\n", xprintf("PhyAdd : %08x\r\n", ReadKbd(&KeyCodeE, 1); */ /* Set user code bits in PDE */ /* Make User PDE in OS PD */ /* Shadow Linear Address of PT */

pOSPD); pPD); pPT); PhyAdd);

/* 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; if (sStack%4096) nPages++; erc = AllocPage(nPages, &pStack); sStack = nPages * 4096;

/* set to whole pages */


Page 420 of 667

nPages = sCode/4096; if (sCode%4096) nPages++; if (!erc) erc = AllocPage(nPages, &pCode); nPages = sData/4096; if (sData%4096) nPages++; if (!erc) erc = AllocPage(nPages, &pData); /* Right now, the OS PD looks exacly like we want the User PD to look. We will now copy the entire OS PD into the User’s New PD. */ CopyData(pOSPD, pPD, 4096); /* Copy OS PD to User PD */

/* All Job memory is now allocated, so let’s LOAD IT! */ if (!erc) erc = SetFileLFA(fh, oCode); if (!erc) erc = ReadBytes (fh, pCode, sCode, &dret); if (!erc) erc = SetFileLFA(fh, oData); if (!erc) erc = ReadBytes (fh, pData, sData, &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, oCDFIX); /* back to fixups */ while ((nCDFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pCode + i; /* Where in CSeg */ *pFix = *pFix - offData + pData; } } if (nCCFIX) { erc = SetFileLFA(fh, oCCFIX); /* back to fixups */ while ((nCCFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pCode + i; /* Where in CSeg */ *pFix = *pFix - offCode + pCode; } } if (nDCFIX) { erc = SetFileLFA(fh, oDCFIX); while ((nDCFIX--) && (!erc)) {

/* back to fixups */


Page 421 of 667

erc = ReadBytes (fh, &i, 4, &dret); pFix = pData + i; /* Where in DSeg */ *pFix = *pFix - offCode + pCode; } } if (nDDFIX) { erc = SetFileLFA(fh, oDDFIX); /* back to fixups */ while ((nDDFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pData + i; /* Where in DSeg */ *pFix = *pFix - offData + pData; } } } /* Clean the OS PD of User memory */ FillData(&pOSPD[256], 1024, 0); /* Clean OS PD of User PDEs */ FillData(&pOSPD[768], 1024, 0); /* Clean OS PD of User Shadow */ /* Now we fill in the rest of the User’s JCB */ pNewJCB->pJcbPD = pPD; pNewJCB->pJcbStack = pStack; pNewJCB->sJcbStack = sStack; pNewJCB->pJcbCode = pCode; pNewJCB->sJcbCode = sCode; pNewJCB->pJcbData = pData; pNewJCB->sJcbData = sData; pNewJCB->sbUserName[0] = 0; pNewJCB->sbPath[0] = 0; pNewJCB->JcbExitRF[0] = 0; pNewJCB->JcbCmdLine[0] = 0; /* /* /* /* /* /* /* /* /* /* /* 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", &pNewJCB->JcbSysIn[1], 3); pNewJCB->JcbSysIn[0] = 3; /* Size */ CopyData("VID", &pNewJCB->JcbSysOut[1], 3); pNewJCB->JcbSysOut[0] = 3; /* Size */ pNewJCB->pVidMem = pVid; pNewJCB->pVirtVid = pVid; pNewJCB->CrntX = 0; pNewJCB->CrntY = 0; pNewJCB->nCols = 80; pNewJCB->nLines = 25; pNewJCB->VidMode = 0; pNewJCB->fCursOn = 1; pNewJCB->fCursType = 0; /* /* /* /* /* /* Default to Virt Vid */ Virtual Video memory */ Vid X Position */ Vid Y Position */ Columns */ Lines */

/* 80x25 VGA Color Text */ /* Cursor On */ /* UnderLine */

/* Finally, we crank up the new task and schedule it for execution!


Page 422 of 667

*/ if (!erc) erc = AllocExch(&i); if (!erc) erc = NewTask(JobNum, 0x18, 25, 0, i, pStack+sStack-4, pStart+pCode-offCode); if (!erc) SetExchOwner(i, pNewJCB); /* Exch now belongs to new JCB */ } CloseFile(fh); /* read run file OK */


if (!erc) *pJobNumRet = JobNum; return(erc); } /********************************************************* This loads a job into an existing PD and JCB. This is called by Chain() and may also be called by ExitJob() if an ExitJob was specified in the current JCB. The PD, pVid & first PT still exist in OS memory. (CleanPD left the first PT for us). ExitJob and Chain are responsible for opening and validating the runfile and setting up the run file variables. *********************************************************/ static long LoadJob(char *pJCB, long fh) { long erc, i, dret, nPages; long *pFix; pNewJCB = pJCB; /* Allocate user memory for Stack, Code and Data */ /* This is done in the context of the USER PD */ nPages = sStack/4096; if (sStack%4096) nPages++; erc = AllocPage(nPages, &pStack); sStack = nPages * 4096; nPages = sCode/4096; if (sCode%4096) nPages++; if (!erc) erc = AllocPage(nPages, &pCode); nPages = sData/4096; if (sData%4096) nPages++; erc = AllocPage(nPages, &pData); /* All Job memory is now allocated, so let’s LOAD IT! */

/* set to whole pages */


Page 423 of 667

if (!erc) erc = SetFileLFA(fh, oCode); if (!erc) erc = ReadBytes (fh, pCode, sCode, &dret); if (!erc) erc = SetFileLFA(fh, oData); if (!erc) erc = ReadBytes (fh, pData, sData, &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, oCDFIX); /* back to fixups */ while ((nCDFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pCode + i; /* Where in CSeg */ *pFix = *pFix - offData + pData; } } if (nCCFIX) { erc = SetFileLFA(fh, oCCFIX); /* back to fixups */ while ((nCCFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pCode + i; /* Where in CSeg */ *pFix = *pFix - offCode + pCode; } } if (nDCFIX) { erc = SetFileLFA(fh, oDCFIX); /* back to fixups */ while ((nDCFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pData + i; /* Where in DSeg */ *pFix = *pFix - offCode + pCode; } } if (nDDFIX) { erc = SetFileLFA(fh, oDDFIX); /* back to fixups */ while ((nDDFIX--) && (!erc)) { erc = ReadBytes (fh, &i, 4, &dret); pFix = pData + i; /* Where in DSeg */ *pFix = *pFix - offData + pData; } } /* Now we fill in the rest of the User’s JCB */ pNewJCB->pJcbStack = pStack; /* Add of Stack */ pNewJCB->sJcbStack = sStack; /* Size of Code */ pNewJCB->pJcbCode = pCode; /* Add of Code */ pNewJCB->sJcbCode = sCode; /* Size of Code */


Page 424 of 667

pNewJCB->pJcbData pNewJCB->sJcbData } CloseFile(fh); return(erc); }

= pData; = sData;

/* Add of Code */ /* Size of Code */

/****************************************************** This is started as new task in the context of a job that is being killed off. This is done to allow memory access and also reuse the code for ExitJob in the monitor. This task, it’s exchange, and TSS will be reclaimed by the monitor along with the JCB and all OS memory pages for the PD,PT and video. ******************************************************/ void _KillTask(void) { GetJobNum(&JobNumE); GetpJCB(JobNumE, &pTmpJCB);

/* Get pJCB to this Job */

/* Clean the PD of all user memory leaving OS memory to be deallocated by the caller (monitor or whoever). */ pPDE = pTmpJCB->pJcbPD; CleanUserPD(pPDE); GetTSSExch(&ExchE); ercE = 0; while(!ercE) /* clear the exchange */ ercE = CheckMsg(ExchE, BogusMsg); ISendMsg(KillExch, ExchE, ErcOpCancel); SetPriority(31); WaitMsg(ExchE, BogusMsg); /* He’s History! */ }

/****************************************************** This called from one job to kill another job or service. This cleans up ALL resources that the job had allocated. This is used to kill run-away jobs, or terminate a job just for the fun ot it. It results in a violent death for the job specified. This must never be called from a task within the job to be killed. A job may terminate itself with ExitJob(). ******************************************************/ long far _KillJob(long JobNum)


Page 425 of 667

3. erc = GetpJCB(JobNum. 0. ExchE). if (erc) return(erc). &pExchJCBE). The task we are in does not belong to the job we are killing so we can remove them all. &pTmpJCB). /* Get pJCB to the Job */ pTmpJCB->ExitError = ErcOpCancel. /* Notify all services */ /*JobNum. iE++. ExchE. /* Make sure it’s not the Monitor. /* Get an Exch */ SetExchOwner(ExchE. CodeSeg. &_KillTask). We send "Abort" messages to all services. 0x08. &TmpStack[127]. /* Clear the error */ /* Now that the user can’t make anymore requests. */ GetJobNum(&JobNumE). } ercE = 0.0 Page 426 of 667 . /* make him the owner */ SendAbort(JobNum. ESP. MMURTL V1. pTmpJCB). */ 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). if ((JobNum == JobNumE) || (JobNum == 1) || (JobNum == 2)) return(ErcBadJobNum). Exch. This closes all files that were opened by the Job and frees up any other resources held for this job by any service. /* It always returns ErcOk */ /* Deallocate ALL exchanges for this job */ ercE = 0. /* Operator said DIE! */ /* Remove ALL tasks for this job that are at the ReadyQue. Priority. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. iE = 0. Debugger or the current job. if ((!ercE) && (pExchJCBE == pTmpJCB)) DeAllocExch(iE).{ long erc. */ RemoveRdyJob(pTmpJCB). fDebug. EIP */ erc = NewTask(JobNum.

******************************************************/ void far _ExitJob(long dError) { /* NO LOCAL VARIABLES BECAUSE WE SWITCH STACKS!! */ GetJobNum(&JobNumE). &pExchJCBE). MMURTL V1. iE = 0. 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. This cleans up ALL resources that the job had allocated.0 Page 427 of 667 . } /* Now that the user can’t make anymore requests. /* He’s History! */ } /****************************************************** This called from Exit() in C or directly from a user job or service. 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). ercE = 0. &pCrntJCB). This closes all files that were opened by the Job and frees up any other resources held for this job by any service. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. /* 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. GetpJCB(JobNumE. Send Abort messages to all services. /* Get pJCB to current Job */ /* Remove ALL tasks for this job that are at the ReadyQue. */ /* Find out what our TSS exchange is so we don’t deallocate it to! */ GetTSSExch(&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).return(erc). iE++. pCrntJCB->ExitError = dError. if ((!ercE) && (iE != ExchE) && (pExchJCBE == pCrntJCB)) DeAllocExch(iE).

EBP. If no exit run file. ESP. MOV EBX. PUSH EAX _pStack _sStack EBX 4 EAX EAX _pStart MMURTL V1. 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!). SUB EAX. we deallocate the PD and JCB then return to JOB 1. &job_fhE). */ GetExitJob(aFileE. PUSH 18h MOV EAX. cbFileE. if (!cbFileE) ercE = ErcNoExitJob. */ #asm MOV EAX. ADD EAX. /* while(!ercE) /* ercE = CheckMsg(ExchE. ercE = 0. if (!ercE) { pStart = pStart+pCode-offCode. if (!ercE) ercE = /* Exit Run File!! */ GetRunFile(aFileE. /* Look for Exit Run file to load if any exists.0 Page 428 of 667 . MOV EBP. /* Clear the error */ clear the exchange */ BogusMsg). MOV ESP. ExchE). job_fhE).*/ SendAbort(JobNumE. ercE = 0. /* Now we RETURN to new job’s address after we put him on his new stack. if (!ercE) ercE = LoadJob(pCrntJCB. CleanUserPD(pPDE). */ pPDE = pCrntJCB->pJcbPD. EAX. */ #asm MOV ADD MOV MOV #endasm EAX. &cbFileE). OFFSET _TmpStack 508 EAX EAX /* Clean the PD of all user memory leaving OS memory for next job if there is one.

This is so you can run program B from program A and return to program A when program B is done. /* if it had a handle at all */ return(ercE). WaitMsg(ExchE. cbFileE = cbFileName. long dExitError) { /* NO LOCAL VARIABLES BECAUSE WE SWITCH STACKS!! */ CopyData(pFileName. Information can be passed to Job B by calling SetCmdLine (if Job B reads it). ercE).0 Page 429 of 667 . Chain will only return to you if there was an error loading the Job. /* we will open it again after SendAbort */ GetJobNum(&JobNumE). &pCrntJCB). 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. ******************************************************/ long far _Chain(char *pFileName. cbFileName). if (ercE) { CloseFile(job_fhE). and also by setting the ExitError value in the parameter. 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. In other words. aFileE. GetpJCB(JobNumE. SetPriority(31). if Chain fails in a critial section we try to load the ExitJob and pass it the error. /* Get pJCB to current Job */ MMURTL V1. BogusMsg). } CloseFile(job_fhE). cbFileName.We are history! /* something failed or we don’t have an ExitRF */ /* 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. /* We are NO MORE */ } } /****************************************************** This is called to execute a program without changing the ExitJob. ercE = GetRunFile(pFileName. &job_fhE).RETF #endasm } if (ercE) { . long cbFileName.

ercE = 0.pCrntJCB->ExitError = dExitError.0 Page 430 of 667 . 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 services. */ #asm MOV ADD MOV MOV #endasm EAX. iE++. } /* Now that the user can’t make anymore requests. OFFSET _TmpStack 508 EAX EAX /* Clean the PD of all user memory leaving OS memory for next MMURTL V1. /* 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. &pExchJCBE). EBP. while (ercE != ErcOutOfRange) { ercE = GetExchOwner(iE. 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). ExchE). if ((!ercE) && (iE != ExchE) && (pExchJCBE == pCrntJCB)) DeAllocExch(iE). EAX. Send Abort messages to all services. ercE = 0. */ SendAbort(JobNumE. ercE = 0. /* Remove ALL tasks for this job that are at the ReadyQue. and will also free up RQBs and Link Blocks. iE = 0. */ /* Find out what our TSS exchange is so we don’t deallocate it to! */ GetTSSExch(&ExchE). /* while(!ercE) /* ercE = CheckMsg(ExchE. ESP. /* Clear the error */ clear the exchange of abort responses*/ BogusMsg). 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!).

cbFileE. /* it was valid before!*/ ercE = LoadJob(pCrntJCB. if (!cbFileE) ercE = ErcNoExitJob. SUB EAX. PUSH 18h MOV EAX.job if there is one. &job_fhE). Don’t bother checking error cause it was valid if we got here! */ GetRunFile(aFileE. If no exit run file. /* Try to load the Chain file. job_fhE). if (!ercE) ercE = LoadJob(pCrntJCB. if (ercE) { /* We have errored in a critical part of Chain (LoadJob). &cbFileE). and we can’t run the chain file (bummer). */ GetExitJob(aFileE. */ pPDE = pCrntJCB->pJcbPD. PUSH EAX RETF #endasm } if (ercE) { /* Something failed loading the job (Chain or Exit) */ _pStack _sStack EBX 4 EAX EAX _pStart . &job_fhE). MOV ESP. job_fhE). MOV EBX.We are history! /* In case there is no job to run or a fatal error has happened MMURTL V1. The only thing left to do is Look for Exit Run file to load if any exists. */ #asm MOV EAX. cbFileE. if (!ercE) ercE = /* Exit Run File!! */ GetRunFile(aFileE. The original user’s job is destroyed.0 Page 431 of 667 . } if (!ercE) { /* No error */ pStart = pStart+pCode-offCode. /* Now we RETURN to new job’s address after we put him on his new stack. CleanUserPD(pPDE). ADD EAX. we kill this guy and return to the monitor if he had the screen. MOV EBP.

0 Page 432 of 667 . /* We are NO MORE */ } } /*********************** End of Module *****************/ /* ISend clears ints! */ MMURTL V1. BogusMsg). WaitMsg(ExchE. ercE). #asm STI #endasm SetPriority(31). ExchE.we send a message (ISendMsg) to the monitor status task with our TSSExch and the Error. 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.

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

we come here PUSH dbgOldEFlgs PUSH dbgOldCS PUSH dbgOldEIP IRETD . [EAX+TSS_CR3] EDX.we MUST save caller’s registers PUSH EBX . This makes the .Set Dbgr as running task MOV BX. with the exception of the disassembler. entry from the current job into the debuggers job. [EDX+TSS_pJCB] EBX. EBX EAX.Go back to the caller Debugger Source Listing Listing 23. OFFSET DbgTSS pRunTSS. task switch into the debugger MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV MOV EAX. debugger operate in the current tasks memory space. then copy the CR3 . This code effectively replaces the interrupted task with the debugger . [EAX+JcbPD] [EDX+JcbPD]. BX POP EDX POP EBX POP EAX JMP FWORD PTR [TSS] . . EAX EAX.pDebuggerJCB -> EDX . we save the current pRunTSS and place the debugger’s TSS in . . then jump to the debugger’s selector. This switches tasks. [EAX+Tid] MOV TSS_Sel.Set up debugger selector . OFFSET DbgTSS [EDX+TSS_CR3].Put the stack back the way it was . . EBX EAX. pRunTSS DbgpTSSSave. pRunTSS.pRunTSS -> EAX . task (without going through the kernel).CrntJob Page Dir -> EBX ." The debugger also has it’s own read-keyboard call in the keyboard service to allow debugging of portions of the keyboard code. First we copy the Page Dir .When the debugger exits. .current CR3 -> EBX . pRunTSS EBX.Install Debugger’s as current . and restore them before the PUSH EDX .CR3 -> DebuggerTSS .2 presents the debugger code in it’s entirety. register from the current TSS into the debugger’s TSS. All of the debugger’s .pCrntJCB -> EAX . EAX . Next. code and data are in OS protected pages (which are shared with all tasks).make his registers right! .Save the current pRunTSS . MMURTL V1. [EAX+TSS_pJCB] EDX..0 Page 434 of 667 . so this is OK to do even if the offending task referenced a bogus address.pDebuggerTSS -> EDX .Page Dir -> Debugger JCB .Switch tasks to debugger . You’ll see much of it is display "grunt work. EnterDebug: PUSH EAX . .

For ReadDbgKbd .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 .INC .0 Page 435 of 667 .INC RQB.To save interrupted video user DB ’00000000’ .For Disassem display (next ins to be executed) .Address we are dumping .Address of instructions we are displaying .INC TSS.CR LF for Dump etc.For line and column display coordination DD 0 DD 0 DD 0 DD 0 DD 0 DD 0 DB 0 DB 0 DB 0Dh.2 – Debugger Data and Code ..Listing 23.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 0 0 0 0 0 0 0 0 0 .TO save the TSS we interrupted . .DATA . 0 = Exch .ALIGN DWORD .INCLUDE .General use buffers DB ’0000000000’ .Address we are setting as next . MMURTL V1. .0FFh is NO FAULT DbgVidSave dbgBuf dbgBuf2 DD 0 .INCLUDE MOSEDF. 0Ah DB 0 .Active bytes in buf2 .INCLUDE .Boolean.INCLUDE .External near Variables EXTRN EXTRN EXTRN EXTRN rgTmrBlks pJCBs RdyQ rgPAM DD DD DD DB .flag 1 = Msg.are we dumping DWORDS? .INC JOB.Crnt Breakpoint Linear addresses .General purpose DDs (used all over) .. .Has Dbg Video been initialized? . .

’EBP: 0B3h. dbgTaskMsg ’SStep’.40 bytes 42 0123456789012345678901234567890123456789012345678901234 DB ’Exch Owner dMsgLo dMsgHi Task’ 0123456789012345678901234567890123456789012345678901234 DB ’Task# Job pJCB pTSS Priority ’ .’Tasks’.’SetBP’.’ ’.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. ESC to quit.0B3h.For Debugger entry conditions dbgFltMsg DB ’FATAL Processor Exception/Fault: ’ sdbgFltMsg DD $-dbgFltMsg .’EBX: 0B3h.’ SS: 0B3h.’ClrBP’.0B3h.’EFL: 0B3h.’ DS: 0B3h.’TSS: 0B3h. dbgExchMsg .’EDX: 0B3h.0B3h.’EDI: 0B3h.0B3h.’CrntAddr’ ’DumpB’.’CR2: 0B3h.’ ’ ’ .’Erc: ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ DB ’Linear address: ’ .’ESI: 0B3h.0 Page 436 of 667 .’AddInfo ’ ’ ’ ’ENTER to continue.For Important Address Info Display dbgM0 dbgM1 dbgM2 dbgM3 dbgM4 dbgM5 dbgM6 dbgM7 dbgM8 dbgM9 DB DB DB DB DB DB DB DB DB DB ’IDT: ’GDT: ’RQBs: ’TSS1: ’TSS3: ’LBs: ’RdyQ: ’JCBs: ’SVCs: ’Exch: ’ ’ ’ ’ ’ ’ ’ ’ ’ ’ MMURTL V1.’ FS: 0B3h.’EIP: 0B3h.’CS:EIP ’ ’Exchs’.’CR3: 0B3h.’ CS: 0B3h.’DumpD’.’ECX: 0B3h.’ESP: 0B3h.0B3h.0B3h.’ GS: 0B3h.0B3h.’ ’.’CR0: 0B3h.’EAX: 0B3h.’ ES: 0B3h.dbgMenu DB DB DB dbgSpace DB dbgCont DB dbgClear DB dbgAsterisk DB .0B3h.0B3h.

dbgOldEflgs .way they were when the exception fired off becuase of the .Save number of vid we interrupted PUSH EAX CALL FWORD PTR _GetVidOwner STI PUSH 2 CALL FWORD PTR _SetVidOwner CMP fDbgInit. . MOV EBX. . so we shouldn’t have to reset it. the handler is a procedure (NOT a task). and flags are not the . The debugger .BX MOV EBX.the stack (entering the debugger) into the caller’s TSS. the values of . I guess I’m not reading it right or ROD SERLING LIVES! . .NOTE: The "book" says the TF flag is reset by the processor .EBX . MOV EBX. is always entered as a procedure.EBX . DbgpTSSSave .==================== End Data. (we chanage the tasks) .dbgOldCS .dbgOldEIP .interrupt procedure they enterred to get to the debugger. OFFSET DbgVidSave . But we do. when the handler is entered.CODE EXTRN EXTRN EXTRN EXTRN DDtoHex NEAR HexToDD NEAR _disassemble NEAR ReadDbgKbd NEAR PUBLIC DbgTask: MOV EAX.dbgPA dbgMB DB ’PAM: ’ DB ’aTmr: ’ ... .EAX still has DbgpTSSSave MOV EBX.When a fault or debug exception occurs.Reset TF in case single steping AND EBX.Store correct CS MOV [EAX+TSS_CS]. Code Seg.0FFFFFEFFh MOV [EAX+TSS_EFlags]. This only applies if .EBX .[EAX+TSS_EFlags] .Store correct flags MOV [EAX+TSS_EFlags].We make them the same by putting the values we got from .the Instruction Pointer. 1 DbgInitDone: MOV EAX.0 Page 437 of 667 .Dbgr is Job 2 MMURTL V1. 0 JNE DbgInitDone CALL FWORD PTR _ClrScr MOV fDbgInit.Store correct EIP MOV [EAX+TSS_EIP]. Begin Code ========================== .

Back to where we were PUSH dbgY CALL FWORD PTR _SetXY .dbgFAULT PUSH EAX PUSH OFFSET dbgBuf CALL DDtoHex LEA EAX.NO .Color Black on RED .reset fault indicator .Display BackLink’s Register values CALL dbgDispMenu . EAX CALL dbgShowBP PUSH EAX MMURTL V1.dbgBuf PUSH EAX PUSH 8 PUSH 70h CALL FWORD PTR _TTYOut MOV DWORD PTR dbgFAULT.dbgFltMsg PUSH EAX PUSH sdbgFltMsg PUSH 40h CALL FWORD PTR _TTYOut MOV EAX.Display Instruction at CS:EIP MOV EBX.=================================================================== dbg000: CALL DbgRegVid . EAX INC EAX MOV dbgY..dbgCRLF PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut XOR EAX. EAX PUSH EAX PUSH EAX CALL FWORD PTR _SetXY LEA EAX. EAX .NOTE: Must eventually add SS/ESP for a change in CPL on faults!!! .Reset X & Y to 0. [EBX+TSS_EIP] MOV dbgCrntAdd. 0FFh LEA EAX. . EAX MOV dbgX.REF .Display fault message and . and number at 0 .1 .Get USER pUserTSS MOV EAX.procedure was entered. CMP DWORD PTR dbgFAULT.Was the dbgr entered on a FAULT? JE dbg000 .We set the FAULT variable based on which interrupt .DbgpTSSSave .Color White on black .See page 3-4 System Software Writer’s Guide XOR EAX.0 Page 438 of 667 .0 .Display menu PUSH dbgX .0FFh .

EBX DR0. .Set TF in flags for single step . 00000002h DR7. MOV EAX.DbgpTSSSave MOV ECX.Lop off key status bytes CMP EAX.Address displayed . MMURTL V1.Set BP (Current Address . EAX dbgBPAdd. 13h JNE dbg06 CALL dbgDispExchs . 11h dbg04 EAX.=========================================================== .00000100h MOV [EBX+TSS_EFlags].This puts the instruction on the line .ECX JMP dbgExit dbg02: CMP JNE MOV MOV MOV XOR MOV MOV MOV JMP dbg03: CMP JNE XOR MOV MOV JMP dbg04: CMP EAX.Get USER pUserTSS . EAX DR6. EAX CALL dbgCheckScroll JMP dbg00 dbg05: CMP EAX. EAX dbg00 . 1Bh JE dbgExit CMP EAX. 0Fh JNE dbg02 MOV EBX. [EBX+TSS_EIP] MOV dbgCrntAdd.Display Exchanges (F5) . EAX EAX.F2) .This puts the instruction on the line .Move into BreakPoint Reg 0 . OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd . EAX DR7.Save it so we know where BP is . .Clear BP saved address .Single Step (F1) .ESCAPE (Exit) . 0FFh .Now we read the keyboard dbg00: MOV EAX. EAX dbg00 .[EBX+TSS_EFlags] OR ECX. EBX EAX.Get USER pUserTSS EAX. dbgCrntAdd dbgBPAdd.DbgpTSSSave MOV EAX. EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP.CALL _disassemble MOV NextEIP. 12h JNE dbg05 MOV EBX. . 10h dbg03 EBX.Clear BP (Current Address .See if we need to scroll up .Return to CS:EIP (F4) . dbgKeyCode AND EAX.Fall through to keyboard loop .BP0 Set global . EAX CALL dbgCheckScroll .F3) EAX.0 Page 439 of 667 .

EAX CALL dbgCheckScroll JMP dbg00 dbg09: CMP AL. 15h JNE dbg08 JMP dbg00 dbg08: CMP AL.See if we need to scroll up dbg14: MMURTL V1.JMP dbg000 dbg06: CMP EAX.Memory Dump Bytes (F9) .Sets NextEIP .This puts the instruction on the line . EAX CALL dbgCheckScroll JMP dbg00 .Back to where we were . . BL CALL dbgDump JMP dbg000 dbg10: CMP AL.This puts the instruction on the line . . . NextEIP MOV dbgCrntAdd. 16h JNE dbg09 CALL dbgSetAddr PUSH dbgX PUSH dbgY CALL FWORD PTR _SetXY MOV EAX. 0FFh MOV dbgfDumpD.0 Page 440 of 667 . 17h JNE dbg10 MOV BL. 14h JNE dbg07 CALL dbgDispTasks JMP dbg000 dbg07: CMP EAX. 18h JNE dbg12 MOV BL. .Set Disassembly Address (F8) .See if we need to scroll up . EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP.Info Address dump (F12) . NextEIP MOV dbgCrntAdd. EAX CALL dbgShowBP PUSH EAX CALL _disassemble MOV NextEIP. 02h JNE dbg14 MOV EAX.Memory Dump DWORDS (F10) .Full display .Display next Instruction (Down Arrow) . BL CALL dbgDump JMP dbg000 dbg12: CMP AL. .Task array display (F6) . 01Ah JNE dbg13 CALL DbgInfo JMP dbg00 dbg13: CMP AL.Not used yet . 00 MOV dbgfDumpD. .

Next time we enter the debugger task it will be here! JMP DbgTask .00 MOV ESI.This compares the current breakpoint the address .3 params to TTYOut . EAX BX.BP Address . dbgCrntAdd MOV ECX.Reverse Vid WHITE on RED .Return saved pRunTSS . dbgBPAdd CMP EBX.TSS Display .we are about to display. BX .[EBX+TSSNum] CALL DispRegs .Address displayed .EAX MOV AX.0 Page 441 of 667 . If they are the same. .Change screens back MOV MOV MOV MOV EAX.dbgY PUSH EAX CALL FWORD PTR _GetXY .EAX must be preserved MOV EBX.JMP dbg00 DbgExit: LEA EAX. DbgpTSSSave pRunTSS.============================================================== .This sets up and calls to display all of the regsiter .DbgpTSSSave MOV ECX.Save EAX across call .Query XY PUSH DbgVidSave CALL FWORD PTR _SetVidOwner .Back to begining .EBX MUST be DbgpTSSSave .Set up caller’s TSS selector JMP FWORD PTR [TSS] .Number of this TSS MMURTL V1. ECX JZ dbgShowBP1 RETN dbgShowBP1: PUSH EAX PUSH OFFSET dbgAsterisk PUSH 01h PUSH 47h CALL FWORD PTR _TTYOut POP EAX RETN .dbgX PUSH EAX LEA EAX.information on the right side of the debugger video display DbgRegVid: MOV EBX. [EAX+Tid] TSS_Sel.we put up an asterisk to indicate it’s the breakpoint .GO back for another key .============================================================== dbgShowBP: .OFFSET DbgTxt00 XOR EAX.


[EBX+TSS_ES] CALL DispRegs MOV ECX.CR0 CALL DispRegs MOV ECX. ESI loaded with EA of text line to display .OFFSET DbgTxt12 XOR EAX.MOV ESI.19 .OFFSET DbgTxt13 XOR EAX.OFFSET DbgTxt17 MOV EAX.OFFSET DbgTxt18 MOV EAX.[EBX+TSS_GS] CALL DispRegs MOV ECX. Call with: EAX loaded with value to display (from TSS reg) .Fault Error Code Display MOV ESI. .[EBX+TSS_DS] CALL DispRegs MOV ECX.17 . We save all registers cause the vid calls don’t DispRegs: PUSHAD PUSH EAX .CR3 Display MOV ESI.EAX MOV AX.Save number to display PUSH 66 PUSH ECX CALL FWORD PTR _SetXY MMURTL V1.16 .14 .========================================================================== .GS Display MOV ESI.EAX MOV AX.OFFSET DbgTxt16 MOV EAX.20 .OFFSET DbgTxt19 MOV EAX.[EBX+TSS_EFlags] CALL DispRegs MOV ECX.CR3 CALL DispRegs MOV ECX.EAX MOV AX.[EBX+TSS_FS] CALL DispRegs MOV ECX.CR0 Display MOV ESI.OFFSET DbgTxt14 XOR EAX.0 Page 443 of 667 . This is for Debugger Register display .CR2 Display MOV ESI.EFlags Display MOV ESI.FS Display MOV ESI.15 .ES Display MOV ESI.OFFSET DbgTxt15 XOR EAX.18 .dbgFltErc CALL DispRegs RETN .EAX MOV AX. ECX loaded with number of text line to display on .CR2 CALL DispRegs MOV ECX.OFFSET DbgTxt20 MOV EAX.13 .

============================================== .dbgMenu PUSH EAX PUSH 78 PUSH 70h CALL FWORD PTR _TTYOut PUSH 25 PUSH 24 CALL FWORD PTR _SetXY LEA EAX.Display Debugger FKey Menu PUSH 24 CALL FWORD PTR _SetXY LEA EAX. This displays the debugger function key menu dbgDispMenu: PUSH 0 .dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut PUSH 51 PUSH 24 CALL FWORD PTR _SetXY LEA EAX.=========================== .PUSH PUSH PUSH CALL ESI 05h 07h FWORD PTR _TTYOut .Allows the user to pick the currently displayed address dbgSetAddr: PUSH 0 .0 Page 444 of 667 .Goto Query Line MMURTL V1.dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut 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 .

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

make it a DD Text LEA EAX.get dump address MOV EAX.Get what it’s pointing to PUSH EAX .byte offset begins at 6 LEA EAX. 0 JNE DumpDone CALL FWORD PTR _ClrScr dbgDump00: MOV DWORD PTR dbgGPdd1.ptr to destination DD . 0 JNE DumpDone MOV EBX.Crnt size PUSH 8 . dbgSpace PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX.number of quads per line PUSH dbgDumpAdd . dbgDumpAdd .Black On White CALL FWORD PTR _EditLine .Max size LEA EAX.pEdString PUSH cbBufLen2 . dbgChar CMP AL. PUSH EAX . [EBX] .. 24 . 6 .line counter begins at 24 dbgDump01: MOV DWORD PTR dbgGPdd3. PUSH EAX .length of string . dbgChar . cbBufLen2 .Convert String to DD .ptr to size returned LEA EAX. dbgBuf PUSH EAX CALL DDtoHex LEA EAX.convert address to text LEA EAX.Go home.PUSH EAX . dbgBuf2 PUSH EAX LEA EAX.did they exit with CR? . dbgBuf .dbgDumpAdd has address to dump! MMURTL V1. 0Dh JE dbgDoDump CALL dbgClearQuery RETN dbgDoDump: LEA EAX. 4 .0 Page 446 of 667 .Ignore error if any MOV AL.. dbgBuf PUSH EAX PUSH 8 PUSH 07h CALL FWORD PTR _TTYOut CMP EAX. 0 JNE DumpDone dbgDump02: MOV DWORD PTR dbgGPdd2.ptr to string . . dbgDumpAdd PUSH EAX PUSH cbBufLen2 CALL HexToDD CMP EAX.ptr to char returned PUSH 70h .

a space MMURTL V1.display 2st byte .ignore error . 0 JE DumpB .ignore error .NO . dbgBuf ADD EAX. dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.go to display bytes LEA EAX. dbgBuf ADD EAX. dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX. dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX. dbgfDumpD CMP AL.Yes display Quad PUSH EAX PUSH 8 PUSH 07 CALL FWORD PTR _TTYOut JMP DumpDin dumpB: LEA EAX. display 1 space .Display 1 spaces .ignore error .Display First byte . dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX.point to second 2 bytes . dbgSpace PUSH EAX PUSH 1 PUSH 07h CALL FWORD PTR _TTYOut . dbgBuf ADD EAX. dbgBuf .Dumping DWORDS .PUSH EAX CALL DDtoHex MOV AL. dbgGPdd2 PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.0 Page 447 of 667 .display 3rd byte .

dbgKeyCode MMURTL V1. dbgCRLF .Put 16 TEXT chars on right PUSH dbgY MOV EAX.ignore error .NO . dbgCont . OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd MOV EAX. dbgDumpAdd SUB EAX. LEA EAX. dbgSpace PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut LEA EAX.ignore error LEA EAX.Query XY PUSH dbgX .NO .go back for next 4 bytes .Display 2 spaces LEA EAX.done with 4 quads?? .dbgY PUSH EAX CALL FWORD PTR _GetXY . dbgBuf ADD EAX. DEC dbgGPdd1 .0 Page 448 of 667 . 16 PUSH EAX PUSH 16 PUSH 07h CALL FWORD PTR _PutVidChars .size of "cont text" PUSH 07h CALL FWORD PTR _TTYOut dbgDump03: MOV EAX.dbgX PUSH EAX LEA EAX."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 .Do CR/LF PUSH EAX PUSH 2 PUSH 07h CALL FWORD PTR _TTYOut .Yes .23lines yet?? JNZ dbgDump01 .display 4th byte .DEC dbgGPdd2 DEC dbgGPdd2 LEA EAX.

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

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

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

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

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

Size 07h FWORD PTR _PutVidChars . 8 .Size 07h FWORD PTR _PutVidChars . dbgKeyCode AND EAX.Convert and display pTSS OFFSET dbgBuf . .0 Page 454 of 667 .Convert and display pJCB OFFSET dbgBuf .Escape (Quit??) .Size 07h FWORD PTR _PutVidChars .Save it for the top MOV EAX.length of Cont string 07h FWORD PTR _PutVidChars . 0FFh CMP EAX. dbgGPdd5 . 8 .Lop off key status bytes .No. nTSS JE dbgDT06 MOV dbgGPdd2.go for next Task number .Convert and display Priority OFFSET dbgBuf .Line we are one OFFSET dbgBuf . 8 . go back for next 0 . 1Bh JE dbgDTDone JMP dbgDT00 dbgDTDone: .Col dbgGPdd1 . . continue prompt dbgDT01 .Line we are one OFFSET dbgBuf . dbgGPdd2 INC EAX CMP EAX.Col 24 . MOV EAX.Col dbgGPdd1 . DDtoHex 40 . DDtoHex 30 .Next line DWORD PTR dbgGPdd1.Line we are one OFFSET dbgBuf . 23 .Back for next screen MMURTL V1. DDtoHex 20 . EAX INC CMP JAE JMP dbgDT06: PUSH PUSH PUSH PUSH PUSH CALL dbgGPdd1 . dbgGPdd6 .Col dbgGPdd1 . 31 .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 .Yes.Line OFFSET dbgCont .23 lines yet? dbgDT06 . OFFSET dbgKeyCode PUSH EAX CALL ReadDbgKbd MOV EAX.

Do it . IDT CALL DispAddr MOV ESI. We save all registers cause the vid calls don’t .GDT LEA EAX.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 .ptr to line . This is for Debugger Address Info display .OFFSET DbgM1 . EAX loaded with address to display (Linear Address) .=================================================================== DispAddr: PUSHAD PUSH EAX .Length of line .IDT LEA EAX.LBs MMURTL V1.OFFSET DbgM5 .CALL FWORD PTR _ClrScr MOV DWORD PTR dbgX.DbgInfo .OFFSET DbgM4 . 0 MOV DWORD PTR dbgY.Vid Attribute .OFFSET DbgM0 .RQBs MOV EAX.MonTSS MOV EAX. pDynTSSs CALL DispAddr MOV ESI. pRQBs CALL DispAddr MOV ESI.pTSS3 MOV EAX.OFFSET DbgM3 .Displays important linear address for the OS DbgInfo: MOV ESI.=============================================== .0 Page 455 of 667 . Call with: .Save number to display PUSH PUSH PUSH CALL ESI 06h 07h FWORD PTR _TTYOut . GDT CALL DispAddr MOV ESI. ESI loaded with EA of text line to display .====================================================================== . 0 RETN . OFFSET MonTSS CALL DispAddr MOV ESI.OFFSET DbgM2 .

prgExch CALL DispAddr MOV ESI.fUp (1) FWORD PTR _ScrollVid PUSH 0 .No. Scroll test area (Col 0-64. The other . rgLBs CALL DispAddr MOV ESI. and line 0 to 24.OFFSET DbgMB .======================================================= . 40 chars) dbgClearQuery: PUSH 0 .0 Page 456 of 667 . RdyQ CALL DispAddr MOV ESI.OFFSET DbgPA .Query XY (See what line and Col) .PAM (Page Allocation map) LEA EAX. Line 0-24) 0 66 . rgSVC CALL DispAddr MOV ESI.dbgY PUSH EAX CALL FWORD PTR _GetXY CMP DWORD PTR dbgY. pJCBs CALL DispAddr MOV ESI.OFFSET DbgM9 .Are we at bottom (just above menu)?? . . Line 23 PUSH 23 PUSH OFFSET dbgClear MMURTL V1.JCBs MOV EAX. we scroll up the text area by one line.If so.This checks to see if the cursor is on line 23.Exchs MOV EAX. .Clear the query line (Line 23. . go back for next key 0 . Line 22 PUSH 22 CALL FWORD PTR _SetXY dbgNoScroll: RETN .======================================================= .Timer Blocks LEA EAX.SVCs LEA EAX. query line.and the register display. rgPAM CALL DispAddr MOV ESI.LEA EAX.Columns 0-65 24 . rgTmrBlks CALL DispAddr RETN .Col 0.Got to Column 0.OFFSET DbgM8 .All of the debugger text is displayed in a window .OFFSET DbgM6 . .between colums 0 and 66.Lines 0-23 1 .dbgX PUSH EAX LEA EAX. dbgCheckScroll: LEA EAX.RdyQ LEA EAX.OFFSET DbgM7 . 23 JB dbgNoScroll PUSH PUSH PUSH PUSH PUSH CALL .Yes.areas are resrved for the menu.

ignore error RETN .===================== module end ====================== MMURTL V1.0 Page 457 of 667 .PUSH 40 PUSH 07h CALL FWORD PTR _PutVidChars .

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

U32 ISendMsg(U32 Exch. . U32 EndOfIRQ(U32 IRQNum). U32 SendMsg(U32 Exch. U16 wPort). While writing the operating system. InWords(U32 dPort. void OutWord(U16 Word.Continuation of IDE Driver Data (protos and defines) /* Near External for troubleshooting */ extern long xprintf(char *fmt. U8 *pDestination.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). It’s called xprintf(). void CopyData(U8 *pSource. S8 *pDCBs.Listing 24. U32 msg1. extern far U32 InitDevDr(U32 dDevNum. MMURTL V1. U8 *pDataIn. U32 Sleep(U32 count).2. U32 WaitMsg(U32 Exch. OutWords(U32 dPort. Listing 24. See listing 24. U32 nDevices. U32 dBytes). U32 dBytes). You must simply keep in mind that it writes to the video screen for the job that called the device driver. extern 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 far U32 UnMaskIRQ(U32 IRQNum). U16 InWord(U16 wPort). void OutByte(U8 Byte. S8 *pIRQ).. U32 dfReplace). U32 *pMsgRet)..1 . U8 InByte(U16 wPort).0 Page 459 of 667 . U32 count). U32 msg2).2 . I needed a method to get data to the screen.). U32 *pMsgRet). void MicroDelay(U32 us15count). U32 KillAlarm(U32 Exch). U8 *pDataOut. U32 CheckMsg(U32 Exch. U8 ReadCMOS(U16 Address). U32 SetIRQVector(U32 IRQNum. Any section of the operating system code included at build time can use this function for displaying troubleshooting information. The monitor has a function that works very similar to printf in C. U32 msg2). U32 dBytes). U32 MaskIRQ(U32 IRQNum). U32 Alarm(U32 Exch. U16 wPort). U32 msg1.

U32 dnBlocks. static U32 hd_recal(U8 drive)./* LOCAL PROTOTYPES */ U32 hdisk_setup(void). static U32 setupseek(U32 dLBA. hddev_init(U32 dDevNum. static void interrupt hdisk_isr(void). U8 *pData). S8 *pInitData. U32 dLBA. static void hd_reset(void). U32 HdSect. U32 dnBlocks). static U32 hd_seek(U32 dLBA). U32 dnBlocks. static U32 send_command(U8 parm). U8 *pDataOut). U32 dOpNum. static U32 hd_read(U32 dLBA. /* The HD interrupt function */ static U32 hd_format_track(U32 dLBA. static U32 hd_init(U8 drive). hddev_stat(U32 dDevice. S8 * pStatRet. static U32 hd_write(U32 dLBA. static U32 hd_wait (void). static U32 check_busy(void). U32 dnBlocks. U32 dStatusMax. U32 sdInitData). U8 *pDataIn). static U32 ReadSector(U32 Cylinder. /* The following 3 calls are required in every MMURTL device driver */ static U32 hddev_op(U32 dDevice.0 Page 460 of 667 . U32 *pdSatusRet). U8 *pDataRet). static U32 hd_status(U8 LastCmd). U32 nBlks). static U32 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 ErcBadBlock ErcAddrMark ErcBadECC ErcSectNotFound ErcNoDrive0 ErcNotSupported ErcBadHDC ErcBadSeek ErcHDCTimeOut ErcOverRun ErcBadLBA ErcInvalidDrive 651 652 653 654 655 656 658 659 660 661 662 663 MMURTL V1.

0 Page 461 of 667 ..sector number (1F3h) MMURTL V1.pre-comp (1F1h) 2 .size/drive/head (1F6h) 7 .sector count (1F2h) 3 .16 bit) 1 . This allows you to read ONE sector specified by Cylinder.sector number (1F3h) 4 .low cyl (1F4h) 5 .#define #define #define #define #define #define #define #define #define ErcBadOp ErcBadRecal ErcSendHDC ErcNotReady ErcBadCmd ErcNeedsInit ErcTooManyBlks ErcZeroBlks ErcWriteFault 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. 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 */ /* CmdReadSect is the only device specific call in the IDE/MFM hard disk device driver.sector count (1F2h) 3 . and Sector number is LowWord in dnBlocks.command register (1F7h) When reading from the port+X (where X =): 0 . Cylinder is HiWord of dLBA in DeviceOp data (1F0h .write data (1F0h . head and Sector number. */ #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 . Head is LoWord of dLBA in DeviceOp call.16 bit) 1 .high cyl (1F5h) 6 .error register (1F1h) 2 .

. Bit Desc 0 Not used 1 Not used 2 Reset Bit .4 5 6 7 */ - low cyl high cyl size/drive/head status register (1F4h) (1F5h) (1F6h) (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. 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 */ */ */ */ */ */ */ */ */ */ MMURTL V1. then Reset 3 Mucho Heads Flag. wait 50us.0 Page 462 of 667 . either one set) */ /* HDC Status Register Bit Masks (1F7h) */ #define #define #define #define #define #define #define #define BUSY READY WRITE_FAULT SEEKOK DATA_REQ CORRECTED REV_INDEX ERROR 0x80 0x40 0x20 0x10 0x08 0x04 0x02 0x01 /* /* /* /* /* /* /* /* busy.Set. Set = More than 8 heads 4 Not used 5 Not used 6 Disable retries 7 Disable retries (same as six. can’t talk now! */ Drive Ready */ Bad news */ Seek Complete */ Sector buffer needs servicing */ ECC corrected data was read */ Set once each disk revolution */ 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.

n sectors to read/write */ hd_sector. hd0_cyls. MMURTL V1. hd1_cyls. /* Calculated from LBA . /* Sectors per track */ U32 nBPS. If I did one member. and sectors set by caller */ static static static static static static static static U8 U8 U8 U16 U8 U8 U8 U16 hd0_type. fDataReq.3 . 0 or 1 */ hd_head.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.*/ U32 LastRecalErc0. hd1_type. /* Calculated from LBA .which head */ hd_nsectors. /* total physical cylinders */ U32 nHead. U8 fNewMedia. */ /* Current number of heads. 32 bytes out to here. U32 LastSeekErc0.Starting sector */ /* Current type drive 0 & 1 found in CMOS or Set by caller. /* Number of bytes per sect. /* padding for DWord align */ U32 nCyl. #define sStatus 64 static struct statstruct { U32 erc. I would have to do them all to ensure they would be contiguous.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]. hd0_heads. U32 blocks_done. The operating system requires the DCB and status record field to be contiguous in memory. /* Current control byte value */ hd_command. hd1_secpertrk. cylinders. /* Calculated from LBA . /* Current Physical Drive.3. hd_control.0 Page 463 of 667 . /* current fdisk_table for drive selected */ U8 resvd0[2]. /* Current Command */ hd_drive. statbyte. Listing 24. U8 type_now. /* total heads on device */ U32 nSectors. U32 BlocksMax. hd1_heads. hd0_secpertrk. See listing 24.

U32 OS6.0 Page 464 of 667 . U32 OS4. static U32 hd_msg2. S8 *pDevInit. LastStatByte1. /* out to 64 bytes */ static struct statstruct hdstatus. this would be the main() section of the C source file. /* two HD device control blocks */ The hdisk_setup() function is called from the monitor to initialize the driver. LastSeekErc1. S16 nBPB. U8 fSingleUser. ResetStatByte. S8 type. fIntOnReset. /* Status Byte immediately after RESET */ U32 resvd1[2]. }. U32 OS3. LastErcByte1. U32 OS2. LastRecalErc1. U8 fDevReent. U32 nBlocks. S16 wJob. U32 OS1. In a loadable driver. LastErcByte0. static long HDDInt. }. static U32 hd_msg.U8 U8 U8 U8 U32 U32 U8 U8 U8 U8 LastStatByte0. /* Exch and msgs space for HD ISR */ static U32 hd_exch. S8 *pDevOp. S8 sbName.4. S8 *pDevSt. filler1. static struct dcbtype { S8 Name[12]. static struct dcbtype hdcb[2]. MMURTL V1. U32 OS5. static struct statstruct HDStatTmp. /* Interrupt was received on HDC_RESET */ filler0. U32 last_erc. See listing 24.

Name[2] hdcb[1].nBlocks hdcb[0]. 3. erc = AllocExch(&hd_exch).nBPB hdcb[0]. SetIRQVector(14.Name[2] hdcb[0]. /* first we set up the 2 DCBs in anticipation of calling InitDevDr */ hdcb[0].pDevInit hdcb[0].2Gb disks*/ &hddev_op.Name[0] hdcb[0].sbName hdcb[1]. /* Max */ hd0_secpertrk = 17. hd1_heads = 16. /* read this but don’t use it */ hd0_heads = 16.type hdcb[1].0 Page 465 of 667 . ’H’. /* largest device handled .pDevOp hdcb[1]. */ hd0_type = ReadCMOS(0x19). ’0’. /* These are defaulted to non zero values to ensure we don’t get a divide by zero during initial calculations on the first read.nBlocks hdcb[1]. /* largest disk handled .4 . 524288. &hddev_init. /* Random */ 512. hd1_secpertrk = 17. hd1_cyls = 1024. /* Max */ hd1_type = ReadCMOS(0x1A). 1. &hddev_stat.Name[0] hdcb[1].pDevOp hdcb[0].type hdcb[0].Listing 24. /* Documentation lists the fixed disk types at CMOS 11h and 12h.Name[1] hdcb[0]. We don’t actually read them because they are not dependable.pDevSt hdcb[1]. ’D’. 1. &hddev_stat. &hdisk_isr). ’D’. and also shows them at 19h and 1Ah. &hddev_init.pDevSt = = = = = = = = = = = = = = = = = = = = ’H’.IDE Device Driver Code (Initialization) U32 { U32 hdisk_setup(void) erc.nBPB hdcb[1]. They vary from BIOS to BIOS. We have to make this sucker work the hard way. */ /* Exhange for HD Task to use */ MMURTL V1. ’1’. /* most common */ hd0_cyls = 1024. 524288. 3.2Gb disks*/ &hddev_op.pDevInit hdcb[1].sbName hdcb[0]. /* Random */ 512.Name[1] hdcb[1]. UnMaskIRQ(14).

It seems like a lot of work.hd_reset resets the controller (which controlls both drives). /* try to recal */ if (erc) { /* try one more time! */ hd_reset(). /* Must not be a valid drive */ return(ErcNoDrive0). some controllers will lock-up (the el-cheapos). &hdcb. The driver MUST be able to recal the first physical drive or the Driver won’t inititalize. /* no error is returned */ /* Now we attempt to select and recal both drives. hdcb[0]./* Reset the HDC . /* try to recal if CMOS says it’s there */ if (erc) { hdcb[1]. 2. } } /* if we got here. They SHOULD simply return a Bad Command bit set in the Error register. but they don’t. Now we try drive 1 if type > 0.0 Page 466 of 667 . /* 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.last_erc = erc. hd0_type = 0. but to make it function with the widest range of IDE and MFM controllers this is the only way I have found that works.last_erc = erc. } } return(erc = InitDevDr(12. } /* recal drive 0 */ MMURTL V1. then try both physical drives. We have to do it once. */ erc = hd_recal(0). */ hd_reset(). */ if (hd1_type) { erc = hd_recal(1). /* try to recal */ if (erc) { hdcb[0]. */ hd_reset(). hd1_type = 0. drive 0 looks OK and the controller is functioning. In this case we have to reset it again so it will work. erc = hd_recal(0). erc = hd_recal(0). If the second drive is not there.last_erc = erc. 1)).

/************************************************************ Reset the HD controller. ISendMsg(hd_exch. This should only be called by DeviceInit or hdisk_setup. HD_REG_PORT). The caller should call check_busy and check the error. If it’s 0 then the controller became ready in less than 3 seconds. 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. ****************************************************************/ static U32 check_busy(void) /* The ISR gets statbyte */ */ MMURTL V1.fIntOnReset = 1. *************************************************************/ static void hd_reset(void) { U32 i.seems some controllers are SLOW!! */ i = CheckMsg(hd_exch. UnMaskIRQ(14). MicroDelay(4). /* Eat Int if one came back hdstatus.fIntOnReset = 0. It just waits for an interrupt. else hdstatus. HDDInt = 1. 0xfffffff0). if (i) hdstatus. &hd_msg). Sleep(20).0 Page 467 of 667 . We will wait up to 3 seconds then error out. 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). HD_REG_PORT). OutByte(4. EndOfIRQ(14).ResetStatByte = statbyte. } /************************************************************* This checks the HDC controller to see if it’s busy so we can send it commands or read the rest of the registers. 0xfffffff0. } /************************************************************* The ISR is VERY simple. This resets the controller and reloads parameters for both drives (if present) and attempts to recal them. It leaves the status byte in the global statbyte. ErcNotReady will be returned otherwise. /* 200ms . /* /* /* 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. and clear the reset bit */ OutByte(hd_control & 0x0f.

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

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

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

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

*/ erc = check_busy(). he interrupted us with status. break. *********************************************************/ static U32 hd_status(U8 { U32 erc. default: break.LastStatByte1 = statbyte.registers are actually not errors. errbyte. but simply indicate status or indicate an action we must take next. else return(erc). /* puts status byte into global StatByte */ if (!erc) statbyte = InByte(HD_PORT+7). else if ((statbyte & SEEKOK) == 0) erc = ErcBadSeek.0 Page 472 of 667 . After all. } return(erc).LastStatByte0 = statbyte. } else { erc = check_busy(). if (hd_drive) hdstatus. else hdstatus. LastCmd) /* We shouldn’t see the controller busy. if (!erc) errbyte = InByte(HD_PORT+1). U8 statbyte. else return(erc). ZERO returned indicates no errors for the command status we are checking. /* 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. case HDC_SET_PARAMS: case HDC_VERIFY: case HDC_FORMAT: case HDC_DIAG: break. if ((statbyte & ERROR) == 0) { /* Error bit not set in status reg */ erc = ok.

nBPS = hdcb[hd_drive]. nSoFar. pDataRet+=nBPS. MMURTL V1. This writes 1 or more whole sectors from the calculated values in hd_head.if (hd_drive) hdstatus. dnBlocks). else hdstatus. else if (errbyte & BAD_IDMARK) erc = ErcSectNotFound. } /************************************************************* This is called for the DeviceOp code Read. else erc = ErcBadHDC.0 Page 473 of 667 . nBPS. else if (errbyte & BAD_SECTOR) erc = ErcBadBlock. nBPS. nBPS). /* no error bits found but should have been! */ } return erc.LastErcByte1 = errbyte. } } return(erc). else if (errbyte & BAD_CMD) erc = ErcBadCmd. U32 dnBlocks. else if (errbyte & BAD_ECC) erc = ErcBadECC. nleft. /* From nBytesPerBlock in DCB */ nleft = dnBlocks. /* wait for interrupt */ if (!erc) erc = hd_status(HDC_READ). --nleft. hd_sector. pDataRet. /* sets up for implied seek */ if (!erc) erc = send_command(HDC_READ). U32 dnBlocks. if (!erc) /* && (statbyte & DATA_REQ)) */ { InWords(HD_PORT.nBPB. hd_sector. and hd_cyl *************************************************************/ static U32 hd_write(U32 dLBA.LastErcByte0 = errbyte. else if (errbyte & BAD_SEEK) erc = ErcBadSeek. } /************************************************************* This is called for the DeviceOp code Write. This reads 1 or more whole sectors from the calculated values in hd_head. U8 *pDataRet) { U32 erc. and hd_cyl *************************************************************/ static U32 hd_read(U32 dLBA. erc = setupseek(dLBA. U8 *pDataOut) { U32 erc. while ((nleft) && (!erc)) { erc = hd_wait(). if (errbyte & BAD_ADDRESS) erc = ErcAddrMark.

nBPS = hdcb[hd_drive]. pDataOut+=nBPS. } /************************************************************* This formats the track beginning at the block address given in dLBA. erc = setupseek(dLBA. nSoFar++. } while ((nSoFar < dnBlocks ) && (erc==ok)) { erc = hd_wait(). /* wait for interrupt */ if (erc==ok) erc = hd_status(HDC_WRITE). nBPS). nBPS). *******************************************************************/ MMURTL V1. erc = hd_wait(). erc = setupseek(dLBA. pDataOut. /* sets up for implied seek */ erc = send_command(HDC_WRITE).nBPB. *************************************************************/ static U32 hd_format_track(U32 dLBA. pDataOut+=nBPS. /* From n BytesPerBlock in DCB */ nSoFar = 0. dnBlocks). /* wait for final interrupt */ if (!erc) erc = hd_status(HDC_WRITE). return(erc). This allows you to read ONE sector specified by Cylinder. U32 dnBlocks) { U32 erc. } } if (!erc) erc = hd_wait(). /* sets up for implied seek */ erc = send_command(HDC_FORMAT). and Sector number is HiWord in dnBlocks. Head is LoWord of dnBlocks in DeviceOp call. nSoFar++. /* wait for interrupt */ if (erc==ok) erc = hd_status(HDC_FORMAT). Cylinder is LoWord of dLBA in DeviceOp call. dLBA must always be a multiple of the number of sectors per track minus 1 for the disk type. head and Sector number. if ((erc==ok) && (statbyte & DATA_REQ)) { OutWords(HD_PORT. erc = check_busy(). } /****************************************************************** ReadSector is the only device specific call in the IDE/MFM hard disk device driver. /* No INT occurs for first sector of write */ if ((!erc) && (statbyte & DATA_REQ)) { OutWords(HD_PORT. dnBlocks). return(erc).0 Page 474 of 667 . pDataOut.

/* hd_sector. This assigns physical device from logical number that outside callers use. The three calls that all MMURTL device drivers must provide are next. return(erc). */ hd_Cmd[2] hd_Cmd[3] hd_Cmd[4] hd_Cmd[5] hd_Cmd[6] = = = = = 1. hd_head = HdSect & 0xffff. pDataRet. /* (hd_drive << 4) | (hd_head & How many sectors */ Which sector to start on */ cylinder lobyte */ cylinder hibyte */ 0x0f) | 0xa0. /* For testing xprintf("\r\nCYL %d. cyl. } Chapter 8.6. SEC %d\r\n". Listing 24. cyl = Cylinder. erc = hd_wait(). This will check to make sure a drive type is assigned and check to see if they are going to exceed max logical blocks. HD %d. if (!erc) InWords(HD_PORT. U16 cyl. /* Reset values in Status record */ hdstatus. U32 dOpNum. there I provided an overview of MMURTL's device driver interface. 12=0 and 13=1. For Hard disk.0 Page 475 of 667 . hd_sector). /* wait for interrupt */ if (!erc) erc = hd_status(HDC_READ). hd_sector = (HdSect >> 16) & 0xffff. 512). /* (cyl >> 8) & 0xff.blocks_done = 0. U8 *pData) { U32 erc. U32 HdSect. “Programming Interfaces. U8 *pDataRet) { U32 erc.static U32 ReadSector(U32 Cylinder. /* cyl & 0xff. *******************************************/ static U32 hddev_op(U32 dDevice. MMURTL V1. U32 dLBA. U32 dnBlocks.Continuation of IDE Driver Code (Device Interface) /****************************************** Called for all device operations. See listing 24. hd_head.” discussed device driver interfaces.6 . erc = send_command(HDC_READ).

pData). /* Check to see if we have a leftover interrupt message from last command.erc = 0. pData). dnBlocks. } else { if (hd1_type==0) erc = ErcInvalidDrive. if (hd_drive==0) { if (hd0_type==0) erc = ErcInvalidDrive. &hd_msg). /* Set drive internal drive number */ if (dDevice == 12) hd_drive = 0. break.0 Page 476 of 667 . dnBlocks). */ break. /* Ignore error */ /* Read */ /* Write */ /* Verify */ /* hd_verify is not supported in this version of the driver */ /* erc = hd_verify(dLBA. case(CmdResetHdw): /* Reset Ctrlr */ MMURTL V1. break. pData). dnBlocks. case(CmdRead): erc = hd_read(dLBA.nBlocks) erc = ErcBadLBA. dnBlocks. If so then we eat it (and do nothing) */ CheckMsg(hd_exch. case(CmdVerify): erc = ErcNotSupported. break. case(CmdSeekTrk): /* Seek Track */ erc = hd_seek(dLBA). } /* make sure they don’t exceed max blocks */ if (!erc) if (dLBA > hdcb[hd_drive]. case(CmdWrite): erc = hd_write(dLBA. else hd_drive = 1. if (!erc) { switch(dOpNum) { case(CmdNull): erc = ok. /* Null Command */ break. break. case(CmdFmtTrk): /* Format Track */ erc = hd_format_track(dLBA.

/* Set status for proper device */ if (dDevice == 12) { hdstatus. hdstatus. Returns 64 byte block of data including current drive geometery. hdstatus. dnBlocks.last_erc. Return no more than asked for! */ if (dStatusMax <= sStatus) i = dStatusMax. /* copy to their status block */ MMURTL V1. U32 *pdStatusRet) { U32 i.type_now = hd0_type. hdstatus.0 Page 477 of 667 . hdstatus. CopyData(&hdstatus. This is called by the PUBLIC call DeviceStat! *******************************************/ static U32 hddev_stat(U32 dDevice.erc = hdcb[1]. } /* Read Sector(s) */ /* update DCB erc */ /* Calculate size of status to return.nCyl = hd1_cyls. case(CmdReadSect): erc = ReadSector(dLBA.last_erc. pStatRet. } /****************************************** Called for indepth status report on ctrlr and drive specified. return(erc). default: erc = ErcBadOp. erc = 0. S8 * pStatRet. hdstatus.nHead = hd1_heads. i). else i = sStatus.nCyl = hd0_cyls. } else { hdstatus. hdstatus.nBPS = hdcb[1]. break.nBPS = hdcb[0]. break. } } hdcb[hd_drive].nBPB. hdstatus.last_erc = erc.nBPB.nHead = hd0_heads. hdstatus.erc = hdcb[0]. U32 dStatusMax.nSectors = hd0_secpertrk.hd_reset().nSectors = hd1_secpertrk.type_now = hd1_type. break. hdstatus. hdstatus. pData).

*pdStatusRet = i. else i = sdInitData.0 Page 478 of 667 . This is called by the PUBLIC call DeviceInit. else hd_drive = 1. } /* tell em how much it was */ /****************************************** Called to reset the hard disk controller and set drive parameters.nSectors. The DCB values are updated if this is successful. This should ONLY be called once for each HD to set it’s parameters before it is used the first time after the driver is loaded.).nHead. hd0_secpertrk = HDStatTmp. *******************************************/ static U32 hddev_init(U32 dDevice. The caller normally reads the block (DeviceStat). makes changes to certain fields and calls DeviceInit pointing to the block for the changes to take effect. if (hd_drive==0) { hd0_type = HDStatTmp. return ok.nCyl. } else erc = ErcInvalidDrive. U32 sdInitData) { U32 erc. CopyData(pInitData. i. The Initdata is a copy of the 64 byte status block that is read from status. /* Set internal drive number */ if (dDevice == 12) hd_drive=0. &HDStatTmp. i).type_now. } else /* no more than 64 bytes! */ /* copy in their init data */ MMURTL V1. or after a fatal error is received that indicates the controller may need to be reset (multiple timeouts etc. erc = 0. hd0_heads = HDStatTmp. if (hd0_type) { hd0_cyls = HDStatTmp. /* Read the init status block in */ if (sdInitData > sStatus) i = sStatus. S8 *pInitData.

hdcb[hd_drive]. This same header file is used in the sample communications program in chapter 16. which is a block oriented random device. This header file was also designed to be used by programs that use the driver.nBPB = HDStatTmp. return(erc).last_erc = erc.nSectors * HDStatTmp.last_erc = 0.type_now. other than that. such as complete flow control for XON/XOFF and CTS/RTS.{ hd1_type = HDStatTmp. and this driver. See listing 24. The C structure for the status record is also defined here and will be needed to initialize. as well as by the driver itself. but. a header file is included in the RS232 driver source file. They are two different animals with the same basic interface.0 Page 479 of 667 . You may notice the differences between the IDE driver. hd1_secpertrk = HDStatTmp. or 16550 UARTs.” The header file defines certain parameters to functions. “MMURTL Sample Software. 16450. it is a fully functional driver. Unlike the IDE disk device driver. update corresponding DCB values */ if (!erc) { hdcb[hd_drive]. if (hd1_type) { hd1_cyls = HDStatTmp. /* If no error. } /* If no error. as well as status. hdcb[hd_drive]. MMURTL V1.nBlocks = HDStatTmp.nHead. if (!erc) erc = hd_recal(hd_drive). which is a sequential byte-oriented device.nCyl * HDStatTmp.nCyl.nHead.7. It needs more work. } else erc = ErcInvalidDrive. the driver. as well as explaining all the error or status codes that the driver will return. } hdcb[hd_drive].nSectors. } /* update DCB erc */ The RS-232 Asynchronous Device Driver The RS-232 driver is designed to drive two channels with 8250. hd1_heads = HDStatTmp. initialize it and recal */ if (!erc) erc = hd_init(hd_drive).nBPS.

. unsigned long resvd[6].Listing 24. unsigned char parity.7 . unsigned long XTimeOut. unsigned long XBufSize. unsigned long LastErc... 150 . 2=odd */ nDatabits for this port.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 Page 480 of 667 .. 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 */ /* Device Driver interface commands (Op numbers) */ #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 /* /* /* /* /* /* /* /* /* /* 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) */ MMURTL V1. unsigned char stopbits. }.. 5-8 */ stop bits for this port. 1=even. unsigned long LastTotal.. /* /* /* /* /* /* /* /* /* /* /* /* /* /* Owner of this comms port. 0 for not in use */ Result of last device operation */ Total bytes moved in last operation */ Baudrate for this port.38400 */ Parity for this port.RS-232. 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. unsigned char databits. */ It’s opened by someone else. unsigned long IOBase. unsigned char IRQNum. */ 150-38400 */ 0. 0=none. unsigned long Baudrate. unsigned long RBufSize. */ It’s already open. unsigned long RTimeOut.

extern extern extern extern extern extern extern extern extern extern extern far far far far far far far far far far far U32 U32 U32 U32 U32 U32 U32 U32 U32 U32 U32 UnMaskIRQ(U32 IRQNum). S8 *pIRQ). See listing 24. extern far U32 DeAllocPage(U8 *pOrigMem. Alarm(U32 Exch. S8 *pMsgRet). Listing 24. U32 msg1. U32 msg2). U32 nDevices. S8 *pDCBs. Further down in the file you will notice that commands are defined 1 though 32. U32 count). GetTimerTick(U32 *pTickRet). extern far U32 AllocOSPage(U32 nPages. KillAlarm(U32 Exch).h" /* MMURTL OS Prototypes */ extern far U32 AllocExch(U32 *pExchRet). U32 dfReplace). ISendMsg(U32 Exch. you will notice the shorthand for the rather lengthy C-type declarations.#define #define #define #define #define #define #define #define CmdReSetRTS CmdBreak CmdGetDC CmdGetDSR CmdGetCTS CmdGetRI CmdReadB CmdWriteB 18 19 20 21 22 23 31 32 /* /* /* /* /* /* /* /* 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.8 . SetIRQVector(U32 IRQNum. All other command are specific to this driver. MMURTL V1.8. SendMsg(U32 Exch. EndOfIRQ(U32 IRQNum). U32 nPages).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. U32 msg2). CheckMsg(U32 Exch. S8 *pMsgRet).0 Page 481 of 667 . MaskIRQ(U32 IRQNum). 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 InitDevDr(U32 dDevNum. U8 **ppMemRet). U32 msg1. WaitMsg(U32 Exch.

void CopyData(U8 *pSource. void MicroDelay(U32 us15count). /* local prototypes. U32 dStatusMax.0 Page 482 of 667 . S8 *pStatRet. S8 *pInitData. static U32 comdev_op(U32 dDevice. U32 *pdStatusRet). */ #define #define #define #define CTS DSR RI CD 0x10 0x20 0x40 0x80 /* /* /* /* Clear To Send Data Set Ready Ring Indicator Carrier Detect */ */ */ */ MMURTL V1. U32 dLBA. U16 wPort). U32 sdInitData). set and reset signal line condition and functions. #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.extern extern extern extern extern far far far far far U32 Sleep(U32 count). U32 dBytes). U32 dOpNum. U32 dnBlocks. static S32 comdev_init(U32 dDevice. U8 *pData). extern far long GetJobNum(long *pJobNumRet). These will be called form the device driver interface. */ static U32 comdev_stat(U32 dDevice. U8 *pDestination. void OutByte(U8 Byte. U8 InByte(U16 wPort).

/* /* /* /* /* /* /* /* /* 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. tail_send[2]. LSR[2]. mstat_byte[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]. static U8 control_byte[2] = 0.#define DTR #define RTS #define OUT2 0x01 0x02 0x08 /* 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. /* 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]. sRecvBuf[2].0 Page 483 of 667 . int_id[2]. fExpectInt[2]. tail_recv[2]. *pRecvBuf[2]. MSR[2]. head_send[2]. /* variables for ISRs */ static static static static static U8 U8 U8 U8 U8 f16550[2]. FCR[2]. cSendBuf[2]. IER[2]. static static static static static static static static static static U8 U32 U32 U32 U32 U8 U32 U32 U32 U32 *pSendBuf[2]. stat_byte[2]. IIR[2]. DLAB_HI[2]. LCR[2]. sSendBuf[2]. head_recv[2]. xmit_timeout[2] = 10. cRecvBuf[2]. DLAB_LO[2]. MCR[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.

0. I liked it so much because it was easy to read in a bit-wise fashion. Listing 24.0 Page 484 of 667 .9 in some documentation while working on an AM65C30 USART.TX data.4 chars MMURTL V1.1 char 01 .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. Divisor Latch LSB IER -.I saw register bits defined like those in listing 24.2 chars 10 . \___________ = 0. \ \_____________ = (16550 only) 00 = FIFOs disabled non-zero = 16550 enabled 6 | | | | | | | | 5 | | | | | | | FCR -. You can very easily see the break-out of the bits and follow what is being tested or written throughout the source code.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.9 . RX data. 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 -.Interrupt Enable. FIFO Rcv Trigger Level 00 . 1 = Multichar 0.

11 .8 chars LCR -Line Control Register 7 | | | | | | | 3 2 1 0 | | | \_ Word Length Select Bit 0. MMURTL V1.0 Page 485 of 667 . The members of this structure are defined in the header file RS232.10.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.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.H. See listing 24.) Loop = Always 0 LSR -. | \_____ Number Stop Bits (0=1. It is documented in the preceding section in this chapter. | | \___ Word Length Select Bit 1. 1=2) \_______ Parity Enable \_________ Even Parity Select \___________ Stick Parity \_____________ Set Break \_______________ Divisor Latch Access Bit 6 | | | | | | 5 | | | | | 4 | | | | MCR -.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 -.

This is the same device control block (DCB) record structure used in all MMURTL device drivers.RS-232 Device Driver Code Continued (DCB) static struct dcbtype { S8 Name[12]. Note that there are two of DCB’s. U32 OS4.Listing 24. static void interrupt comISR1(void). This means it's ready for business.12. One for each channel. U32 OS2. U32 nBlocks. U32 OS6.0 Page 486 of 667 . }. S8 fSingleUser. S8 *pDevInit. static struct dcbtype comdcb[2]. U32 OS1. U32 last_erc. S8 fDevReent. U32 OS3. static struct statRecC *pCS.11 . MMURTL V1. S8 type. See listing 24. Listing 24.10 . as is required by MMURTL. /* Two RS-232 ports */ /* THE COMMS INTERRUPT FUNCTION PROTOTYPES */ static void interrupt comISR0(void). the following initialization routine is called once to set up the driver. U32 OS5. See listing 24. It calls InitDevDr() after it has completed filling out the device control blocks and setting up the interrupts. and they are contiguously defined. S8 *pDevOp. S8 sbName.11. S8 *pDevSt. S16 nBPB.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]. Just as with the IDE hard disk device driver. S16 wJob.

’O’.type comdcb[1]. 4. 2.nBPB comdcb[0].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.Name[2] comdcb[0].12 .XTimeOut comstat[0]. /* Sequential */ 1. ’M’. /* 0 for Sequential devices */ &comdev_op. MMURTL V1.pDevOp comdcb[1]. comstat[1].Name[1] comdcb[1]. 4. 2.sbName comdcb[1]. 8N1. &comdev_stat. *********************************************************/ U32 { U32 coms_setup(void) erc.pDevSt = = = = = = = = = = = = = = = = = = = = = = ’C’. /* first we set up the 2 DCBs in anticipation of calling InitDevDr */ comdcb[0]. = 0.Name[2] comdcb[0]. /* none */ comstat[1].Listing 24. = 100.0 Page 487 of 667 .databits comstat[0].pDevOp comdcb[0].databits = 8. /* 1 byte per block */ 0.Name[2] comdcb[1].pDevInit comdcb[0].nBPB comdcb[1].parity comstat[0]. &comdev_init.XBufSize comstat[0]. = 4096.Name[0] comdcb[0]. &comdev_init.Name[1] comdcb[0]. /* Sequential */ 1. ’M’.pDevSt comdcb[1].sbName comdcb[0]. /* Set default comms params in stat records */ comstat[0].Baudrate = 9600. = 2. sRecvBuf[0] = 4096.nBlocks comdcb[1].IRQNum = comstat[0]. /* 1 byte per block */ 0. comstat[1]. It sets up defaults for both channels to 9600.Name[0] comdcb[1]. 4. ’O’. ’C’.type comdcb[0]. &comdev_stat. /* 0 for Sequential devices */ &comdev_op. /* none */ = 8. = 4096. = 1.Name[2] comdcb[1].nBlocks comdcb[0]. ’2’.Baudrate comstat[0].RTimeOut comstat[0]. 0x3F8.RBufSize sSendBuf[0] = 4096.stopbits comstat[0]. ’1’.pDevInit comdcb[1].parity = 0. = 9600.IOBase = comstat[0].

there are two items for each array. I broke my own rule on doing ISRs in assembler. It would be more efficient. SetIRQVector(3. I would have done this in assembler if I had the time. I haven’t had any problems with it. *********************************************************/ static void handleISR(U32 i) { U8 *pRBuf.XBufSize comstat[1]. *pXBuf. = 100. Listing 24. /* clear error conditions */ break.13.RBufSize sSendBuf[1] = 4096. while (TRUE) { int_id[i] = InByte (IIR[i]). &comISR0).IRQNum = comstat[1]. This code must be re-entrant for two channels. one for each channel.IOBase = comstat[1]. = 4096. pRBuf = pRecvBuf[i]. Just the same.comstat[1]. 2. Four channels would be very easy to implement. /* Get ID Byte from IIR */ switch (int_id[i]) { case RCVSTAT: stat_byte[i] = InByte(LSR[i]). = 4096. = 1. /* COM1 */ /* COM2 */ return(erc = InitDevDr(5. SetIRQVector(4. } The following function does all of the work for the interrupt service routine functions.13 . sRecvBuf[1] = 4096. Right now. even at higher baud rates. you must expand the variable arrays that this works with.XTimeOut comstat[1]. 0x2F8.stopbits comstat[1]. MaskIRQ(3). pXBuf = pSendBuf[i]. = 2. &comISR1). If you want to add channels. case RVCDATA: /* i is the device */ MMURTL V1. See listing 24.RS-232 Device Driver Code Continued (ISR and code) /********************************************************* This does the grunt work for each of the two ISRs This MUST remain reentrant for two ISRs. 3. MaskIRQ(4).0 Page 488 of 667 . 1)).RTimeOut comstat[1]. &comdcb.

if (++head_recv[i] == SRECVBUF) head_recv[i] = 0. } break. case NOINT: stat_byte[i] = InByte(LSR[i]). needed if asm is first in function */ MMURTL V1. /* Do not put in buf */ InByte (THR[i]).if (cRecvBuf[i] == sRecvBuf[i]) { /* Overflow!! */ recv_error[i] = ErcRcvBufOvr. } } } /* Send the byte */ /* Get Modem Status */ /* clear error conditions */ static void interrupt comISR0(void) { . /* . #asm STI #endasm recv_error[i] = 0. default: return. break. if (++tail_send[i] == sSendBuf[i]) tail_send[i] = 0. THR[i]).0 Page 489 of 667 . } else fExpectInt[i] = FALSE. --cSendBuf[i]. /* Get the byte */ ++cRecvBuf[i]. #asm STI #endasm break. case MDMSTAT: mstat_byte[i] = InByte (MSR[i]). case TXEMPTY: #asm CLI #endasm if (cSendBuf[i]) { OutByte(pXBuf[tail_send[i]]. fExpectInt[i] = TRUE. /* trash the byte */ } else { #asm CLI #endasm pRBuf[head_recv[i]] = InByte (THR[i]).

RTimeOut. return. } /********************************************/ MMURTL V1.0 Page 490 of 667 . } counter = comstat[device]. } /********************************************/ static long ReadByteC(U32 device. if (++tail_recv[device] == sRecvBuf[device]) tail_recv[device] = 0. pRBuf = pRecvBuf[device]. U8 *pRBuf. if (cRecvBuf[device]) { *pByteRet = pRBuf[tail_recv[device]]. if (cRecvBuf[device]) { *pByteRet = pRBuf[tail_recv[device]]. unsigned char *pByteRet) { U32 counter. --cRecvBuf[device]. EndOfIRQ(3). /* set up for timeout */ while (counter--) { Sleep(1).#asm STI #endasm handleISR(0). /* . --cRecvBuf[device]. } static void interrupt comISR1(void) { . needed if asm is first in function */ #asm STI #endasm handleISR(1). return. } } return (ErcRecvTimeout). if (recv_error[device]) return (recv_error[device]). EndOfIRQ(4). return (0). return (0). if (++tail_recv[device] == sRecvBuf[device]) tail_recv[device] = 0.

if (!erc) ++cb. /* one more in buf */ } MMURTL V1. /* never got sent */ } #asm CLI #endasm if (!fExpectInt[device]) { /* Xmit buf empty. } *pcbRet = cb. counter. } /********************************************/ static long WriteByteC(U32 device. send ourself */ OutByte(b. /* tell em how many bytes */ return (erc).0 Page 491 of 667 . unsigned int *pcbRet) { int erc. U8 *pXBuf. while ((cb < sDataMax) && (!erc)) { erc = ReadByteC(device. /* set up for timeout */ while (cSendBuf[device] == sSendBuf[device]) { Sleep(1). erc = 0. cb.static long ReadRecordC(U32 device. THR[device]).XTimeOut. counter = comstat[device]. unsigned int sDataMax. if (!counter) return (ErcXmitTimeout). pDataRet++). } else { pXBuf[head_send[device]] = b. pXBuf = pSendBuf[device]. counter--. unsigned char *pDataRet. fExpectInt[device] = TRUE. cb = 0. unsigned char b) { U32 erc. erc = 0. if (++head_send[device] == sSendBuf[device]) head_send[device] = 0. ++cSendBuf[device].

&b). stop_bit = comstat[device].RTimeOut = 1. ********************************************/ static U32 SetParams(U32 device) { U32 U8 divisor. stop_bit. or while a channel is in use. unsigned int cbSendData) { int erc.RTimeOut = saveto. saveto = comstat[device]. } /******************************************** This sets comms params prior to opening. U8 b.Baudrate.#asm STI #endasm return (erc). erc = 0. } /********************************************/ static long WriteRecordC(U32 device.0 Page 492 of 667 .RTimeOut. --cbSendData.databits. *pSendData++).parity. comstat[device]. while ((cbSendData) && (!erc)) { erc = WriteByteC(device. } /********************************************/ static long DiscardRecvC(U32 device) { U32 saveto. erc. unsigned char *pSendData. c. speed = comstat[device]. erc = 0. while (!erc) erc = ReadByteC(device. MMURTL V1. parity = comstat[device]. temp. speed. parity. comstat[device]. } return (erc). bits.stopbits. bits = comstat[device]. return (0).

(((divisor>>8) & 0x00ff). LCR[device]). } /******************************************** This allocates buffers. (c. ((c | 0x80). break. temp |= ((stop_bit == 1) ? 0x00 : 0x04). if (comstat[device]. break. MMURTL V1. DLAB_LO[device]). LCR[device]). switch (parity) { case NO_PAR : temp |= 0x00. case EV_PAR : temp |= 0x18. /* set coms params */ temp = bits .commJob). port_base. #asm CLI #endasm c=InByte OutByte OutByte OutByte OutByte #asm STI #endasm (LCR[device])./* Set up baud rate */ divisor = 115200/speed. *********************************************/ static U32 { U32 U16 U8 OpenCommC(U32 device) erc. case OD_PAR : temp |= 0x08. c.5. ((divisor & 0x00ff). GetJobNum(&comstat[device]. } #asm CLI #endasm OutByte (temp. break.0 Page 493 of 667 . #asm STI #endasm return (0). sets up the ISR and IRQ values and open the channel for use.commJob) return(ErcChannelOpen). LCR[device]). DLAB_HI[device]).

6.erc = AllocOSPage(comstat[device]. } if (erc) { comstat[device]. if (!erc) { erc = AllocOSPage(comstat[device]. OutByte(control_byte[device]. 3. head_send[device] = 0. recv_error[device] = 0. comstat[device]. } port_base = comstat[device]. MCR[device]). 2. cRecvBuf[device] = 0. port_base + port_base + port_base + port_base + port_base + port_base + port_base + port_base + port_base. /* Int Enable Reg */ /* See if we have a 16550 and set it up if we do!! */ MMURTL V1.0 Page 494 of 667 . tail_send[device] = 0. 5. &pRecvBuf[device]). THR[device] IER[device] IIR[device] FCR[device] LCR[device] MCR[device] LSR[device] MSR[device] DLAB_HI[device] DLAB_LO[device] = = = = = = = = = = port_base.XBufSize/4096.RBufSize/4096. return (erc). /* Set up buffer variables for this port */ cSendBuf[device] = 0. #asm /* reset any pending ints on chip */ CLI #endasm control_byte[device] = RTS | DTR | OUT2. head_recv[device] = 0. 2. InByte(THR[device]). 1. tail_recv[device] = 0. if (erc) /* get rid of Xmit buf if we can’t recv */ DeAllocPage(pSendBuf[device]. /* Mod Ctrl Reg */ OutByte(0x0F. 4. &pSendBuf[device]).IOBase. 1.XBufSize/4096).commJob = 0. IER[device]). InByte(LSR[device]).

else f16550[device] = 0. /* 8250 or 16450 */ #asm STI #endasm SetParams(device).RS-232 Device Driver Code Continued (Interface) /****************************************** Called for all device operations. return (0).0 Page 495 of 667 . They are the last functions defined in this file. OutByte(0. This was done prior to calling InitDevDr(). CloseCommC (U32 device) MaskIRQ(comstat[device].IRQNum).RBufSize/4096). MCR[device]). For RS-232. MMURTL V1. OutByte(0. erc = DeAllocPage(pSendBuf[device]. This assigns physical device from logical number that outside callers use. IER[device]). This driver is re-entrant. UnMaskIRQ(comstat[device]. FCR[device]). Listing 24. ********************************************/ static int { U32 erc. sets the owner to 0 and deallocates the buffers. and therefore all data must be associated with the particular comms port you are addressing.14 .14.IRQNum). 5=0 and 6=1. See listing 24.commJob = 0.OutByte(0x03. erc = DeAllocPage(pRecvBuf[device]. three functions provide the interface from the outside. return (erc). } Just like in the IDE hard disk driver (and all other drivers in MMURTL). comstat[device].XBufSize/4096). comstat[device]. c = InByte(IIR[device]). } /******************************************** This closes the port. Their offsets (entry points) have been defined in the device control block for each of the two devices. if (c & 0xC0) /* we have a 16550 and it’s set to go! */ f16550[device] = 1. comstat[device].

dnBlocks). case CmdSetRTO: comstat[device]. case CmdReadRec: erc = ReadRecordC(device.LastTotal). /* 10ms intervals */ break. pData). case CmdWriteRec: erc = WriteRecordC(device. pData. if ((!comstat[device]. break. U32 dLBA. else device = 1. pData. *pData). break. case CmdSetXTO: /* default error */ MMURTL V1.commJob) && (dOpNum != CmdOpenC)) return(ErcNotOpen). U8 *pData) { U32 erc. break.commJob) { if ((comstat[device]. U32 dnBlocks. U8 c. break.commJob != Job) && (Job != 1)) return(ErcNotOwner). case CmdWriteB: erc = WriteByteC(device. &comstat[device]. U32 Job. dnBlocks.RTimeOut = dLBA. /* Set internal drive number */ /* 5 RS-232 1 COM1 (OS built-in) */ /* 6 RS-232 2 COM2 (OS built-in) */ if (dDevice == 5) device = 0.*******************************************/ static U32 comdev_op(U32 dDevice. /* Null Command */ case CmdReadB: erc = ReadByteC(device. GetJobNum(&Job). } erc = 0.0 Page 496 of 667 . switch(dOpNum) { case(0): break. device. U32 dOpNum. if (comstat[device].

LCR[device]). LCR[device]). case CmdGetDSR: *pData = mstat_byte[device] & DSR. This is called by the PUBLIC call DeviceStat MMURTL V1. OutByte(control_byte[device]. case CmdBreak: c = InByte(LCR[device]). break. case CmdGetDC: *pData = mstat_byte[device] & CD. return(erc). /* 10ms intervals */ break. OutByte((c | 0x40). case CmdReSetRTS: control_byte[device] &= ~RTS. break. case CmdDiscardRcv: erc = DiscardRecvC(device). default: break. break. OutByte(control_byte[device].comstat[device]. break.XTimeOut = dLBA.0 Page 497 of 667 . OutByte(control_byte[device]. Returns 64 byte block for channel specified. } /****************************************** Called for status report on coms channel. LCR[device]). break. case CmdReSetDTR: control_byte[device] &= ~DTR. case CmdGetRI: *pData = mstat_byte[device] & RI. OutByte(c. Sleep(dLBA). case CmdOpenC: erc = OpenCommC(device). break. break. break. case CmdSetRTS: control_byte[device] |= RTS. MCR[device]). } comstat[device].LastErc = erc. break. case CmdSetDTR: control_byte[device] |= DTR. break. case CmdCloseC: erc = CloseCommC(device). case CmdGetCTS: *pData = mstat_byte[device] & CTS. LCR[device]). MCR[device]). break. OutByte(control_byte[device].

if (dStatusMax > 64) i = 64. Xbufsize. bits. IRQNUM. } /****************************************** Called to set parameters for the comms channels prior to opening or while in use. Rbufsize. This is called by the PUBLIC call DeviceInit. if (dDevice == 5) device = 0. XTO. U32 *pdStatusRet) { U32 i. Some comms channel params may not be changed while the channel is in use. else device = 1. S8 *pInitData. If an invalid value is passed in. all params remain the same as before. U32 sdInitData) { U32 U32 U16 U8 /* copy the status data */ /* copy the status data */ /* give em the size returned */ erc.*******************************************/ static U32 comdev_stat(U32 dDevice. pStatRet. device. stop_bit. S8 *pStatRet. device. /* Set internal device number */ if (dDevice == 5) device = 0. else device = 1. pStatRet. } *pdStatusRet = dStatusMax. /* Set internal device number */ MMURTL V1. *******************************************/ static S32 comdev_init(U32 dDevice. port_base. return(0). speed. RTO. } else { CopyData(&comstat[1]. i). else i = dStatusMax. parity. i).0 Page 498 of 667 . if (!device) { CopyData(&comstat[0]. U32 dStatusMax.

Baudrate comstat[device]. if ((parity < NO_PAR) || (parity > OD_PAR)) return (ErcBadParity). pCS->RBufSize.databits comstat[device].commJob) { /* Channel Open! */ SetParams(device). The channel is open if the JobNumber the commstat record is NON-ZERO. */ if ((speed > MAX_BAUD) || (speed < MIN_BAUD)) return (ErcBadBaud). Now we check see if the channel is open and call SetParams so. pCS->IRQNum. /* /* /* /* /* /* Non Non Non Non Non Non Volatile Volatile Volatile Volatile Volatile Volatile */ */ */ */ */ */ /* Non Volatile params can be set whether or not the channel is open. pCS->XBufSize. bits.RTimeOut /* If to if in */ we got here. RTO = 1. if ((stop_bit < 1) || (stop_bit > 2)) return (ErcBadStopBits). comstat[device]. XTO. } /* Channel is not open so we check and set rest of params */ MMURTL V1. pCS->stopbits.if (sdInitData < 40) return(ErcBadInitSize). Do these first and return errors. if (!XTO) if (!RTO) XTO = 1. pCS->RTimeOut. /* Get the callers new params */ speed parity bits stop_bit XTO RTO port_base Xbufsize Rbufsize IRQNUM = = = = = = = = = = pCS->Baudrate. pCS->parity. pCS = pInitData. pCS->XTimeOut. = = = = = = speed.XTimeOut comstat[device].0 Page 499 of 667 . if ((bits < 5) || (bits > 8)) return (ErcBadDataBits). the params are OK. pCS->databits. if (comstat[device]. pCS->IOBase. RTO. stop_bit.stopbits comstat[device]. parity.parity comstat[device].

if (Rbufsize % 4096) Rbufsize+=4096. } /* Size of buffer (allocated) */ /* Size of buffer (allocated) */ MMURTL V1. sRecvBuf[device] = Rbufsize.else { if (!port_base) return (ErcBadIOBase). /* We now round up buffer sizes to whole pages */ Xbufsize = Xbufsize/4096 * 4096. = Xbufsize. IRQNUM.XBufSize comstat[device]. } return(erc).RBufSize port_base. /* Local copies so we don’t work from a structure in ISR */ sSendBuf[device] = Xbufsize. /* another page */ comstat[device].IOBase = comstat[device].0 Page 500 of 667 . erc = 0. /* another page */ Rbufsize = Rbufsize/4096 * 4096.IRQNum = comstat[device]. if (IRQNUM < 3) return (ErcBadCommIRQ). if (Xbufsize % 4096) Xbufsize+=4096. = Rbufsize.

INC . alt.of the keyboard shift. which actually go to an 8042 (or equivalent) microprocessor.1 . This is because it has a little bit of everything in it. multilevel lookup tables. Some of the ISA machines support PS-2 scan sets. I use it to support the widest array of machines possible.0 Page 501 of 667 . . control.DATA . The first provides the initial translation from the raw scan codes.ALIGN DWORD EXTRN ddVidOwner DD .1. Keyboard Data The data portion of the file is much larger than for most of the files in MMURTL. It contains an interrupt service routine. Hence. a complete system service with an additional task.The keyboard service is designed to return the complete status . I have the original PC-AT documentation and PS-2 documentation. which was the original scan set 2 for the PC-AT. and several hardware handling calls. are used for a wide variety of things. The systems are not as standardized as I once thought. but only the original scan set 2 was supported on all of the systems I tested. It has several look-up tables used to translate scan codes from what was supposed to be a standardized system.INCLUDE TSS. and the third is for shifted keystrokes.Keyboard Data and Tables .the key in the buffer that the state applies to. See listing 25.INC . Even though the keyboard is technically a device. and some of them only support two out of three. second only to the file system. I stuck with the most common scan code set.INC . The second reason is that it is only an input device and is always shared among applications. Some of them support all of the modes for the IBM PC-AT. There are three tables that provide the translation. There are several reasons for this. This is a device found on almost all system boards on PC-AT ISA-compatible platforms.Begin Keyboard Data . Listing 25. the second is for the scan codes that are preceded by the E0 hex escape sequence. Another reason is that the keyboard I/O ports. MMURTL V1. it does not use the standard MMURTL device driver interface.Chapter 25.INCLUDE MOSEDF. The first is that it was one of the very first devices coded on the system. Keyboard Source Code Introduction The keyboard code is probably one of the most complicated source files. and lock keys as well as .INCLUDE RQB.

KbdState DB 0 .Job that is cancelling global request rgbKbdBuf DB 20h DUP (0) . Shift.Set true when KBD Service is up and running .Message buffers for Kbd Service KbdMsgBuf1L DD 0 KbdMsgBuf1H DD 0 KbdMsgBuf2L DD 0 KbdMsgBuf2H DD 0 KbdOwner KbdAbortJob KbdCancelJob DD 1 DD 0 DD 0 .that holds keys from the interrupt service routine which contains . else 0 .Next byte (bits 8-15) Shift State (Ctrl. They are BIT OFFSETS and NOT MASKS for logical operations!!!!! CtrlLeftBit CtrlRiteBit ShftLeftBit EQU 0 EQU 1 EQU 2 MMURTL V1. The first is a "RAW" key buffer . This DWord contains the . .This is done by using two buffers.ptr to next char going in pKbdOut DD OFFSET rgbKbdBuf . rgdKBBuf DD 40h DUP (0) .Low byte (bits 0-7) Partially translated application keystroke . .Used .Next byte (bits 16-23) Lock State (CAPS.(See Lock Masks below) .Used .ZERO for Cooked. . KBDSvcName DB ’KEYBOARD’ KbdMainExch KbdHoldExch KbdGlobExch KbdWaitExch KbdTempExch dGlobalKey DD DD DD DD DD 0 0 0 0 0 .. bKbdMode DB 0 .ptr to codes going out . These "masks" are for keyboard states that change with special keys: .ptr to next char going out .in the form of DWords (a 4 byte quantity times 64).Used .Hi Byte (bits 24-31) Key Source (Bit 1 Set = Key From Numeric Pad) .64 Dwords for translated key buffer dKBCnt DD 0 pKBIn DD OFFSET rgdKBBuf . This buffer is 32 bytes long.Current owner of Kbd (Mon is default) .32 byte RAW buffer dKbdCnt DD 0 pKbdIn DD OFFSET rgbKbdBuf . .following information: . 1 for RAW fKBDInitDone DB 0 . .The second buffer is 256 bytes and contains the translated information .ptr to codes comming in pKBOut DD OFFSET rgdKBBuf .all raw keystrokes including shift keys going up and down.0 Page 502 of 667 . Alt) .(See State Masks below) KbdLock DB 0 . for an application. Scroll) .This information is needed to build coherent keystroke information .Used by the Kbd Process for requestors that don’t own the keyboard to hold Global CTRL-ALT requests for Kbd Wait-For-Key requests to rifle thru requests DD 0 .Global Key Found. Num.Job that is aborting .Used .

Lights NumLock LED and processes keys accordingly . The following special keys are processed by the Keyboard Task and handled . r 13 52h DB 074h . u 16 55h DB 069h . . 3 04 23h # DB 034h . BkSpc 0E DB 009h . Value KbdTable DB 0. TAB 0F DB 071h . ALT . SHIFT . y 15 59h DB 075h . 8 09 2Ah * DB 039h . 6 07 5Eh ^ DB 037h . 4 05 24h $ DB 035h . 7 08 26h & DB 038h . i 17 49h MMURTL V1. 0C 5Fh _ DB 03Dh . Mask to tell CtrlDownMask ShftDownMask AltDownMask . 00 DB 01Bh . Alt) EQU 00000011b EQU 00001100b EQU 00110000b EQU 2 EQU 1 EQU 0 DB 00000100b DB 00000010b DB 00000001b . . . This table is used to translate all active editing keys from . Esc 01 DB 031h . 9 0A 28h ( DB 030h .Lights ScrollLock LED and flag . q 10 51h DB 077h .Sets Alt flag. 2 03 40h @ DB 033h .Lights CapsLock LED and processes keys accordingly . as follows: .Sets Ctrl flag .Sets shift flag and processes keys accordingly . e 12 45h DB 072h . 0 0B 29h ) DB 02Dh .ShftRiteBit AltLeftBit AltRiteBit . . 5 06 25h % DB 036h . SHIFT . t 14 54h DB 079h . . 1 02 21h ! DB 032h . SCRLLOCK . . the raw value provided by the hardware. NUMLOCK . BIT OFFSETS CpLockBit NmLockBit ScLockBit .0 Page 503 of 667 . = 0D 2Bh + DB 008h . Shift. MASKS CpLockMask NmLockMask ScLockMask EQU 3 EQU 4 EQU 5 if one of the 3 states exist (Ctrl. CAPSLOCK . . CTRL . w 11 57h DB 065h .

. . . . . . . . . . . . . . . . . . . . . ’ ‘ LfShf \ z x c v b n m . . . . .0 Page 504 of 667 . . . o p [ ] CR LCtrl a s d f g h j k l (L) . / RtShf Num * LAlt Space CpsLk F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 NumLk ScrLk Num 7 Num 8 Num 9 Num Num 4 Num 5 Num 6 Num + Num 1 Num 2 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 4Fh 50h 7Bh 7Dh Special handling 41h 53h 44h 46h 47h 48h 4Ah 4Bh 4Ch 3Ah 22h 7Eh Special handling 7Ch 5Ah 58h 43h 56h 42h 4Eh 4Dh 3Ch 3Eh 3Fh Special handling Num pad Special handling Special handling Special handling Special handling 37h Num Home 38h Num Up 39h Num Pg Up Num Pad 34h Num Left 35h Num (Extra code) 36h Num Right Num Pad 31h Num End 32h Num Down MMURTL V1. . . . . . . . . . . . . . . . . . . . . . . . . .DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB 06Fh 070h 05Bh 05Dh 00Dh 0h 061h 073h 064h 066h 067h 068h 06Ah 06Bh 06Ch 03Bh 027h 060h 0h 05Ch 07Ah 078h 063h 076h 062h 06Eh 06Dh 02Ch 02Eh 02Fh 0h 02Ah 0h 020h 0h 00Fh 010h 011h 012h 013h 014h 015h 016h 017h 018h 0h 0h 086h 081h 085h 0ADh 083h 09Fh 084h 0ABh 08Bh 082h . . . . . .

71 DB 0h . 6E DB 0h .from the original scan set 2 for the IBM PC-AT. 59 DB 000h .table above. The Kbd translates incoming keystrokes . Delete 6A Cursor pad DB 0AFh . 7B DB 0h . 70 DB 0h . 6D DB 0h .to another unique character which is looked up in the primary . 53 2Eh Num Del DB 01Ch . 78 DB 0h . Right 66 Cursor pad DB 006h . Up 68 Cursor pad DB 005h . ENTER 6C Num Pad DB 0h . 74 DB 0h . This gives us unique single characters for every key! . Num 3 51 33h Num Pg Dn DB 08Eh . 77 DB 0h . 7D DB 0h . 7F . / 6B Num Pad DB 08Dh . All PCs are are set to this . 7C DB 0h . 6F DB 0h . 5A DB 000h . Left 64 Cursor pad DB 000h . 5C DB 000h . . 75 DB 0h . 56 DB 019h . Num 0 52 30h Num Insert DB 0FFh . 65 DB 004h . F11 57 DB 01Ah . Ins 60 Cursor pad DB 00Bh . PgUp 69 Cursor pad DB 07Fh .DB 08Ch . PgDn 63 Cursor pad DB 003h . Down 62 Cursor pad DB 00Ch . 7E DB 0h .keypad use a two character escape sequence begining with E0 hex. Pr Scr 54 SYS REQUEST DB 000h . Keys on the 101 keyboard that were common to the numeric . 7A DB 0h . F12 58 DB 000h . 79 DB 0h . MMURTL V1. Home 67 Cursor pad DB 001h . Num . 73 DB 0h . 55 DB 000h . .The following chars are subs from table2 DB 00Eh .If we see an E0 hex we scan this table and provide the translation .provided by the keyboard. 5D DB 000h . 5B DB 000h .by default.This table does an initial character translation from the characters .0 Page 505 of 667 . 5F . End 61 Cursor pad DB 002h . 76 DB 0h . 72 DB 0h . 5E DB 000h .

DB 31h . . DB 38h . DB 04Fh. DB 1Dh . DB 047h. DB 053h.Num ENTER . 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E Up Dn Left Rite PgUp Home 8 2 4 6 9 7 Numeric Numeric Numeric Numeric Numeric Numeric pad pad pad pad pad pad End 1 PgDn 3 Ins 0 Numeric Pad Numeric pad Numeric pad MMURTL V1.In Shift-ON state ALL keycodes are translated through this table. DB 15h . . KbdTable2 DB 052h. DB 07h .Delete .Right Ctrl DOWN . DB 038h. DB 13h . DB 12h . DB 0Dh . DB 37h .Rite . KbdTableS DB 0.End . 060h 061h 062h 063h 064h 066h 067h 068h 069h 06Ah 06Bh 06Ch 070h 071h 0F0h 0F1h . DB 037h. DB 049h. DB 14h .In CAPS LOCK state codes 61h to 7Ah are translated through this table .Pg Down . DB 1Eh . DB 051h. DB 0Fh .This table provides shift level values for codes from the primary KbdTable. DB 30h . DB 01Dh.Num / . DB 17h . DB 01Ch. DB 1Ch .Home . DB 1Bh . DB 08h . DB 038h. DB 0Ah .Right Ctrl UP These are special cause we track UP & DOWN!!! . . DB 01Dh.Right ALT UP . DB 33h . DB 050h.Pg Up .Down . DB 1Ah .Up . DB 04Dh.0 Page 506 of 667 . DB 048h. DB 36h .nKbdTable2 EQU 10 . DB 18h . DB 39h . DB 16h .Insert . DB 04Bh. DB 18h . DB 32h . DB 09h .Left . DB 10h . DB 34h .Right ALT DOWN .In NUM LOCK state only codes with High Bit set are translated . DB 11h .

. . . . . .DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB 35h 20h 21h 22h 23h 24h 25h 26h 22h 28h 29h 2Ah 2Bh 3Ch 5Fh 3Eh 3Fh 29h 21h 40h 23h 24h 25h 5Eh 26h 2Ah 28h 3Ah 3Ah 3Ch 2Bh 3Eh 3Fh 40h 41h 42h 43h 44h 45h 46h 47h 48h 49h 4Ah 4Bh 4Ch 4Dh 4Eh 4Fh 50h 51h 52h 53h 54h 55h 56h 57h . . . . . . .0 Page 507 of 667 . . . . . . . . . . . . . . . . . . . . . . . . = < _ > ? ) ! @ # $ % ^ & * ( : + MMURTL V1. . . . . . . 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 Blnk 5 Numeric pad ’ " . . . . . . . . . . . . . . / 0 1 2 3 4 5 6 7 8 9 . . . .

. . . Numeric Pad KbdScvStack KbdSvcStackTop DD 127 DUP(0) DD 0 . . . .DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB DB 58h 59h 5Ah 7Bh 7Ch 7Dh 5Eh 5Fh 7Eh 41h 42h 43h 44h 45h 46h 47h 48h 49h 4Ah 4Bh 4Ch 4Dh 4Eh 4Fh 50h 51h 52h 53h 54h 55h 56h 57h 58h 59h 5Ah 7Bh 7Ch 7Dh 7Eh 2Eh . . . . . . . .========================================================================= . . . . . . . . Port Hex 0064 Read . . . . . . . . . .The following equestes are for the hardware handling code. 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F [ \ ] { | } ‘ a b c d e f g h i j k l m n o p q r s t u v w x y z ~ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Del . .8042 Status Byte.========================================================================= STATUSPORT COMMANDPORT DATAPORT PARITYERROR GENERALTIMEOUT AUXOUTBUFFFULL EQU 64h EQU 64h EQU 60h EQU 10000000b EQU 01000000b EQU 00100000b MMURTL V1. . . . . . . . .512 byte stack for KbsSvc . . .0 Page 508 of 667 .

2 . the second is a small routine that reads scan codes from the raw ISR buffers. The first section is the ISR. a message is sent to the keyboard service to tell it that the raw buffer has something in it.Set up pointer XOR EAX. OFFSET rgbKbdBuf+20h . This also sends .Don’t save it MOV BYTE PTR [ESI]. When a code is placed in the buffer.should be.2.Buffer is full .ISR for the keyboard.Back to beginning of buffer KbdEnd: MOV pKbdIn.INT 21 fires off. See listing 25. OFFSET rgbKbdBuf . PUBLIC IntKeyBrd: .past end yet? JB KbdEnd MOV ESI. .Read byte MOV EBX. (most are not this easy though).Set up pointer for next time MMURTL V1.EAX IN AL. and finally all of the support routines. 20h .. Short and sweet the way all ISRs . pKbdIn .One more in the buf INC ESI . AL . This uses ISendMsg().KBD processor into the buffer. This puts the single byte from the 8042 .Next byte in CMP ESI.0 Page 509 of 667 . Keyboard ISR The keyboard ISR places all raw scan codes into a 32-byte buffer.Keyboard ISR Code . . ESI . the third is the shift translation code. Listing 25.if no keyboard requests are waiting. followed by the keyboard service.a message to the KBD Task (using ISend) when the buffer is almost .Buffer size JE KbdEnd .Move into buf INC dKbdCnt .Key Board (KB) INT 21 PUSHAD .Save all registers MOV ESI. 60h .CODE .full so it will be forced to process some of the raw keys even .INHIBITSWITCH COMMANDDATA SYSTEMFLAG INPUTBUFFFULL OUTPUTBUFFFULL EQU EQU EQU EQU EQU 00010000b 00001000b 00000100b 00000010b 00000001b Keyboard Code The code is divided into several sections.See if buffer full CMP EBX. which is used by ISRs to send messages when interrupts are disabled. dKbdCnt . This is vectored to by the processor whenever ..

. 0 JE KbdExit .CMP BYTE PTR fKBDInitDone.Zero is returned if no key exists. in the translated buffer if it is an edit key. For instance.Get ptr to next char to come out MOV EAX. dKbdCnt .tell him to come and get it. ReadKBDBuf: CLI MOV ESI. KbdExit: PUSH 1 CALL FWORD PTR _EndOfIRQ POPAD IRETD . if you press the shift key then let it go without hitting another key.This gets one byte from the raw Kbd Buffer and returns it in AL .past end yet? JB RdKBDone MOV ESI.See if there are any bytes CMP to send to PUSH 0FFFFFFFFh .0 Page 510 of 667 . OFFSET rgbKbdBuf+20h .EAX MOV AL. ESI STI RETN . BYTE PTR [ESI] . and places them and their proper state bytes and into the next DWord . Some interrupts may not necessarily result in a key code being sent to the final buffer.ISend Message to KbdTask PUSH EBX .Service isn’t ready for messages yet . pKbdOut .. this routine will be called to translate them and place them in the final 64-key buffer.Save ptr to next char to come out Keyboard Translation When the keyboard is notified that raw scan codes have been placed in the keyboard buffer. MMURTL V1. the result will be no additional key codes in the final buffer.Leave 0 in EAX DEC dKbdCnt .Yes .3.3 .bogus msg CALL FWORD PTR _ISendMsg . See listing 25. Reads and processes all bytes from the RAW keyboard buffer .ISend a msg to Keyboard task MOV EBX.Put byte in AL INC ESI CMP ESI.make cnt right XOR EAX.. OFFSET rgbKbdBuf . 0 JE RdKBDone . Listing 25.bogus msg PUSH 0FFFFFFFFh .Back to beginning of buffer RdKBDone: MOV pKbdOut. KbdMainExch .No .Keyboard Translation Code .Yes .

No . ECX BL. AL has byte from buffer.bit to indicate this..Key = NumLock ? CMP AL.BH has Num.two key code into a single code and sets a state . KbdLock . KB001: CMP AL. Alt..BL has SHIFT state. [ESI] .EAX MOV AL.We got another char .keep going .Yes.Key PREFIX??? . CL has LOCK State. EBX ECX. .this is a two key code that needs to be translated from ..0 Page 511 of 667 .. AL KB005 ESI ESI ECX KB004 KB006 .XLateRawKBD: CALL ReadKbdBuf CMP EAX.Now we check to see if the byte is 0Eh which tells us .. leave the key in AL .Back to get another char .See if we are RAW. Caps. . KbdState CL.Try 50 times to get second char .Guess one isn’t comming.Fall thru to check on char. OFFSET KbdTable2 MOV ECX. bKbdMode BL.BL has Shift. (for testing ONLY) . nKbdTable2 KB004: CMP JE INC INC DEC JNZ JMP KB005: INC ESI XOR EAX. 45h MMURTL V1.NO . BYTE PTR [ESI].Go to next table entry . & Scroll Lock . Ctrl states .. etc.. KB006: XOR XOR MOV MOV EBX. alt. This turns the .This next section checks for special keys (shift. 1 KB001 XLateDone ..Two byte further into table 2 .) . 0 JE XLateDone MOV CMP JNE JMP BL. 0E0h JNE KB006 MOV ECX.our special table before processing. 50 KB002: CALL ReadKbdBuf CMP EAX.One byte further over to get Xlate byte . 0 JNE KB003 LOOP KB002 JMP XLateDone KB003: MOV ESI.NO.

Compliment bit in BH . 09Dh KB016 EBX. CtrlRiteBit KB021 AL. ShftRiteBit KB021 AL. CpLockBit KB022 .Left Ctrl On? ..Char Left Shift On? . 2Ah KB010 EBX.Compliment bit . 0AAh KB012 EBX.Left Shift Off? . ShftLeftBit KB021 AL.Right Shift Off? .Left Ctrl Off? MMURTL V1. 1Dh KB014 EBX.0 Page 512 of 667 .JNE KB007 BTC ECX.Right Ctrl On? . ShftRiteBit KB021 AL. 46h KB009 ECX. NmLockBit JMP KB022 KB007: CMP JNE BTC JMP KB008: CMP JNE BTC JMP KB009: CMP JNE BTS JMP KB010: CMP JNE BTS JMP KB011: CMP JNE BTR JMP KB012: CMP JNE BTR JMP KB013: CMP JNE BTS JMP KB014: CMP JNE BTS JMP KB015: CMP JNE BTR JMP AL.Compliment bit in BH . 0B6h KB013 EBX. ShftLeftBit KB021 AL. 3Ah KB008 ECX. CtrlLeftBit KB021 AL. 71h KB015 EBX.. CtrlLeftBit KB021 AL. ScLockBit KB022 AL. 36h KB011 EBX. .Right Shift On? .Scroll Lock? .Caps Lock? .NO.

Left Alt On? AL. AltLeftBit KB021 . .If we got here. AltLeftBit KB021 . else fall through MMURTL V1.Left Alt Off? AL.Chop off any upper bit junk . 80h JNZ XLateDone OR AL.Go back. 70h KB019 EBX. 0F0h JNE KB023 BTR EBX. else fall through .Go back.Zero not a valid code . else fall through . .Now we lookup the code and do a single translation. BL JMP XLateDone KB022: MOV KbdLock. AltRiteBit KB021 .Set up to index table . 0FFh MOV ESI. 38h KB018 EBX.We jumped here if it wasn’t a key that is specially handled KB023: TEST AL.Go back. AltRiteBit KB021: MOV KbdState.Right Alt Off? AL.Put Kbd Shift State back . AND EAX. CL CALL SetKbdLEDS JMP XLateRawKBD .Put Kbd Lock State back .AL JZ XLateDone .Save in DL .Check for high bit (key-up code) . BYTE PTR [ESI+EAX] OR AL. CtrlRiteBit KB021 .Zero not a valid code .0 Page 513 of 667 . IT’S AN EDIT KEY DOWN! . OFFSET KbdTable MOV DL. 0B8h KB020 EBX.Set LEDs on keyboard .Right Ctrl Off? . 0F1h KB017 EBX.AL JZ XLateDone .KB016: CMP JNE BTR JMP KB017: CMP JNE BTS JMP KB018: CMP JNE BTS JMP KB019: CMP JNE BTR JMP KB020: CMP AL.Right Alt On? AL.

No .translation table.Get Shift state .No AND CH. NmLockMask .Do not shift DASH (-) Special Case JE KB026 JMP SHORT KB027 .Yes.No CMP DL.TO let the user know if the key came from the Numeric .Fall though to put key in final buffer .Set up to index table MOV DL. 0ADh . 07Fh . DH EAX.MOV CL. KbdState MOV CH.table for these keys.into the high byte of the returned key code.See if Caps Lock is on and if key is between 61h and 7Ah .Yes.No.If the high bit is set comming from the primary .No CMP DL. ShftDownMask JZ KB025 JMP SHORT KB027 KB025: .Is CpLock ON JZ KB029 . go do the translation .Place DL in the LOW byte of the DWord to go into the .High bit set? .Do the shift Xlation .Get lock state . MOV DH.Is key <= ’z’ JA KB029 . This next piece of code tests for it . is NumLock ON JZ KB026 CMP buffer (the data the user will get) .Save in DL . KbdLock .Do the shift translation and leave in DL MOV AL.Indicates key came numeric pad . 0 TEST DL. CpLockMask .Get lock state MMURTL V1.numeric keypad so we set the numpad bit in status KB029: MOV SHL MOV MOV AH.Either shift key down? .Fall through to do the translation KB026: KB027: .keypad we set the high bits in the first translation . BYTE PTR [ESI+EAX] .Put in AL AND EAX. OFFSET KbdTableS .See if shift key is down and shift all keys it is is TEST CL. this means the key was from the . 80h .Is key >= ’a’ JB KB029 . KbdLock . 7Ah .See if key is from Numerc Keypad (high bit will be set) TEST DL.and sets the low bit in DH if it its. DH is later moved .Chop all above 7 bits MOV ESI. 8 AL. 61h .Get Shift state . 80h MOV DH. DL . KbdState AH. 1 . go look for locks .Num Pad indicator .0 Page 514 of 667 .High bit set? JZ KB026 .do the translation TEST CH.

Get ptr to next IN to final buffer [ESI].No . EAX . DL .Back for more (if there is any) KB029A: MOV CMP JE MOV MOV INC ADD CMP JB MOV KB030: MOV JMP pKBIn. Listing 25. CtrlDownMask JZ KB029A TEST AH.Save it JMP XLateRawKBD . 8 AND DL. OFFSET rgdKBBuf+100h KB030 ESI. 7Fh MOV AL.Save ptr to next in EBX.EAX now has the buffered info for the user (Key.0 Page 515 of 667 .Either Alt Down? . dKBCnt . Returns zero in EAX if buffer is empty. OUT: EAX has Key or 0 if none . pKBOut .One more DWord in the buf ESI. ReadKBFinal: MMURTL V1. AltDownMask JZ KB029A . . OFFSET rgdKBBuf . . This buffer rgdKBBuf holds the 32-bit full encoded key value.Lop of high bit (if there) . IN : Nothing .Either Ctrl Down? . ESI XLateRawKBD . Returns a keyboard code from FINAL keyboard buffer. ESI.number of DWords in final buffer XLateDone . . 4 ESI. EAX .Buffer is FULL.. pKBIn .Now we put it in the DWord buffer if it is NOT a GLOBAL. Shifts & Locks) .4. See listing 25. ESI .SHL EAX. TEST AH.If global.Read Keyboard Buffer Code .No . EAX RETN Reading the Final Buffer This routine is called to take a key out of the final buffer if one is available. 64 . USED: EAX.It IS a global key request! MOV dGlobalKey. MODIFIES: dKBCnt. .See if buffer full EBX.Move into buf dKBCnt . we put it in dGlobalKey.4.Reset to buf beginning XlateDone: XOR EAX.

.Yes if ZERO MMURTL V1. This service also accepts messages from the keyboard ISR to indicate that translation of raw keyboard scan codes must be done.. It services five codes including Abort (0). It calls XLateRawKBD to process raw keyboard buffer data. Reset to beginning KBF02: MOV pKBOut.Update pKBOut The Keyboard Service KBDServiceTask is a complete system service. It handles requests from multiple clients.No MOV ESI. ESI KBFDone: RETN . OFFSET rgdKBBuf .Nothing final buffer DEC dKBCnt .MOV EAX. This is the keyboard Service task. .Next code please. . OFFSET rgdKBBuf+100h . this means it must hold the requests for those jobs that do not own the keyboard.0 Page 516 of 667 . KBDServiceTask: CALL XLateRawKBD . . . dKBCnt CMP EAX. 4 . .Processes RAW buffer if not empty CMP DWORD PTR dGlobalKey.Yes.No global key came in KBDGlobal1: PUSH KbdGlobExch PUSH OFFSET KbdMsgBuf1L CALL FWORD PTR _CheckMsg OR EAX. CMP ESI.5. Listing 25.======================================================== . EAX . the requests it was holding must be reevaluated to see if the new job had an outstanding keyboard request. See listing 25.Past end of buff? JB KBF02 .Put it in EAX ADD ESI. and waits at the KeyBoard Service Main Exchange for users. When the keyboard is reassigned to a new job. [ESI] .5. pKBOut . When it gets a request it checks the service code and handles it accordingly.Check to see if RqWaiting . 0 JE KST01 .One more DWord in the buf MOV ESI.Keyboard system service code. 0 JE KBFDone ..ptr to next code out MOV EAX. It is an infinite loop that services requests from users of the keyboard.Where to return pRqBlk .

JZ KBDGlobal2 ;Rq waiting for global MOV DWORD PTR dGlobalKey, 0 ;Wipe out global key (no one wants it) JMP KBDServiceTask ;Start over again KBDGlobal2: MOV EBX, KbdMsgBuf1L MOV ESI, [EBX+pData1] OR ESI, ESI JNZ KBDGlobal3 PUSH EBX PUSH ErcNullPtr CALL FWORD PTR _Respond JMP KBDServiceTask KBDGlobal3: MOV EDX, dGlobalKey MOV [ESI], EDX PUSH EBX PUSH 0 CALL FWORD PTR _Respond JMP KBDGlobal1 KST01: CMP DWORD PTR ddVidOwner, 2 ;Debugger has video JNE KST01ND ;NOT in Debugger PUSH 20 CALL FWORD PTR _Sleep ; CALL XLateRawKBD ;Processes RAW buffer JMP KBDServiceTask ;Go back to the top KST01ND: PUSH KbdMainExch PUSH OFFSET KbdMsgBuf1L CALL FWORD PTR _WaitMsg ;See if someones "Requesting" ; ;Wait for the message

;pRqBlk into EBX ;Ptr where to return key ;Is it null?? (Bad news if so) ;No, probably good ptr ;Yes, BAD PTR. Push pRqBlk ;Push error ;Go back to the top

;Give em the key! ;Push pRqBlk ;Push NO ERROR ;Go back to see if other want it

;If we got here we got a Request or Msg from ISR CMP DWORD PTR KbdMsgBuf1L, 0FFFFFFFFh ;Is it a msg from the KBD ISR? JNE KST02 ;No, jump to handle Request

;If we got here, ISR sent msg to us (something in the buffer) CALL XLateRawKBD ;Processes RAW buffer

CMP DWORD PTR dGlobalKey, 0 JNE KBDGlobal1 ;A global key came in PUSH KbdWaitExch PUSH OFFSET KbdMsgBuf1L CALL FWORD PTR _CheckMsg OR EAX, EAX JNZ KBDServiceTask KST02: ;If we got here we’ve got a Request from Main or Wait Exch MOV EBX, KbdMsgBuf1L ;pRqBlk into EBX MOV CX, [EBX+ServiceCode] ;Save in CX ;See if owner is waiting for a key ;Where to return Request or msg ;Check to see if another msg came in ;Yes if ZERO ;No Rq/Msg waiting, Go back to the top ;Fall thru to check request


Page 517 of 667

CMP CX, 0 JE KSTAbort CMP CX, 1 JE KSTRead CMP CX, 2 JE KSTReadGlobal CMP CX, 3 JE KSTCancelGlobal CMP CX, 4 JE KSTAssignKbd PUSH EBX PUSH ErcBadSvcCode CALL FWORD PTR _Respond JMP KBDServiceTask

;Job Abort Notify ; ;ReadKbd ; ;ReadKbdGlobal ; ;CancelGlobal ; ;AssignKBD ; ;HOMEY DON’T SERVICE THAT! ;Bad service code ;Go back to the top

;------------------------------------------------------KSTRead: MOV EAX, [EBX+RqOwnerJob] ;Who’s Request is it? CMP EAX, KbdOwner JE KSTRead00 ;This guy owns it! PUSH EBX ;Not the owner, so send to Hold Exch PUSH KbdHoldExch ; CALL FWORD PTR _MoveRequest JMP KBDServiceTask ;Go back to the top KSTRead00: CALL ReadKBFinal CMP EAX, 0 JE KSTRead02 MOV ESI, [EBX+pData1] CMP ESI, 0 JNE KSTRead01 PUSH EBX PUSH ErcNullPtr CALL FWORD PTR _Respond JMP KBDServiceTask KSTRead01: MOV [ESI], EAX PUSH EBX PUSH 0 CALL FWORD PTR _Respond JMP KBDServiceTask

;Get Code from Buf (Uses EAX, ESI) ;No Key in Final Buffer ;Go see if they asked to wait ;Ptr where to return key ;Is it null?? (Bad news if so) ;No, probably good ptr ;Yes, BAD PTR. Push pRqBlk ;Push error ;Go back to the top

;Give them the key code ;RqHandle ;NO Error ;Go back to the top

KSTRead02: CMP DWORD PTR [EBX+dData0], 0 ;Wait for key? 0 in dData0 = Don’t wait JNE KSTRead04 ;Yes PUSH EBX PUSH ErcNoKeyAvail ;Error Code (No key to give you) CALL FWORD PTR _Respond JMP KBDServiceTask ;Go back to the top KSTRead04: PUSH EBX PUSH KbdWaitExch

;They opted to wait for a key ;Send em to the wait exch


Page 518 of 667

CALL FWORD PTR _MoveRequest JMP KBDServiceTask ;Go back to the top ;-------------------------------------------------------KSTAbort: ;Respond to all requests we are holding for Job in dData0 ;with Erc with ErcOwnerAbort. Then respond to Abort ;request last. Requests can be at the HoldExch, the WaitExch, ;or the GlobalKeyExch. We must chack all 3! ;Save abort job for comparison MOV EAX, [EBX+dData0] MOV KbdAbortJob, EAX KSTAbort10: PUSH KbdWaitExch PUSH OFFSET KbdMsgBuf2L CALL FWORD PTR _CheckMsg OR EAX, EAX JNZ KSTAbort20 MOV EDX, KbdMsgBuf2L MOV EBX, [EDX+RqOwnerJob] CMP EBX, KbdAbortJob JE KSTAbort11 PUSH EDX PUSH KbdMainExch CALL FWORD PTR _MoveRequest JMP KSTAbort10 KSTAbort11: PUSH EDX PUSH ErcOwnerAbort CALL FWORD PTR _Respond JMP SHORT KSTAbort10 KSTAbort20: PUSH KbdHoldExch PUSH OFFSET KbdMsgBuf2L CALL FWORD PTR _CheckMsg OR EAX, EAX JNZ KSTAbort30 MOV EDX, KbdMsgBuf2L MOV EBX, [EDX+RqOwnerJob] CMP EBX, KbdAbortJob JE KSTAbort21 PUSH EDX PUSH KbdMainExch CALL FWORD PTR _MoveRequest JMP KSTAbort20 KSTAbort21: PUSH EDX PUSH ErcOwnerAbort CALL FWORD PTR _Respond JMP SHORT KSTAbort20 KSTAbort30: ;Get aborting job number ;this is aborting job ;Check the WaitExch ;See if he was "waiting" for a key ;Where to return Request ; ;Yes (someones waiting) if ZERO ;No more waiters ;pRq of holding job into EDX

;Go to respond with Erc ;Else move Request to MainKbd Exch ; to be reevaluated ;Go back to look for more waiters ; ;Respond to this request ;cause he’s dead

;Check HoldExch for dead job ;See if anyone is on hold ;Where to return Request ; ;Yes (someones holding) if ZERO ;No more holders ;pRq of holding job into EDX

;Go to respond with Erc ;Else move Request to MainKbd Exch ; to be reevaluated. It’s not him. ;Go back to look for more holders ; ;Respond to this request ;cause he’s dead ;Go back to look for more holders ;Check GlobalExch for dead job


Page 519 of 667


;See if anyone is at global ;Where to return Request ; ;Yes (someones holding) if ZERO ;No more holders ;pRq of holding job into EDX

;Go to respond with Erc ;Else move Request to MainKbd Exch ; to be reevaluated ;Go back to look for more globals ; ;Respond to this request ;cause he’s dead

KSTAbort40: ;Respond to original abort Req MOV EBX, KbdMsgBuf1L ;pRqBlk of original Abort Request PUSH EBX ; PUSH 0 ;Error Code (OK) CALL FWORD PTR _Respond JMP KBDServiceTask ;Go back to the top ;---------------------------------------------------------KSTReadGlobal: PUSH EBX ;They want a global key PUSH KbdGlobExch ;Send em to the Global exch CALL FWORD PTR _MoveRequest JMP KBDServiceTask ;Go back to the top ;----------------------------------------------------------;Assign a new owner for the keyboard. We must check to see ;if the new owner had any keyboard requests on hold. ;We do this by sending al the requests that were on hold ;back to the main exchange to be reevaluated. ;Then we respond to the original request. KSTAssignKBD: ;Change owner of Kbd MOV EAX, [EBX+dData0] CMP EAX, KbdOwner JNE KSTAssign01 PUSH EBX PUSH 0 CALL FWORD PTR _Respond JMP KBDServiceTask KSTAssign01: MOV KbdOwner, EAX KSTAssign02: PUSH KbdWaitExch PUSH OFFSET KbdMsgBuf2L CALL FWORD PTR _CheckMsg

;Get new owner ;New Owner! ;Same owner ;Error Code (OK) ;Go back to the top

;Set new owner ;Move all waiters to main exch ;See if anyone is "waiting" for a key ;Where to return Request ;


Page 520 of 667


;Yes (someones waiting) if ZERO ;No more waiters ;pRq into EDX ;Move Request to MainKbd Exch ; to be reevaluated ;Go back to look for more waiters ;Waiter have been move, Respond to Req ;See if anyone is on hold ;Where to return Request ; ;Yes if ZERO ;No more holders ;pRq into EDX ;Move Request to MainKbd Exch ; to be reevaluated ;Go back to look for more holders

KSTAssign04: ;Waiter have been move, Respond to Req MOV EBX, KbdMsgBuf1L ;pRqBlk of original Assign Request PUSH EBX ; PUSH 0 ;Error Code (OK) CALL FWORD PTR _Respond JMP KBDServiceTask ;Go back to the top ;------------------------------------------------------KSTCancelGlobal: ;Rifle thru Global Exch ;to those with the same MOV EAX, [EBX+dData0] MOV KbdCancelJob, EAX

and respond with ErcNoKeyAvail JobNum as dData0 ;Save Job that is cancelling global request ; ;Check GlobalExch for canceled job ;See if anyone is at global ;Where to return Request ; ;Yes (someones holding) if ZERO ;No more globals ;pRq of holding job into EDX

KSTCancel10: PUSH KbdGlobExch PUSH OFFSET KbdMsgBuf2L CALL FWORD PTR _CheckMsg OR EAX, EAX JNZ KSTCancel20 MOV EDX, KbdMsgBuf2L MOV EBX, [EDX+RqOwnerJob] CMP EBX, KbdCancelJob JE KSTCancel11 PUSH EDX PUSH KbdMainExch CALL FWORD PTR _MoveRequest JMP KSTCancel10 KSTCancel11: PUSH EDX PUSH ErcNoKeyAvail CALL FWORD PTR _Respond JMP SHORT KSTCancel10 KSTCancel20: MOV EBX, KbdMsgBuf1L

;Go to respond with Erc ;Else move Request to MainKbd Exch ; to be reevaluated ;Go back to look for more globals ; ;Respond to this request ;cause he cancelled it! ;Back to check for more ;Respond to original cancel Req ;pRqBlk of original Request


Page 521 of 667

PUSH EBX ; PUSH 0 ;Error Code (OK) CALL FWORD PTR _Respond JMP KBDServiceTask ;Go back to the top ;=============================================================

Keyboard Procedural Interface When programs don’t need the power of the request interface, such as multiple asynchronous requests, services can provide a blocking procedural call to make the application’s job easier. This code actually makes the request for the caller. He doesn’t have to know anything about the request interface to use this. See listing 25.6. Listing 25.6 - Code for Blocking Read Keyboard Call
;PUBLIC blocking call to read the keyboard. This uses the ;Default TSS exchange and the stack to make the request to ;the keyboard service for the caller. The request is a standard ;service code one (Wait On Key) request. ;If fWait is NON-ZERO, this will not return without a key unless ;a kernel/fatal error occurs. ; ;The call is fully reentrant (it has to be...). ; ; Procedural interface: ; ; ReadKbd(pKeyCodeRet, fWait): dError ; ; pKeyCodeRet is a pointer to a DWORD where the keycode is returned. ; [EBP+16] ; fWait is NON-ZERO to wait for a key. ; [EBP+12] ; ; Stack Variables: ; Hndl [EBP-4] ; PUBLIC __ReadKBD: PUSH EBP ; Save the Previous FramePtr MOV EBP,ESP ; Set up New FramePtr SUB ESP, 4 ; Two DWORD local vars MOV EAX, OFFSET KBDSvcName PUSH EAX PUSH 1 MOV ECX,pRunTSS MOV EBX,[ECX+TSS_Exch] PUSH EBX LEA EAX, [EBP-4] ;’KEYBOARD ’

;Service Code (Read Keyboard) ;Get TSS_Exch for our use ;Exchange (TSS Exch) ; ;Rq Handle (Local Var)


Page 522 of 667

PUSH EAX PUSH 0 MOV EAX, [EBP+16] PUSH EAX PUSH 4 PUSH 0 PUSH 0 XOR EAX, EAX CMP DWORD PTR [EBP+12], 0 JE ReadKbd1 MOV EAX, 1 ReadKbd1: PUSH PUSH PUSH CALL ;npSend ;Key Code return (Their Ptr) ;pData1 ;Size of key code ;pData2 ;cbData2

;Don’t wait for Key? ;No wait ;Set up to wait!

EAX 0 0 FWORD PTR _Request

;Wait value (dData0)

;make the Request

;The request is made. Now we call Wait! MOV ECX,pRunTSS MOV EBX,[ECX+TSS_Exch] PUSH EBX ADD ECX,TSS_Msg PUSH ECX CALL FWORD PTR _WaitMsg ;Get TSS_Exch for our use ; ;Pass exchange (for WaitMsg) ;Offset of TSS msg area ;Wait on it

;When we get here the caller should have the key code ;HOWEVER, we want to pass any errors back via EAX OR EAX, EAX JNZ ReadKbdEnd MOV ECX,pRunTSS ADD ECX,TSS_Msg MOV EBX, [ECX] MOV EAX, [ECX+4] ReadKbdEnd: MOV ESP,EBP POP EBP RETF 8 ;Was there a kernel error? ;YES.... bummer ;Get TSS_Msg area so we can get error ;Offset of TSS msg area ;pRqBlk (lets look!!) ;Service error in second DWord

; ; ; Rtn to Caller & Remove Params from stack

Debugger Keyboard The debugger requires a special function to read the keyboard. This allows the debugger to completely bypass the keyboard system service so it doesn’t have to go through the kernel for anything. This reads keystrokes directly from the final coded buffer. See listing 25.7. Listing 25.7 - Debugger Read Keyboard Code


Page 523 of 667

;========================================================== ;Special Call for Debugger so it doesn’t have to pass thru ;the kernel Request mechanism for a keystroke. ;It acts like ReadKbd with fWait set to true. ;It sucks keys directly from the Final Keyboard buffer. ; ; Procedural interface: ; ; ReadDbgKbd(pKeyCodeRet) ; ; pKeyCodeRet is a pointer to a DWORD where the keycode is returned. ; [EBP+8] ; PUBLIC ReadDbgKBD: PUSH EBP ; Save the Previous FramePtr MOV EBP,ESP ; Set up New FramePtr RDKB0: CALL ReadKBFinal OR EAX, EAX JNZ RDKB1 PUSH 2 CALL FWORD PTR _Sleep JMP RDKB0 RDKB1: MOV ESI, [EBP+8] MOV [ESI], EAX XOR EAX, EAX MOV ESP,EBP POP EBP RETN 4 ;Ptr where to return key ;Get Code from Buf (Uses EAX, ESI) ;Got a key?? (non zero) ;No. Loop back again ;Sleep for 20 ms ;Check again

; ; ; Rtn to Caller & Remove Params from stack


Keyboard Hardware Set Up InitKBD is called very early in the operating system initialization code to set up the keyboard hardware. The 8042 provides hardware interrupts, but we don’t have to call SetIRQVector() because the keyboard interrupt routines address was known at build time. See listing 25.8. Listing 25.8 - Code to Initialize the Keyboard Hardware
;This sets the Keyboard Scan Set to #2 with 8042 interpretation ON ; PUBLIC InitKBD: PUSH 1 ;KBD IRQ CALL FWORD PTR _MaskIRQ


Page 524 of 667


;Wait for Input Buffer to Empty ;Set ALL keys typematic/make/break ;Send Command to KBD (not 8042) ;Eat response

;Wait for Input Buffer to Empty ;Set Scan code set ;Send Command to KBD (not 8042) ;Eat response


;Wait for Input Buffer to Empty ;Scan set 2 ;Send Command ;Eat response

;Wait for Input Buffer to Empty ;Set up to write 8042 command byte ;Send Command ;Wait for Input Buffer to Empty ;Enable IBM Xlate ;Send Command

PUSH 1 ;KBD IRQ CALL FWORD PTR _UnMaskIRQ CALL SetKbdLEDs RETN ;=================================================================

Keyboard Service Initialization InitKBDService is called from the monitor to start the keyboard service. The service is a completely separate task that runs in a loop servicing requests. This will only be called once and it is called after the keyboard hardware and the keyboard ISR are functional. See listing 25.9. Listing 25.9 - Keyboard Service Initialization
PUBLIC _InitKBDService: ;All initial requests and messages from the ISR come to ;this exchange MOV EAX, OFFSET KbdMainExch ;Alloc Main Kbd exch for service PUSH EAX CALL FWORD PTR _AllocExch


Page 525 of 667


;Check for error on AllocExch ;YUP, we got bad problems

;Requests for ReadkeyBoard (ScvCode #1) that are from jobs ;that do NOT currently own the keyboard get sent here using ;MoveRequest. MOV EAX, OFFSET KbdHoldExch ;Alloc Hold Kbd exch for Kbd service PUSH EAX CALL FWORD PTR _AllocExch OR EAX, EAX ;Check for error on AllocExch JNZ InitKBDSvcEnd ;YUP, we got bad problems ;Requests for ReadkeyGlobal (SvcCode #3) wait here until we ;get a global key from the keyboard. ; MOV EAX, OFFSET KbdGlobExch ;Alloc Global Wait exch for Kbd service PUSH EAX CALL FWORD PTR _AllocExch OR EAX, EAX ;Check for error on AllocExch JNZ InitKBDSvcEnd ;YUP, we got bad problems ;Requests for ReadkeyBoard (ScvCode #1) that are from job ;that currently owns the keyboard waits here if it wants ;to wait for a key. MOV EAX, OFFSET KbdWaitExch ;Alloc Hold Kbd exch for Kbd service PUSH EAX CALL FWORD PTR _AllocExch OR EAX, EAX ;Check for error on AllocExch JNZ InitKBDSvcEnd ;YUP, we got bad problems ;Used to "rifle" thru RqBlks waiting at an exchange. MOV EAX, OFFSET KbdTempExch ;Alloc Tmp exch for Kbd service PUSH EAX CALL FWORD PTR _AllocExch OR EAX, EAX ;Check for error on AllocExch JNZ InitKBDSvcEnd ;YUP, we got bad problems ;Spawn the Keyboard Service task MOV EAX, OFFSET KBDServiceTask PUSH EAX PUSH 7 ;Priority PUSH 0 ;fDebug MOV EAX, OFFSET KbdSvcStackTop PUSH EAX PUSH 1 ;OS Job task CALL FWORD PTR _SpawnTask OR EAX, EAX ;Check for error on AllocExch JNZ InitKBDSvcEnd ;YUP, we got bad problems MOV EAX, OFFSET KBDSvcName PUSH EAX PUSH KbdMainExch CALL FWORD PTR _RegisterSvc


Page 526 of 667

InitKBDSvcEnd: MOV BYTE PTR fKBDInitDone, 1 ;We’re UP! RETN ; ;========================================================================= ;This tells the 8042 Controller to Disable the Keyboard device. ;========================================================================= KbdDisable: PUSH EAX CALL InBuffEmpty ;Wait for Input Buffer to Empty MOV AL,0ADh ;Set Command to "Write the 8042 Command Byte" OUT COMMANDPORT,AL ;Send Command CALL InBuffEmpty ;Wait for Input Buffer to Empty POP EAX RETN

Hardware Helpers The rest of the routines are used for various hardware functions, such as reading or writing the 8042 or keyboard data ports, as well as setting the keyboard LED’s to the proper state. See listing 25.10. Listing 25.10 - Low-level Keyboard Control Code

;========================================================================= ; This tells the 8042 Controller to Enable the Keyboard Device. ;========================================================================= KbdEnable: CALL InBuffEmpty ;Wait for Input Buffer to Empty MOV AL,0AEh ;Set Command to "Write the 8042 Command Byte" OUT COMMANDPORT,AL ;Send Command CALL InBuffEmpty ;Wait for Input Buffer to Empty RETN ;========================================================================= ; Waits until the 8042 Input Buffer is EMPTY ;========================================================================= InBuffEmpty: PUSH EAX PUSH ECX MOV ECX,2FFFFh IBE: JMP IBE1 IBE1: JMP IBE2 IBE2: IN AL,STATUSPORT TEST AL,INPUTBUFFFULL

;check 128k times

;Read Status Byte into AL ;Test The Input Buffer Full Bit


Page 527 of 667

LOOPNZ IBE POP ECX POP EAX RETN ;========================================================================= ; Waits until the 8042 Output Buffer is FULL so we can read it ;========================================================================= ; Before calling this makes sure that the Keyboard interrupts have been ; masked so the keyboard interrupt doesn’t eat the byte you’re ; looking for!! ; OutBuffFull: PUSH EAX PUSH ECX MOV ECX,2FFFFh OBF: JMP OBF1: OBF1: JMP OBF2: OBF2: IN AL,STATUSPORT ;Read Status Byte into AL TEST AL,OUTPUTBUFFFULL ;Test The Output Buffer Full Bit LOOPZ OBF POP ECX POP EAX RETN ;========================================================================= ; This sets the indicators on the keyboard based on data in KbdState ;========================================================================= SetKbdLEDs: PUSH EAX PUSH 1 ;KBD IRQ CALL FWORD PTR _MaskIRQ CALL InBuffEmpty MOV AL,0EDh OUT DATAPORT,AL CALL OutBuffFull IN AL, DATAPORT CALL InBuffEmpty MOV AL,KbdLock AND AL,00000111b OUT DATAPORT,AL CALL OutBuffFull IN AL, DATAPORT ;Wait for Input Buffer to Empty ;Set/Reset Status Indicators ;Send KBD Command ;Eat response

;Wait for Input Buffer to Empty ;Get Current Lock Status Byte ;Mask all but low order 3 bits ;Send KBD Command ;Eat response



Page 528 of 667

Chapter 26, Video Code
The code presented in this chapter implements a VGA text-based video driver. This driver does not use the standard MMURTL device driver interface. The reason it doesn’t use the interface is that it was one of the first things written for the operating system. All of my earliest testing depended on being able to see results from test programs.

Virtual Video Concept
In a multitasking operating system that supports text-based video, more than one application at a time may need to write to or read data from it's video screen buffer. This would not be possible if all of the programs running wrote to the real video screen buffer. In VGA Text mode, which is the default setup in BIOS on machines that have VGA monitors, there are actually eight buffers available. You could use those and switch between them, but doing so would limit you to eight active programs. To allow as many programs as possible, you allocate a page of system memory (4096 bytes) for each job (program) as a virtual video buffer. This solved another problem for the future. If I want to add a graphical user interface, text-based programs will still run and can be displayed in a window directly from each job's buffer if needed. The video status for each job is kept in its job control block (JCB). The status includes screen coordinates, video screen size, video mode, a pointer to the job's virtual buffer, and also a pointer to the current active buffer. When a new task is assigned to the video screen, the contents of it virtual buffer are copied to the real screen buffer, and the pointer to the current active buffer is changed to point to the real video screen buffer. The reverse occurs for the old job (the one that was using the real screen). The real buffer is copied into it's virtual buffers, and its active pointer is changed back to it's own buffer.

VGA Text Video
Many books have been written about how to control standard VGA video hardware, and it seems to be fairly standardized as well. I am not going to go into great detail on how it works for two reasons. First, I'm not a video "guru," and second, I let the BIOS code in the machine set up the standard VGA text mode. You may want to do this differently in your system. If so, the shelves of your local bookstore are filled with brain dumps from people that have much more knowledge than I. You'll find implementing this code was really a simplicity issue with me.


Page 529 of 667

DATA . with the exception of the those calls that actually manipulate video hardware. I only use one.1 presents the initial items in the source file. These include constants for the video hardware.JCB that currently owns video . BEGIN INTERNAL CODE FOR VIDEO . assembler was all I could work with.Register for lo byte of Cursor address CRTC0C DB 0 .Register for lo byte of Video address CRTCAddLo EQU 0Dh .CODE . These registers are defined in the data section of the code listing later in this chapter. You can only work “blind” for so long.CRT Reg 0C HiByte address value CRTC0D DB 0 .Video Equates and Types .Video constants and data .1.Default to monitor (Job 1) . Only one program on the system should act as a video and keyboard manager (such as the MMURTL Monitor). .CRT Reg 0D LoByte address value PUBLIC ddVidOwner DD 1 .I control the registers that deal with cursor positioning and which video buffer I used. When I first started testing MMURTL.Register for lo byte of Cursor address CRTCCurLo EQU 0Fh .================================================================== . Listing 26.End of Data & Equates . EXTRN GetpJCB NEAR EXTRN GetpCrntJCB NEAR MMURTL V1.================================================================== . The Video Code You will notice that all of this code is in assembler (as is most of MMURTL).INC .0 Page 530 of 667 . CRTCPort1 EQU 03D4h . The first item in the source file (which is the way all of the MMURTL source files are set up) is the data declarations and INCLUDE files which define certain constant values.Index port for CRTC CRTCPort2 EQU 03D5h . and I needed to see things.INCLUDE MOSEDF.Register for lo byte of Video address CRTCCurHi EQU 0Eh .INCLUDE JOB. . A very important point to bring out is that this code is completely re-entrant.Data port for CRTC CRTCAddHi EQU 0Ch .INC . The code in Listing 26.

0 Page 531 of 667 . BEGIN PUBLIC CODE FOR VIDEO . RETN CRTCAddLo CRTCPort1 AL CRTC0D CRTCPort2 AL . OUT DX. The internal debugger is the only code that will use this call and not move the keyboard at the same time.Index Port . CRTC0C MOV DX. Listing 26.===================================================================== .Index of lo byte . CRTCPort1 OUT DX.2. This is only called once when the operating system is initialized. See listing 26.EXTRN ReadDbgKbd NEAR EXTRN GetCrntJobNum NEAR The InitVideo() makes video screen 0 (zero) the default screen for standard VGA hardware. MOV AL.hi byte value to send . the application should be the same for both. MOV DX. This makes the VGA text base address 0B8000h.Index of hi byte .Data Port .lo byte value to send . AL MOV AL. The parameter (ddJobNum) is the new job to get the active screen (the one to be displayed).Code to Set the Video Owner. EAX returns 0.2 . It certainly wasn’t easy.===================================================================== SetVidOwner(ddJobNum) selects the screen that you see. See listing 26.3.Data Port . Listing 26.Code to Initialize Video PUBLIC InitVideo: MOV AL.Index Port . MOV DX.ESP . AL MOV AL. This call is used by the monitor in conjunction with the SetKbdOwner call to change which application gets the keystrokes and video. ddJobVidCV EQU DWORD PTR [EBP+12] PUBLIC __SetVidOwner: PUSH EBP MOV EBP. Don’t even ask how I debugged the debugger keyboard code. This is because the debugger has it’s own keyboard code so I could debug the real keyboard code.3 . if the call was successful. CRTCPort2 OUT DX. CRTCAddHi MOV DX. MMURTL V1. As always. This address is a constant in one of the INCLUDE files called VGATextBase. . Data ports on standard VGA video hardware are accessed as an array with an index. OUT DX.

Yes MOV EAX.Destination PUSH 4000 . VGATextBase MOV [EAX+pVidMem]. ddVidOwner CALL GetpJCB MOV ECX. EBX .MOV EAX.Got valid video memory??? JNE ChgVid02 .Make new pVidMem real video screen for new owner MOV EAX.Make pVidMem same as pVirtVid for CURRENT OWNER MOV EAX. [EAX+pVirtVid] .Current Y .Already own it? JNE ChgVid01 .Set Cursor position MOV EAX. ddVidOwner . [EAX+pVirtVid] MOV [EAX+pVidMem]. . CMP EAX.Leaves ptr to new vid JCB in EAX MOV EBX. ddJobVidCV .Source PUSH EBX PUSH VGATextBase .Source MOV EBX. ddVidOwner CALL GetpJCB MOV EBX.Do it! .Copy in Data from new pVirtVid MOV EAX.Do it! .Save data on screen to CURRENT job’s pVirtVid MOV EAX. ddVidOwner CALL GetpJCB PUSH VGATextBase .Size of video CALL FWORD PTR _CopyData .NO! Give em an error! JMP ChgVidDone ChgVid02: . [EBX+CrntY] CALL HardXY .No XOR EAX.Yes JMP ChgVidDone ChgVid01: CALL GetpJCB . ddJobVidCV MOV ddVidOwner.Size of video CALL FWORD PTR _CopyData . ErcVidNum . EAX .Get current X for new screen . ddVidOwner CALL GetpJCB MOV EBX. EAX MOV EBX. EBX . EAX .Set it up MMURTL V1. [ECX+CrntX] MOV EAX. ddVidOwner CALL GetpJCB . [EAX+pVirtVid] .0 Page 532 of 667 .Leaves ptr to new vid JCB in EAX CMP DWORD PTR [EAX+pVidMem] .Destination PUSH EBX PUSH 4000 .Update current video owner to NEW owner MOV EAX.0 .

Code to get the Normal Video Attribute pdNormVidRet EQU DWORD PTR [EBP+12] PUBLIC __GetNormVid: PUSH EBP MOV EBP. The ClrScr(). pdNormVidRet MOV [ESI]. MMURTL V1. MOV EBP. There’s no mistake. . EAX ChgVidDone: MOV ESP.Code to set Normal Video Attribute. It is saved in the JCB for each job.6. EditLine(). The EAX register returns zero for no error. Listing 26. XOR EAX.No Error . and you’ll notice that’s all it can return. The SetNormVid(dCharAttr) selects the normal background attribute and fill character used by ClrScr and ScrollVid on the screen. EAX POP EBP . . MOV [EAX+NormAttr]. CALL GetpCrntJCB .4 . The parameter dCharAttr (listed as ddNormVid in the EQU statement in Listing 26. EBX .XOR EAX. .pJCB -> EAX MOV EBX. BL XOR EAX. See listing 26. ddNormVid . . This call expects a pointer to a byte where this value is returned. If you noticed that you only return a byte although you passed in a word to SetNormVid(). you are correct. See listing 26. ddNormVid EQU DWORD PTR [EBP+12] PUBLIC __SetNormVid: PUSH EBP . Listing 26. EAX POP EBP RETF 4 .4) is the character and attribute values used in standard video operation on the current screen. This will get the value that is set with the SetNormVid() call.4. . [EAX+NormAttr] MOV ESI.pJCB -> EAX .0 Page 533 of 667 .5. The upper 3 bytes really aren't used (yet). The GetVidOwner(pdJobNumRet) returns the job number that is currently assigned the active video screen. .ESP . RETF 4 The GetNormVid(pVidRet) call returns the value the normal screen attribute.ESP CALL GetpCrntJCB MOV EBX.EBP POP EBP RETF 4 .5 . and ScrollVid() calls use this value. See Listing 26.

EAX MOV EDI. . .Attr .Code to Get the Current Video Owner pVidNumRet EQU DWORD PTR [EBP+12] . no error obviously MOV ESP. Listing 26. XOR EAX.Fill Char & Attr . See Listing 26. 16 MOV AX.0 Page 534 of 667 . EAX . RETF 4 The ClrScr() call clears the screen for the executing job. PUBLIC __GetVidOwner: PUSH EBP .EBP . ddTextOut is the number of chars of text in the buffer you are pointing to. EAX .EBP POP EBP RETF . the real video buffers get wiped.Leaves ptr to current JCB in EAX .7 .[EBX+pVidMem] MOV EAX. POP EBP . and ddAttrib is the attribute or color you want for all of the characters.Listing 26. [EBX+NormAttr] SHL EAX. pVidNumRet . MMURTL V1.ESP . . . The beginning X and Y coordinates are those found in the caller’s JCB.6 .ESP CALL GetpCrntJCB MOV EBX. only the caller’s virtual video buffer gets cleared. MOV EAX. If this is the active screen.7. The TTYOut(pTextOut. MOV ESI. 20h MOV DX.Code to Clear the Screen PUBLIC __ClrScr: PUSH EBP MOV EBP. If not. .EDI points to his video memory . MOV [ESI]. 8 MOV AL.0400h CLD REP STOSD PUSH 0 PUSH 0 CALL FWORD PTR _SetXY MOV ESP. ddTextOut. DX MOV ECX.Erc in EAX on Return . This uses the value that you set in SetNormVid() for the color attribute and a space for the character. ddAttrib) function translates the text buffer pointed to with pTextOut into a stream of characters placed in the callers video buffer. ddVidOwner . MOV EBP. AX SHL EAX.

0 TTY06 DataByte. CR? .0Dh TTY03 EAX.Line Feed. .ESP CALL GetpCrntJCB MOV EBX.pTextOut CMP JNE INC CMP JB JMP TTY02: CMP JNE MOV JMP TTY03: CMP DataByte. EAX has CrntX (col) . . EDX has CrntY (line) . Backspace will have no effect. If already at column 0.Code for TTY Stream Output to Screen.0 Page 535 of 667 . The cursor (next active character placement) will be on the following line at column 0. EAX JZ TTYDone TTY00: MOV EAX. 0D . If you want to go beyond these. 08 . LF? .Leaves ptr to current JCB in EAX .[EBX+CrntY] MOV ECX. the entire screen will be scrolled up one line.[EBX+CrntX] MOV EDX.The following characters in the stream are interpreted as follows (listed in hex): 0A . sTextOut OR EAX.Carriage Return. If this line is below the bottom of the screen.make sure count isn’t null . pTextOut sTextOut dAttrText DataByte EQU DWORD PTR [EBP+20] EQU DWORD PTR [EBP+16] EQU DWORD PTR [EBP+12] EQU BYTE PTR [ECX] PUBLIC __TTYOut: PUSH EBP MOV EBP. Equal to or past the bottom? . Yes. BackSpace? MMURTL V1. Listing 26. The cursor will be moved one column to the left. No .08h JNE TTY04 CMP EAX. you may consider writing an ANSI device driver instead of modifying this code. goto scroll .backspace. 07F (Delete). The backspace is nondestructive (no character values are changed). I will eventually add 07h (Bell).0Ah TTY02 EDX EDX.8 . the bottom line will be blanked. EAX MOV EAX. and 0Ch (Form Feed). The Cursor will be moved to column zero on the current line.[EBX+nLines] TTY06 TTYScr . and the cursor will be placed on the last line in the first column.0 DataByte.

0 JNE TTYDone DEC sTextOut JZ TTYDone INC pTextOut . back up one line . [EBX+CrntY] INC EAX .Y (Param 2) PUSH ECX .restore ptr to VCB CMP EAX.JE TTY04 DEC EAX JMP TTY06 TTY04: PUSH EBX .Fall thru to TTY06: PUSH EBX PUSH EAX PUSH EDX CALL FWORD PTR _SetXY POP EBX CMP EAX. 0 JNE TTYDone MOV EAX. No .save ptr to pJCB MMURTL V1. past the bottom? JNE TTY06 . POP EBX .goto 06 else fall thru TTYScr: DEC EDX PUSH EAX PUSH EBX PUSH ECX PUSH EDX .restore registers POP ECX POP EBX POP EAX .Make cursor follow MOV EAX.[EBX+nCols] JNE TTY06 .dAttrText .Next column CMP EAX.0 INC EDX CMP EDX.Param 5 CALL FWORD PTR _PutVidChars . PUSH ECX .0 Page 536 of 667 .Save registers (scroll eats em) PUSH 0 PUSH 0 PUSH 80 PUSH 25 PUSH 1 .Save pointer to VCB PUSH EAX .fUP (non zero) CALL FWORD PTR _ScrollVid . [EBX+CrntX] MOV EDX.[EBX+nLines] .Ignore error POP EDX .Restore ptr to VCB .Param 4 (nchars) MOV ECX.X (Param 1) PUSH EDX .pointer to text char (Param3) PUSH 1 .

JMP TTY00 TTYDone: MOV ESP.EBP POP EBP RETF 12 . Go back for next char .EAX MOV EAX.Pass the char value . It is independent of the current video "stream.oADDy MOV ECX.sADDColor CLD pcAMore: INC EDI STOSB LOOP pcAMore XOR EAX.0A0h MUL ECX ADD EAX." This is a fill function and does not set multiple independent attributes.No Error! .1 MOV EAX.Param . ErcVidParam JMP PcADone PutAttrs00: MOV ECX.9. [EBX+pVidMem] MOV EBX.0F9Eh JBE PutAttrs00 MOV EAX. dAttr) call sets screen colors (attributes) for the positions and number of characters specified without affecting the current TTY coordinates or the character data. sChars.Times nColumns .Move Color in .Times 160 (char/attrs per line) . .Param . .9 Listing 26.oADDx SHL EBX. .Param . .x Position .ESP CALL GetpCrntJCB MOV EBX.sADDChars OR ECX.Last legal posn on screen .point to this VCBs video memory . ddLine. ECX JZ PcADone ADD EDI.y Position .Leaves ptr to current JCB in EAX .Code to Screen Video Attributes oADDX oADDY sADDChars sADDColor EQU EQU EQU EQU DWORD DWORD DWORD DWORD PTR PTR PTR PTR [EBP+24] [EBP+20] [EBP+16] [EBP+12] . EAX MOV EDI. See Listing 26. EAX pcADone: MOV ESP.EBP POP EBP RETF 16 . The PutVidAttrs(ddCol.Times 2 .EBX CMP EAX.0 Page 537 of 667 .Param 1 2 3 4 COLUMN LINE sChars Attr PUBLIC __PutVidAttrs: PUSH EBP MOV EBP. MMURTL V1.

.oDDy MOV ECX.g.EAX MOV EAX.pChars. The starting position in screen memory is (Line * 80 + (Column*2)).0F9Eh JBE PutChars00 MOV EAX. EAX MOV EDI. See listing 26.10 .Times 2 .Move Color in .Param ." The parameters include the position (column and line). Listing 26. [EBX+pVidMem] MOV EBX.The PutVidChars(ddCol.Param .10.0 Page 538 of 667 . and the attribute. You alternate between characters and attributes as we move through screen memory because this is how the video hardware interprets its buffers for display (e.Times nColumns . .).Param 1 2 3 4 5 COLUMN LINE pChars sChars Attr PUBLIC __PutVidChars: PUSH EBP MOV EBP. EAX pcDone: MOV ESP.0A0h MUL ECX ADD EAX. ECX JZ PcDone MOV ESI.Last legal posn on screen .EBX CMP EAX.pDDChars ADD EDI.EBP POP EBP RETF 20 . then color.point to this VCBs video memory .Leaves ptr to current JCB in EAX .oDDx SHL EBX. then character.sDDChars OR ECX.No Error! .sChars. then color etc.ESP CALL GetpCrntJCB MOV EBX.1 MOV EAX.Param . .Param . ErcVidParam JMP PcDone PutChars00: MOV ECX.Move Char in . MMURTL V1.Times 160 .sDDColor CLD pcMore: MOVSB STOSB LOOP pcMore XOR EAX. a pointer to the characters to be placed.Code to Place Video chars Anywhere on the Screen oDDX oDDY pDDChars sDDChars sDDColor EQU EQU EQU EQU EQU DWORD DWORD DWORD DWORD DWORD PTR PTR PTR PTR PTR [EBP+28] [EBP+24] [EBP+20] [EBP+16] [EBP+12] . .ddAttrib) call places characters on the screen without affecting the current TTY coordinates or the TTY data. put character.ddLine. the count of characters. It is independent of the current video "stream.

AL INC EDI MOV ESI. .No Error! .oGDDy MOV ECX. Listing 26.Param 1 2 3 4 COLUMN LINE pCharRet pAttrRet PUBLIC __GetVidChar: PUSH EBP MOV EBP. The line left blank is filled with NormAttr from JCB. This is because the pointer in the JCB is set to either his virtual screen or the real one. oGDDX oGDDY pGDDCRet pGDDARet EQU EQU EQU EQU DWORD DWORD DWORD DWORD PTR PTR PTR PTR [EBP+24] [EBP+20] [EBP+16] [EBP+12] .nddLines. .Move to Attr .pGDDARet MOV AL.EBX CMP EAX.ddLine. ddULCol and ddULLine describe the upper-left corner of the area to scroll.11.Times 2 .1 MOV EAX. If you want to scroll the entire screen up one line. .oGDDx SHL EBX. If ddfUp is not zero the scroll will be up.25. See Listing 26. 0.point to this VCBs video memory .1).Param .11.pCharRet. ddfUp) function scrolls the described square area on the screen either up or down one line. [EBX+pVidMem] MOV EBX. and this is the pointer you use to acquire the attribute.nddCols.Times 160 . the parameters would be ScrollVid(VidNum.ESP CALL GetpCrntJCB MOV EBX. EAX pcGDone: MOV ESP. [EDI] MOV [ESI].EAX MOV ESI. It doesn’t matter if the caller is the active video screen or not.0F9Eh JBE GetChar00 MOV EAX. the top line is lost (not really scrolled).Give them the Attr .0 Page 539 of 667 .0. [EDI] MOV [ESI].pAttrRet) call returns the current character and attribute from the screen coordinates you specify. The ScrollVid(ddULCol.EDI now points to char .Give them the char . AL XOR EAX. ErcVidParam JMP PcGDone GetChar00: ADD EDI. EAX MOV EDI.Last legal posn on screen .Leaves ptr to current JCB in EAX .EBP POP EBP RETF 20 . nddCols and nddLines are the size of the area.The GetVidChar(ddCol.80. and the bottom line would be MMURTL V1. In this case.pGDDCRet MOV AL.Code to Get a Character and Attribute.Times nColumns .0A0h MUL ECX ADD EAX.Param .Param .ddULline.

1).Leaves ptr to current JCB in EAX .24.ESI is 1st source line ESI .Yes.How many lines to move .times nBytes per line [EBX+pVidMem] ..Scroll UP? .Param . EDI.add again for attributes EDI . ECX EDI. 24 JA svErcExit ADD less than window height .EDI points to video memory 0. ECX.Move a line (of WORDS!) . In fact.Param . EAX oULY nddLines 160 .0 Page 540 of 667 . EAX.blanked. . EDI. nddLines CMP EAX.0 EBX . 160 . EDI ESI.Param .ESP CALL GetpCrntJCB MOV EBX. EAX.EDI is ptr to 1st dest line oULX . if you specified ScrollVid (0.Param 1 2 3 4 5 COLUMN LINE pChars sChars Attr PUBLIC __ScrollVid: PUSH EBP MOV EBP. oULY CMP EAX. 0 JNE svUP0 . ESI.Save pJCB & use in EBX .80. EBX ESI.Scroll DOWN begins MOV ADD MOV MUL MOV MOV ADD ADD ADD MOV SUB MOV MOV DEC svDOWN1: MOV REP MOV MOV SUB ECX.1. EAX MOV EAX.Param . you would get the same results.Save pJCB EAX . EDX. Listing 26. . ESI.First line ..Last line . See Listing 26. 25 JA svErcExit CMP ddfUP.How many WORDS per line to move .12. nddCols CMP EAX. Scroll UP! .Save in EBX for reload nDDLines . EBX. oULX CMP EAX. 160 EAX.offset into line oULX .Reload Dest to next line MMURTL V1.12. nddCols MOVSW EDI.Code to Scroll an Area of the Screen oULX oULY nddCols nddLines ddfUP EQU EQU EQU EQU EQU DWORD DWORD DWORD DWORD DWORD PTR PTR PTR PTR PTR [EBP+28] [EBP+24] [EBP+20] [EBP+16] [EBP+12] . 80 JA svErcExit MOV EAX. EDI. 79 JA svErcExit ADD EAX.

ECX EDI.0 Page 541 of 667 ..times nBytes per line [EBX+pVidMem] .Put the last line into EDI ECX. 8 MOV AL. HardXY() is called to ensure the hardware follows the new setting.Save in EBX for reload nDDLines . 20h . EBX . EAX svDone . nddCols STOSW EAX. EBX. EAX oULY 160 .0 EBX . EDI.No. 8 AL. [EDX+NormAttr] . 160 MOV ECX..EDI points to video memory 0.Save again EAX svDOWN1 EAX. EDI.Error exits will jump here MOV EAX.ESI is 1st source line ESI . 160 MOV EBX. When a video call that repositions the cursor is made and the caller owns the real video screen. RETF 20 The HardXY() call is used to support positioning the cursor in hardware. EAX. EAX .Put the last line into EDI SUB EDI. POP EBP .Save pJCB EAX .MOV DEC JNZ MOV SHL MOV MOV MOV CLD REP XOR JMP svUP0: MOV MOV MUL MOV MOV ADD ADD ADD MOV ADD MOV MOV DEC svUP1: EBX. This supports SetXY() and SetVidOwner().How many WORDS per line to move REP MOVSW . ESI.add again for attributes EDI . EDI ADD ESI. ESI. EBX .Normal video attributes!!! SHL EAX. [EDX+NormAttr] . ErcVidParam svDone: MOV ESP.Space EDI. MMURTL V1. EDX. EBX .offset into line oULX . EDI. ESI . 20h .Save again DEC EAX JNZ svUP1 MOV EAX.No error JMP svDone svErcExit: .Move a line (of WORDS!) MOV EDI. nddCols . 160 .Reload Dest to next line MOV ESI. ECX.Normal video attributes!!! EAX.How many lines to move .First line .No error .two less than window height MOV ECX. ESI .EBP .Space MOV EDI. nddCols CLD REP STOSW XOR EAX. scroll down begins EAX.EDI is ptr to 1st dest line oULX .

NewY [EBX+CrntX].14. Line plus column . See listing 26. HardXY: MOV ECX. Send High byte out The SetXY(ddNewX. Send Low byte out .CRTCPort2 OUT DX.80 MUL ECX ADD EAX.ESP CALL GetpCrntJCB MOV EBX.EDX .AL RETN . If the caller also happens to own the real video screen buffer (being displayed).CRTCPort1 MOV AL. Index for High byte .13. Index register . See Listing 26. Line * 80 .AL SHR EAX. Listing 26. Index 0Fh for low byte . Listing 26. Column .13.ECX [EBX+CrntY]. and EBX is set with the new X position before the call is made.Leaves ptr to current JCB in EAX . This saves it in the VCB .AL POP EAX MOV DX.Support Code to Set Hardware Cursor Position.Leaves ptr to current JCB in EAX CALL GetCrntJobNum CMP EAX. .The parameters to this call are via registers.14.CRTCCurLo OUT DX. Line .NewX EDX.CRTCCurHi OUT DX. . then the hardware cursor is also repositioned by calling the internal support call HardXY() from listing 26.EBX MOV DX.CRTCPort1 PUSH EAX MOV AL.13 .Code to Set Cursor Position NewX NewY EQU DWORD PTR [EBP+16] EQU DWORD PTR [EBP+12] PUBLIC __SetXY: PUSH EBP MOV EBP. EAX MOV MOV MOV MOV ECX. Data register . EAX is set with the new Y position.AL POP EAX MOV DX.0 Page 542 of 667 .08 PUSH EAX MOV DX. .CRTCPort2 OUT DX. shift hi byte into AL . thus no stack parameters are used. ddNewY) function positions the VGA cursor (text mode) to the X and Y position specified in the parameters ddNewX and ddNewY. ddVidOwner MMURTL V1.

ESP CALL GetpCrntJCB MOV EBX.If not on Active screen. pddYRet) returns the X and Y cursor position from the caller’s job control block.Setup to call HardXY .EBP POP EBP RETF 8 EAX. dEditAttr) function reads a line of text from the keyboard and puts it into the string pointed to by pStr.pYret [ESI]. The EditLine(pStr. pdLenRet. Column . See listing 26. skip it .Leaves ptr to current JCB in EAX .EAX MOV ESP. where you want the exit character returned (pbExitChar). The positions in the JCB are updated with each character placement.15. .EBP POP EBP RETF 8 .EAX . Listing 26. EAX EAX.[EBX+CrntY] ESI. so this will be accurate even if the caller is not currently displayed on the real video screen. pbExitChar.No Error .0 Page 543 of 667 . If pStr points to a valid string (dCrntLen > 0) then the string is displayed.NewY MOV EBX. dMaxLen. . but should be a library function instead. . and finally the attribute for the text you are editing (dEditAttr). EditLine() probably shouldn’t even be included with the video code. and it came in so handy as part of the operating system code I just left it where it was.15.Code to Return Current Cursor Position pXret pYret EQU DWORD PTR [EBP+16] EQU DWORD PTR [EBP+12] PUBLIC __GetXY: PUSH EBP MOV EBP.pXret [ESI]. MMURTL V1. EAX EAX.JNE GotoXYDone MOV EAX.NewX CALL HardXY GotoXYDone: XOR EAX. . EAX MOV MOV MOV MOV MOV MOV XOR QXYDone: MOV ESP.[EBX+CrntX] ESI. You also specify the maximum length the string can be (dMaxLen). The GetXY(pddXRet. Line . The editing of this string is done at the current X and Y positions. dCrntLen. I added this call while doing a lot of testing.

EditX ADD EAX. ddSzMax JA BadEdit . 80 JA BadEdit CMP ddSzMax.16 . ddSzMax . The following keys are recognized and handled inside. EditY PUSH EAX CALL FWORD PTR _GetXY CMP EAX.Code to Edit a Line on the Screen pEdString ddSzCrnt ddSzMax pddSzRet pExitKeyRet dEditAttr EQU EQU EQU EQU EQU EQU DWORD DWORD DWORD DWORD DWORD DWORD PTR PTR PTR PTR PTR PTR [EBP+32] [EBP+28] [EBP+24] [EBP+20] [EBP+16] [EBP+12] . Listing 26. 08 . 80 JA BadEdit MOV EAX. a destructive backspace.make PosnX end of string MMURTL V1.CrntX is the cursor postion PosnX EditX EditY KeyCode EQU EQU EQU EQU DWORD DWORD DWORD DWORD PTR PTR PTR PTR [EBP-04] [EBP-08] [EBP-12] [EBP-16] PUBLIC __EditLine: PUSH EBP MOV EBP. EditX . 16 CMP ddSzCrnt.Is it currently too long? . ddSzCrnt CMP EAX.16. . .Is Crnt len > Max??? LEA EAX.Backspace. Any other key causes Editline() to exit which returns the string in it’s current condition and also returns the key that caused it to exit. this moves the cursor to left. See listing 26.. EAX MOV ECX.Is Max len to long? .0 Page 544 of 667 .ESP SUB ESP. Any of these places this character in the current X and Y position and advances the position of the cursor. replacing char with 20h. 20h-7Eh .Get current cursor posns in local vars PUSH EAX LEA EAX. 0 JNE EditDone .Bad Erc from call MOV EAX.Display and keyboard are handled entirely inside of EditLine().ASCII text.Local vars EditX and EditY hold position of first char of text . ddSzCrnt MOV PosnX.

Display current string PUSH EditY PUSH pEdString PUSH ddSzMax PUSH dEditAttr . KeyCode EAX.ECX how many bytes to zero .Next test MMURTL V1. EAX EdLn03 EAX. 83h .Wait for a key CALL FWORD PTR _ReadKbd . 2 . 0 JNE EditDone EdLn03: LEA EAX.None to zero out .Get a key JMP SHORT EdLn036 EdLn035: CALL ReadDbgKbd EdLn036: MOV AND OR JZ CMP JNE EdLn037: CMP ddSzCrnt.0 Page 545 of 667 .ESI ptr to 1st empty byte . 0 JE EdLn01 DEC PosnX DEC ddSzCrnt MOV ESI. 20h . ddSzCrnt AL.Initialize currrent string . 08h EdLn04 . 20h JMP Edln01 EdLn04: CMP EAX. AL INC ESI LOOP Edln00 EdLn01: PUSH PosnX PUSH EditY CALL FWORD PTR _SetXY CMP EAX. pEdString ESI.Num-Left? EAX.Next test JMP EdLn037 EdLn045: CMP EAX.fill with spaces MOV [ESI]. 0 JNE EditDone EdLn02: PUSH EditX .SUB JZ MOV ADD MOV Edln00: ECX.Left? JNE EdLn045 . ddSzCrnt MOV BYTE PTR [ESI+ECX]. ddSzCrnt EdLn01 ESI. pEdString MOV ECX.Attribute they selected CALL FWORD PTR _PutVidChars CMP EAX.No .Debugger??? JE EdLn035 PUSH 1 . KeyCode PUSH EAX CMP ddVidOwner. 07Fh EAX.No .BackSpace? . 03h .

CR? .JNE EdLn046 JMP EdLn037 EdLn046: CMP JNE JMP EdLn05: CMP JNE JMP EdLn06: CMP JA CMP JB MOV MOV MOV MOV CMP JAE INC INC JMP EdLn07: MOV MOV MOV MOV MOV EAX.Escape? . ddSzCrnt BYTE PTR [ESI+ECX].Yes. . MMURTL V1. Relating MMURTL’s Video Code to Your Operating System The video hardware on your platform will determine how much work you have to do to implement a video interface. 0Dh EdLn05 EdLn07 EAX. 20h EdLn07 ESI. ddSzMax ddSzCrnt.Next test .0 Page 546 of 667 . pEdString ECX.Ignore error (we are leaving anyway) XOR EAX. you no doubt will want to experiment with both character-based and graphical interfaces. 7Eh EdLn07 EAX. ddSzCrnt [ESI].No . [EAX+NormAttr] PUSH EBX .Is it above text? .No .pddSzRet EAX. From this chapter. ECX. EAX JMP EditDone BadEdit: MOV EAX.pExitKeyRet [ESI]. EAX . ECX EdLn01 PosnX ddSzCrnt EdLn01 ESI. you can see that simplicity was my goal. AL ESI.It’s really a char! AL PUSH EditX .Yes. Exit! .Normal Attribute from JCB CALL FWORD PTR _PutVidChars .Next test .Is it below text?? .Display current string w/Norm Attrs PUSH EditY PUSH pEdString PUSH ddSzMax CALL GetpCrntJCB . ErcEditParam EditDone: MOV ESP. I recommend you keep the virtual character video concept in mind because it allows both character-based and graphical interfaces to coexist on a system.Leaves ptr to current JCB in EAX MOV EBX. In this age of graphics.No .Next test .EBP POP EBP RETF 24 . 1Bh EdLn06 EdLn07 EAX. Exit .

Physical (Absolute) Sector Numbers Physical sector numbers (absolute) begin at Cylinder 0.Chapter 27. There are usually no "hidden sectors" on floppy disks. the cylinder number is incremented. Head numbers run from 0 to nMaxheads-1.0 Page 547 of 667 . When the head number rolls over (nMaxHeads is reached). you have 6 tracks per cylinder. Reverse engineering provides some satisfaction. next head). The partition tables are kept at the very end of the first sector in this hidden area (offset 01BEh in the first sector to be exact). And you can. There are better file systems to be designed than the one that came with a 15-year-old operating system. Sector numbers run from 1 to nMaxSectorsPerTrack. The partition tables are 16-byte entries that describe "logical" sections of the disk that can be treated as separate drives. Head 0. This is usually the very first track on the disk (begins at Cylinder 0. head 0. As the physical sector number rolls over (nMaxSectorsPerTrack+1). nor are there any partition tables. How MMURTL Handles FAT The physical disk layout. which moves you to the next track (same cylinder. Sector 1). but never the amount that an original design would. Sector 1. but none are as wide spread as the MS-DOS FAT file system. the head number is incremented. is as follows: Cylinder numbers run from 0 to nMaxCyls-1. This area is called the hidden sectors. If you have six heads on your drive. File System Code Introduction The MMURTL FAT file system is truly no great accomplishment. MMURTL V1. Track and cylinder are not interchangeable terms in the above text. This can be confusing because many books and documents use the terms interchangeably. so long as you know that’s what you’re doing. as seen from the disk controller’s standpoint. Hidden Sectors MS-DOS reserves a section of the physical hard disk.

). To maintain some sanity. After you have the drive geometry information. This includes the logical-letter-to-physical-drive conversions. Each of the MS-DOS logical partitions will have a boot sector. heads and sectors per track are on the physical disk. You must not service file system requests until this is done. If you were building a loadable file system to replace the one that's included in MMURTL. MMURTL can access each of the DOS logical disks as a separate disk drive. although only the first will be marked as bootable (if any are). MMURTL supports two floppy drives (A & B) and up to eight logical hard disk (C-J). you setup the MMURTL device driver. File System Initialization The MMURTL-FAT file system reads the partition table and saves the starting LBA and length of each of DOS logical disk that is found. The MMURTL file system reads the partition tables. you read the boot sector from the first DOS logical drive. which are also placed in the Logical Drive structures. the device driver assumes a minimum drive size. and sector to retrieve the data. just like MS-DOS. It's position on the disk is calculated from the partition table information. Until this is done. DO NOT confuse MMURTL’s LBA for the sector number in an MS-DOS logical drive. Armed with this information. and you should only read the partition table (or boot sector if no partition table is on the disk). After you have the layout of each of the partitions. head. This is referred to as the Logical Block Address (LBA) and is the value passed in to the DeviceOp call for the MMURTL hard/floppy disk device drivers (LBAs are used with all MMURTL devices). MMURTL V1. etc. then sets up the device driver to span the entire physical disk as 0 to nMaxBlockNumbers-1. MS-DOS Boot Sector The first sector of an MS-DOS logical drive is its boot sector. The boot sector contains several pieces of important information about the drive geometry (numbers of heads. All information on the logical drives are kept in an array of records (Ldrvs). MMURTL calls these “logical blocks” because you still have to convert them into physical cylinder. you would call your routine to initialize the file system very early in the main program block. sectors per track.0 Page 548 of 667 . This tells the device driver how many cylinders.Logical Block Address (LBA) MMURTL device drivers treat the entire disk as a single physical drive. the MMURTL file system gives all of its logical drives a letter. This provides enough information to do a DeviceInit call to set up proper drive geometry.

extern far U32 GetTSSExch(U32 *pExchRet). extern far SpawnTask(char *pEntry. char *pMsgRet). long dPriority. char *pMsgRet). /* From MData */ MMURTL V1. extern far long WaitMsg(long Exch. All resources are allocated in this function before we spawn the new file system task and register the service with the operating system.0 Page 549 of 667 . unsigned char *pData1. unsigned long dData1. extern far long Request(unsigned char *pSvcName. long dStatRet).1 . unsigned int wSvcCode.1 has ample comments to describe the purpose of each function. unsigned long dcbData2. extern far long Respond(long dRqHndl. char *pStack.1. unsigned long dRespExch. unsigned long *pRqHndlRet. extern far long CheckMsg(long Exch.MS-DOS FAT-Compatible File System Code #define #define #define #define #define #define #define #define U32 unsigned long U16 unsigned int U8 unsigned char S32 long S16 int S8 char TRUE 1 FALSE 0 /*********** MMURTL Public Prototypes ***************/ /* From MKernel */ extern far AllocExch(long *pExchRet).File System Listing The file system implementation in Listing 27. Listing 27. long fDebug. unsigned long dcbData1. unsigned long dData0. At the very end of Listing 27. An important concept to note is that the file system itself is a separate task that runs at a relatively high priority of 5. which is called from the Monitor to initialize the file system. unsigned char *pData2. unsigned long dnpSend. unsigned long dData2). long fOSCode). you will find a function called InitFS().

/* From MJob. extern far long CompareNCS(U8 *pS1. S8 *pInitData.h" /* From MDevDrv */ extern far U32 DeviceOp(U32 dDevice. */ MMURTL V1. extern far long GetTimerTick(long *pTickRet). *pdSatusRet)... char *pPathRet.. long ddAttrib).. U8 *pDestination.h */ extern far U32 AllocOSPage(U32 nPages. U32 cBytes. pStatRet. U8 *ppMemRet). /* NEAR support for debugging */ extern long xprintf(char *fmt. extern far long GetNormVid(long *pNormVidRet). U32 dSize). U32 dOpNum. extern far U32 DeviceStat(U32 S8 * U32 U32 dDevice. */ You can’t modify it bubba */ We’re really hurtin. long cb). extern far U32 DeAllocPage(U8 *pOrigMem.0 Page 550 of 667 .*/ Ain’t no such dir. U32 dBytes). U32 sdInitData). U32 dLBA. /* File System error codes */ #define ErcOK #define ErcEOF #define ErcBadSvcCode #define #define #define #define #define #define #define ErcBadFileSpec ErcNoSuchDrive ErcNotAFile ErcNoSuchFile ErcNoSuchDir ErcReadOnly ErcNoFreeFCB 0 1 32 200 201 202 203 204 205 206 /* Alls Well */ /* DUH. /* From MVid. U32 Exch).h */ extern far long TTYOut (char *pTextOut. extern far U32 RegisterSvc(S8 *pName. extern far long GetCMOSDate(long *pTimeRet). #include "MKbd..h */ extern far U32 GetPath(long JobNum. U8 *pS2. extern U32 Dump(unsigned char *pb. U32 dnBlocks. extern far void FillData(U8 *pDest. The END */ /* Service doesn’t handle that code */ /* /* /* /* /* /* /* invalid file spec (not correct format)*/ Try another letter bozo */ Open a directory?? NOT */ No can do! It ain’t there. U32 nPages).. U8 bFill). U8 *pData)..)...h */ extern far long GetCMOSTime(long *pTimeRet). dStatusMax.extern far void CopyData(U8 *pSource. . extern far U32 DeviceInit(U32 dDevNum. /* From MMemory.. long *pdcbPathRet). /* From MTimer. long ddTextOut.

. except the first one which is 3 sectors for floppies (FAT12 types). bit 1 = Locked */ static struct fattype Fat[nFATBufs]. U32 LBASect. /* 16 bytes * 17 */ /* We read 3 sectors worth of floppy fat buf in cause cluster entries span sectors */ MMURTL V1. /* U16 iClstrStart.0 Page 551 of 667 . U32 LastUsed. Initially filling out the Fat control structure is part of the file system initialization. out of File User Blocks */ WHOAAA. }. bad handle buddy! */ Cluster chain broken on file */ We got REAL problems. This is because the FAT12 entires span sectors! */ #define nFATBufs 17 */ static struct fattype { U8 *pBuf..#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define ErcBadOpenMode ErcFileInUse ErcNoFreeFUB ErcBadFileHandle ErcBrokenFile ErcBadFCB ErcStreamFile ErcBlockFile ErcBeyondEOF ErcNoParTable ErcBadFATClstr ErcRenameDrv ErcRenameDir ErcNoMatch ErcWriteOnly ErcDupName ErcNotSupported ErcRootFull ErcDiskFull 207 208 209 210 211 213 214 215 217 218 220 222 223 224 225 226 227 228 230 605 /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* Say what? Mode??? */ File is open in an incompatible mode */ Sorry. We never want to have more than one copy of a FAT sector in memory at one time. /* 1 Static for floppies + 16 * 512 = 8192. U8 Drive. 2 pages /* */ /* points to beginning of fat buffer */ /* Tick when last used (0 = Never) */ LBA of first FAT sect in buf (where it came from) */ /* Starting cluster for each buf */ /* LDrive this FAT sector is from */ /* Bit 0 = Modified. */ Operation not allowed on Stream File */ Operation not allowed on Block File */ SetLFA or Read/WriteBlock beyond EOF */ No partiton table found on disk!!! */ File system screwed up (or your disk) */ They have tried to rename across Dir/Vol*/ They have tried to rename across Dir/Vol*/ No matching directory entry */ Attempt to read write-only device */ Name exists as a file or dir already */ Not supported on this file */ The Root Directory is Full */ No more free CLUSTERS!!! */ #define ErcNewMedia /* for floppy mounting from FDD */ /**************** FAT Buffer control structure **********************/ /* The Fat structures are for keeping track of the FAT buffers. and we also never want to read one when its already here (a waste of time)! We also keep track of the last time it was used and deallocate the oldest (LRU Least Recently Used). If the FAT sector we are in has been modified (data written to clusters in it & FAT updated) we write it ASAP! Each FAT buffer is 1 sector long. U8 fModLock.

LFA of first byte in Clstr. */ /* pointer to one FCB */ /********************** File User Blocks **************************/ /* Each user of an open file is assigned a FUB. we would have to "run" the cluster chain everytime we wanted to read or access a file beyond the last MMURTL V1.0 Page 552 of 667 . If we didn’t save this information on the last access. /* This file was modified! */ U8 Resvd[22]. S8 Attr. /* a pointer to array of allocated FCBs. 1 & 2 are reserved for NUL. U16 StartClstr. LFAClstr . KBD and VID devices. /* LBA of directory sector this is from */ U16 oSectDirEnt.LFA of first byte in buffer for a stream file. /* 0 or 1 (Read or Modify). ) 0. The actual directory entry structure from the disk is embedded in the FCB so it can be copied directly to/from the directory sector on the disk. /* last entry in FAT Dir Ent (32 bytes) */ U32 LBADirSect. /* Offset in sector for the dir entry */ U8 Ldrv. /* At least one per file!! */ U32 FileSize. */ #define nFCBs 128 #define sFCB 64 static struct FCB { S8 Name[8]. It is used to hold information on files opened in stream and block mode. /* From here to Filesize is copy of DirEnt */ S8 Ext[3]. /* Active FUBs for this file (255 MAX). static struct FCB *paFCB. /* Out to 64 bytes */ }. */ U8 nUsers. 0-9) */ U8 Mode. /* from MS-DOS */ U8 Resvd1[10]. /* Only changed when created or updated */ U16 Date. 0= Free FCB */ U8 fMod. /* Logical drive this is on (A-J. LFAClstr and Clstr give us a relative starting point when reading a file from disk. /* ???????? */ U16 Time. Three important fields in the FUB are: LFABuf .Clstr of last block read or stream fill. static struct FCB *pFCB. */ #define nFUBs 128 #define sFUB 32 /* The FUB contains information on a file related to a user’s view of the file. /* floppy fat buffer */ #define FATMOD 0x01 #define FATLOCK 0x02 /**************** File Contol Block Structures (FCBs) **********/ /* One FCB is allocated and filled out for each file that is open. Clstr .U8 FatBufA[1536]. The FUB number is the filehandle (beginning with 3).

Set up at /* a pointer to allocated FUBs. */ struct FUB { U16 Job. U8 HeadEnd. U8 OEMname[8]. U16 Sectors. /* /* /* /* /* /* /* /* /* /* /* User’s Job Number. */ static struct FUB *pFUB. U8 Media. U8 Rsvd1. U8 CylStart. 0 if FUB is free. U16 ResSectors. U32 CrntLFA. */ FCB number for this file (0 to nFCBs-1) */ Current Logical File Address (File Ptr) */ Ptr to buffer if stream mode */ Size of buffer for Stream file in bytes */ S-First LFA in Clstr Buffer */ LFA of Clstr (below).point we read. U32 HiddenSecs. */ Last Cluster read */ Data in buffer was modified */ NonZero for STREAM mode */ Pad to 32 bytes */ static struct FUB *paFUB. U8 BootSig. U16 bps. 16 bytes */ struct partent { U8 fBootable. U8 Rsvd[4]. MMURTL V1. U8 HeadStart. U32 LFABuf. U8 VolLabel[11]. U8 FileSysType[8]. U8 fModified. U32 VolID.0 Page 553 of 667 . }. U32 LFAClstr. U8 SecPerClstr. U16 RootDirEnts. U16 SecPerFAT. U8 SecStart. U8 FATType. /* 62 bytes */ }. static struct fsbtype fsb. U32 sBuf. U16 Heads. U8 *pBuf. U8 SecEnd. U8 DriveNum. init. U16 iFCB. init. U8 fStream. U16 Clstr. */ /* a pointer to allocated FUBs. Set up at /* Boot sector info (62 byte structure) */ struct fsbtype { U8 Jmp[3]. /* Partition Table Entry info. U16 SecPerTrack. U32 HugeSecs. U8 FATs.

static struct dirstruct dirent. static U16 partsig. U8 Ext[3]. U32 nSectorsTotal. For instance. If you were to open "A:\Dog\Food\IsGood. U8 Attr.U8 CylEnd. /* a pointer to a dir entry */ static struct dirstruct *pDirEnt. static U8 SpecDepth. U16 StartClstr. /* When a file is opened. the filename is parsed into an array to facilitate searching the directory tree. U8 Rsvd[10].0 Page 554 of 667 . 32 bytes */ struct dirstruct { U8 Name[8]. */ static U8 FDrive static U8 FileSpec[7][11]. }. The FileSpec array contains the fully parsed path of the file.txt" the FileSpec array would look like this: FileSpec[0] = "DOG " FileSpec[1] = "FOOD " FileSpec[2] = "ISGOOD TXT" FileSpec[3][0] = NULL. /* Used for Rename */ static U8 FDrive1 /* Drive parsed from file operation */ /* Hierarchy from file spec parsing */ /* Depth of parse (0=Root File) */ /* Drive parsed from file operation */ MMURTL V1. /* 4 partition table entries 64 bytes */ /* Bit definitions in attribute field for a directory entry */ #define #define #define #define #define #define #define ATTRNORM READONLY HIDDEN SYSTEM VOLNAME DIRECTORY ARCHIVE 0x00 0x01 0x02 0x04 0x08 0x10 0x20 /* Directory Entry Record. static struct partent partab[4]. SpecDepth tells us how many directories deep the name goes. U32 nFirstSector. }. and the next unused FileSpec entry contain NULL in the first byte. IN MS-DOS all dir and file names are SPACE padded (20h). U16 Date. Note that the DOT is not inlcuded (it’s not in the DOS directory either). U16 Time. U32 FileSize.

/* Max lba for logical drive */ U32 LBARoot. */ #define nPDrvs 4 static struct phydrv { U32 nHeads. abTmpSector[516]. /* True for FAT16 else FAT12 */ }. /* nSectors in a FAT */ U8 DevNum. /* lba for Start of LDrive (bootSect) */ U32 LBAData. U8 BS1Head. */ #define nLDrvs 10 static struct ldrvtype { U32 LBA0. U32 nCyl. U8 BS1Sect. /* number of FATs */ U8 fFAT16. U32 nSecPerTrk. /* lba for Start of Data Area */ U32 LBAMax. /* Setup after boot sector is read */ U16 nRootDirEnt. /* Setup after boot sector is read */ U16 nSecPerTrk. /* For each logical drive */ U8 nFATS. static U8 SpecDepth1. U32 BlocksMax. } static struct phydrv /* /* /* /* /* heads per drives */ Sectors per track */ Cyl of 1st boot sector on disk */ Head of 1st boot sector on disk */ Sector of 1st boot sector on disk */ PDrvs[nPDrvs]. It is peculiar to the HD Drvr */ struct hddevtype{ U32 erc. /* Device Number for this ldrv FF = NONE */ U8 SecPerClstr. /* This is the Hard Disk Device Status record. /* /* /* /* /* current fdisk_table for drive selected */ padding for DWord align */ total physical cylinders (we really don’t care) */ total heads on device */ Sectors per track */ MMURTL V1. static struct ldrvtype Ldrv[nLDrvs]. U32 nHead. /* This array of structures keeps track of logical drive data (A-J). U8 type_now. U8 resvd0[2]. /* Hierarchy from file spec parsing */ /* Depth of parse (0=Root File) */ /* raw sector buffer for all kinds of stuff */ static U8 static U8 static U8 abRawSector[516]. U8 fNewMedia. /* lba of the Root directory */ U32 LBAFAT. /* Number of Root directory entries */ U16 sFAT. U32 nSectors. abDirSectBuf[516]. /* lba of first FAT */ U16 nHeads.0 Page 555 of 667 . U32 blocks_done. U16 BS1Cyl.static U8 FileSpec1[7][11]. /* These arrays keep track of physical drive data (0-4).

char npSend. /* Number of bytes per sect. U8 resvd1[2]. long dData2. It is peculiar to the FD Drvr */ struct fdstattype{ U32 erc. resvd1[2]. U8 type_now. LastStatByte1. LastErcByte0. /* This is the Floppy Device Status record. long dData1. char *pRqHndlRet. LastSeekErc1. U32 resvd4. U32 resvd3. LastErcByte1. struct reqtype { long ServiceExch. LastRecalErc1. U32 nSectors. int ServiceCode. LastStatByte0. U32 nHead. /* Last Error from device */ /* /* /* /* /* /* /* /* current fdisk_table for drive selected */ padding for DWord align */ total physical cylinders */ total heads on device */ Sectors per track */ Number of bytes per sect */ begin device specific fields */ status returned from FDC (for user status) */ /* 64 bytes total */ static struct fdstattype FDDevStat. ResetStatByte. U8 STATUS[8]. long RqOwnerJob. }. U32 BlocksMax. char npRecv.0 Page 556 of 667 . 32 bytes out to here. /* 64 byte request block structure */ MMURTL V1. /* out to 64 bytes */ static struct hddevtype HDDevStat. long dData0. nBPS.U32 U32 U32 U8 U8 U8 U8 U32 U32 U8 U8 U8 U8 U32 }. U32 nBPS. /* Status Byte immediately after RESET */ filler1. U32 blocks_done. fIntOnReset. long RespExch. /* Interrupt was received on HDC_RESET */ filler0. /* 2048 byte stack for Fsys task */ static long FSysExch. long ServiceRoute. U8 params[16]. U8 fNewMedia. char *pData1. static long FSysStack[512]. U32 nCyl. LastSeekErc0.*/ LastRecalErc0.

i is the index into the partition table we read in. *************************************************/ static void GetBSInfo(U32 d. RQBRsvd1.nSecPerTrk = partab[i].DevNum= 11.DevNum= 10. */ Ldrv[0]. Ldrv[1]. /* for testing */ /*========================== BEGIN CODE ============================*/ /************************************************ Called from read_PE.SecStart.nHeads = partab[i]. if (!i) { /* primary partition info . head and sector for the first boot sector on a physical drive and stores it in the phydrv array.BS1Sect = partab[i]. 64. d is the drive.long char long long long long }.0 Page 557 of 667 . cbData2.BS1Cyl = partab[i]. this gets the starting cylinder.HeadEnd. i.CylStart. &i). PDrvs[d]. static unsigned long keycode. /* Set gets status for the floppy type from the FDD and sets logical paramters for Ldrvs. *************************************************/ static U32 StatFloppy(U8 ld) { U32 erc. RQBRsvd3.use it for PDrv info */ PDrvs[d]. U32 i) { PDrvs[d]. PDrvs[d]. It is called when the file system is first initialized and when there has been an error on the floppy. static char *fsysname = "FILESYSM". MMURTL V1. PDrvs[d]. cbData1.HeadStart. &FDDevStat. static struct reqtype *pRQB. RQBRsvd2. /* Device Numbers for floppies */ erc = DeviceStat(ld+10. *pData2. } } /** InitFloppy ********************************* This gets status from the floppy drive (device ld) and sets the physical & logical drive parameters for the type.nFirstSector & 0xff.BS1Head = partab[i].

nSecPerTrk = FDDevStat.DevNum = 0xff. i++) { /* default to no logical hard drives */ Ldrv[i]. return erc. /* Max lba for logical drive 0 */ Ldrv[ld]. 1. 0.LBAMax= FDDevStat. fFound2. Ldrv[ld]. } /************************************************ Reads the partition table entries from hard drives and sets up some of the the logical drive array variables for hard Disks. abRawSector). ercD13.nSectors. if (!erc) { MMURTL V1. } i = 2. It also saves first cylinder. This info will be set correctly when the partition table and boot sectors are read. j++) { /* Array index Numbers for 2 physical hard Disks */ erc = DeviceOp(j+10. head and sector of the first partiton on each physical drive so we can get more info for the LDrv arrays from the boot sector of that partition. /* first Logical Number for hard drives "C" */ for (j=2. j<4.nHeads = FDDevStat.LBA0 = 0.nHeads = FDDevStat.nSectors. /* Have we found first valid partition on drive */ /* Set defaults for 4 physical drives. /* add 10 for Disk device nums */ if (j==2) ercD12 = erc. U8 fFound1. PDrvs[ld]. */ for (i=2. j. ercD12. fFound1 = 0.nHead. i< nLDrvs. erc = 0.if (!erc) { PDrvs[ld]. fFound2 = 0.always 0 */ Ldrv[ld]. 1. else ercD13 = erc.nSecPerTrk = FDDevStat.nHead. } else Ldrv[ld]. *************************************************/ static U32 read_PE(void) { U32 erc.BlocksMax-1. Ldrv[ld]. /* Floppy Boot Sector .DevNum = 0xff. i.0 Page 558 of 667 .

} if ((j==3) && (!fFound2)) { GetBSInfo(3. ReadKbd(&keycode.nSectorsTotal > 0) { Ldrv[i]. Ldrv[i]. 2). /* Max lba for logical drive */ if (partab[0].nSectorsTotal > 0) { Ldrv[i].LBA0 =partab[0]. Ldrv[i].LBAMax =partab[0]. fFound2=1.LBAMax =partab[0]. fFound1=1. &partab[0]. } if ((j==3) && (!fFound2)) { GetBSInfo(3.fBootable.LBA0 = partab[2]. if ((j==2) && (!fFound1)) { GetBSInfo(2. } i++.nFirstSector. */ if (partab[0]. if (partab[1]. /* Dump(&partab[0]. 1). fFound2=1.LBA0 = partab[1]. /* if valid partition go to next LDrv */ } if (partab[1]. fFound2=1. fFound1=1.nSectorsTotal > 0) { MMURTL V1.nSectorsTotal. } if ((j==3) && (!fFound2)) { GetBSInfo(3. } i++.nFirstSector.nSectorsTotal. if ((j==2) && (!fFound1)) { GetBSInfo(2. } i++. 0).FATType > 3) Ldrv[i]. Ldrv[i]. /* if we had a valid partition go to next */ } if (partab[2]. /* Max lba for logical drive */ Ldrv[i]. CopyData(&abRawSector[0x01be]. 64). 1).DevNum = j+10. /* It MUST have a partition table or we can’t use it! */ if (partsig != 0xAA55) return ErcNoParTable.DevNum = j+10. Ldrv[i]. &partsig.fFAT16 = 1. fFound1=1. /* if we had a valid partition go to next */ } if (partab[3]. if ((j==2) && (!fFound1)) { GetBSInfo(2.fFAT16 = 1.nFirstSector. 2). 2). 1). 0).LBAMax = partab[2].DevNum = j+10.fBootable.nSectorsTotal > 0) { Ldrv[i].nSectorsTotal.CopyData(&abRawSector[0x01fe]. Ldrv[i]. if (partab[2].FATType > 3) Ldrv[i].LBAMax = partab[1]. /* lba for Start of LDrv (bootSect) */ Ldrv[i].fFAT16 = 1.nSectorsTotal.0 Page 559 of 667 .FATType > 3) Ldrv[i]. 64).

HDDevStat. Ldrv[i]. *********************************************************************/ static U32 SetDriveGeometry(U32 d) */ { U32 erc.Ldrv[i]. This includes number of heads and sectors per track. } if ((j==3) && (!fFound2)) { GetBSInfo(3. if ((j==2) && (!fFound1)) { GetBSInfo(2. &HDDevStat. 64. /* if we had a valid partition go to next */ } } } if (ercD12) return ercD12.nFirstSector. } i++.nSectors = PDrvs[3]. if (!erc) { HDDevStat. &HDDevStat.nHeads.nHead = PDrvs[2]. HDDevStat. 3).nHead = PDrvs[3]. Then we call DeviceInit for each physical device to set its internal drive geometry. /* d is the device number (12 or 13) /* there may be no Device 13 */ if (d==12) { erc = DeviceStat(12.DevNum = j+10. &i). else return 0. This must be done before we even try to read the other boot sectors if the disk has mulitple partitions (otherwise it fails).fFAT16 = 1.nHeads. 3). Ldrv[i].nSecPerTrk. fFound2=1. &HDDevStat. 64). if (partab[3]. } /******************************************************************** Reads in the first boot sector from each physical drive to get drive geometry info not available in partition table.LBAMax = partab[3]. 64. &HDDevStat.nSectors = PDrvs[2]. /* Set up drive geometry */ } } if (d==13) { erc = DeviceStat(13. if (!erc) { HDDevStat.nSectorsTotal.LBA0 = partab[3]. 64). erc = DeviceInit(13. i. erc = DeviceInit(12.FATType > 3) Ldrv[i]. &i). fFound1=1.nSecPerTrk. /* Set up drive geometry */ MMURTL V1.0 Page 560 of 667 .

} /******************************************************* This gets the CMOS date & time and converts it into the format for the DOS FAT file system.Heads.sFAT = fsb. 1.LBAData = Ldrv[i].fFAT16 = 0.Jmp.nSecPerTrk = fsb.DevNum. } CopyData(abRawSector. if ((erc==ErcNewMedia) && (i<2)) { erc = DeviceOp(j. This is two words with bits representing Year/Mo/day & Hr/Min/SecDIV2. /* j is MMURTL Device number */ erc = DeviceOp(j. if (Ldrv[i].nFATS = fsb.LBA0. /* number of FATs */ Ldrv[i].SecPerClstr. Ldrv[i]. } /******************************************************************** Read boot sector from logical drive (i) and sets up logical and physical drive array variables for the FAT file system found on the logical drive (described in the boot sector).FATs * fsb. Ldrv[i]. Ldrv[i].} } return erc. if (erc==0) { Ldrv[i]. 1.SecPerTrack. /* nSectors in a FAT */ Ldrv[i]. if (fsb.SecPerFAT).FATs. } /* if erc */ } /* if valid logical device */ return 0.LBA0 + fsb.LBA0 + (fsb.ResSectors.LBA0.SecPerFAT.nRootDirEnt = fsb.LBARoot = fsb. Ldrv[i]. ********************************************************/ static void GetFATTime(U16 *pTimeRet. 62).LBARoot + (fsb. &fsb. 1. /* n Root dir entries */ Ldrv[i].0 Page 561 of 667 .nHeads = fsb.RootDirEnts.SecPerClstr = fsb. Ldrv[i].RootDirEnts / 16). Ldrv[i]. j.FileSysType[4] == ’2’) Ldrv[i]. 1.LBAFAT = Ldrv[i]. abRawSector). U16 *pDateRet) { MMURTL V1. abRawSector). *********************************************************************/ static U32 read_BS(U32 i) { U32 erc. Ldrv[i].DevNum != 0xff) { j = Ldrv[i].ResSectors + Ldrv[i].

/* secs/2 */ w = (((time >> 12) & 0x0f) * 10) + ((time >> 8) & 0x0f). time.Date).Time. i. } return erc. ********************************************************/ MMURTL V1. *pDateRet = DDate. Drive = paFCB[iFCB]->Ldrv. i. The date is also updated at this time.0 Page 562 of 667 . 1. GetCMOSTime(&time). &paFCB[iFCB]. w. GetCMOSDate(&date). /* hours */ DTime |= (w << 11). ********************************************************/ static U32 UpdateDirEnt(U32 iFCB) { U32 erc. } /******************************************************* This updates a directory entry by reading in the sector it came from and placing the modifed entry into it then writing it back to disk. DTime |= (w << 5). i. &abDirSectBuf[j]. DTime. /* Read sector into a buffer */ erc = DeviceOp(Ldrv[Drive]. w = (((date >> 28) & 0x0f) * 10) + ((date >> 24) & 0x0f). abDirSectBuf). 32). *pTimeRet = DTime.DevNum. 2. U8 Drive.1980) << 9. /* day */ w = (((date >> 20) & 0x0f) * 10) + ((date>>16) & 0x0f) + 2. /* Do the date */ DDate = (((date >> 12) & 0x0f) * 10) + ((date >> 8) & 0x0f). U16 DDate. 1. abDirSectBuf). else a proper error code is returned. /* year */ DDate |= (w + 1900 .DevNum. if (!erc) { CopyData(&paFCB[iFCB]. /* month */ DDate |= (w << 4). j. The function return OK (0) if handle is good. /* Do the time */ DTime = (((((time >> 4) & 0x0f) * 10) + (time & 0x0f))/2). /* Sector on disk */ j = paFCB[iFCB]->oSectDirEnt. /* offset in sector */ /* update time in dir entry */ GetFATTime(&paFCB[iFCB]. /* What logical drive are we on? */ i = paFCB[iFCB]->LBADirSect. /* mins */ w = (((time >> 20) & 0x0f) * 10) + ((time >> 16) & 0x0f). } /******************************************************* Checks the validity of the a file handle and also returns the index to the FCB if the handle is OK. 1.U32 date. erc = DeviceOp(Ldrv[Drive].

if (dHandle >= nFUBs) return ErcBadFileHandle. U8 Drive) { U32 LBA. if (Fat[iFAT]. erc = 0. i = Fat[iFAT].LBASect.Job) return ErcBadFileHandle. This gives us the LBA of the first sector of data that the cluster number represents. Clstr-=2. ********************************************************/ static U32 UpdateFAT(U32 iFAT) { U32 erc. /* Looks like a valid handle */ *iFCBRet = paFUB[dHandle]->iFCB. U32 *iFCBRet) { /* get some checks out of the way first */ if (dHandle < 4) return ErcBadFileHandle.LBAData. return 0. /* What logical drive are we on? */ /* Where to write it back */ if (!iFAT) { /* This is the floppy buffer [0] */ /* set up to write upto 3 sectors from the buffer */ if (i+2 < Ldrv[Drive]. The sector number is returned from the fucntion.sFAT + Ldrv[Drive]. } /******************************************************* This writes out the specified FAT sector back into the FAT.fModLock & FATMOD) { /* Modified?? */ Drive = Fat[iFAT]. return LBA. i.LBAData Ldrv[CrntDrv]. k. U8 Drive.static U32 ValidateHandle(U32 dHandle. It also checks to see if there is more than one copy of the fat and updates the second copy if it exists. Uses: Ldrv[CrntDrv]. /* Minus 2 cause 0 and 1 are reserved clusters */ LBA = Ldrv[Drive]. if (!paFUB[dHandle].0 Page 563 of 667 . } /********************************************* Returns absolute disk address for the cluster number you specify.SecPerClstr **********************************************/ static U32 ClsToLBA(U16 Clstr.Drive. LBA += Ldrv[Drive].LBAFAT) MMURTL V1.SecPerClstr * Clstr.

3.fFAT16 Ldrv[LDrive]. i. we have one 3 sector fat buffer for fat12 devices (floppies). U16 Clstr. *******************************************************/ static U32 FindFatSect(U8 Drive.DevNum Each sector of the FAT contains 256 cluster entries for FAT16 types. (i+1 < Ldrv[Drive]. that we allocate the FAT buffers on a Least Recently Used (LRU) basis for hard disk drives. k. we Divide the cluster number by the count of entries (256). Returns Error if not in FAT.sFAT + Ldrv[Drive].fModLock &= ~FATMOD.sFAT. U32 first. /* Not modified anymore */ if (Ldrv[Drive]. Tick. erc = DeviceOp(Ldrv[Drive].nFATS > 1) { /* 2 FATS! */ /* if we have two FATS we must update the second fat also.LBAFAT Ldrv[LDrive]. iFound. MMURTL V1. Fat[iFAT].k = else if k = else k = } else k=1. erc = DeviceOp(Ldrv[Drive]. 1. U32 *piFatRecRet. It’s more complicated for a FAT12 types (floppies) because cluster entries span fat sectors (they have an odd number of nibbles). Fat[iFAT]. erc.DevNum. if (!erc) { Fat[iFAT]. 2.pBuf). For this reason. This will be located directly aftrer the first FAT (by exactly LDrv. j.sFat sectors). This is because the last entry may span the sectors and we must be able to read it. } /******************************************************* Reads in the FAT sector that contains the Cluster we specified into a FAT buffer if it isn’t already in one.DevNum. We fill it with up to 3 sectors. 2. and add this to the beginning sector of the FAT. The index to the FAT buffer is returned. It is SOOO important (for speed) to have the FAT sectors in memory. oSector. k. k. To find it. */ i+= Ldrv[Drive]. LRU. i. iLRU. Uses: Ldrv[LDrive]. U8 fLock) { U32 i.0 Page 564 of 667 . } } } return erc.pBuf). There are 1024 cluster entries in a FAT12 3 sector buffer.LBAFAT) 2.

} GetTimerTick(&Tick). /* saves tick of oldest one so far */ iLRU = 1. } else { /* Else we get it for them */ /* /* /* /* Loop through the Fat bufs and see if its in one already.fFAT16) MaxClstr = 0xfff8. */ Set iFound to index of FatBuf (if found). if (Ldrv[Drive]. */ Otherwise.. /* FAT12 */ if (Clstr >= MaxClstr) return(ErcEOF). /* If FAT sector is out of range there’s a BAD problem.0 Page 565 of 667 .fFAT16) { oSector = Clstr/256.LastUsed > 0) { /* Valid ? (ever been used) */ if ((first == Fat[j]. /* Set i to LBA of FAT sector we need by adding offset to beginning of FAT */ i = oSector + Ldrv[Drive]. /* default */ for (j=1.. first = Clstr-(Clstr%256).sFAT + Ldrv[Drive]. /* default to no error */ /* Set oSector to offset of sector in FAT There are 256 cluster entries in 1 sector of a FAT16. if (Clstr < 2) { return (ErcBadFATClstr). else MaxClstr = 0xff8. */ Save the index of the LRU in case it’s not there.U16 MaxClstr. erc = 0.iClstrStart) && MMURTL V1. j<nFATBufs.LBAFAT) { return (ErcBadFATClstr). */ if (i >= Ldrv[Drive]. LRU = 0xffffffff. j++) { if (Fat[j]. Set up iLRU to indicate what the oldest buffer is */ iFound = 0xffffffff.LBAFAT. and 1024 in a FAT12 (3 sectors) */ if (Ldrv[Drive].

LBASect = i. /* Already IN! */ } } if (Fat[j].pBuf).Drive)) { iFound = j.0 Page 566 of 667 . i.LBAFAT. break. */ if (Fat[j]. j = 0.LastUsed = Tick. if (fLock) Fat[j].LastUsed < LRU) { LRU = Fat[j]. MMURTL V1.LastUsed = Tick. first = Clstr-(Clstr%1024). 1.fModLock & FATMOD) erc = UpdateFAT(j). /* */ Fat[j]. /* update LRU */ } else { /* else put into oldest buffer */ j = iLRU. write it out before we read the next one into this buffer.Drive = Drive. /* LBA this FAt sect came 1. iLRU = j. Fat[j]. } } if (iFound != 0xffffffff) { /* Its already in memory */ Fat[j].fModLock |= FATLOCK. Fat[j]. /* Fat[j]. Update Drive */ update LRU */ update first cluster num /* X3 cause we read 3 at a time */ /* Set i to LBA of FAT sector we need by adding offset (oSector) to beginning of FAT */ i = oSector + Ldrv[Drive].DevNum.iClstrStart = first. if (!erc) { erc = DeviceOp(Ldrv[Drive].(Drive == Fat[j]. This done by calling UpdateFAT(iFatRec). /* Check to see if Fat[iLRU] is valid and has been modified.LastUsed. If it is. from */ } } } } /* This is for FAT12s */ else { oSector = (Clstr/1024) * 3. /* Fat[j].

/* set up to read upto 3 sectors into buffer */ if (i+2 k = else if k = else k = < Ldrv[Drive]./* If FAT sector is out of range there’s a BAD problem. Fat[0].LBASect = i.LBAFAT) 2.pBuf). */ iFound = 0xffffffff. 1. This done by calling UpdateFAT(iFatRec).DevNum.fModLock & FATMOD) erc = UpdateFAT(0).Drive)) { iFound = 0. i. */ if (Fat[0]. if (fLock) Fat[0]..iClstrStart) && (Drive == Fat[0]. Fat[0]. Fat[0].Drive = Drive.iClstrStart = first. } } if (iFound == 0xffffffff) { /* It’s not the one we want or isn’t there.sFAT + Ldrv[Drive]. */ Fat[0]. write it out before we read the one we want into the buffer.. If it is. Check to see if Fat[0] is valid and has been modified. Fat[0]. */ /* Set iFound to index of FatBuf (if found). k. else { /* Else we get it for them */ /* Check the single floppy fat buf and see if its already there. } } } } /* LBA this FAT sect came from */ /* Update Drive */ /* update LRU */ /* update first cluster num MMURTL V1.0 Page 567 of 667 .sFAT) return (ErcBadFATClstr).fModLock |= FATLOCK.sFAT + Ldrv[Drive].LastUsed = Tick. */ if (i >= Ldrv[Drive]. (i+1 < Ldrv[Drive].LBAFAT) 3. if (Fat[0]. 1.LastUsed > 0) { /* Valid ? (nonzero means it’s been used) */ if ((first == Fat[0]. if (!erc) { erc = DeviceOp(Ldrv[Drive].

must shift */ ClstrVal >>= 4. U8 Drive. then take the proper nibble by ANDing or shifting. return (erc).5 bytes long (what a pain). } *pValRet= ClstrVal. *iFatBufRet = iFat. fLock). This means we get the offset and see whether it is an odd or even byte. *pClstr. **********************************************/ static U32 GetClstrValue(U16 Clstr. oClstr = Clstr .Fat[iFat]. erc = FindFatSect(Drive. U8 fLock. U16 ClstrVal. return(erc). if (erc) { *pValRet= 0..0 Page 568 of 667 . return(erc).iClstrStart. U32 *iFatBufRet) { U32 erc. &am