0% found this document useful (0 votes)
962 views939 pages

PC System Programming PDF

Uploaded by

Dungeon Master
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
962 views939 pages

PC System Programming PDF

Uploaded by

Dungeon Master
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 939

fll fl1

19
[il~ II.
[-1[111

~11 1111
~ mI)~
System 1I1 ~
Programming
for developers ~
Michael Tischer

A Data Becker Book

Published by

AbacuslliliiiiiUiil

Third Printing, April 1990


Printed in U.S.A.

Copyright © 1989, 1990 Abacus


5370 52nd Street, SE.
Grand Rapids, MI 49512

Copyright © 1988, 1989, 1990 DATA BECKER GmbH


Merowingerstrasse 30
4000 Duesseldorf, West Germany

This book is copyrighted. No part of this book may be reproduced, stored in a retrieval
system, or transmitted in any form or by any means, electronic, mechanical, photocopying,
recording or otherwise, without the prior written permission of Abacus or Data Becker,
GmbH.

Every effort has been made to ensure complete and accurate information concerning the
material presented in this book. However, Abacus can neither guarantee nor be held legally
responsible for any mistakes in printing or faulty instructions contained in this book. The
authors always appreciate receiving notice of any errors or misprints.

This book contains trade names and trademarks of many companies and products. Any
mention of these names or trademarks in this book are not intended to either convey
endorsement or other associations with this book.

PC-DOS, IBM PC, XT, AT, PS/2, OS/2 and PC-BASIC are trademarks or registered
trademarks of International Business Machines Corporation. Ventura Publisher is a
trademark or registered trademark of Xerox Corporation. GEM and CP/M are trademarks or
registered trademarks of Digital Research Corporation. Microsoft Works, Microsoft Quick
C, Microsoft Windows, MS-DOS, XENIX and GW-BASIC are trademarks or registered
trademarks of Microsoft Corporation. Lotus 1-2-3 is a trademark or registered trademark of
Lotus Development Corporation. dBASE is a registered trademark of Ashton-Tate, Inc.
Sidekick, Turbo C and Turbo Pascal are trademarks or registered trademarks of Borland
International. UNIX is a registered trademark of Bell Laboratories. Mickey Mouse is a
registered trademark of Walt Disney Corporation.
Library of congress Cataloging-in-publication Oata
Tischer, Michael, 1953­
PC system programming for d'evelopers I Michael Tischer.
p. ClI.
"A Data Becker book ,'I
1. System programming (computer science) 2. Microcomputers-programming. I. Title
QA76.66.T57 1989 DDS.26S--dc20 85-183S0

ISBN 1-55755-036-0 (book and disk set)

ii
Table of Contents

1. Introduction ............................................................................................ 1

2. The PC's Brain ........................................................................................ 3

2.1 8088 Registers ........................................................................................ 6

2.2 Segment and Offset Addressing ................................................................... 8

2.3 The CPU Support Chips ..........................................................................13

2.3.1 The DMA Controller ...............................................................................13

2.3.2 The Interrupt Controller ...........................................................................13

2.3.3 The Programmable Peripheral Interface........................................................13

2.3.4 The Clock .............................................................................................14

2.3.5 The Timer .............................................................................................14

2.3.6 The Screen Controller ..............................................................................14

2.3.7 The Disk Controller ................................................................................14

2.3.8 The Math Coprocessors (8087/80287/80387) ...............................................14

2.4 The CPU and Memory .............................................................................16

3. Introduction to Interrupts ......................................................................... .19

3.1 The Structure of the Interrupt Vector Table ..................................................20

3.2 Interrupt Types ...................................................................................... .22

3.2.1 Software Interrupts ..................................................................................22

3.2.2 Hardware Interrupts..................................................................................22

3.3 Interrupts at a Glance ...............................................................................24

4. Using Interrupts from High Level Languages ...............................................27

4.1 Interrupt Calls from BASIC ......................................................................28

4.2 Interrupt Calls from Turbo Pascal ............................................................. .36

4.3 Interrupt Calls from C .............................................................................40

5. Using Interrupts from Assembly Language ................................................. .47

5.1 Using Assembler Macro Functions ............................................................ .48

5.2 A Sample Macro.................................................................................... .49

6. The Disk Operating System ..................................................................... .51

6.1 A Short History of OOS ..........................................................................52

6.2 Internal Structure of OOS ........................................................................ .56

6.3 Booting OOS .........................................................................................59

iii
Table of Contents PC System Programming

6.4 COM and EXE Programs .........................................................................60

6.4.1 COM Programs ......................................................................................62

6.4.2 EXE Programs .......................................................................................66

6.5 Character Input and Output from DOS ........................................................70

6.5.1 Handle Functions ....................................................................................70

6.5.2 Traditional DOS Functions .......................................................................74

6.6 File Management in DOS.........................................................................84

6.6.1 Handle Functions ....................................................................................84

6.6.2 FCB Functions.......................................................................................86

6.7 Accessing the DOS Directory ....................................................................92

6.7.1 Searching for Files using FCB Functions ....................................................94

6.7.2 Searching for Files using Handle Functions .................................................95

6.8 The EXEC Function.............................................................................. 110

6.9 Memory Allocation from DOS ................................................................ 119

6.10 DOS Filters ......................................................................................... 132

6.11 <Crtl><Break> and Critical Error Interrupts ............................................... 142

6.12 DOS Device Drivers .............................................................................. 148

6.12.1 Character Device Drivers ........................................................................ 150

6.12.2 Block Device Drivers ............................................................................. 151

6.12.3 Structure of a Device Driver .................................................................... 151

6.12.4 Device Driver Functions ......................................................................... 155

6.12.5 Clock Driver ........................................................................................ 168

6.12.6 Device Driver Calls from DOS ............................................................... 169

6.12.7 Direct Device Driver Access .................................................................... 170

6.12.8 Tips on Developing Device Drivers .......................................................... 172

6.12.9 Driver Examples ................................................................................... 172

6.12.10 CD-ROMs........................................................................................... 192

6.13 DOS Mass Storage................................................................................ 196

6.14 Tips on Compatibility between Computers ................................................206

6.15 Undocumented DOS Structures ................................................................ 208

6.16 DOS 4.0 .............................................................................................213

7. The BIOS ............................................................................................ 219

7.1 Booting the System ............................................................................... 221

7.2 Determining BIOS Version ..................................................................... 223

7.3 Determining the PC Type .......................................................................224

7.4 BIOS Screen Output Functions ................................................................226

7.4.1 The EGA and VGA BIOS .......................................................................254

7.5 Determining System Configuration using BIOS .........................................289

7.6 Determining Available RAM using the BIOS .............................................291

7.7 Accessing the Floppy Disk from the BIOS ................................................297

7.8 Accessing the Hard Disk from the BIOS .................................................... 323

7.9 Accessing the Serial Port from the BIOS ................................................... 330

7.10 The Cassette Interrupt. ........................................................................... 336

7.11 Accessing the Keyboard from the BIOS ............................. ~ ....................... 358

7.12 Accessing the Printer from the BIOS ........................................................ 384

iv
Abacus Table of Contents

7.13 Reading the Date and Time from the BIOS ................................................. 395

7.14 BIOS Variables..................................................................................... 398

8. Terminate and Stay Resident Programs ..................................................... .407

9. Sound on the PC ..................................................................................447

10. Accessing and Programming the Video Cards ............................................ .457

10.1 Anatomy ofa Video Card .......................................................................460

10.2 The IBM Monochrome Card ....................................................................469

10.3 The Hercules Graphic Card ......................................................................482

10.4 The IBM Color Card ................................,:..............................................497

10.5 EGA and VGA Cards ............................................................................. 519

10.6 Determining the Type of Video Card......................................................... 537

10.7 Accessing Video RAM from High Level Languages .................................... 554

11. Accessing and Programming the AT Realtime Clock ................................... 563

12. Keyboard Programming .......................................................................... 575

13. Expanded Memory Specification .............................................................. 597

14. Mouse Programming .............................................................................617

15. Determining Processor Types ..................................................................653

16. PC Hardware Interrupts .......................................................................... 667

17. Hard Disk Partitioning ...........................................................................687

18. The PC Ports .......................................................................................699

19. Interaction between Keyboard, BIOS and OOS ............................................701

Appendices ...................................................................................................... 709

A. Important Hardware Interrupts ..................................................................710

B. BIOS Interrupts and Functions .................................................................713

C. DOS Interrupts and Functions ................................................................. 766

D. EMM Functions ................................................................................... 849

E. EGNVGA BIOS Functions .................................................................... 856

F. Mouse Driver Intemlpts ......................................................................... 882

G. Introduction to Number Systems ..............................................................900

H. Glossary of Terms.................................................................................903

I. Scan Codes .......................................................................................... 918

J. ASCII Character Set .............................................................................. 919

Index ............................................................................................................. 921

v
Chapter 1

Introduction

A few years ago, my computer was a small home computer. When I became
interested in the IBM PC, I had to learn a lot of new things. I learned about MS­
DOS and became familiar with 8088 assembly language. I soon reached a point
where I started developing commercial PC programs in partnership with my friend
Axel Sellemerten. All of this happened some time ago, but I still clearly
remember sitting at my desk, looking through dozens of PC books and technical
manuals, trying to find a critical piece of information.

These books and manuals were expensive and hard to find. Besides, none of them
covered all aspects of the PC. Some books tell you about PC hardware m: the
BIOS QI DOS. I could never find a book that dealt with the PC as a total system.
No single book was able to provide me with a complete system overview.

This book is the result of my experience with all of these references. The three
main areas of the PC (hardware, the BIOS and DOS) are combined in this book
from a software developer's point of view. This book was written to serve as an
instruction book as well as a reference manual. It is not, and was never intended to
be, a book for the beginner. The book assumes that you're familiar with MS-DOS
and are able to program in one of the four most popular PC programming
languages (machine language, BASIC, Pascal or C).

Organization

The book is divided into five parts. Part 1 (Chapters 1-5) gives an introduction to
the PC's internal components. Part 2 (Chapter 6) describes the Disk Operating
System (DOS) and Part 3 (Chapter 7) describes the Basic Input Output System
(BIOS). PC hardware that is not part of the central processor is discussed in Part 4
(Chapters 8-18). Part 5 (Chapter 19) describes the interaction between these
components and the keyboard. The book concludes with a large reference section
(Appendices) containing all functions of DOS and the BIOS, among other things.

To understand the content of this book, you must first know something about the
different number systems used in computers. Readers unfamiliar with the binary

1
1. Introduction PC System Programming

and hexadecimal number systems should read Appendix G (Introduction to Number


Systems) before continuing.

Chapters 2 through 5 contain descriptions of PC microprocessors and interrupts. If


you're an experienced assembly language programmer you can skip these chapters,
but you may learn something new by reading them anyway.

BASIC, Pascal and C programmers should read Chapters 2 and 3 and should work
through the subsections in Chapter 4 devoted to your preferred language. Chapter 5
is devoted exclusively to assembly language programming and may be skipped.

2
Chapter 2

The PC's Brain

While working with the PC, many users have wondered about its ability to solve
complex problems. Users often attribute these abilities to the software or operating
system. The fact is, hardware is as important as the software.

Microprocessor
The microprocessor is the brain of the PC. It understands a limited number of
machine language instructions and processes or executes programs in this machine
language. These instructions are very simple and can't be compared to commands
in high level languages such as BASIC, Pascal or C. Commands in these
languages must be translated into a large number of machine language instructions
that the PC's microprocessor can then execute. For example, displaying text with
the BASIC PRINT statement requires the equivalent of several hundred machine
language instructions.

Machine language instructions differ for each microprocessor used in different


computers. When you hear someone talk about Z-80, 6502 or 8088 machine
language, these terms refer to the microprocessor being programmed.

Intel's 80xx series

The PC has its own family of microprocessor chips, all designed by the Intel
Corporation. The figure on the next page describes the Intel 80xx family tree.
Your PC may contain an 8086, an 8088 (used in the PClXn, an 80186, an 80286
(used in the An or even an 80386 microprocessor. The first generation of this
group (the 8086) was developed in 1978. The successors of the 8086 were different
from the original chip. The 8088 is actually a step backward since it has the same
internal structure and instructions of the 8086, but is slower than the 8086. The
reason is that the 8086 transfers 16 bits (2 bytes) between memory and the
microprocessor at one time. The 8088 is slower since it transfers only 8 bits (1
byte) at one time.

3
2. The PC's Brain PC System Programming

Multiprocessing

The three other microprocessors of this family are improved versions of the 8086.
The 80186 offers auxiliary functions. The 80286 has additional registers and
extended addressing capabilities. The 80286's biggest advantages over its
predecessors are its multiprocessing and virtual memory capabilities.
Multiprocessing allows several programs to execute at the same time, such as
compiling a program while using a word processor. This capability, which is
based on the fast switching between the individual programs, can also be
implemented through software (e.g., Microsoft Windows®), but working directly
through the processor is more efficient.

Virtual memory

Virtual memory means that a program appears to use much more memory than is
available in the computer's RAM. Portions of the programs or data which don't fit
into memory are temporarily stored on the mass storage device (floppy or hard
disk). The computer loads these sections into RAM as needed. The CPU and the
operating system share the task of virtual memory management. Earlier versions
of MS-DOS don't support the multiprocessing or virtual memory capabilities of
the 80286, so most AT computers aren't working to their full potential.

80486

8028~
6 8~?A •

t
Relative
power

74 75 76 77 78 79 80 81 8283 84 85 86 8788 89 90
Year

The Intel80xx processor family

4
Abacus 2. The PC's Brain

The 80386 represents current state of the art technology. It has a more extensive
instruction set than the 80286, and offers additional memory protection features.

These processors are all upwardly compatible with software. This means that
machine language programs developed for the 8086 can be executed on the other
processors of this series. On the other hand, a program written for the 80386 may
not run correctly on the 80286 or the 8088, because instructions available on the
80386 may not be available in the earlier processors.
Throughout this book the PC processor is designated as the 8088, even though
your PC may use a different processor.

5
2. The PC's Brain PC System Programming

2.1 8088 Registers


Registers are memory locations within the processor itself, instead of in RAM.
These registers can be accessed much faster than RAM. In addition, registers are
specialized memory locations. The CPU perfonns arithmetic and logical operations
using its registers.

Common Registers Segment Registers


15 87 0
AX ACCUMULATOR OS DATA SEGMENT
AH I AL
BX ES EXTRA SEGMENT
BASE

BH I Bt
CODE SEGMENT
CS
Cif9~LC( COUNT

SS STACK SEGMENT
Dif~~LD( DATA

01 DESTINATION INDEX
Program Counter
SI SOURCE INDEX
\ INSTRUCTION
IP POINTER
SP STACK POINTER \
BP BASER POINTER

Flag Register
_OIDIIITlslz~
15 11 10 9 8 7 6 4 2 0

8088 registers

All registers are 16 bits (2 bytes) in size. If all 16 bits of a register contain a I,
this is the largest number that can be represented within 16 bits. This number is
the decimal number 65535. Therefore, a register can contain any value from 0 to
65535.

Register groupings

As shown in the above figure, registers are divided into four groups: common
registers, segment registers, the program counter and the flag register. The different
register assignments are designed to duplicate the way in which a program
processes data-which is the basic task of a microprocessor.

The disk operating system and the routines stored in ROM use the common
registers a great deal, especially the AX, BX, ex and DX registers. The contents
of these registers tell DOS what tasks it should perfonn and which data to use for
execution.

6
Abacus 2.1 8088 Registers

These registers are affected mainly by mathematical (addition, subtraction, etc.) and
input/output instructions. They are assigned a special position within the registers
of the 8088 because they can be separated into two 8-bit (I-byte) registers. Each
common register may be thought to consist of three registers: a single 16-bit
register, or two smaller 8-bit registers.

bit 15 bit 8 bit 7 bit 0

AL

bit 15 bit 0

AX register

The registers have designators of H (high) and L (low). Thus the 16-bit AX
register may be divided into an 8-bit AH and an 8-bit AL register. The H and the L
register designators occur in such a way that the L register contains the lower 8
bits (bit 0 through 7) of the X register, and the H register the higher 8 bits (bits 8
through 15) of the X register. The AH register consists of bits 8-15 and the AL
register of bits 0-7 of the AX register. However, the three registers cannot be
considered independent of each other. For example, if bit 3 of the AH register is
changed, then the value of bit 11 of the AX register also changes automatically.
The values change in both the AH and the AX registers. The value of the AL
register remains constant since it is made of bits 0-7 of the AX register (bit 11 of
the AX register does not belong to it). This connection between the AX, the AH
and the AL register is also valid for all other common registers and can be
expressed mathematically.

You can determine the value of the X register from the values of the H and the L
registers, and vice versa. To calculate the value of the X register, multiply the
value of the H register by 256 and add the value of the L register.

Example: The value of the CH register is 10, the value of the CL register is
118. The value of the CX register results from CH*256+CL, which
is 10*256+ 118 = 2678.

Specifying register CH or CL, you can read or write an 8-bit data item from or to
any memory location. Specifying register CX, you can read or write a 16-bit data
item from or to a memory location.

7
2. The PC's Brain PC System Programming

2.2 Segment and Offset Addressing


One of the design goals of the 8088 was to provide an instruction set that was
superior to the earlier 8-bit microprocessors (6502, Z80, etc.). A second goal was
to provide easy access to more than 64 kilobytes of memory. This goal was of
special importance since increasing processor capabilities allow programmers to
write more complex applications, which in tum require more memory. The
designers of the 8088 increased the memory capacity or address space of the
microprocessor by more than 16 times to one megabyte.

Address register
The number of memory locations which a processor can access depends on the
width of the address register. Since every memory location is accessed by
specifying a unique number or address, the maximum value contained in the
address register determines the address space. Earlier microprocessors used a 16-bit
address register enabling access to addresses from 0 to 65535. This corresponds to
the 64K memory capacity of these processors.

To address one megabyte of memory the address register must be at least 20 bits
wide. At the time the 8088 was developed, it was impossible to use a 20-bit
address register, so the designers used an alternate way to achieve the 20-bit width:
the contents of two different 16-bit numbers are used to form the 20-bit address.

Segment register
One of the numbers is contained in a segment register. The 8088 has four segment
registers. The second number is contained in another register or in a memory
location. To form a 2O-bit number, the contents of the segment register are shifted
left by 4 bits (thereby multiplying the value by 16) and the second number is added
to the first

Segment and offset addresses


These addresses are the segment address and the offset address. The segment address
is formed by a segment register and indicates the start of a segment of memory.
During the address formulation, the offset address is added to the segment address.
The offset address indicates the number of the memory location within the segment
whose beginning was defmed by the segment register. Since the offset address can
never be larger than 16 bits"a segment can be no larger than 65,535 bytes (64K).
Segmented address
The segmented address results from the combined segment and offset addresses.
This segmented address specifies the exact number of the memory location which
should be accessed. Unlike the segmented address, the segment and the offset
addresses are relative addresses or relative offsets.

8
Abacus 22 Segment and Offset Addressing

Logical
address
1514 13 ... •.. 210 0ff
(16 bits
I I I I I I I I I I I I I I I I Iadd~:~s
Physical 191817 •..
- BIT

. .. 2 1 0

(2~dr:I~:) IIIIIIIIIIIIIIIIIIII I BIT

Memory structure using segment and offset addresses


A segment cannot start at everyone of the million or so memory locations.
Multiplying the segment register by 16 always produces a segment address that is
divisible by 16. For example, it's not possible for a segment to begin at memory
location 22.

Combining the segment and offset addresses requires special notation to indicate a
memory location's address. This notation consists of the segment address in four­
digit hexadecimal format, followed by a colon, and the offset address in four-digit
hexadecimal format. For example, a memory location with a segment address of
2000H and an offset address of AF3H would appear in this notation as 2000:0AF3.
Because of this notation, you can omit the H suffIx from hexadecimal numbers.

9
2. The PC's Brajn PC System Programming

::J
...
n
CI)

= 1104H
-
CI)
::J
CI)
Q.

Segment 3
CI)

3
Offset ...
o
'<
II)
Q.

...
Q.
CI)
til
Segment til
CI)
address = 1600H til

Segment and offset address

The 8088 has four segment registers, which have special roles in the execution of
an assembly language program. There are four registers to accommodate the basic
structure of any program. A program consists of a set of instructions (code). There
are also variables and data items that are processed by the program. A structured
program keeps the code and data separate from each other while they reside in
memory. Assigning code and data their own segments conveniently separates
them.

Each needs a segment address and a segment register. The CS (Code Segment)
register uses the IP (Instruction Pointer) register as the offset address. The CS then
determines the address at which the next assembly language instruction is located.
The IP is also called the Program Counter. When the processor executes the
current instruction, the IP register is automatically incremented to point to the
next assembly language instruction. This ensures the execution of instructions in
the correct order.

Like the CS register, the DS (Data Segment) register contains the segment address
of the data which the program accesses (writing or reading data to or from

10
Abacus 22 Segment and Offset Addressing

memory). The offset address is added to the content of the DS register and may be
contained in another register or may be contained as part of the current instruction.

The SS (Stack Segment) register specifies the starting address of the stack. The
stack acts as temporary storage space by some assembly language programs. It
allows fast storage and retrieval of data for various instructions. For example,
when the CALL instruction is executed, the processor places the return address on
the stack. The SS register and either the SP or BP registers form the address that is
pushed onto the stack.

The last segment register is the ES (Extra Segment) register. It is used by some
assembly language instructions to address more than 64K of data or to transfer data
between two different segments of memory.

ES:FFFF ES: FFFF


CS:FFFF
ES:OOOO ES:OOOO
CS: FFFF CS:OOOO

CS:OOOO

SS:FFFF

SS:FFFF

SS:OOOO DS:FFFF

DS:FFFF SS:oooo

DS:OOOO

DS:OOOO

Non-overlapplng Overlapping
segments segments

Overlapping and non-overlapping segments

As the figure above shows, two segment registers can specify areas of memory
which overlap, or are completely different from one another. In many cases, a
program doesn't require a full 64K segment for storing code or data. You can
conserve memory by overlapping the segments. For example, you can store data
immediately following the program code by setting the DS and CS registers
accordingly.

11
2. The PC's Brain PC System Programming

The flag register is of special importance. Various bits in this register indicate or
signal the special conditions which may occur during execution of an assembly
language instruction. For example, if an arithmetic operation results in a negative
number, the processor sets the S (sign) flag to 1 to indicate this change.

The C (carry) flag is set to 1 if the sum of two 8-bit numbers cannot be
represented as an 8-bit number.

As the figure above shows, the processor doesn't use all 16 bits of this register.
The unused bits normally contain the value O.

This ends our short trip into the PC's brain. If you didn't quite follow some of
these concepts, the sample application programs in the sections on the BIOS and
DOS functions should help you understand.

12
Abacus 2.3 The CPU Support Chips

2.3 The CPU Support Chips


The microprocessor is the computer's brain, and is probably the most intelligent
component in a computer system. However, it cannot supervise all the computer's
functions on its own. For this reason, other components called support chips
perform many other tasks, leaving the processor to concentrate on its primary task
of executing assembly language programs.

These support chips communicate with and control external peripherals such as a
disk drive or the screen display.

Some of these support chips can be programmed using the assembly language
instructions IN and OUT. Since the programming of most support chips is very
complex, we recommend that you leave this up to DOS, unless you have a
complete understanding of the structure and operation of these chips.

The following sections define the most important support chips in the PC.

2.3.1 The DMA Controller

This chip gets its name from the acronym DMA which stands for Direct Memory
Access. This chip can directly write data to or read data from RAM. The DMA
controller performs disk input/output operations, moving data from RAM to disk
or from disk to RAM. This relieves the processor of this task and accelerates
program execution.

2.3.2 The I nterrupt Controller

Interrupts are signals from individual components of the system to get the CPU's
attention and to initiate certain tasks. Several interrupts or requests for services
from different system components can be outstanding at one time. These requests
are initially handled by the interrupt controller, which passes them on to the CPU.
It assigns priority to every interrupt request according to its source and passes the
request with the highest priority to the CPU. The interrupt controller in the
PC/XT can process up to 8 interrupt requests at the same time. ATs require more
power, so they use two interconnected interrupt controllers which can process up
to 15 interrupt requests simultaneously.

2.3.3 The Programmable Peripheral Interface

This chip provides a link between the CPU and the peripherals such as the
keyboard or an audio speaker. However, it only operates as a mediator, addressed by
the CPU for unit access and transmission of certain signals. You cannot bypass
the PPI for direct communication between the CPU and peripherals.

13
2. The PC's Brain PC System Programming

2 .3 . 4 The Clock

If the microprocessor is the brain of the computer, then the clock could be
considered the heart of the computer. This heart beats several million times a
second (about 14.3 megaHertz) and paces the microprocessor and the other chips in
the system. Since almost none of the chips operate at such high frequencies, each
support chip modifies the clock frequency to its own requirements.

2.3.5 The Timer

The timer chip can be used as a counter and timekeeper. This chip transmits
constant electrical pulses from one of its output pins. The frequency of these
pulses can be programmed as needed, and each output pin can have its own
frequency. Each output pin leads to another component. One line goes to the audio
speaker and another to the interrupt controller. The line to the interrupt controller
triggers interrupt 8 at every pulse, which advances the timer count.

2 .3 . 6 The Screen Controller

Unlike the chips discussed up until now, the CRT (Cathode Ray Tube) controller
is separate from the main circuit board of the PC. You'll find this chip on the
video board which is mounted in one of the computer's expansion slots. Even
though there are many boards that differ widely in their capabilities (monochrome
display, color display, etc.), all video boards are based on the 6845 CRT controller.
It produces a display on the monitor connected to the computer. The controller has
several internal registers which control the output of the display.

2 .3.7 The Disk Controller

This chip is also usually located on an expansion board. It is addressed by the


operating system and controls the functions of the disk drive. It moves the
read/write head of the disk drive over the disk, reads data from the disk and writes
data to the disk.

2.3.8 The Math Coprocessors (8087/80287/80387)

The 8088, 80286 and the 80386 are not capable of performing floating point
arithmetic operations directly. There is a socket on the main circuit board of the
PC for adding a special math coprocessor. The PC/XT uses the 8087, the AT the
80287 and the new 80386 uses the 80387 coprocessor.

While floating point arithmetic can be performed using software routines, a math
coprocessor is up to 100 times faster. The 8087 and the 80287 can perform basic

14
Abacus 23 The CPU Support Chips

math functions such as addition, subtraction, multiplication and division,as well


as the trigonometric functions sine, cosine, etc. They can also compute square
roots of numbers.

In general, only a few application software packages support the math


coprocessors.

15
2. The PC's Brain PC System Programming

2.4 The CPU and Memory


r
While the chips described up until now are intelligent system components,
memory is a passive element. Data can be stored and later retrieved from memory.
Each memory location is used to store one byte (8 bits) of data. Memory locations
are identified by a unique address, starting from zero.

The support chips communicate with memory using a bus or path over which the
electronic signals travel.

Address bus
The address bus carries the number of the memory location to be accessed. The
signals on the bus represent a binary number whose value indicates the memory
location for access. Since only those memory locations represented on the address
bus can be accessed, the number which make up the bus lines determine the
number of addressable memory locations .

. The PC/XT has a 20-bit address bus and can address a maximum of 2'}J) (about 1
million) different memory locations. The AT has a 24-bit address bus and can
address more than 16 million memory locations.

Data bus

Once the bus knows the address of the memory location to be accessed, data can be
transferred between the individual chips and the memory location over the data bus.
The number of lines in this circuit determine how many bits are transferred to or
from memory simultaneously.

The PC/XT has 8 lines so it can transfer one byte at a time. However, since the
8088 is a 16-bit processor, 16-bit data must often be transferred. There aren't
enough lines to transfer 16-bit data, so the system divides a 16-bit data item into
two 8-bit numbers. These two 8-bit data bytes are transferred one after the other
along the bus.

The 8086 and 80286 processors can transfer 16 bits simultaneously over their 16­
bit-wide data buses. This is one reason why the AT executes programs faster than
the 8088 processor. The 80386 processor can transfer 32 bits at a time.

Word storage

All members of the Intel 80xx processor family share the same method of storing
words (16-bit data) in memory. The lower numbered memory location contains
bits 0-7 (the low byte) and the higher numbered memory location contains bits 8­
15 (the high byte). For example, if you store the word 3F87H starting at address
0000:0400, memory location 0000:0400 accepts the low byte 87H and memory
location 0000:0401 accepts the high byte 3FH.

16

Abacus 2,4 The CPU and Memory

Two details were left out of the discussion of memory so far:

1.) The processor doesn't care if a memory address is located in a RAM chip
or a ROM chip. The main difference between RAM and ROM lies in the
fact that you can't write or store new data into ROM (hence its name:
Read Only Memory).

2.) The addressable space of the microprocessor (1 megabyte) is allocated into


16 storage segments of 64K each. This is an almost universal division
used on IBM PC/XTs and most compatible machines.

IBlock " 1n.... "T'iotion

FOOII: IBIOS ROM

J- RC ca.:r LC:tq~
RC cartr Ldoe
ac Lt~ona H JS RUM
I,; .eo RAM
1: : itio:lal ieo RAM
uo :0
1-: uo :0
,-' UO
110
0
:0
,-!
,-I
,-:
UO
UO
'n
:0
uo :0

,-; 110 :0

un :0

) 1000 :00 '-U ':

Memory allocation

The first 10 memory segments are reserved for the main RAM memory, limiting
maximum RAM to 640K. A computer's memory size may differ from one PC
manufacturer to another but has at least 64K installed in segment O. If you install
additional RAM, its first memory address must immediately follow the last
existing memory address, since no gaps may exist between individual RAM
memory segments. Memory segment 0 has a special role since it contains
important data and operating system routines.

Memory segment A follows the RAM memory. In this case, an EGA (Extended
Graphics Adapter) is installed. This board uses the memory for the screen display
in different graphic modes.

Memory segment B is reserved for a monochrome or color graphics board. They


share the segment as screen memory. The monochrome board uses the lower 32K
and the color board uses the upper 32K. Each board uses only as much memory as
it needs for the screen display. The monochrome board uses 4K; the color board
uses 16K because of the additional color capabilities.

17
2. The PC's Brain PC System Programming

The next memory segment contains ROM beginning at segment C. Some


computers store the BIOS routines which aren't part of the original BIOS kernel at
this location. For example, the XT uses these routines for hard disk support. Since
this area isn't fully utilized, it is possible that BIOS routines supporting future
hardware enhancements will also be placed in this memory range.
ROM cartridges
Segments D and E are reserved for ROM cartridges. These cartridges extend the
computer with certain ROM routines. The PC has rarely used them and the area
usually remains unused.

Segment F contains the actual BIOS routines, the system loader and the ROM
BASIC available on many computers.

18
Chapter 3

Introduction to Interrupts

This chapter presents a view of interrupts, which are vitally important to the
operation of the 8088 processor. An interrupt is a signal from a peripheral device
or a request from a program to perform a specific service. When an interrupt
occurs, the currently executing program is temporarily suspended and an interrupt
routine begins execution to handle the condition that caused the interrupt

Program Interrupt routine

"tJ Save register contents


....
o
c.c
....
D)
3
CD
~
()
c: Restore register contents
!:!:
o
:l
IRET

Program interrupt
When a program is suspended, the processor saves the contents of the CS and IP
registers on the stack, and begins the interrupt routine. After the interrupt routine
has completed its task, it issues the IRET (Interrupt RETurn) instruction which
restores the contents of the CS and IP registers from the stack, thus resuming the
program.

The interrupt routine saves and restores contents of the other registers before
returning to the interrupted program.

19
3. Introduction to Interrupts PC System Programing

3.1 The Structure of the Interrupt Vector Table


So far we've talked about a single interrupt and a single interrupt routine. In fact,
the 8088 has 256 possible interrupts numbered from 0 to 255, not just one.

Each interrupt has an associated interrupt routine to handle the particular condition.
To organize the 256 interrupts, the starting address of the corresponding interrupt
routines are arranged in the interrupt vector table.

When an interrupt occurs, the processor automatically retrieves the starting address
of the interrupt routine from the interrupt vector table.

The starting address of each interrupt routine is specified in the table in terms of
the offset address and segment address. Both addresses are 16 bits (2 bytes) wide.
Therefore each table entry occupies 4 bytes. The tota1length of the table is 256*4
or 1024 bytes (IK).

Interrupt Purpose

0000:003FE
,...---
cs
... number:
Free
255
0000:003FC IP

~
OOOO:OOOE CS
OOOO:OOOC 3 Breakpoint
IP
OOOO:OOOA CS
0000:0008 IP
2 NMI

0000:0006 CS

1 Single-step
0000:0004 IP
0000:0002 CS
IP

o Division by 0
0000:0000
15 o

Interrupt vector table

The table itself is located in memory from OH to 3FFH. Since the interrupt's
number is the same as the table entry for the corresponding interrupt routine, the
interrupt routine address for interrupt 0 is the zero table entry in locations OH-3H.

20
Abacus 3.1 The Structure of the Interrupt Vector Table

Memory locations 4H-7H contain the address for the interrupt routine for
interrupt I, etc. The last interrupt, interrupt 255, occupies the end of the table at
locations 3FCH-3FFH.

To calculate the starting address of an interrupt, simply multiply the interrupt


number by four.

Advantages

An advantage of using the interrupt vector table is that it's easy to change an entry
in the table to the starting address of a user-written interrupt routine. This makes a
new interrupt routine available to any program which can invoke the routine
simply by executing the corresponding interrupt instruction.

The next section explains the different types of interrupts and how they are used in
the system.

21
3. Introduction to Interrupts PC System Programming

3.2 Interrupt Types


Until now. we haven't talked about different types of interrupts. There are two
major types of interru~hardware interrupts and software interrupts.

The figure below shows the different interrupt types.

Interrupt types

3.2.1 Software Interrupts

A software interrupt is an interrupt called by the !NT instruction in a machine


language program. The INT instruction includes the number of the interrupt to be
signalled. For example, the instruction to call interrupt 5, which sends a hardcopy
of the current screen to the printer, appears as !NT 5. The INT instruction allows
you to call anyone of the 256 interrupts.

Software interrupts make it possible to use many of the basic operating system
services from either the assembler (or machine language) level or from many of the
higher level languages which support interrupt processing.

3.2.2 Hardware Interrupts

A hardware device such as a disk drive or keyboard can trigger a hardware interrupt.
This is a simple and efficient mechanism for handling events which require
attention.

One example is the keyboard. When you press or release a key, interrupt 9 (the
keyboard interrupt) is signalled. The standard DOS interrupt routine responds by
placing the character value corresponding to the key that was pressed into the

22
Abacus 3.2 Interrupt Types

keyboard buffer following any value which may have been previously there. If the
keyboard buffer is full, the routine generates a short beep. As in any other
interrupt, the original program continues after the completion of the interrupt
routine.

Maskable interrupts

This interrupt is designated as an external hardware interrupt, because it was


triggered by an external device. For these interrupts, a distinction is also made
between maskable and non-maskable interrupts. The keyboard interrupt just
described belongs in the maskable interrupt category. You can mask (disable) this
interrupt by using the assembler instruction STI (SeT Interrupt flag). If you mask
interrupt 9H, the keyboard ignores any characters you type. To reverse this
condition, use the CLI instruction (CLear Interrupt flag) to re-enable the interrupt.

Non-maskable interrupts
In contrast, a non-maskable interrupt cannot be disabled by the STI instruction.
One example is interrupt 2. This interrupt indicates an error in the PC's memory.
It displays a message on the screen that one or more of the RAM chips is defective
and should be replaced.

The last interrupt type to be described is the internal hardware interrupt. The
processors on the main circuit board of the PC trigger this interrupt. One example
is interrupt 8 which is designated as a timer interrupt. The timer triggers this
interrupt at a rate of 12.8 times per second. It also disables the disk drive motor if
no disk access is in progress.

23
3. Introduction to Interrupts PC System Programming

3.3 Interrupts at a Glance


The tables here show the significance which these interrupts occupy in the control
and use of the PC. The next few chapters explain these interrupts in more detail.
, Nr. Vector Purpose
00 000 - 003 CPU: Division by zero
01 004 007 CPU: Single step
02 008 OOB CPU: NMI (Error in RAM chip)
03 OOC OOF CPU: Breakpoint
04 010 - 013 CPU: Numeric overflow
05 014 -017 Hardcopy
06 018 - 01B Unknown instruction (80286 only)
07 OlD - 01F reserved
08 020 - 023 IRQO: Timer (Call 18.2 per/sec.)
09 024 - 027 IRQ1: Keyboard
OA 028 - 02B IRQ2: Second 8259 (AT only)
OB 02C - 02F IRQ3: Serial interface 2
OC 030 - 033 IRQ4: Serial interface 1
00 034 - 037 IRQ5: Hard disk
OE 038 - 03B IRQ6: Diskette
OF 03C - 03F IRQ7: Printer
10 040 - 043 BIOS: Video functions
11 044 - 047 BIOS: Determine configuration
12 048 - 04B BIOS: Determine RAM storage size
13 04C - 04F BIOS: Diskette/hard disk functions
14 050 - 053 BIOS: Access to serial interface
15 054 - 057 BIOS: Cassette/enhanced functions
16 058 - 05B BIOS: Keyboard sensing
17 05C - 05F BIOS: Access to parallel printer
18 060 - 063 Call of ROM-BASIC
19 064 - 067 BIOS: System boot (ALT+CTRL+DEL)
1A 068 - 06B BIOS: Read time/date
1B 06C - 06F Break key not activated (not CTRL-C)
1C 070 - 073 called after every INT 08
1D 074 - 077 Address of the video parameter table
1E 078 - 07B Address of the disk parameter table
1F 07C - 07F Address of the character bit pattern
20 080 - 083 DOS: Terminate program
21 084 - 087 DOS: Call DOS function
22 088 - 08B Address of DOS end of program routine
23 08C - 08F Address of DOS CTRL-BREAK routine
24 090 - 093 Address of DOS error routine
25 094 - 097 DOS: Read diskette/hard disk
26 098 - 09B DOS: Write diskette/hard disk
27 09C - 09F DOS: End Prg., remain resident
28­ OAO ­ Reserved for various, non-
3F - OFF documented DOS functions
40 100 - 103 BIOS: diskette functions
41 104 - 107 Address of hard disk table 1
42­ 108 ­ Reserved
45 - 117
46 118 - 11B Address of hard disk table 2
47­ 11C ­ can be used by application programs
49 - 127 for any purpose

24
Abacus 3.3 Interrupts at a Glance

Nr. Vector P~ose


4A 128 - l2B Alarm time reached (AT only)
4B- l2C - Can be used by application programs
67 - 19F for any purpose
68- lAO - Unused
6F - lBF
70 1CO - 1c3 IRQ08: Realtime clock (AT only)
71 1c4 - lC7 IRQ09: (AT only)
72 lC8 - 1CB IRQ10: (AT only)
73 1CC - 1CF IRQll: (AT only)
74 100 - 103 IRQ12: (AT only)
75 104 - 107 IRQ13: 80287 NMI (AT only)
76 108 - lOB IRQ14: Hard disk (AT only)
77 10C - 10F IRQ15: (AT only)
78- lEO - Unused
7F - lFF
80- 200 - Used by the BASIC
FO - 3C3 interpreter
Fl- 3C4 - Unused
FF - 3CF

General overview--interrupts

25
Chapter 4

Using Interrupts from High


Level Languages

The assembly language programmer can invoke an interrupt by loading the


parameters required by the interrupt routine into designated registers and executing
the INT instruction. Although these capabilities aren't available in all higher level
languages, some languages such as Turbo Pascal®, Turbo C® and Microsoft C®
have built-in functions, procedures or subroutines to call the interrupt.

A BASIC programmer can call an interrupt using a short assembly language pro­
gram. You'll find an example of this in Section 4.1.

This chapter provides information on calling interrupts from Pascal, BASIC and
C. Each describes how interrupts can be called in the particular language and the
rules the programmer must observe. Each section concludes with a short
demonstration program.

Read through the section devoted to the language with which you feel most
comfortable. A comparison of the three sample programs could be interesting for
those of you who wish to compare the similarities and differences in the three
languages.

The programs are only examples. Experiment as much as you want-you won't
damage your computer if you change them a little.

27
4. Using Interrupts from High Level Languages PC System Programming

4.1 Interrupt Calls from BASIC


The two most commonly used BASIC interpreters are BASICA (from IBM) and
GW-BASIC (from Microsoft). This book refers to GW-BASIC, since it can be
used on IBM PCs as well as any compatible PC. The command sets of both are
nearly identical.

GW-BASIC does not have a function for calling interrupts. However, the CALL
command can be used to execute a machine language program. You can also use
the CALL command to pass certain parameters to the called program. The called
machine language program must be located in the 64K used by GW-BASIC for
program statements and variable storage. Because of this, the interpreter must be
told to reserve part of program memory for the machine language routine.
Otherwise the program or variables may overwrite the machine language routine,
causing a system crash. You can reserve memory directly when you call BASIC
from the operating system. Enter the name GWBASIC followed by the 1M:
parameter. After the colon, enter the highest memory location you want used by
BASIC. For example, since the sample program starts at memory location 60000,
start the GW-BASIC interpreter as follows:
gwbasic /m:60000

This reserves the required memory space. Now you can place the machine language
routine into memory by making it part of the current BASIC program and loading
it into memory using a suitable subroutine. The current BASIC program must
contain the following commands:
60000
60010
60020
•• initialize the routine for the interrupt call .
.***************.****************** •• *******.******.*.**********1
'
,* ____________________________________________________----_____ *1
60030 '. Input: none
60040 '. Output: IA is the Start address of the Interrupt routine *,
60050 1.********************************.*.*.******.****.*************'
60060 '
60070 IA=60000! 'Start address of the routine in the BASIC segment
60080 DEF SEG 'set BASIC segment
60090 RESTORE 60130
60100 FOR 1% = 0 TO 160 : READ X% : POKE IA+I%,X% : NEXT 'poke Routine
60110 RETURN 'back to caller
60120 '
60130 DATA B5, 139, 236, 30, 6,139, llB, 30,139, 4,232,140, 0,139, llB
60140 DATA 12,139, 60,139,118, 8,139, 4, 61,255,255,117, 2,140,216
60150 DATA 142,192,139,118, 28,138, 36,139,118, 26,138, 4,139,118, 24
60160 DATA 138, 60,139,118, 22,138, 28,139,118, 20,138, 44,139,118, 18
60170 DATA 138, 12,139,118, 16,138, 52,139,118, 14,138, 20,139,118, 10
60180 DATA 139, 52, 85,205, 33, 93, 86,156,139,118, 12,137, 60,139,118
60190 DATA 28,136, 36,139,118, 26,136, 4,139,118, 24,136, 60,139,118
60200 DATA 22,136, 28,139,118, 20,136, 44,139,118, 18,136, 12,139,118
60210 DATA 16,136, 52,139,118, 14,136, 20,139,118, 8,140,192,137, 4
60220 DATA 88,139,118, 6,137, 4, 88,139,118, 10,137, 4, 7, 31, 93
60230 DATA 202, 26, 0, 91, 46,136, 71, 66,233,108,255

The DATA statements contain the machine language routine which performs the
interrupt call. The routine is READ and then POKEd into memory. To start this
routine at another memory location, change the value in line 60070. Remember

28
Abacus 4.1 Inlerrupt Calls from BASIC

that the parameters used to start GW-BASIC must also be changed so that the
routine cannot be overwritten by the variables of the program.

To use the machine language routine to call an interrupt, this subroutine must of
course be called frrst. The frrst line of the user program should therefore be:
100 GOSUB 60000

The actual program which calls the interrupt function during its execution can be
stored between line numbers 100 and 60000. The following program line
demonstrates how this can be done:
200 CALL IA(INTNRt,AHt,ALt,BHt,BLt,CH',CL\,DH\,DLt,Dlt,SI\,ES\,FLAGSt)

The variables within parentheses are the variables passed to the assembly language
program. All variables must pass true integer variables and not constants. The
variable names mentioned above may be changed but their order must remain
unchanged. Within your program they can have other names.

The frrst variable in this example, called INTNR%, is the number of the interrupt
you want to call. Be careful to specify the exact interrupt number. Also, avoid
passing a variable which has not been initialized. Otherwise, you may call the
wrong interrupt, which could lead to a system crash. The variables following
INTNR % are copied into the processor registers of the same names. If a register is
not used by an interrupt routine, you can pass any integer variable in the
corresponding register variable. The value of the ES register is treated differently. If
the value of ES% is -1, the contents of the DS register is copied to the ES
register.

Following the completion of the interrupt call, the values are returned in the
designated register variables.

This technique works only with half registers (AH, AL, BH ... ). It may be
necessary to transform these half registers into a whole register. This can be done
as follows:
300 AX' - AH' * 256 + AL\

On the other hand, a whole register can be split into two half registers with the
following commands:
410 AHt - INT (AXt / 256)
420 AL\ - AX' AND 255

After calling interrupt functions, the carry flag in the flag register indicates if the
called functions were executed correctly. In a BASIC program, it may be necessary
to test the carry or zero flags. Since the content of the flag register is in the
variable FLAGS% after the interrupt call, the status of individual flags can be
inspected through this variable. This is possible with the following program
statements:

29
4. Using Interrupts from High Level Languages PC System Programming

200 IF FLAGS% AND 1=0 THEN PRINT "CARRY-FLAG OFF" ELSE

PRINT "CARRY-FLAG SET"

210 IF FLAGS' AND 64.=0 THEN PRINT "ZERO-FLAG OFF" ELSE

PRINT "ZERO-FLAG SET"

Another problem with interrupt calling is passing variable addresses (e.g., character
string output). BASIC stores this set of characters as a string. To determine the
offset address of such a string (the segment address of all variables is constant), use
the VARPTR function. The LO and ill byte of the offset address can be determined
with the following two program lines:
300 LO=PEEK (VARPTR (STRING NAME) +1) 'LQ-Byte of the Offset address
310 HI=PEEK(VARPTR(STRING=NAME) +2) 'HI-Byte of the Offset address

Garbage collection

These addresses should be determined at the beginning of a BASIC program as well


as immediately before each interrupt call, since BASIC frequently performs garbage
collection (removing unused variables and junk data). Garbage collection frees up
variable memory, rearranges remaining data in memory and changes addresses. If a
string address is determined at the beginning of a program, it may change several
times before the interrupt call is made.

Remember to include an end marker ("$" or a CHR$(O» at the end of the string
(BIOS and DOS functions expect one of these).

Note: Before copying this subroutine and trying it, we have a small
suggestion. During your first attempts something will probably go
wrong. This is perfectly normal, and you can even expect the
computer to crash a couple of times. Save programs
frequently...especially ~ running the program. This way, you
won't have to type in the program again from the beginning.

Here is a short sample program which uses the subroutine described above to
display text on the screen with function 9 of interrupt 21H.
100 ,**** ••• **********.***********************************************,
110 '. I N T DOS B
120 '*---------------------------------------------------------------*,
130 '. Assignment outputs as an example of an Interrupt
140 '. a String through a DOS function on

.
150 the display screen
160 Author MICHAEL TISCHER
170 '. developed 07/30/87 ,
180 last Update 04/08/89
190 •••••••••••••••••••••••••••••••••••••••••••••••••••••• ************'
200 '
210 CLS : KEY OFF
220 PRINT"NOTE: This program can only be started if the GWBASIC was "
230 PRINT"started from the DOS level with the command"
235 PRINT"<GWBASIC /m:60000>."
240 PRINT: PRINT"If this is not the case, please input <s> for Stop."
250 PRINT"otherwise press any key ... ";
260 A$ - INKEY$ : IF A$ = "s" THEN END
270 IF A$ = "" THEN 260
280 PRINT
290 GOSUB 60000 'install function for interrupt call

30
Abacus 4.1 In.terrupt Calls from BASIC

300 TS = CHRS(13) + CHRS(10) + "this text was output through"


305 TS = TS + "Function 9 of Interrupt 21H ... S"
310 INR% = &H21 'Number of interrupt to be called
320 FKT% = 9 'Number of functions to be called
330 OFSLo\ = PEEK(VARPTR(TS)+l) 'LO-Byte Offset address to the String
340 OFSHI% = PEEK(VARPTR(TS)+2) 'HI-Byte Offset address to the String
350 CALL IA(INR%,FKT%,Z%,Z%,Z%,Z%,Z%,OFSHI%,OFSLO%,Z%,Z%,Z%,Z%)
360 PRINT : PRINT : PRINT 'output three blank lines
370 END
380 '
60000 •• *********.********************* ••••• **** •••••• ****************,
60010 '* initialize the routine for the interrupt call
60020 ,*-------------------------------------------------------------*'
60030 ,* Input: none
60040 ,* Output: IA is the Start address of the Interrupt routine
60050 •••••••••••••••••••••••••••••••••••••••••••••••••••••• **********'
60060 '
60070 IA=60000! 'Start address of the routine in the BASIC segment
60080 DEF SEG 'set BASIC segment
60090 RESTORE 60130
60100 FOR 1% = 0 TO 160 : READ X% : POKE IA+I%,X% : NEXT 'poke Routine
60110 RETURN 'back to caller
60120 '
60130 DATA 85,139,236, 30, 6,139,118, 30,139, 4,232,140, 0,139,118
60140 DATA 12,139, 60,139,118, 8,139, 4, 61,255,255,117, 2,140,216
60150 DATA 142,192,139,118, 28,138, 36,139,118, 26,138, 4,139,118, 24
60160 DATA 138, 60,139,118, 22,138, 28,139,118, 20,138, 44,139,118, 18
60170 DATA 138, 12,139,118, 16,138, 52,139,118, 14,138, 20,139,118, 10
60180 DATA 139, 52, 85,205, 33, 93, 86,156,139,118, 12,137, 60,139,118
60190 DATA 28,136, 36,139,118, 26,136, 4,139,118, 24,136, 60,139,118
60200 DATA 22,136, 28,139,118, 20,136, 44,139,118, 18,136, 12,139,118
60210 DATA 16,136,52,139,118,14,136,20,139,118, 8,140,192,137, 4
60220 DATA 88,139,118, 6,137, 4, 88,139,118, 10,137, 4, 7, 31, 93
60230 DATA 202, 26, 0, 91, 46,136, 71, 66,233,108,255

How it works

The program is composed of separate parts. Lines 210-290 call the subroutine to
initialize the machine language function for the interrupt call. Then the individual
variables for the interrupt call are loaded. T$ accepts the string to be output.
CHR$(13) and CHR$(lO) print a blank line before the output of the actual text
This text ends with the "$" character because the 005 function which outputs the
string expects this character as an end marker (it will not display this character).
INR% and FKT% contain the interrupt number and the function number to be
called. Besides these two variables, the variables OFSLO% and OFSHI% contain
the offset address of 1'$.

The CALL command (line 350) calls the interrupt. The first variable passed is
INR% with the number of the interrupt to be called. Then follows FKT%, which
transfers to the AH register before the interrupt call and informs interrupt 21H of
the function number to be called. Several Z% variables follow. These act as
dummy variables for all registers which have no special significance to the
function which is called. The content of Z% is unimportant. The content of the
register into which it is copied is irrelevant for the called function. After the Z%
variables, which determine the contents of the AL, BH, BL, CH and CL registers,
follow the variables OFSHI% and OFSLO%, which set the offset address of the
string in the DX register. The remaining register contents are unimportant for the
function call and are fIlled with Z%.

31
4. Using Interrupts from High Level Languages PC System Programming

To permit the DOS function which is called to output the text, its offset and
segment address must be known. This address is expected in the DS register and
wHI be set automatically by GW-BASIC.

To conclude this section, here is the listing of the assembler program that we just
used to call an interrupt.
:***********************************************************************
;* BASINT.ASM: This routine offers the capability of
;* calling any interrupt from BASICA or
;* GWBASIC
;*
;**-------------------------------------------------------------------**
:* Call: *
;* CALL ADR(INTNR%,AH%,AL%,BH%, BL%,CH%,CLl,OH%,OL%,OI%, SI%,ES%,FLAGS%) *
:**-------------------------------------------------------------------**
;* On passing control to the machine language program BASIC
;* deposits the variables on the following positions of the stack
;* INTNR% SP+30 AH% SP+28 ALl SP+26 BH% SP+24
;* BLl SP+22 CH% SP+20 CLl - SP+l8 OH% SP+l6
;* OLl SP+l4 OH SP+l2 SH SP+lO ES% SP+8
;* FLAGS% SP+6
i**-------------------------------------------------------------------**
;* for ES the value -1 is passed, then ES is set to OS
i***************************************************** *************** •• !

code segment

assume cs:code,ds:code,es:code,ss:code

;-- the Routine for Interrupt call ----------------------------------­


basint proc far ;GW expected during CALL far procedure

push bp ;GW base pointer saved

mav bp,sp ;Send SP to BP

push ds ;GW dta segment stored

push es iGW-extra segment saved

mov si, [bp+30] ;Get address of variable INTNR


mov ax, lsi] ;Move content of this variable to AX
call set intnr ;Store interrupt number

ad 1 label near ;Address for SET INTNR

mov si, [bp+l2] ;Get address of 01% variables


mov d1, lsi] ;Move content of variables to 01
mov si, [bp+B] ;Get address of variable ES%
mov aX,[si] ;Move content of variable to AX
crop ax,-l ;was -1 passed?
jne setes ;No --> set ES

mav ax,ds ;Set AX to OS and thereby ES os


setes: mov es,ax ;transfer AX to ES
mov si, [bp+2B] ;Get address of variable AH%
mov ah, lsi] ;Move content of variable to AH
mov si, [bp+26] ;Get address of variable AL%
mov al, lsi] ;Move content of variable to AL
mov si, [bp+24] ;Get address of variable BH%
mov bh, lsi] ;Move content of variable to BH
mov si, [bp+22] ;Get address of variable BL%
mov bl, lsi] ;Move content of variable to BL
mov s1, [bp+20] ;Get address of variable CH%
mov ch,[si] ;Move content of variable to CH
mov si,[bp+18] ;Get address of variable CLl
mov cl, lsi] ;Move content of variable to CL

32
Abacus 4.1 Interrupt Calls from BASIC

mov si, [bp+16J ;Get address of variable DH'


mov dh, [siJ ;Move content of variable to DH
mov si, [bP+14 I ;Get address of variable DL\
mov dl, [siJ ;Move content of variable to DL

mov si, [bp+l0J ;Get address of variable SIt


mov si, [siJ ;Move content of variable to SI
push bp ;Store base pointer

ad_2 label near ;Address for SET_INTNR

int 21h ;Call interrupt

pop bp ;Replace base pointer

push si ;Store SI

pushf ;Store flag register

mov si, [bp+12J ;Get address of variable DI'


mov [siJ ,di ;Move content of variable to D1
mov si, [bp+28J ;Get address of variable AHt
mov [siJ,ah ;Store AH in this variable
mov si, [bp+26J ;Get address of variable ALt
mov [siJ ,al ;Store AL in this variable
mov si, [bp+24J ;Get address of variable BH\
mov [siJ ,bh ; Store BH in this variable
mov si, [bP+22J ;Get address of variable BL%
mov [siJ ,bl ;Store BL in this variable
mov si, [bP+20J ;Get address of variable CH%
mov [siJ, ch ;store CH in this variable
mov si, [bp+18J ;Get address of variable CLt
mov [siJ, cl ;Store CL in this variable
mov si, [bp+16J ;Get address of variable DHt
mov [sil, dh ;Store DH in this variable
mov si, [bp+l4J ;Get address of variable DLt
mov [sil,dl ;Store DL in this variable
mov si, [bp+8J ;Get address of variable ESt
mov ax,es ;transfer ES to AX
mov [siJ,ax ;Store ES (AX) in this variable
pop ax ;Move flag register from stack to AX
mov si, [bp+6J ;Get address of variable FLAGS%
mov [siJ ,ax ; Store FLAGs in this variable
pop ax ;Move D1 register from stack to AX
mov si, [bp+l01 ;Get address of variable SIt
mov [siJ,ax ;Store S1 (AX) in this variable

pop es ;Get GW extra segment back

pop ds ;Get GW data segment back

pop bp ;Return GW base pointer

ret 26 ;Addresses of variables on the stack


iare no longer needed

basint endp

i---------------------------------------------------------------------­
set intnr proc near ;stores the interrupt number

pop bx

mov cs:[bx+ad 2-ad l+lJ,al

jmp ad 1 - ­

set intnr endp

;---------------------------------------------------------------------­
code ends

end

33
4. Using Interrupts from High Level Languages PC System Programmi.ng

Some brief notes on this program follow for those not familiar with the calling
and linking of assembly language programs in GW-BASIC: The program ftrst
pushes the base pointer on the stack since it will be reset by the next instruction.
During re-entry into GW-BASIC, the base pointer must have the value it had
during the call of the routine. Then the base pointer is set to the value of the stack
pointer for access to data on the stack. This is necessary for GW-BASIC to pass
the BASIC variables named in the CALL command to the stack. In the next step,
the DS and the ES registers are stored on the stack, because their content may
change during execution of the routine and must be preserved for return to GW­
BASIC.

Now the routine can read in the variables and set the various processor registers. It
is important to note that the stack does not contain variable contents, but their
addresses relative to the DS register. Because of this, the address of the variable
must be loaded fIrst and then the relative value of this address.

Which addresses contain the addresses of the individual variables stored on the stack
can be determined from the header of the assembly language routine. First you
must determine the number of the interrupt to be called. This value must be treated
in a different manner than the other variables on the stack because it isn't passed in
one of the processor registers, but is a part of the INT instruction which calls the
interrupt. It is indicated as a byte following the code of the !NT instruction (CDH).

To set the interrupt number, the number to be passed must be stored following the
CDH code of the !NT instruction. This creates a small problem since this routine
can be POKEd by the BASIC program into any memory location. Because of this,
the address of the INT instruction depends on the current starting address of the
routine instead of remaining constant. The routine doesn't know where the !NT
instruction is located.

A small trick can be used to help here. The routine does not know where it is
stored, but the processor knows the location of the !NT instruction (it has to
know, otherwise it couldn't execute the routine). The subroutine SET_INTR is
called after the interrupt number is loaded into the AX. register. The processor, as
in any CALL instruction, stores the address where the program execution is to
continue on the stack, before calling any subroutine. This is the instruction which
precedes the label AD_I.

Subroutine SET_INTR gets the address of AD_I from the stack. While the address
of the INT instruction is still not known, the distance between AD_I and the !NT
instruction remain constant, the address of the INT instruction can be calculated
and the interrupt number can be stored following the instruction. The task ends and
the routine returns to the main program (to the label AD_I).

The rest of the routine consists of repeating instructions which determine the
contents of the different variables and pass them to the corresponding processor

34
Abacus 4.1 1111errupt Calls from BASIC

registers. The value for the ES register is given a special test: if it is equal to -I,
the value of the DS register is copied to the ES register.

After all registers are loaded, the interrupt is called and the contents of the
processor registers are transferred back to the corresponding BASIC variables. The
last step is to restore the contents of all registers which had been saved on the
stack. Finally control returns to GW-BASIC.

3S

4. Using Interrupts from High Level Languages PC System Programming

4.2 Interrupt Calls from Turbo Pascal


Calling interrupts from Turbo Pascal is very easy. Throughout this book we'll be
using Turbo Pascal Version 4.0.

INTR

Turbo Pascal uses the INTR procedure. Since this parameter can accept any value
between 0 and 255, all available interrupts can be called.

MSDOS

A special form of this INTR procedure is the MSDOS procedure. It is called in a


manner similar to INTR:
MsDos( Regs:Registers );

The InterruptNumber parameter needed by Turbo Pascal Version 3.0 isn't required
in this procedure since it always calls interrupt 21H, through which almost all
operating system functions can be called.

In both procedures, the parameter register is a record type which holds the contents
of the registers to be passed. These are copied into the registers before the interrupt
call.

The DOS unit contains the parameters for the type called Registers:
type Registers = record

case integer of

o : (AX, BX, CX, DX, BP, sr, D1, DS, ES, Flags: word);
1 : (AL, All, BL, BH, CL, CH, DL, DH : byte);

end;

Once the DOS unit has been included in a Turbo Pascal source code, the var
statement can be used to define the register variables under the name Regs:
var Regs : Registers;

Now Turbo Pascal can easily communicate with the following processor registers:
Regs.ax,

Regs.bx,

Regs .ex,

Regs.ah, etc.

You then pass the values to the registers through standard assignments. For
example:
Register.ax := 254;

The same method is used with all other registers.

36
Abacus 4.2 Interrupt Calls from Turbo Pascal

Unfortunately, the contents of the half registers AH, AL, BL, etc. can't be defined
this way. In this case, a trick can be used by defining the half registers as normal
integer or byte variables and then merging them together into a whole register.

In the case of the AX register, this could be done as follows:


var aI,
ah : integer;

Register.ax :- ah shl 8 + ali

In this statement, the AX register is assigned value composed of the sum of the
AH register multiplied by 256 (shifting a variable left by 8 places is equivalent to
multiplying it by 256) and the AL register.

If you must do this repeatedly in a program, it would be useful to defme a small


function for this:
function WholeRegister(1o, Hi : integer) : integer;

begin
WholeRegister :- 10 + Hi shl 8;
end;

Instead of the above, the following could be written:


Register.ax := WholeRegister(al, ah);

Before calling the interrupt, you must first specify the interrupt value in the
register. The contents of all other registers are unimportant here. If the called
interrupt returns values to the calling program through registers, they can be
examined by looking at the individual components of the variable register.

Sometimes individual flags pass information from the interrupt to the calling
program. In most cases, the Carry flag serves this purpose. If an error occurs
during the execution of an interrupt, the flag is set.

To test for a set flag, the following Pascal statements are used. They return lRUE
or FALSE as a result depending on whether the corresponding flag was set or not.
carry flag: (register. flags and 1)
zero flag: (register. flags and 64)
sign flag: (register. flags and 128)

Often the address of a variable (usually a text buffer) must be passed to an


interrupt. In this case the Turbo functions Ofs and Seg are used to obtain the offset
or segment addresses of a variable. The name of the variable whose address should
be determined is passed to both functions as the argument:
ofs(variablename)
seg(variablenarne)

Turbo Pascal uses a different format than DOS and BIOS for string storage,
especially for text buffers (mostly variables of type string).

37
4. Using Interrupts from High Level Languages PC Systtfm Programming

These formats are illustrated below.

TURBO PASCAL

2 I" p "I "c" I C - No end of string marker

+.. - -----String length

DOS & BIOS

NULL BIOS (and often in DOS)


I"p "I "C" t----f

1 DOS
"$"

tL..._ _ End of string marker

~------- No string length parameter

String storage - TUrbo Pascal and BIOS-DOS

To convert a Turbo Pascal string into DOS or BIOS format, an end character
(ASCII code 0) or the dollar sign "$" (ASCII code 36) is appended. Which of these
two characters you should use for indicating the end of the string is described
during the discussions of individual interrupts. Regardless of which format you
use, the characters appear as in either of the following commands:
string := string+iO;
string := string+i36;
The address returned by the Ofs function ~ 1 must be passed to the interrupt,
otherwise the byte which indicates the length of the string is accepted by the
interrupt as its ftrst character.
Here is the sample program. Just like the example in Section 4.1, it displays text
on the screen using function 9 of interrupt 21H:
{******.**********************.****************************************}
{* INTDOS *1
{*-----------------------------~--------------------------------------*1

{* Task : as an e~pmple this interrupt call outputs *1

{* a string'through a function of DOS on *1

{* the displ ..y *1

{*----------------------------------,----------------------------------* 1

{* Author : MICHAEL TISCHER *1

{* developed : 07/30/87 *1

{* last update : 05/04/89 *1

{* •• ************************.**** ••• *******.************x •••••••••••••• }

program INTDOSP;

38
Abacus 42 Interrupt Calls from Turbo Pascal

Uses Dos;

var Regs : Registers; { Register variables for interrupt call}


Text : string[12B]; { accepts the output text }

{******************************** ••• *.***** •• ****.*.*.*****************}


{* MAIN PROGRAM *I
{*************************************** •• *****************************1

begin

Text := .13.10·this text was output with Function 9 of DOS-'+


'Interrupt 21H ..• ·.13.10+·$·;
Regs.ah := $09; { Function number 9 in the AH-Register
Regs.dx := Ofs(Text)+l; ( Offset address of the text
Regs.ds :- Seg(Text); ( Segment address of the text
MsDos(Regs); ( Call DOS-Interrupt 21(h)
end.

The variable TEXT contains the text to be displayed. The sequence "#13#10"
places the ASCII code 13, followed by ASCII code 10, at the beginning and the
end of the text, creating a blank line before and after the text. The last character is
the "$" character which indicates the last character of text to DOS.
The number of the function being called (9) is copied to the AH register. Since
Turbo Pascal doesn't allow access to the AH register alone, the entire AX register
must be addressed. The value 0 is loaded into the AL register, but any other value
could be entered into this register since its content has no significance to the called
function. As a last step, before calling interrupt 21H using the MSDOS procedure,
the segment address of the string is placed in the DS register and the offset address
in the DX register.

39
4. Using Interrupts from High Level LangUQges PC System Programming

4.3 Interrupt Calls from C

The C language is the language of choice for most developers. Since it was
originally designed for operating system development, C has provisions to include
machine language routines, which is a benefit within the scope of this book.

The standard libraries of both the Microsoft C and Borland Turbo C compilers have
a number of functions for calling interrupts.

The following functions are of interest to us in this book:


int86

int86x

intdos

intdosx

seqread

All functions and applicable data structures are declared in the OOS.R library me.
A program which wants to access one of these functions must therefore link the
me to the current program using the #include preprocessor command

The three structures WOROREGS, BYTEREGS and SEGREGS pass register


values. WOROREGS contains the whole registers AX, BX, CX, OX, SI, DI and
the Carry flag. On the other hand, BYTEREGS contains the half registers AH,
AL, BR, BL, CH, CL, OH and OL, while SEGREGS represents the segment
registers OS, CS, SS and ES.

The BYTEREGS and the WOROREGS structures are joined in the union REGS
which lets the programmer work selectively with either half or whole registers.

Using a variable of the type REGS (called register here for simplicity's sake) gives
us the following:
union REGS register;

This allows access to individual registers:


AX: register.x.ax

BX: reqister.x.bx etc.

AH: register.h.ah

AL: register.h.al

BH: register.h.bh etc.

The carry flag is represented by the variable register.x.cflag. If this variable is equal
to 0, the carry flag remains unset. Any other value sets the carry flag.

In the case of the segment register a representative variable can be defined as


follows:
struct SREGS SegRegister;

40
Abacus 43 Interrupt Calls from C

The individual components of the variables SegRegister.ds, SegRegister.es, etc.,


correspond to the equivalent processor registers.

The functions starting with the characters int all serve to call interrupts. The
SEGREAD function reads the current contents of the segment register.

The functions that call interrupts use different register variables for input to the
interrupt routine, and output from the interrupt routine. There is an advantage to
this method over returning information to the same register variable in that the
input information is not overwritten.

Since the individual functions pass only the address of the variable representing the
register and not the variable itself, it is possible to combine the input and output
registers into a single variable. In this case, the address of one variable is provided
for the variable representing the input and the output registers (this method is used
in the sample program at the end of this section).

Before calling the interrupt, the contents of the input variable are copied to the
corresponding processor registers. Following the interrupt call their contents
become the output variables.

All interrupt functions return the content of the AX register as a result code after
the interrupt call.

Here are the details of the functions and their calls:

int86

The int86 function is called as follows:


int86 (IntNumber, InRegister, OutRegister);

IntNumber is a variable or constant indicating the number of the interrupt to be


called. InRegister and OutRegister contain the address of two (or one) variables of
the REGS type. As the variable name suggests, InRegister contains the register
contents before the interrupt call, and OutRegister contains the register contents
after the interrupt call.

int86x

The int86x function differs from the int86 function in that it requires an additional
argument of the SREGS type. Its contents are copied into the segment register
before calling the interrupt, but are not copied back following the call to the
interrupt routine.

The call of the function is as follows:


int86x(IntNumber, InRegister, OUtRegister, SegRegister);

41
4. Using Interrupts from High Level Languages PC System Programming

The intdos and the intdosx functions differ from the two functions described above,
in that the number of the interrupt to the call is not passed. As the names suggest,
they call DOS interrupt 21H through which most DOS functions can be accessed.

intdos
Only the addresses of the input and the output variables representing the processor
registers are passed to the intdos function:
intdos(InRegister, OutRegister);

intdosx
The intdosx function, like the int86x function, has an additional parameter for the
segment register. The function call is as follows:
intdosx(InRegister, OutRegister, SegRegister);

So far you've seen how to call an interrupt from C and how to set the registers.
You also have to determine the address of a variable.

In C, you can easily determine the address of a variable. To do this, use the address
operator &, which returns the offset address of any desired variable. Use the
SEGREAD function mentioned above to determine the segment address of a
variable. The address of a variable of the SREG type is passed to the function
(using the address operator &) into which the content of the segment register can
be copied.

If, for example, the address of the variable SegRegister is passed to the function
and the variable was previously defined by the command:
union SREG SegRegister;

Then the variable SegRegister.ds contains the segment address of the variable
SegRegister, after calling the SEGREAD function.

While C supports interrupt calls with numerous functions, the library of the
Microsoft C compiler library does not have a function to return the contents of a
memory location. Since such a function could be very valuable in some programs,
the assembler program below contains the PEEKB and POKEB functions for
inclusion in programs created with the Microsoft C compiler. PEEK returns the
contents of a memory location (one byte), while the POKE function writes a one­
byte value into a memory location.

Note: If you use the Borland Turbo C compiler, you won't need to use this
program since the Turbo C library already contains the PEEK,
PEEKB, POKE and POKEB functions. Because of this, linking the
assembler program into the C example programs of this book is

42
Abacus 43 Interrupt Calis from C

unnecessary. Additional infonnation is presented in the header of each


program.

If you are using the Microsoft C compiler, enter the following program with a text
editor and save it under the name PEPO.ASM. It can then be assembled with:
masm pepo;

Here's the program:


:*********************************************************************i
:. PEPO *:
:*-------------------------------------------------------------------*:
;. Task : Makes the PEEKB and POKEB function available for ';
;. inclusion in a C program *;
:*-------------------------------------------------------------------*;
;* Author MICHAEL TISCHER *;
;. developed : 08/13/87 *;
;. last Update : 04/08/89 *;
;*-------------------------------------------------------------------*;
, assemble : MASM PEPO: *i
:**•• ** •••• ***••••••• *********•••••• ********•••• ************ ••• *******;

IGROUP group _text ;Grouping of program segments


DGROUP group const,_bss, _data ;Grouping of data segments
assume CS:IGROUP, DS:DGROUP, ES:DGROUP, SS:DGROUP

public PeekB :Functions become accessible to


public PokeB :other programs

CONST segment word public 'CONST' ;this segment accepts a 11 constant s


CONST ends ;which are readable

BSS segment word public 'sss' ;this segment accepts all non­
BSS ends ;initialized static variables

_DATA segment word public 'DATA' ;all initialized global and


DATA ends ;static variables are stored in this
; segment

TEXT segment byte public 'CODE' ;the Program segment

;-- PEEKS: read a byte from memory ----------------------------­


;-- call of C: int - PeekB(int Segment, int Offset)

PeekB proc near

push bp ;store BP on the stack


mov bp,sp ;transmit SP to BP
push ds ;store data segment register
mov ax, [bpj +4 ;get first argument (Segment)
mov ds,ax ;set as data segment
mov bx, [bpj+6 ;get second argument (Offset)
mov ai, [bxJ ; read memory locat ion
xor ah,ah ;HI-byte of INT to 0
jmp short fctend ;terminate function

PeekB endp

POKEB: write a byte into memory -------------------------­


Call C: PokeB(int Segment, int Offset, short int Wert)

_PokeB proc near

push bp ; store BP on the stack


mov bp,sp ;transmit SP to BP

43
4. Using Interrllpts from High Level LanglUlges PC System Programming

push ds ;store data segment register


mov ax, [bp] +4 ;Get first argument (Segment)
mov ds,ax ;Set as data segment
mov bx, [bp]+6 ;Get second argument (Offset)
mov aI, [bp] +8 ;Get third argument (Value)
mov [bx],al ;write into memory location
fctend: pop ds ;Return data segment register
mov sp,bp ;Restore stack pointer
pop bp ;Get BP from stack
ret ;Return to calling C program

_PokeB endp

i---------------------------------------------------------------------­
text ends ;End of the program segment

end ;End of the assembler source

The example program below uses the two functions described above. This next
program examines the model identification number or code of the PC and displays
PC type on the screen using a ooS function:
/*******.***** ••• *****.************************.***********************/
1* I N T DOS *I
1*--------------------------------------------------------------------*/
1* Task an example of an interrupt call, outputs *1
1* a string through a DOS function on *I
1* the display screen *I
1*--------------------------------------------------------------------*1
1* Author MICHAEL TISCHER *I
1* developed : 08/30/87 *1
1* last update : 04/08/89 */
/*--------------------------------------------------------------------*/
1* (MICROSOFT C) */
1* Creation MSC INTDOSC *1
/* LINK INTDOSC PEPO; *1
1* Call INTDOSC *1
1*--------------------------------------------------------------------*1
1* (BCRIJ\ND TURBC C v2.0) *1
1* Creation through the RUN cO!lVTland in the menu •.. or... *1
1* tcc -K intdosc *1
1* Call intdosc *1
/.**** •• ******************** ••• ******************* ••• ************.*****/

'include <dos.h> 1* include header file *1


1* Microsoft C user must uncomment the following line *1
1* extern short int peekb(); 1* PEEKS must be linked to *1
1* Microsoft C object code *1
,******.*** •• **** ••••• ********•• ***.********.* •••••• **** ••• *** •••• *****/
1** MAIN PROGRAM **1
,.********** •••• **.********** •• ** ••• ***********************************/

void main()

static char AT[] = "\r\nthis computer is an AT\r\nS";


static char XT[J "\r\nthis computer is an XT\r\nS";
static char PC[] "\r\nthis computer is an PC\r\nS";

union REGS Register; 1* Register variable for interrupt call *1


Register .h.ah - 9; 1* Function number for output of string *1
switch (peekb(OxFOOO, OxFFFE» 1* detect model of PC *1
{
case OxFE : Register.x.dx - (int) XT; 1* Address of XT text *1
break;

44
Abacus 43 Interrupt Calls from C

case OxFC : Register.x.dx - (int) AT; /* Address of AT text */


break;
case OxFF :

default : Register.x.dx - (int) PC; /* Address of PC text */

I
intdos(&Register, &Register); /* Call DOS interrupt 21H */
I

The main function defines three CHAR pointers which point to the text for each
PC type. Each of them starts and ends with an "'n" character. This creates a blank
line before and after the text itself.

In the first instruction of the main program the AH register is loaded with the
DOS function number for string output on the screen. Then the model
identification byte is read from memory location FOOO:FFFE using the PEEKB
function. Depending on the value read, the offset address of the accompanying text
is transferred to the OX register where it is expected by the interrupt 21H function.

In addition to this offset address, the function also requires the segment address of
the text in the OS register. Since the compiler automatically sets this register, you
don't have to be concerned with the segment address. The last instruction of the
program calls the INTDOS function which in turn calls interrupt 21H with the
registers which were dermed earlier.

The file header states how it can be executed: If you are using the Microsoft C
computer, then it is important that you link the file with the previously assembled
PEPO program so that the new program contains the PEEKB and POKEB
functions. These can then be called from the C program.

The integrated environment of the Turbo C compiler requires a different procedure.


Compiler options must be set to default values except for under "code generation."
You must set "default char type" to "unsigned", then select Run from the menu.
The options file appears on the disk under the ftlename INTBSPC.TC.

A small comment about using Borland Turbo C compiler. Several programs in


this book include assembly language routines within the programs. Since Turbo C
differentiates between upper and lowercase characters in function names, you may
have problems compiling programs as entered from this book. To avoid this,
select the OPTION command, then the LINKER command in the command line of
Turbo C before creating a program. The lowest line in the window displays the
option "Case sensitive link", Select OFF here to avoid difficulties with upper and
lowercase letters.

45
Chapter 5

Using Interrupts from


Assembly Language

Unlike programmers using any of the higher level languages, the assembly
language programmer doesn't have to rely on complicated functions or procedures
to call an interrupt. The MOV instruction loads the input parameters into the
registers provided, and the !NT instruction calls the interrupt.

Certain interrupts, or the functions hidden behind these interrupts, are called
frequently in many programs. An example of this is interrupt 21H function 9,
which displays text on the screen. You call it by placing function number 9 in the
AH register and the offset address of the text you want displayed in the DX
register. This process looks like this in assembly language:
mov ah,9 ;load function number 9
mov dx,offset Text ;load offset address of text
int 21h ;call DOS interrupt 21h

Even if you call the function very frequently, it doesn't pay to write a subroutine
for it since the address of the text to be displayed must be passed. All that remains
is to load the value 9 into the AH register and to call the interrupt. You'll fmd the
three program lines described above included for every function call in a program in
this chapter.

47
5. Using Interrupts from Assembly Language PC System Programming

5.1 Using Assembler Macro Functions


An alternative to this method are macros which most assemblers support.
Macros
A macro is a "shorthand" way to write a series of assembly language instructions.
It has a name and may have one or more parameters. During assembly, if the
macro name is encountered, the series of instructions and parameters replace the
macro.

Below is an example of defining and calling a macro using the Microsoft


Assembler (MASM). See your assembler's reference manual for information on
macro handling (and whether your assembler supports macros). Since this macro
displays text, we've named the macro PRINT:
print macro string ;Macro header with Name and Parameter

mov ah,9 ;load function 9


mov dx,offset string ;load offset address of the text
int 21h ;call DOS interrupt 21h

endm ;the endm command terminates a macro

The first line declares the macro name (PRINT). In this case, the macro also has
one parameter (string). The assembly language instructions follow in successive
lines until the ENDM instruction terminates the macro.

Now you can use the macro to display text:


print Message

In this example, Message is the name of a variable containing the text to be


displayed. In the macro declaration, string is a parameter. During assembly, string
is replaced by Message and creates the following program lines:
mov ah,9
mov dx,offset Message

int 21h

48
Abacus 5.2 A Sample Mocro

5.2 A Sample Macro


The following program demonstrates the macro just described.
,.****** •• ********************************.*******************.******.*.,
;* MACRO *;
;*-------------------------------------------------------------------*;
;* Task : in this Program a Macro is used for output *;
;* of a String with Function 9 of Interrupt 21H *;
;*----------------------------------------------------
;* Author MICHAEL TISCHER
---------------*i
*;
;* developed : 08/30/87 *;
;* last Update : 04/08/89 *;
i*----------------------------------------------------
;* assembly : MASM MACRO;
---------------*;
*;
;* : LINK MACRO; *;
:*-------------------------------------------------------------------*;
;* Call: : MACRO *;
i******·***************************·**********************************i

;== Macro =--=-=====--=-------====~----=--=====--~-----======-------==

Print macro String ;this is the macro

mov ah,9 ;load function number

mov dx,offset String ; load offset address of text

int 21h ;call DOS interrupt

endm ;End of macro

;== Constants ========-=================================-==============

CR equ 13 ;ASCII-Code of carriage return


LF equ 10 ;ASCII-Code of linefeed
TEND equ "$" ;End of a character string

;== Data ==============================================================

Data segment

Text db CR, LF, "This is how MACROS are used", CR, LF, TEND

Data ends

;-- stack ==-===~==========-=====================~-=--================

stack segment STACK

dw 64 dup (7)

stack ends

;== Code ===-=----=====-=--==-==========--====================---======


Program segment

assume CS:Program, DS: Data, SS:stack

Start proc far ;program starts here

rnov ax, Data ;set data segment register

rnov ds,ax

Print Text ;Macro inserted here

mov ax,4COOh ;Program terminated with call of a


int 21h ;DOS function with return of error-code a

49
5. Using Inlerrupts from Assembly Language PC System Programming

Start endp ;End of procedure

Program ends
end Start ;beqin with START

After you enter the source program, it can be assembled, linked and executed as
indicated in the header.

Most of the lines in this listing have nothing to do with the actual program but
are definitions and declarations for the assembler.

The macro and constants are defmed in the fIrst part of the program, which helps to
make the listing more understandable to the reader. The definition of the data
segment follows, where the string to be displayed is stored as a character string. It
is preceded and followed by a carriage return and a linefeed to display a blank line
before and after the actual text. The text ends with the character "$" (the DOS
function used for text display always looks for this as the last character in a
string).

Following the data segment is the stack segment, which controls the stack during
program execution. Since the program is not very large, the stack can be fairly
small. The last segment is the code segment which contains the program
instructions. It consists of only fIve commands: The first two instructions
initialize the program. They load the segment address of the data segment into the
DS register to provide access to the text in this segment Then the macro PRINT
is called, and the text is passed to it.

The following instructions terminate the program by calling a DOS function.

Note: You may fmd it useful to group together certain macros into a me or
library. When one of these macros will be used in a program, the
library may be linked or included with the assembly language code.

50
Chapter 6

The Disk Operating System

The following chapter discusses the PC's operating system, which the PC loads
from floppy diskette or hard disk. It is commonly referred to as PC-DOS, MS­
DOS or just DOS.

What is DOS?

Most users only know the user interface of DOS, with which you run programs,
format disks, etc. In the following sections, however, you'll view DOS from an
angle you may not have known existed.

Beneath the surface of DOS many processes takes place. DOS uses a large number
of different routines (called/unctions) to accomplish its tasks. These functions are
available to the user as well as to DOS. The main focus is on how these functions
can be used in practical applications.

This chapter includes a historical sketch of the development of DOS, highlighting


its origins in the CP/M operating system. You'llieam the differences between
transient and resident commands, COM and EXE mes, and DOS me access.

The data structures which act as the connecting link between the different DOS
functions will also be examined in this chapter. These data structures make mass
storage devices such as floppy disks and a hard disk possible.

Finally, this chapter discusses each DOS function in detail, and includes a brief
look at DOS Version 4.0.

51
6. The Disk Operating System PC System Programming

6.1 A Short History of DOS


DOS appeared in 1980, at a time when 8-bit systems and CP/M 80 operating
systems made up the majority of microcomputers. A few years before, Intel had
designed the 8086 microprocessor, the first generation of l6-bit microprocessors.

In April 1980 the CP/M-86 operating system announced by Digital Research for
use on the 8086 processor was unavailable. A programmer named Tim Paterson
began developing a new operating system. This system is the ancestor of the
current MS-DOS.

At this time a lot of software was available for CP/M-80 systems. The
development of new software for an 8086 operating system would have required
enormous expenses and effort. Paterson's goal was to allow easy conversion of
existing software from CP/M-80 to the new operating system. He tried to include
the functions and the most important data structures of the CP/M-80 operating
system, while removing the weak points of CP/M-80. The fmished product was an
operating system that required only 6K of memory. Programs developed for CP/M­
80 could also be converted with little effort to the 8086. The new system was
named 86-DOS.

Meanwhile IBM was developing a l6-bit microcomputer. Microsoft offered to


develop an operating system for it. Microsoft obtained a prototype of the new
computer from IBM, bought the rights to Paterson's operating system, and made
some enhancements to the software. Even though Paterson was participating in the
project, the strict security provisions of IBM prevented him from seeing the
machine for which he had developed an operating system. Despite this, the
development work was concluded in August of 1981. The new operating system
was released for the IBM PC under the name MS-DOS.

Many changes have been made to DOS since 1981. Because these changes are of
great significance to the DOS programmer, this chapter contains a segment for
each major version of DOS. Each segment lists changes from preceding versions
with explanations. Many components of DOS are explained here, which will give
you some idea of the complexity of an operating system.

Version 1.0

This version represented a compromise for Microsoft They had relied heavily on
CP/M-80 and needed to transfer existing programs quickly and easily. This can be
seen in the fact that the file names (eight-character filename, three-character
extension) was identical with CP/M-80. Also, the designation of the disk drives
and the internal structure had many similarities to the successful 8-bit operating
system.

52
Abacus 6.1 A Short History of DOS

During this time many improvements and enhancements of the hardware occurred,
such as more RAM and faster disk drives. Microsoft decided to make DOS more
hardware independent by removing the association between physical file length and
logical file length.

In CP/M-80 every disk was divided into 128-byte units which could only be
accessed as a whole. This is why you couldn't access individual bytes on the disk
(this created a programming problem that shouldn't have existed in the frrst place).
DOS solved this problem by making the logical and physical data length
independent of one another. In addition, functions were implemented to permit
reading or writing of more than one data set of a file on a disk. Treating the input
and output devices like files achieved hardware independence. These input and
output devices were assigned their own names:

CON (Keyboard and Display)


PRN (Printer)
AUX (serial Interface)

If you used one of these three names instead of a filename to access a file with a
DOS routine, then the computer addressed the corresponding device and not the
disk drive. This also permitted redirecting input and output from the keyboard or
screen to a file or other device.

Before this time, DOS only supported program files which loaded and executed
from a fixed location in memory. This proved to be impractical, and so Version
1.0 introduced a new program file type. This new file type had a file extension of
.EXE instead of .COM. An .EXE file could be stored and executed from almost
any memory location.

Two changes were made to the command processor, the part of the operating
system which accepts commands from the user and controls the execution of these
commands. The first change was to store the command processor in a separate file
named COMMAND.COM. This allowed programmers to develop a customized
command processor and link it to the system.

The second change was to divide the command processor into a resident and a
transient portion. This approach was taken because early PC systems contained
only a small amount of memory. The resident portion was written to be as small
as possible. Many DOS commands were stored on disk and loaded and run only
when required, hence the name transient. Examples of transient commands are
DISKCOPY and FORMAT.

A major innovation that took MS-DOS Version 1.0 beyond CP/M-80 was the
introduction of the FAT (file allocation table) on disk. Every entry in this table
corresponds to a data area of 512 bytes (called a sector) on the disk. The FAT
indicates whether the sector is allocated to a file or is still available.

S3
6. The Disk Operating System PC System Programming

The FAT has special significance in connection with the directory entry which
exists for every file type. Besides the filename and other information, it also
indicates the number of an entry in the FAT which corresponds with the fIrst
sector of a file on the disk. This FAT entry points to another FAT entry which
indicates the next sector which was allocated to the file. The other FAT entries on
a disk perform the same task.

In conclusion two additional developments should be mentioned which make work


with the PC easier for the user:

The introduction of batch processing offers the user the option of placing several
DOS commands into one file. When you "run" this file (which has a fIle extension
of .BA1'), DOS executes the individual commands from this fIle as if you had
entered the commands from the keyboard, thus saving the user time in entering
frequently used groups of commands repeatedly.

The current date and time follows every filename. DOS includes this data to help
the user determine the last time a file was modified.

When IBM introduced a new PC in 1982 which used both sides of a disk for data
storage, Microsoft released DOS Version 1.1.

Version 2.0

IBM announced a new personal computer in March of 1983, called the PC XT,
which in addition to the floppy disk drive also had a hard disk (also called afued
disk). The enormous capacity of this hard disk (10 megabytes) allowed the user to
store several hundred files on one unit, but created some problems for the operating
system. The largest problem was that DOS could only handle one directory for
each storage unit. It would be nearly impossible for the hard disk user to maintain
hundreds of fIles in a single directory. Microsoft had two options to solve this
problem: They could either borrow an idea from the CP/M-SO operating system, or
from the UNIX operating system.

CP/M views a hard disk as several individual disk drives which share the total
storage on the hard disk, each with only one directory.

UNIX uses a hierarchical file system, in which every storage unit has a root
directory which can contain subdirectories as well as files. Every one of these
subdirectories can have subdirectories within them. This creates a directory tree
whose trunk is the root directory and whose branches are represented by the
individual subdirectories.

Microsoft chose the hierarchical file system, which has since become a popular
component of DOS. This· was another step away from CP/M-80 toward an
efficient 16-bit operating system. With the introduction of an hierarchical fIle
system some major changes had to be made in the area of fIle control by DOS.
Before this time, fIle access was conducted through afile control block or FCB.

54
Abacus 6.1 A Short History of DOS

This file control block had been introduced for compatibility with CP/M-80. The
FCB contained important information about the name, size and location of a file
on disk. This CP/M would not allow access to a file in another directory.

The DOS developers standardized file access through DOS functions. The access to
a file occurs exclusively through the file handles. A handle is a numerical value
passed to the program as soon as it opens a file through a DOS function. The
FCBs were not eliminated, but the programmer no longer came in contact with
them since DOS took over the control block manipulation.

An important innovation was the introduction of installable device drivers. They


offer the programmer the capability of easily including different devices in DOS,
such as an exotic hard disk, a mouse or a tape drive. Version 2.0 introduced the
display device driver ANSI.SYS which gave the programmer flexibility in cursor
positioning and color selection through DOS functions.

Version 2.0 added the option of formatting the individual tracks of a disk with nine
sectors instead of eight. This increased the storage capacity of a single-sided disk
from 160K to 180K, and the capacity of a double-sided disk from 320K to 360K.

Version 3.0
Version 3.0, like Version 2.0, was developed for a new PC, the IBM PC AT. It
was released in August of 1984 and supported the 20 megabyte hard disk of the
ATs as well as the high capacity 1.2 megabyte floppy disk drive. Many changes
occurred in DOS's internal routines. They contributed to faster execution of certain
operations, but are transparent to the programmer.

Version 4.0
DOS 4.0 appeared on the market in August 1988. Before this, Microsoft released a
new multiprocessing operating system called OS/2. Before OS/2, multiprocessing
was unknown to MS-DOS.

The user can easily seethe changes to DOS 4.0 over earlier versions of DOS. In
place of the line-Oriented command line interpreter used by DOS versions 3.3 and
earlier, DOS 4.0 has a Shell allowing user-defined menus, easy selection of
applications, files and directories from both mouse and keyboard.

Most important are the unseen changes made to DOS, particularly in adapting the
operating system to the new hardware standards on the market. As the operating
system has grown in power, it has also grown in complexity and memory use. For
example, earlier versions of DOS were limited to "only" 640K of RAM and a 32
megabyte hard disk. However, DOS 4.0 handles the Expanded Memory System
(EMS) following the LIM standard, normal RAM capacity of up to 8 megabytes,
and hard disks up to 2 gigabytes (2048 megabytes) capacity.

55
6. The Disk Operating System PC System Programming

6.2 Internal Structure of DOS


Several major components comprise DOS, each with a certain task within the
system. The three most important components are the DOS-BIOS, the DOS kernel
and the command processor. Each appear in a separate file.

DOS-BIOS

DOS-BIOS is stored in a system file which appears under various names


(IBMBIO.COM, IBMIO.SYS or IO.SYS). This file has the fIle attributes Hidden
and Sys, which means this system file doesn't appear when the DIR command is
entered. The DOS-BIOS contains the device drivers for the following units:

CON (Keyboard and Display)


PRN (Printer)
AUX (Serial Interface)
CLOCK (Clock)
Disk drives and/or hard disks which have the unit
designations A, Band C

If DOS wants to communicate with one of these, it accesses a device driver


contained in this module, which in tum uses the routines of ROM-BIOS. The
DOS-BIOS (i.e., the connection between individual device drivers and other
hardware dependent routines) are the most hardware dependent components of the
operating system, and vary from one computer to another.

Do not confuse the device drivers in this module with the installable device drivers.
The DOS-BIOS device drivers cannot be changed by the user.

DOS kernel

The DOS kernel in the IBMDOS.COM or MSDOS.SYS fIle is normally invisible


to the user. It contains file access routine handles, character input and output, and
more. The routines operate independent of the hardware and use the device drivers
of DOS-BIOS for keyboard, screen and disk access. The module can be used by
different PCs without being limited to one machine. User programs can access
these functions in the same manner as the ROM-BIOS functions: every function
can be called with a software interrupt. The processor registers pass the function
number and the parameters.

Command processor

Unlike the two modules described above, the command processor is contained in
the file named COMMAND. COM. It displays the "A">" or "C>" prompt on the
screen, accepts user input and controls input execution. Many users wrongly think
that the command processor is actually the operating system. In reality it is only a
special program which executes under DOS control.

S6
Abacus 6.2 Internal Structure of DOS

The command processor, also called a shell in programmer's terminology, actually


consists of three modules: A resident portion, a transient portion and the
initialization routine.
The resident portion (the part that always stays in the computer's memory)
contains various routines called critical error handlers. These allow the computer to
react to different events, such as pressing the <Ctrl><C> or <Ctrl><Break> keys
or errors during communication with external devices (e.g., disk drives and
printers). The latter cause the message:
Abort, Retry, Ignore
or
Abort, Retry, Fail

The transient portion contains code for displaying the (A» prompt, reading user
input from the keyboard and executing the input. The name of this module is
derived from the fact that the RAM memory where it is located is unprotected, and
can be overwritten under certain circumstances. When a program ends, control
returns to the resident portion of the command processor. It executes a checksum
program to determine whether the transient portion was overwritten by the applica­
tion program. If so, the resident portion reloads the transient portion.

The initialization portion loads during the booting process and initializes DOS.
This part of the command processor will be examined in detail in the next chapter.
When its job ends, it is no longer needed and the RAM memory it occupies can be
overwritten by another program. The commands accepted by the transient portion
of the command processor can be divided into three groups: internal commands,
external commands and batch files.

Internal commands lie in the resident portion of the command processor. COPY,
RENAME and DIR are internal commands.

External commands must be loaded into memory from diskette or hard disk as
needed. FORMAT and CHKDSK are external commands.

After execution the command processor releases the memory used by these
programs. This memory can then be used for other purposes.
Batch files

A batch file is a text file containing a series of DOS commands. When a batch file
is started, a special interpreter in the transient portion of the command processor
executes the batch file commands. Execution of batch file commands is the same
as if the user entered them from the keyboard. An important batch file is the
AUTOEXEC.BAT file which executes immediately after DOS is flfSt loaded.

Like all commands of a batch file, these commands are checked for internal
commands, external commands or calls to other batch files. If the ftrst is true, the

57
6. The Disk Operating System PC System Programming

command executes immediately, since the code is already in memory (in the
transient part of the command processor). If it is an external command or another
batch file, the system searches the current directory for the command. If such a file
doesn't exist in this directory, all directories specified in the PATII command are
searched in sequence. During the search, only files with the .COM•.EXE or .BAT
extensions are examined.

Since the command processor cannot search for all three extensions at the same
time, it first searches for files with .COM extensions, then for .EXE files and
finally for .BAT files. If the search is unsuccessful, the screen displays an error
message and the system waits for new input.

58
Abacus 63 Booting DOS

6.3 Booting DOS


When a PC is turned on, the program contained in ROM begins executing. This
ROM program is sometimes called the ROM-BIOS, POST (power-on self test),
resident diagnostics or bootstrap ROM. It performs several tests on the hardware
and memory and then starts to load the DOS.

First the PC checks for a disk in the floppy disk drive. If a disk exists in the
floppy disk drive, the PC checks the disk for the boot sector. If a disk is not in the
drive, the PC searches for a hard disk from which to boot DOS. If no hard disk
exists, the PC displays an error message asking the user to insert a system disk.

The ftrst sector on a bootable floppy disk or hard disk is called the boot sector. The
program in the boot sector is read into memory and executes. First it checks for
the presence of two files: IBMBIO.COM (sometimes called IO.SYS) and
IBMDOS.COM (sometimes called MSooS.SYS). A bootable floppy disk or hard
disk must contain these two files or an error message is displayed. Next these
program fIles are loaded into memory.

The program file IBMBIO.COM consists of two modules. The ftrst contains the
basic device drivers-keyboard, display and disk. The second contains the
initialization sequence for DOS. When the IBMBIO.COM program executes it
continues to initialize the system by moving the DOS kemal (loaded in the
IBMooS.COM program fIle) to the last available memory location.

The DOS kemal builds several important tables and data areas, and performs
initialization procedures for individual device drivers which were loaded with the
IBMBIO.COM program fIle.

Next, DOS searches the boot disk for a file named CONFIG.SYS. If found, the
commands contained in the me are executed. These commands add device drivers to
DOS, allocate disk buffers and file control blocks for DOS and initialize the
standard input and output devices.

Lastly the command processor COMMAND.COM (or other shell specifted in the
CONFIG.SYS fIle) is loaded and control is passed to it. The booting process ends
and the initialization routines remain as "garbage" data in memory until
overwritten by another program.

S9

6. The Disk Operating System PC System Programming

6.4 COM and EXE Programs


DOS recognizes three types of "program" files: those with file extensions of BAT,
COMandEXE.

This section describes the structure and functions of these last two program types.

One difference between COM and EXE program files is in the size limitation for
each type of program. A COM program cannot exceed 64K in size. An EXE
program can be as large as the memory capacity available to DOS.

In a COM program, the program code, data and stack are stored in one 64K
partition. All of the segment registers are set at the start of the program and remain
fixed for the duration of the program execution. They point to the start of the 64K
memory segment. The contents of the ES register may be changed however, since
it has no direct effect on program execution.

In an EXE program, the code, data and stack may be stored in different segments,
and depending on program size, may be distributed over severnl segments.

While a COM program file is stored on disk as an image copy of RAM memory,
an EXE program file is stored in a special format that will be described shortly.

EXEC

Both program types can be loaded and started using the DOS EXEC function. Any
user can access this, but the command processor uses it for executing external
commands. Before the EXEC function loads the program into memory, it reserves
the RAM memory to hold the program. At the beginning of this memory the
EXEC function stores a PSP (program segment prefIX) data structure. The program
is then loaded immediately following the PSP. The segment registers and the stack
are initialized and the program is given control. Later, when the program ends, the
memory is released based on the contents of the PSP.

60
Abacus 6.4 COM and EXE Programs

+ OOH Interrupt 20H call (2 bytes)


+ 02H Segment address of meIlOry (1 word)
allocated for a program
+ 04H Reserved (1 byte)
+ OSH Interrupt 21H call (5 bytes) RAM
+ OAR Copy of interrupt (2 words)
0000:0000
vector 22H

+OEH

+ 12H
Copy of interrupt
vector 23H

Copy of interrupt
vector 24H

(2 words)

(2 words)
1
+ 16H reserved (22 bytes)
+ 2CH Segment address of (1 word)

environment block

+ 2EH reserved (46 bytes)

+SCH FCB 1 (16 bytes)

+ 6CH FCB 2 (16 bytes)


+ SOH Number of characters (1 byte)

in command line

+ SlH Command line (ended by CR) (127 bytes)

Structure of the PSP

The PSP itself is always 256 bytes long and contains information important for
DOS and the program to be executed.

Memory location OOH of the PSP contains a DOS function call to terminate a
program. This function releases program memory and returns control to the
command processor or the calling program. Memory location OSH of the PSP
contains a DOS function call to interrupt 21H. Neither of these are used by DOS,
but are leftovers from the CP/M system.

Memory location 02H of the PSP contains the segment address to the end of the
program. Memory location OAH contains the previous contents of the program
termination interrupt vector. Memory location OEH contains the previous contents
of the <Ctrl><C> or <Ctrl><Break> interrupt vector. Memory location 12H
contains the previous contents of the critical error interrupt vector. For each of
these memory locations, the program changes one of the corresponding vectors
during execution; DOS can use the original vector in the event that it detects an
error.

Location 2CH contains the segment address of the environment block. The
environment block contains information such as the current search path and the
directory in which the COMMAND.COM command processor is located on disk.

61
6. The Disk Operating System PC System Programming

Memory locations 5CH through 6CH contain a file control block. This FCB is
not often used by DOS since it does not support hierarchical files (paths) and is
also left over from CP/M.

The string of parameters that are entered on the command line following the
program name is called the command tail. The command tail is copied to the
parameter buffer in the PSP beginning at memory location 81H and its length is
stored at memory location 80H. Any redirection parameters are eliminated from the
command tail as it is copied to the parameter buffer. The program can examine the
parameters in the parameter buffer to direct its execution.

The parameter buffer is also used by DOS as a disk transfer area (DTA) for
transmitting data between the disk drive and memory. Most OOS programs do not
use the DTA contained in the PSP because it is another leftover from CP/M.

SS:OOOO
DS:OOOO ES:OOOO
ES:OOOO DS:OOOO
PSP (256 BYTES) PSP (256 BYTES)
cs:oooo
CS:IP
Code
Code, data
and stack in (Address defined
one 64K segment by the END
CS:IP ...... command in an
assembler
SS:SP - Stack adjusts
to the direction
program)

SS:FFFF of data and code DS:OOOO


CS:FFFF Data
DS:FFFF SS:OOOO
ES:FFFF
Stack
SS:SP

A comparison of COM and EXE programs in memory

6.4.1 COM Programs

COM program files are stored on disk as an image copy of memory. Because of
this, no further processing is required during loading. Therefore COM programs
load faster and start execution faster than EXE programs.

A COM program loads immediately following the PSP. Execution then begins at
the ftrst memory location following the PSP at offset lOOH. For this reason, a
COM program must begin with an executable instruction, even it if is only a
jump instruction to the actual start of the program.

62
Abacus 6.4 COM and EXE Programs

COM program memory limits

As described in the previous section, a COM program is limited to 64K (65,536


bytes) in length. The PSP (256 bytes) and at least 1 word (2 bytes) for the stack
must be reserved from this total. Even though the length of the COM program can
never exceed 64K, DOS reserves the entire available RAM for a program.
Therefore DOS can allocate no further memory, and the COM program cannot call
another program using the EXEC function. This limitation can be overcome by
releasing the unused memory for other uses with a DOS function.

When control is turned over to the COM program, all segment registers point to
the beginning of the PSP. Because of this, the beginning of the COM program
(relative to the beginning of the PSP) is always at address lOOH. The stack pointer
points to the end of the 64K memory segment containing the COM program
(usually FFFEH). During every subroutine call within the COM program, the
stack is adjusted by 2 bytes in the direction towards the end of the program. The
programmer is responsible for preventing the stack from growing and overwriting
the program, which would cause it to crash.

There are several ways to end a COM program and return control to DOS or the
calling program:

If the program runs under DOS Version 1.0, it can be terminated by calling
interrupt 2lH function 0, or by calling interrupt 20H. It can also be terminated by
using the RET (RETurn) assembler instruction. When this instruction executes,
the program continues at the address which is at the top of the stack. Since the
EXEC function stored the value 0 at this location before turning control over to
the COM program, program execution continues at location CS:O (the start of the
PSP). Recall that this location contains the call for interrupt 20H which
terminates the program.

Programs that run on versions later than DOS Version 1.0, are terminated using
interrupt 2lH function 4CH. The terminating program can pass a numeric return
code to the calling program. For example, a value of 0 may indicate that the
program executed successfully, while a non-zero value indicates an error during
execution.

Next we'll talk about a few of the details that the assembly language programmer
will have to take care of in developing a COM program. Note that the high level
language programmer is usually insulated from these details by the compiler or
interpreter, so you may want to skip ahead.

A COM program is limited to a 64K size. The code and data for the program must
be contained within a single segment and addressed through NEAR procedures.
Therefore an assembly language program that is to become a COM program may
not contain any FAR procedures.

63
6. The Disk Operating System PC System Programming

Before calling a COM program, DOS reserves all available memory for the
program even though it normally uses only one 64K segment and indicates this by
setting memory location 2 in the PSP. Usually the program terminates and the
memory is made available to DOS again.

In some circumstances you may want to write a program which is to remain


resident after execution. But DOS thinks that there isn't any memory available.
This prevents other programs from loading and executing.

In other circumstances you may want to execute another program from this COM
program using the EXEC function. Again, since DOS thinks that memory is
unavailable, it won't allow the new program to run.

Both of these problems can be circumvented by freeing up the unused memory.

There are two approaches in doing this: release only the memory outside of the
64K COM segment or release memory outside of the 64K COM segment plus any
unused memory within the 64K COM segment. This creates more memory for
other programs, but relocates the stack outside the protected COM segment
memory, leaving it open to be overwritten by other programs. Because of this, the
stack must be relocated to the end of the code segment before releasing the
memory. The stack must have a certain limit in size (in most cases 512 bytes will
be more than enough).

The following sample program can serve as an example for developing a COM
program. A small (init) routine relocates the stack to the end of the code segment
after the start of the program and releases all remaining memory. Even when this
program loads another program, itremains resident. This routine can be useful to
applications, and can be part of any COM program.
itestcorn.asrn
code segment para 'CODE' ;Definition of CODE-segments

org IOOh ;starts at Address IOOH


;directly behind the PSP

assume cs:code, ds:code, es:code, ss:code

;all segments point to the CODE


; segment

start: jmp init ;Call of the Initialization Routine

;~= Data ========_============a========_===_=====================-====

;-- Data, Buffers and ---------­


;-- Variables can be stored here

;== Program ===========================================================


prog proc near ;this Procedure is the actual
;Main program and is executed after
;the Initialization

mov aX,4COOh ;Terminate Program through calling a

64
Abacus 6.4 COM and EXE Programs

int 21h ;DOS function on error code 0

prog endp ;End of the PROG procedure

;-- Initialization ---------------------------------------------------­


init: mov ah,4Ah ;Change Function number for memory size
mov bx,offset endp ;Calculate number of paragraphs (16 byte
mov cl , 4 leach) available to the program
shr bx, cl
inc bx
int 21h ;Call function through DOS-Interrupt
mov sp,offset endp ;Set new stack-Pointer
jmp prog

init end label near

;-= stack ============---========--=================================--­

dw (256-«init_end-init) shr 1» dup (?)

;the stack has 256 Words, but includes


;the code of the INIT-Routine which
;after its execution is no longer needed

endp equ this byte ;End of memory used by this


; program

;== End ====================================-==========================


code ends ;End of the CODE-segment
end start ;End of the Assembler-Program. For
;execution use START command

First you must assemble the source program using the assembler. In the following
example, we are using the Microsoft assembler. Following assembly, you then
link the object code using the LINK program. When you execute the LINK
program, the following message appears:
Warning: no stack segment

You can disregard this message. If the program contains no errors, the LINK
program creates an EXE file. Since you want a COM program and not an EXE
program developed, you must run the EXE2BIN program as the last step. This
converts EXE programs into COM programs. Here are the steps for preparing an
assembly language program using the Microsoft assembler. The program to
assemble is named TESTCOM.ASM.
masrn testcom:

link testcom;

exe2bin testcom.exe testcom.com

If all steps were carried out correctly, the program TESTCOM.COM can be
executed from DOS by simply typing TESTCOM.

6S
6. The Disk Operating System PC System Programming

6.4.2 EXE Programs

EXE programs have an advantage over COM programs because they are not
limited to a maximum length of 64K for code, data and stack. The disadvantage of
this is the greater complexity of these files. This means that in addition to the
program itself, other information must be stored in an EXE file.

EXE vs. COM

EXE programs contain separate segments for code, data and stack which can be
organized in any sequence. Unlike a COM program, an EXE program loads into
memory from disk and undergoes processing by the EXEC function and then
finally begins execution. This is necessary because of the limitations already
described for COM programs.

EXE programs aren't limited to loading at a fixed memory location, but to any
desired location in memory that's a multiple of 16. Since an EXE program can
have several segments, this requires the use of FAR machine language
instructions. For example, a main program can be in one segment and call a
subroutine in another segment. The segment address must be provided for this
FAR instruction in addition to the offset for the routine to be called. The problem
is that the segment address may be different for every execution of the program.

COM files avoid this problem since the program size is limited to 64K, which
makes the use of FAR commands unnecessary. EXE programs solve this problem
in a more complex way: the LINK program places a data structure at the beginning
of every EXE file which contains the addresses of all segments, among other
things. It contains the addresses of all memory locations in which the segment
address of a certain segment is stored during program execution.

If the EXEC function loads the EXE program, it knows the addresses where the
various segments should be loaded. It can therefore enter these values into the
memory locations at the beginning of the EXE file. Because of this, more time
elapses between the initial program call and when the program actually begins
execution than for a COM program. The EXE program also occupies more
memory than a COM program. The following illustration shows the structure of
the header for an EXE file.

66
Abacus 6.4 COM and EXE Programs

EXE file header structure


Address Contents Tvpe
+OOH EXE program identifier (5A4Dh) 1 WORD
+02H file length MOD 512 1 WORD
+04H file length DIV 512 1 WORD
+06H Number of segment addresses for passing 1 WORD
+OBH Head size in paragraphs 1 WORD
+OAH Minimum no. of extra paragraphs needed 1 WORD
+OEH Maximum no. of extra paragraphs needed 1 WORD
+10H SP reglster contents on program start 11 WORD
+12H Checksum based on EXE file header 1 WORD
+14H IP reqister contents on program start 1 WORD
+16H Start of code segment in EXE file 1 WORD
+l8H Relocation table address in EXE file 1 WORD
+lAH OVerlay number 1 WORD
+lCH Buffer memory 1 WORD
+??H Address of passing segment addresses 1 WORD
(relocation table)
+??H Program code, data and stack segment 1 WORD

EXE file header construction

After the segment references within the EXE program have been resolved to the
current addresses, the EXEC function sets the DS and the ES segment register to
the beginning of the PSP which also precedes all EXE programs in memory.
Because of this, the EXE program can access the information contained in the
PSP, such as the address of the environment block and the parameters contained in
the command line (command tail). The stack address and the contents of the stack
pointer are stored in the EXE file header and accessed from there. This also applies
to the code segment address containing the first instructions of the program, and
the program counter. After the values have been assigned, the program execution
starts.

To ensure compatibility with future DOS versions, an EXE program should


terminate by calling interrupt 21H function 4CH.

Of course, memory must be available for the EXE program. The EXE loader
determines the total program size based on the size of the individual segments of
the EXE program. Then it can allocate this amount of memory and some
additional memory immediately following the EXE program. The first two fields
of the EXE program file header contain the minimum and maximum size of
memory required in paragraphs (1-6 bytes).

First, the EXE loader tries to reserve the maximum number of paragraphs. If this
is not possible the loader tries to reserve the remaining memory which may be no
smaller than the minimum number of paragraphs. These fields are determined by
the compiler or assembler, llil1 the linker. The minimum is 0 and the maximum

67
6. The Disk Operating System PC System Programming

allowed is FFFFH. This last number is unrealistic in most cases (it adds up to 1
megabyte) but reserves the entire memory for the EXE program.

This brings us back to the same problem as in COM programs. EXE files make
poor resident programs, but an EXE program may need to call another program
during execution. This is possible only by fIrst releasing the additional reserved
memory. The following program below contains a routine which reduces the
reserved memory to a minimum.

The program uses separate code, data and stack segments. It can serve as a model
for other EXE programs that you can write.
; testexe.asm
i== stack ============================================_ac============-­

stacie segment para stacie ;Definition of the stacie-segment

dw 256 dup (1) ;the stacie has 256 Words

stacie ends ;End of the stack-segment

;== Data ==========--================================----============-=


data segment para 'DATA' ;Definition of the Data-segment

;all data, buffers and variables can be stored here

data ends ;End of the Data segment

i== Code ==========-====================================================

code segment para 'CODE' ;Definition of the CODE-segment

assume cs:code, ds:data, ss:stacK

;CS defines the Code, DS


;the Data and SS the stacie
; segment

prog proc far ;this procedure is the actual


;Main program and is executed after
;the program start

mov ax,ctata ;Load segment address of the Data segment into


mov ds,ax ;the DS-Register
call set free ;release memory not needed

;store application program here ---------------------­

mov aX,4COOh ;terrninate with call of DOS function


int 21h ;on return of error code a
iterrninate

prog endp ;End of PROG Procedure

i--SETFREE release memory storage not occupied ---------------­


i--Inputt ES - Address of PSP
i--Output none
, Register AX, BX, CL and FLAGS are changed
i-- Info Since the stacie-segment is always the last segment in an
EXE file, ES:OOOO points to the beginning and SS:SP
to the end of the program in storage. Because of this the
length of the program can be calculated.

68
Abacus 6.4 COM and EKE Programs

setfree proc near

mov bx,ss ;subtract the two segment addresses


mov ax,es ;from each other. The result is the
sub bx,ax ;number of paragraphs from PSP to
;the beginning of the stack
mov ax,sp ;since the stackpointer is a the end
mov cl,4 ;of the stack segment, its content
shr ax/el ;gives the length of the stacks
add bx,ax ;add to the present length
inc bx ;one more paragraph as a precaution
mov ah,4ah ;pass new size to DOS
int 2Ih

ret ;back to calling program

set free endp

code ends ;End of the CODE-segment


end prog ;End of the Assembler program.
;Start execution with the PROG procedure

To develop an EXE program, it must be assembled like a normal program with an


assembler. Then it is linked with the LINK program. If the program contains no
errors, the LINK program creates an EXE flle.

Here are the individual steps for preparing an EXE program from the assembly
language source named 1ES1EXE.ASM.
masm testexe;
link testexe;

If all these steps were executed correctly, the program TESTEXE.EXE can be
started from the DOS level by typing TESTEXE.

69
6. The Disk Operating System PC System Programming

6.5 Character Input and Output from DOS


When flIst learning a programming language, many beginners learn the basic input
and output instructions of the language. In much the same way, programmers get
their experience writing DOS accessible programming by using the functions for
character input and output. For this reason, this book starts with these input and
output functions instead of more complex functions. These input and output
functions can address the keyboard, screen, printer and serial interface.

The functions can be divided into two types: those carried over from the CP/M
operating system and those borrowed from the UNIX operating system. While the
two types of functions can be intermixed, we recommend that you use one type of
function throughout a program for the sake of consistency.

The UNIX type functions use a handle as an identifier to a device. Because of


recent DOS trends to move closer to UNIX, you may want to give the handle
functions precedence.

6.5.1 Handle Functions

The handle functions perform file access as well as character input to or output
from a device. DOS recognizes the difference by examining the name assigned by
the handle. If the handle is a device name, it addresses the device; otherwise it
assumes that file access should occur. The device names are as follows:

CON Keyboard and display


AUX Serial Interface
PRN Printer
NUL Imaginary device (nothing happens on access)

Output and input go to and from the AUX, PRN and NUL devices. For the device
CON, output is sent to the screen and input is read from the keyboard.

When DOS passes control to a program, five handles are available for access to
individual devices. These handles have values from 0 to 4 and represent the
following devices:

0 Standard input (CON)


1 Standard output (CON)
2 Standard output for error messages (CON)
3 Standard serial interface (AUX)
4 Standardprinter (PRN)

Here is a short example to help demonstrate the use of this table:

70
Abacus 65 Character Input and Output from DOS

Display error message


If a program wants to accept input from the user, the handle function 0 indicates
this during the call since the standard input device is addressed. Handle 0 normally
represents the keyboard, permitting user input from the user to the program. Since
the user can redirect standard input, you can redirect input to originate from a file
instead of the keyboard This redirection remains hidden from the program.

Before discussing these devices, here are some functions used to access any device.

Function 40H of interrupt 21H sends data to a device. The function number (40H)
is passed in the AH register and the handle is passed in the BX register. For
example, to display an error message, the value 2 indicates the handle for
displaying the error message (this device cannot be redirected, so handle 2 always
addresses the console). The number of characters to be in the error message is
passed in the ex register. The characters making up the message are stored
sequentially in memory whose segment address is stored in the DS register and
offset address in the DX register.

Following the call to the function, the carry flag signals any error. If there was no
error, the carry flag is reset and the AX register contains the number of characters
that were displayed. If the AX register contains the value 0, then there was no
more space available on the storage medium for the message. If the carry flag is
set, the error message was not sent and an error code is indicated in the AX
register. An error code of 5 indicates that the device was not available. An error
code of 6 indicates that the handle was not opened.

Function 3FH of interrupt 21H reads character data from a device and has many
similarities to the previous function. Both functions have identical register usage.
The function number is passed in the AX register and the handle in the BX
register. The number of characters read is passed in the ex register and the
memory address of the characters transferred are passed in the DS:DX register pair.

Following the call to the function, the carry flag also signals any error. Again, any
error code is passed in the AX register. Error codes 5 and 6 have the same meaning
as when using function 40H. If the carry flag is reset, then the function executed
successfully. The AX register then contains the number of characters read into the
buffer. A value of 0 in the AX register means that the data to be read should have
come from a file, but that this file contains no more data.

As we already mentioned, it's possible to redirect the input or output when


accessing DOS. For example, a program that normally expects input from the
keyboard can be made to accept the input from a file. So, to avoid having input or
output redirected, you can open a new handle to a specific device which insures that
the transfer of data to or from the desired device takes place instead of to or from a
redirected device.

Use function 3DH of interrupt 21H to open such a device.

71
6. The Disk Operating System PC System Programming

The function number 3DH is passed in the AH registel'. The AL register contains 0
to enable reading from the device, 1 to enable writing to the device and 2 for both
reading and writing to the device. The name of the device is placed in memory
whose address is passed in the DS:DX register pair. So that the DOS can properly
identify the device name, the names must be specified in uppercase characters. The
last character of the string must be an end character (ASCII value 0).

Following the function calls the status is indicated by the carry flag. A reset flag
means that the device was opened successfully and the handle number is passed
back in the AX register. A set flag indicates an error and the AX register contains
any error code.

The handle is closed using function 3EH of interrupt 21H. The function number is
passed in the AH register and the handle number is passed in the BX register. The
carry flag again indicates the status of the function call. A set carry flag indicates
an error.

You can also close the predefined handles 0 through 4 using this function. But if
you close handle 0 (the standard input device) you'll no longer be able to accept
input from the keyboard

Let's examine the special characteristics of each device.

Keyboard

The keyboard can perform only read operations. The results of the read operations
depend on the mode in which the device was addressed. Here DOS differentiates
between raw and cooked. In the cooked mode OOS checks every character sent to a
device or received from a device to see if it is a special control character. If DOS
finds a special control character, it performs a certain action in response to the
character. In raw mode the individual characters are passed through unchecked and
unmanipulated. DOS normally operates the device in cooked mode for character
input and output. However, you can switch to raw mode within a program (see
below).

The difference between cooked and raw mode can be best explained by an example
of reading the keyboard. Assume that 30 characters are read from the keyboard in
cooked mode. As you enter the characters DOS allows you to edit the input using
several of the control keys. For example <Ctrl><C> and <Ctrl><Break> abort the
input. <Ctrl><S> temporarily halts the program until another key is pressed.
<Ctrl><P> directs subsequent data from the screen to the printer (until <Ctrl><P>
is pressed again). <Backspace> removes the last character from the DOS buffer. If
the <Enter> key is pressed, the first 30 characters (or all characters input up to
now if there are less than 30) are copied from the DOS buffer into the input buffer
of the program without the control characters.

In raw mode all characters entered (including control characters) are passed to the
calling program without requiring the user to press the <Enter> key. Mter exactly

72
Abacus 6.5 Character Input and Output from DOS

30 characters, control passes to the calling program, even if you pressed the
<Enter> key as the second character of the input.
Screen
To display characters on the screen, handle 1 is usually addressed as the standard
output device. Since this device can be redirected, output through this handle can
pass to devices other than the screen. On the other hand, you cannot redirect the
standard error output device (handle 2), so error messages that pass through this
handle always appears on the screen. This handle is recommended for character
display on the screen ~.

The screen is normally addressed in cooked mode-every character displayed on the


screen is tested for the <Ctrl><C> or the <Ctrl><Break> control characters. This
test slows down the screen output, so sometimes changing to raw mode decreases
program execution time.

Printer
Unlike the keyboard and screen, printer output cannot be redirected-at least not
from the user level. An exception to this rule is redirecting output from a parallel
printer to a serial printer. Characters ready to print can be sent to a buffer before
they are sent to the printer. Handle 4 is used to address the standard printer. There
are three standard printer devices LPTl, LPT2 and LPT3. Device PRN is
synonymous with LPTI. When this handle is opened the device name is specified
as one of the three: LPTl, LPT2 or LPTI.
Serial interface
Much of the information that applies to the printer also applies to the serial
interface. For example, serial input and output cannot be redirected to another
device (e.g., from a serial printer to a parallel printer). The programmer can use the
predefined handle 3 for serial access, through which you can address the standard
serial interface (AUX).

Handle 3 is used to address the standard serial device. The two are names COMl
and COM2. A PC can have multiple serial interfaces. Only the first two (COMl
and COM2) are supported by DOS. Since the system doesn't know exactly which
interface to access during AUX device access, you should open a new handle for
access to the specific device.

Errors during read operations in DOS mode are returned to the serial interface in
cooked mode. The number returned to the AX register will not match the number
of characters actually read. We recommend that you operate the serial interface in
the raw mode, even if this mode ignores control characters such as <Ctrl><C> and
EOF (end-of-fIle).

73
6. The Disk OperaJing System PC System Programming

6.5.2 Traditional DOS Functions

The DOS functions for input and output aren't based on the handle oriented
functions. If you use these functions you won't need to specify a handle, since
each function pertains to a specific device.

Below are the various input and output devices and the way in which these
functions work with them.

Keyboard

There are seven DOS functions for addressing the keyboard but they differ in many
ways. For example, they respond differently to the <Ctrl> <Break> key. Some
functions echo the characters on the screen; others don't.

You can use DOS functions OIH, 06H, 07H and OSH to read a single keyboard
character. The function number is passed in the AH register. Following the call,
the character is returned in the AL register.

For DOS function OIH, DOS waits for a keypress if the keyboard buffer is empty.
When this happens, the character is echoed on the screen. If the keyboard buffer is
not empty, a new character is fetched and returned to the calling program. DOS
function 06H can be used for both character input and output. To input a character
a value of FFH is loaded into the DL register. This function doesn't wait for a
character to be input but returns immediately to the calling program. If the zero
flag is set, a character was not read. If the zero flag is reset, a character was read and
returned in the AL register. The character is not echoed on the screen.

DOS functions 07H and 08H are used to read the keyboard similar to function 1.
Both either fetch a character from the keyboard buffer or wait for a character to be
entered at the keyboard. Neither echo the character to the screen. They differ in that
function OSH responds to <Ctrl><C> and function 07H does not.

By using function OBH, a program can determine whether one or more characters
are in the keyboard buffer before calling any functions that read characters. After
calling this function, the AL register contains 0 if the keyboard buffer is empty,
and FFH if the keyboard buffer is not empty.

DOS function OCH is used to clear the keyboard buffer. After it is cleared, the
function whose number was passed to function OCH in the AL registered is
automatically called.

DOS function OAH is used to read a string of characters. Again this function
number is passed in the AH register. In addition, the memory address of a buffer
for the character string is passed in the DS:DX register pair. This buffer is used to
hold the character string. The first byte of the buffer indicates the maximum
number of characters that may be contained in the buffer.

74
Abacus 65 Chmacter Inplll tmd Outplll from DOS

When this function is called, OOS reads up to the maximum number of characters
and stores them in the buffer starting at the third byte. It reads until either the
maximum number of characters is entered or the <Enter> key is pressed. The
actual number of characters is stored in the second byte of the buffer. Extended key
codes which occupy two bytes each in the buffer may be entered. The fIrst byte of
the pair (ASCII value 0) signifies that an extended key code follows. This means,
for example, that for a maximum buffer size of 10 bytes, only five extended
characters may be entered.

The following table illustrates how the various functions respond to <CtrlxC>
or <Ctrb<Break>, and provides a quick overview of the individual functions for
character input
Pet. Task <Ctrl><C> Echo
01H Character input yes I yes
06H direct character input ro ro
07H Character in~ut ro ro
OBH Character input yes ro
OAll Character string input Yf!s ro
OBH Read input-status yes no
OCH Reset input-buffer then input varies varies

Screen output

There are three OOS functions for character output

DOS function 02H outputs a single character to the screen or standard output
device. The character is passed to the DL register.

OOS function 06H which is multi-purpose is also used to output a single


character. The character is passed in the DL register. You can see that the character
whose value is 255 cannot be output since this indicates that the function is to
perform an input operation. Output using this function is faster than using
function 02H since it doesn't test for the <Ctrb<C> or <Ctrl><Break> keys.

DOS function 09H is used for string output. Again, the function number is passed
in the AH register. The address of the string is passed in the DS:DX register pair.
The last character of the string is a dollar sign. In addition, the following control
codes are recognized.

Code Operation
7 "Bell", rinqs the bell on the PC
B "Backspace", erases the preceding character and moves the cursor
back by one character
10 ULine Feed 11 , (LF) moves the cursor one line down
13 "Carriage Return", (CR) moves the cursor to the beginning of the
current line

As with function 02H, this function also checks for <Ctrl><C> or


<Ctrb<Break> .

75
6. The Disk Operating System PC System Programming

Printer

DOS function 05H is used to output a single character to the printer. If the printer
is busy, this function waits until it is ready before returning control to the calling
program. During this time, it will respond to the <Ctrl><C> and <Ctrl><Break>
keys.

The function number is passed in the AH register. The character to output is


passed in the DL register. The status of the printer is not returned. Most
programmers will elect to use the BIOS function instead of the DOS function for
printer output since you can specify the exact printer device and determine the
printer status using the BIOS version. See Section 7.12 for more detailed
information.

Serial interface

There are two DOS functions for communicating using serial interface-one for
input and one for output. Both functions respond to <Ctrl><C> and
<Ctrl><Break>, but they don't return the status of the serial interface, nor do they
recognize transmission errors.

DOS function 03H is used to input data from the serial interface. The character is
returned in the AL register. Since the data is not buffered, the data can overrun the
interface if the interface receives data faster than this function can handle it

DOS function 04H is used to output data over the serial interface. The character to
output is passed in the DL register. If the serial interface is not ready to accept the
data, this function waits until it is free.

Again, most programmers prefer to use the BIOS equivalent functions (see Section
7.9) to perform serial data transmission because of their more complete data
handling capabilities.

Demonstration programs

Earlier we mentioned that it was possible to switch a device from cooked mode to
raw mode and back. The BASIC, Pascal and C programs that follow show you
how to do this. They use the IOCTL functions which permit access to the DOS
device drivers (see Section 6.11.7 for details on this routine). These are routines
which serve as interfaces between the DOS input/output functions and the
hardware. The IOCTL functions in these programs tell the CON device driver
(responsible for the keyboard and the display) whether it should operate in the
cooked mode or in the raw mode.

To demonstrate how differently characters respond in the two modes, the programs
switch the CON driver into raw mode fIrst. Then this driver displays a sample
string several times. Unlike cooked mode, pressing <Ctrl><C> or <Ctrl><S> in
raw mode has no effect on stopping program execution or text display.

76
Abacus 65 Character Input and Output from DOS

After the program finishes displaying the sample string, the driver switches to the
cooked mode. The sample string is displayed again several times. When you press
<Ctrl><C> the program stops (Turbo Pascal version). For the BASIC and C
versions, you can press <Ctrl><C> to stop the program, or press <Ctrl><S> to
pause or continue the display.

Switching between the raw and the cooked mode does not take place directly
through a function. First the device attribute of the driver is determined. This
attribute contains certain information which identifies the driver and describes its
method of operation. One bit in this word indicates if the driver operates in raw or
cooked mode. The programs set or reset this bit, depending on the mode you want
running the driver.

BASIC listing: RAWCOOK.BAS


100
110
120
RAWCOOK
'*---------------------------------------------------------------*'
..
'.***•• *•••••••• ****••••••••• ***.****••• ******* ••••• ****.****••••• ,

130 •• Task make two subroutines available


140 •• to switch the character driver into RAW- or
150 '. COOKED mode *'
160 '. Author MICHAEL TISCHER .'
170 developed 07/23/87 ••

180 last Update 04/08/89

190 ,.**********.******************************* •••• ****.*.***********,

200 '

210 CLS : KEY OFF

220 PRINT"WARNING: This program can only be started if the GWBASIC was"

230 PRINT"started from DOS with the command <GWBASIC Im:60000>."

240 PRINT : PRINT"If this is not the case, please input <s> for Stop.·

250 PRINT"Otherwise press any key ... ";

260 A$ = INKEY$ : IF A$ = "s' THEN END

270 IF ·A$ = .... THEN 260

280 GOSUB 60000 'Install function for interrupt call

290 CLS 'erase display

300 HANDLE, = 0 'handle is connected with console driver

310 PRINT"RAWCOOK (c) 1987 by Michael Tischer" : PRINT

320 PRINT"The Console driver (Keyboard and Display) is now in RAW-"

330 PRINT"Mode so that during input and output no control characters ..

335 PRINT"are recognized."

340 PRINT"Because of this not even <CTRL> + <S> can stop the "

345 PRINT"following output."

350 PRINT"Try it •••• : PRINT

360 PRINT "Press any key to start output

365 GOSUB 25000 'Clear keyboard buffer

370 A$ - INKEY$ : IF A$ = .... THEN 370 'wait for a key

380 GOSUB 52000 'Switch console driver into RAW mode

390 GOSUB 50000 'Output Test-String

400 CLS

410 PRINT"The Console driver (Keyboard and Display) is now in •

420 PRINT"COOKED mode. Control characters will be recognized during ..

425 PRINT"input/output."

430 PRINT"The following output can be stopped with <CTRL> + <S>."

440 PRINT"Try it ..... : PRINT

77
6. The Disk Operating System PC System Programming

450 PRINT "Press any key to start the output .....


455 GOSUB 25000 'Clear the keyboard buffer
460 A$ = INKEY$ : IF A$ - .... THEN 460 'wait for a key
470 GOSUB 51000 'change console driver to the COOKED mode
480 GOSUB 50000 'output Test-String
490 CLS
500 END
510 '
25000 A$ - INKEY$ : IF A$ = NN THEN RETURN 'Clear the keyboard buffer
25010 goto 25000
50000 ,*.*.*.************.*****.*.*.*.******************* ••• **.*******,
50010 ,* outputs a Test-String on the Standard output device *'
50020
50030
'*-------------------------------------------------------------*'
,* Input : none *,
50040 ,* Output: none .'
50050 1**************.****••• *************.***********.***********.***,
50060 '
50070 T$ = .. Test.... • 'Output Test-String
50080 FOR I - I TO 250 '250 times
50090 FCT% = &H40 : FeT1% = 0 'Write function number for handle
50100 INR% = &H21 'Call DOS-Interrupt 21H
50110 ADRLO% = 9 : ADRHI% = 0 'output 9 characters at a time
50120 OFSLO% - PEEK (VARPTR (T$) +1) 'La-byte of offset address of string
50130 OFSHI% = PEEK (VARPTR(T$) +2) 'HI-byte of offset address string
50140 HANDLO% = 1: HANDHI% - 0 'address the standard output device
50150 CALL IA(INR%,FCT%,FCT1%,HANDHI%,HANDLO%,ADRHI%,ADRLO%,OFSHI%,
OFSLO%,Z%,Z%,Z%,Z%1
50160 NEXT 'next run
50170 PRINT
50180 RETURN 'back to caller
50190 '
51000 ,*********************.**************** •• *********** •• ******.***,
51010 '* change device driver to COOKED mode *'
51020
51030
51040
'*-------------------------------------------------------------*,
,* Input: HANDLE% - handle connected with the driver
'. Output: none
51050 ,************.************************************************* ••
.'
*,

51060 '
51070 GOSUB 53000 'Get device attribute of driver
51080 ATTRIB% = ATTRIB% AND 223 'Find COOKED-Bit
51090 GOSUB 54000 'Set device attribute of driver
51100 RETURN 'back to caller
51110 '
52000 .******* •• ****************** •• ***** •••• *****.*.*******.****.***.­
52010 ,* change device driver to RAW mode
52020 ,*-------------------------------------------------------------.'
52030 ,* Input: HANDLE% ~ handle connected to the driver

52040 '* Output: none *,

52050 1.***.******.*****.******.*.***********.**.******** ••• **.******.1


52060
52070 GOSUB 53000 'Get device attribute of driver
52080 ATTRIB% = ATTRIB\ OR 32 'Set RAW-Bit
52090 GOSUB 54000 'Set device attribute of driver
52100 RETURN 'back to caller
52110 '
53000
53010
53020
,*************.***.*******.*******.*.***.*.**************.******'
'. Get device attribute of a driver
.'
'* _____ --------------------------------------------------- _____ *'
53030
53040
53050
,*
,*
Input : HANDLE\ = handle connected with a driver
Output: ATTRIB% = Attribute of driver
Info Z\ used as Dummy-Variable .'
*'
*,

78
Abacus 6.5 Character Input and Output from DOS

53060 only Bits 0 to 7 of the device attribute

53070 '.
determined
53080 ,****** •• ******.***************** •• ****.******.****.*.**********,
53090 '
53100 FCT%-&H44 'Function number for IOCTL
53110 FCTl%-O 'Read Function number for IOCTL: Read device attribute
53120 INR%=&H21 'Call DOS-Interrupt 21H
53130 HANDHH = INT(HANDLE%/256) 'HI-byte of the handle
53140 HANDLO% - HANDLE% AND 255 'LO-byte of the handle
53150 CALL IA(INR%,FCT%,FCT1%,HANDHI%,HANDLO%,Z%,Z%,Z%,ATTRIB%,Z%,Z%,Z%,Z%)
53160 RETURN 'back to caller
53170 '
54000 1*********.*****************************************************,
54010 '1*. _____________________________________________________________
Set device attribute of a driver *'
54020

..'
54030 ' . Input : HANDLE% - handle connected to a driver *,
54040 ,* ATTRIB% - the attribute of the driver
54050 Output: none
54060 Info Z% used as Dummy-Variable ,
54070 1**************************************************.*.*.********,
54080
54090 FCT%=&H44 'Function number for IOCTL
54100 FCT1%-1 'Set function number for IOCTL: device attribute
54110 INR%-&H21 'Call DOS-Interrupt 21(h)
54120 HANDHI% - INT(HANDLE%/256) 'HI-byte of the handle
54130 HANDLO% - HANDLE% AND 255 'LO-byte of the handle
54140 ATHH - INT (ATTRIB%/256) 'HI-byte of the Attribute
54150 ATLO% - ATTRIB% AND 255 'LO-byte of the Attribute
54160 CALL IA(INR%,FCT%,FCTl%,HANDHI%,HANDLO%,Z%,Z%,ATHI%,ATLO%,Z%,Z%,Z%,Z%)
54170 RETURN 'back to caller
54180
60000 ,*******************************.*******************************,
60010 '. Initialize the Routine for Interrupt call *,
.*____________________________________________________----_____ *1

.'.'
60020
60030 '* Input : none
60040 '* Output: IA is the Start address of the Interrupt-Routine
60050 1******** •• ******************* •• * •••• *****.*****.* •••• **********'
60060
60070 IA-60000! 'Start address of the routine in the BASIC-Segment
60080 DEF SEG 'Set BASIC-Segment
60090 RESTORE 60130
60100 FOR 1% - 0 TO 160 READ X% POKE IA+I%,X% NEXT 'Poke Routine
60110 RETURN 'back to caller
60120 '
60130 DATA 85,139,236, 30, 6,139,118, 30,139, 4,232,140, 0,139,118
60140 DATA 12,139, 60,139,118, 8,139, 4, 61,255,255,117, 2,140,216
60150 DATA 142,192,139,118, 28,138, 36,139,118, 26,138, 4,139,118, 24
60160 DATA 138, 60,139,118, 22,138, 28,139,118, 20,138, 44,139,118, 18
60170 DATA 138, 12,139,118, 16,138, 52,139,118, 14,138, 20,139,118, 10
60180 DATA 139, 52, 85,205, 33, 93, 86,156,139,118, 12,137, 60,139,118
60190 DATA 28,136, 36,139,118, 26,136, 4,139,118, 24,136, 60,139,118
60200 DATA 22,136, 28,139,118, 20,136, 44,139,118, 18,136, 12,139,118
60210 DATA 16,136, 52,139,118, 14,136, 20,139,118, 8,140,192,137, 4
60220 DATA 88,139,118, 6,137, 4, 88,139,118, 10,137, 4, 7, 31, 93
60230 DATA 202, 26, 0, 91, 46,136, 71, 66,233,108,255

79
6. The Disk Operating System PC System Programming

Pascal listing: RAWCOOK.PAS


{**** •• *********************************** •• ***** •• *** •••••• *****.**.*}
(* RAWCOOK *J
(*-------------------------------------------------------------------*J
(* Task provide two functions to switch *)
(* a character sevice driver to the RAW- *J
(* or the COOKED mode *1
(*-------------------------------------------------------------------*)
(* Author MICHAEL TISCHER *)
(* developed : 08/16/87 *1
(* last Update : 05/11/89 *)
{* •• *************.*.* •••• ******************************.***********.**}

program RAWCooKP;

Uses Crt, Dos; ( CRT and DOS units

const STANDARDIN = 0; ( handle 0 is connected with Standard input


STANDARDOUT ~ 1; ( handle 1 is connected with Standard output

var Keys : char; ( only needed for Demo program

{****** ••************* ••• ********************** •••••••• ***************}


(* GETMODE: read attribute of device driver in *)
(* Input : the handle passed must be connected to device addressed *1
(* Output: the device attribute *1
{*_._-_._._._._-_._._._-_....-. __ ._.-._-_._.-.-._. __ .-*** •• ***********}
function GetMode(Handle integer) : integer;

var Regs : Registers; ( register-Variable for Interrupt call )

begin
Regs .ah : ~ $44; ( Function number for IOCTL: Get Mode
Regs.bx := Handle;
MsDos ( Regs ); Call DOS-Interrupt 21H
GetMode Regs.dx ( Pass device attribute
end;

{* •• __ ._.-----_._._._----_._._------------_._-_ •• _._--************ •• **}


(* SETRAW Change a character driver into RAW-Mode *1
(* Input the handle passed must be connected with *1
(* addressed device *1
(* Output none *1
{** •• ***************************** ••• *********************************}

procedure SetRaw(Handle integer) ;

var Regs : Registers; ( register-Variable for Interrupt call I

begin
Regs.ax $4401; ( Function number for IOCTL: Set Mode
Regs.bx Handle;
Regs.dx GetMode(Handle) and 255 or 32; ( new device attribute
MsDos ( Regs ); Call DOS-Interrupt 21H
end;

(*********************************************************************}
(* SETCOOKED Change a character driver into the COOKED-Mode *)
(* Input the handle passed must be connected with the *J
{* device addressed *1
(* Output none *)
{*********************************************************************}

procedure SetCooked(Handle integer) ;

var Regs : Registers; { register-Variable for Interrupt call I

80
Abacus 65 Character Input and Output from DOS

begin
Regs.ax .= $4401; 1 Function number for IOCTL: Set Mode
Regs.bx := Handle;
Regs.dx .= GetModelHandle) and 223; new device attribute
MsDosl Regs ); Call DOS-Interrupt 21H
end;

{* ••••• **•• *** •• ******** ••••••• ***** •• ** •• ******.*********************}


1* TESTOUTPUT Output a Test-String 1000 times on the Standard *}
1* output device *)
1* Input none *}
1* Output none *}
{*********************************************************************}

procedure TestOutput;

var Regs : Registers; register-Variable for Interrupt call


LoopCnt integer; 1 Loop variable
Test : string[9]; 1 The Test-String for output

begin
Test := ·Test ..... ':
Regs.bx .- STANDARDOUT; { output on the Standard output device

Regs.cx .= 9; { Number of characters

Regs.ds := Seg(Test); { Segment address of the text

Regs.dx := Ofs(Test)+l; { Offset address of the text

for LoopCnt :- 1 to 1000 do

begin

Regs.ah :- $40; 1 Write function number for handle

MsDos ( Regs ); { Call DOS-Interrupt 21H

end:
writeln;
end:

{************************************* •• ******************************}
{* MAIN PROGRAM *}
{************************************.*************.*.********* •• *****}

begin
ClrScr; { Clear screen }
writeln('RAWCOOK (c) 1987 by Michael Tischer"13flO);
writeln('The Console driver is now in RAW-Mode. Control keys such as <Ctrl><C>');
writeln('are not recognized during output. Press a key to display a text on
'113110) ;
writeln{'the screen, and try stopping the display by pressing <Ctrl><C>');
Keys : = ReadKey; ( wait for key )
SetRaw(STANDARDIN); { Console driver in RAW mode }
TestOutput; Output Test-String 1000 times }
ClrScr; ( Clear Screen )
while KeyPressed do
Keys := ReadKey; ( Empty keyboard buffer

writeln{'The Console driver is now in COOKED mode. Control keys such as');

writeln('<CTRL><C> are recognized during output');

writeln('Press a key to start, then press <Ctrl><C> to stop the display');

Keys := ReadKey; ( Wait for key

SetCooked(STANDARDIN);

TestOutput; { Output Test-String 1000 times

end.

81
6. The Disk Operating System PC System Programming

C listing: RAWCOOK.C
/***************************.**********************.*.******** •••••• **/
1* RAW COO K *1
1*-------------------------------------------------------------------*1
1* Task provides two functions for *1
1* switching a character device driver into the RAW *1
1* or into the COOKED mode *1
1*-------------------------------------------------------------------*1
1* Author MICHAEL TISCHER *1
1* developed on : 08/16/87 *1
1* last Update : 04/08/89 *I
1*-------------------------------------------------------------------*1
1* (MICROSOFT C) *1
1* Creation MSC RAWCOOKC; *1
1* LINK RAWCOOKC; *1
1* Call RAWCOOKC *1
1*-------------------------------------------------------------------*1
1* (BORLAND TURBO C) *1
1* Creation : through command RUN in the menu *1
'*._----------_._._---._._--_._._---_._.... _-._._.-._-****************/
'include <dos.h> 1* include Header files *1

'include <stdio.h>

'include <conio.h>

'define STANDARDIN 0 1* handle 0 is the Standard input device *1

'define STANDARDOUT 1 1* handle 1 is the Standard output device *1

/*********** ••• **************************** •• *****.*******************/


1* GETMODE: read the attribute of an device driver *1
1* Input : the handle must be connected with the addressed device *1
1* Output : the device attribute *1
/*************************** •• ***** ••• *******.******************** •• **/

int GetMode(Hand1e)

int Handle; 1* points to the character driver *1

union REGS Register; 1* register-Variable for Interrupt call *1


Register.x.ax - Ox4400; 1* Function number for IOCTL: Get Mode *1
Register.x.bx - Handle;
intdos(&Register, &Register); 1* Call DOS-Interrupt 21H *1
return(Register.x.dx); 1* Pass device attribute *1
}

1*·------------·-··· __ ·_·_·_--_·_·_------_·-·_·····_-- ****************/


1* SETRAW Change a character driver into RAW mode *1
1* Input the handle passed must be connected with the addressed *1
1* device *1
1* Output none *1
/****************** •• ******* •• ****************************.***********/

int SetRaw(Handle)

lnt Handle; 1* points to the character driver *1

union REGS Register; 1* register-Variable for Interrupt call *1


Register.x.ax Ox4401; /* Function number for IOCTL: Set Mode *1
Register.x.bx Handle;
Register.x.dx GetMode (Handle) & 255 I 32; 1* new device attribute *1
intdos(&Register, &Register); 1* Call DOS-Interrupt 21H *1
}

82
Abacus 65 Character Input and Olllput from DOS

/** •••• *.*************.* •••• *.*******.*.*.*****************.*.******.*/


/* SETCooKED: Changes a character driver into the COOKED mode */
/* Input the handle passed must be connected with the device */
/* addressed */
/ * Output none */
/ •••• **************.****.*.*.***********.*.*******.*.******* •• ********/

int SetCooked(Handle)

int Handle; /* points to the character driver */

union REGS Register; /* register-Variable for Interrupt call */

Register.x.ax - Ox4401; /* Function number for IOCTL: Set Mode */

Register.x.bx - Handle;

Register.x.dx GetMode(Handle) , 223; /* new device attribute */

intdos('Register, 'Register); /* Call DOS-Interrupt 21H */

I
/**********************.*** ••• *****.***** •• ********.*.**********.*****/
/* TESTOUTPUT: outputs a Test-String 1000 times on the Standard */
/* output device */
/* Input none */
/* Output none */
/******** ••• ************************************* ••• **.*.* ••• *** ••• ***/

void TestOutput()

int i; /* Loop Variable */


static char Test [] = "Test .... '1; /* the text for output */

printf ("\n");
for (i - 0; i < 1000; i++) /* output 1000 times */
fputs(Test, stdout); /* Output String on the Standard output. */
printf ("\n");
I
/*******************.*.*.*.*******************.**********.************/
/** MAIN PROGRAM **/
/******** •••• *.*************** •• **** ••• ********* ••• *******************/

void main ()

(
printf("\nRAWCOOK (c) 1987 by Michael Tischer\n\n");

printf ("The Console Driver (Keyboard, Display) is now in ");

printf ("RAW Mode. \nDuring the following output control characters, \n");

printf ("such as <CTRL-S> will not be recognized. \n");

printf("Try it.\n\n");

printf ("Please press a key to start. .• ");

getch () ; /* wait for key */

SetRaw(STANDARDIN); /* Console driver into RAW mode */

TestOutput () ;

while (kbhit(» /* in the meantime remove key codes from */

getch(); /* keyboard buffer */


printf("\nThe console driver is now in COOKED mode. ");
printf ("Control ke:rs such as\n<CTRL-S> are recognized during ");
printf("output and answered accordingly!\n");
printf("Please press a key to start ..• ");
getchO; /* wait for key */
SetCooked(STANDARDIN); /* Console driver in the COOKED mode */
TestOutput () ;

83
6. The Disk Operating System PC System Programming

I 6.6 File Management in DOS


The DOS file management functions are among the most basic available to the
programmer. These functions are used to:

Create and delete files

Open and close flles

Read from and write to files

Operating systems such as DOS provide the programmer with functions for file
management. For example, DOS provides functions which return special file
information or functions to rename a file. One peculiarity of DOS is that these
functions exist in two forms because of the combined CP/M & UNIX
compatibility. For every UNIX compatible file function, there is also a CP/M
compatible file function.

FeB functions

The CP/M compatible functions are designated as FCB functions since they are
based on a data structure called the FCB (File Control Block). OOS uses this data
structure for information storage during file manipulation. The user must reserve
space for the FCB within this program. The FCB permits access to the FCB
functions which open, close, read from and write to files.

Since the FCB functions were developed for compatibility with CP/M's functions,
and since CP/M has no hierarchical file system, FCB functions do not support
paths. As a result, FCB functions can only access files which are in the current
directory.

UNIX handle functions


The UNIX compatible handle functions don't have this problem. With these
functions, a handle is used to identify the file to be accessed. The OOS stores
information about each open file in an area that is separate from the program.

6.6.1 Handle Functions

It is easier for the programmer to access a file using the handle functions than to
access a file using the FCB functions. The handle functions do not require a
programmer to use a data structure for file access like the FCB functions do. In a
manner similar to the functions of the UNIX operating system, file access is
performed using a filename. The filename is passed as an ASCII string when the
file is opened or fIrst created. This must be performed before the fIrst write or read
operation to the file. In addition to the filename, it may contain a device
designator, a pathname and a file extension. The ASCII string ends with the end

84
Abacus 6.6 File Management in DOS

character (ASCII code 0). After the file is opened, a numeric value called the handle
is returned. Any further operations to this file are performed using this 16-bit
handle. For a subsequent read or write operation, the handle and not the filename is
passed to the appropriate function.

For each open file, DOS saves certain information pertaining to that file. If the
FCB functions are used, DOS saves the information in the FCB table within the
program's memory block. When the handle functions are used, the information is
stored in an area outside of the program's memory block in a table that is
maintained by the DOS. The number of open files is therefore limited by the
amount of available table space. The amount of table space set aside by DOS is
specified by the FILES parameter of the CONFIG.SYS file:

FILES = x

In DOS Version 3.0, this maximum is 255. If you change the maximum number
of files in the CONFIG.SYS file, the change will not go into effect until the next
time that DOS is booted.

FILES

While the FILES parameter specifies the maximum number of open files for the
entire operating system, DOS limits the number of open files to 20 per program.
Since five handles are assigned to standard devices such as the keyboard, monitor
and line printer, only 15 handles are available for the program. For example, if a
program opens three files, DOS assigns three available handles and reduces the
number of additional handles available by three. If this program calls another
program, the three files opened by the original program remain open. If the new
program opens additional files, the remaining number of handles available is
reduced even further.

In addition to the standard read and write functions, there is also a file positioning
function. This lets you specify an exact location within the file for the next data
access. Knowing both a record number and the length of each data record allows
you to specify the position to access a particular data record:

position = record number * length of record

This function is not used during sequential file access since DOS sets the file
pointer during opening or creation of a file to the first byte within the file. Each
subsequent read or write operation moves the file pointer by the number of bytes
read towards the end of the me so that the next me access starts where the previous
onecnded.

The following table summarizes the handle functions. For a more detailed
description of these functions, see Appendix C.

85
6. The Disk Operating System PC System Programming

Function No. Operation


3CH create file
3DH Open file
3EH Close file
42H Move file pointer/determine file size
43H Read/Write file attribute
56H Rename file
57H Read/Write modifications & date/time of file

Here are a few general rules to follow when using these functionsl

Functions which expect a filename or the address of a filename as an argument


(e.g., Create File and Open File) expect the segment address of the name in the DS
register and the offset address in the DX register. If the function successfully
returns a handle, it is returned in the AX register.

Functions which expect a handle as an argument expect it in the DX register. After


the call, the carry flag indicates if an error occurred during execution. If an error
occurs, the carry flags is set and the error code is returned in the AX register.

Function 59H of DOS interrupt 21H returns very detailed infonnation concerning
errors which occur during disk operations. This function is available only in DOS
Versions 3.0 and higher.

6.6.2 FeB Functions

As discussed earlier, DOS uses an FeB data structure for managing a file. The
programmer can use this data structure to obtain information about a fIle or change
infonnation about a file. For this reason we shall examine the structure of an FeB
before discussing the individual FeB functions.

The FeB is a 37-byte data structure which can be subdivided into different data
fields. The following figure illustrates these fields.

86
Abacus 6.6 File MQNJgement in DOS

RAM

1\ 0000(00
+ OOH Device name (1 byte)
+ 01H Filename (S bvtesl
+ 09H File mode (3 bytes)
+ OCH Current block number (1 word)
+ OEH Data record size (1 word)
+ 10H File size (2 words)
+ 14H Modification date (1 word)
+ 16H Modification time (1 word)
+ ISH Reserved (8 bytes)
+ 20H Current data record number(l byte)
+ 21H Data record number for (2 words)

random access

Structure ofan FeB

~otice that the name of the file is found beginning at offsets om through OBH of
the FCB. The byte at offset 0 is the device indicator, 0 is the current drive, I drive
A, 2 drive B, etc.

The filename which begins at offset I is an ASCII string. It may not contain a
pathname since it's limited to 8 characters. For this reason, the FCB functions can
access only files in the current directory. Filenames shorter than eight characters
are padded with spaces (ASCII code 32). The file extension, if any, occupies the
next three bytes of the FCB.

At offset OCH of the FCB is the current number of the block for sequential file
access. The two bytes at offset OEH are the record size. The four bytes at offset
lOH are the length of the file.

The date and time of the last modifications to the file are stored beginning at offset
14H of the FCB in encoded form.

87
6. The Disk Operating System PC System Programming

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 bit
I~~1~1~1~1~1~1~1~1~1~lrrl~ITI~I~1~I
~' ____ ~Ir- _____A'______ ~I ______-,A'____ ~I ______'
Hour Minute Seconds in
2-second
Increments (e.g.,
13 means 26)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 obit

I I I I I I I I II II II I I I
\. A .A. J
I I I
Year (relative to 1980) Month Day of month

Format of time and date stamps in the FeB

An eight· byte data area follows and is reserved for DOS (no user modifications
allowed). The use of this area varies from one version of DOS to another.

Following this reserved data area is the current record number which is used in
connection with the current block number to simulate CP/M operations.
Random files

The last data field of the FCB is used for a type of access in which the data within
the file may be retrieved or written in a non-sequential order. This field is four
bytes long. If a record is equal to or larger than 64 bytes, only the first three bytes
are used for indicating the current record number. All four bytes of this field are
used for records smaller than 64 bytes.

Extended FCB

Besides a standard FCB, DOS also supports the extended FCB. Unlike normal
FCBs, extended FCBs access files with special attributes, such as hidden files or
system files. Furthermore, they permit access to volume names and subdirectories
(this doesn't mean that you can access files in other directories besides the current
directory).

An extended FCB is similar to a standard FCB, but it's seven bytes larger. These
seven bytes are located at the beginning of the data structure. All subsequent fields
are therefore displaced by seven bytes.

88
Abacus 6.6 File Management in DOS

RAM
+ OOH FF (1 byte)

1\ OOT OO
+ 01H Reserved(O) (5 bytes)
+ 06H File attribute n byte)
+ 07H Device name (1 byte)
+ 08H Filename (8 bytes)
+ 10H File extension (3 bytes)
+ 13H Current block number (1 word)
+ ISH File record size (1 word)
+ 17H File size (2 words)
+ 1BH Modifications-date (1 word)
+ 1DH Modifications-time (1 word)
+ 1FH Reserved (8 bytes)
+ 27H Current data record number (1 byte)
+ 28H Data record number (2 words)

Structure of an extended FeB

The ftrst byte of an extended FCB always contains the value 255 and identifies this
as an extended FCB. Since this address contains the device number in a normal
FCB and can therefore not contain the value 255, OOS can tell the difference
between a normal and an extended FCB. The next five bytes are reserved
exclusively for the use by OOS. They should not be changed. The seventh byte is
a fIle attribute byte. See Section 6.1.2 for the details of the file attribute byte.

Now that you're familiar with the FCB structures, the next section focuses on
using FCBs for accessing fIles.

FCB and file access

Before accessing a fIle, an FCB must be built in the program's memory area. The
area can be reserved within the data segment of the program or by allocating
additional memory using another DOS function (see Section 6.9).

Although it is possible to write the data directly into the FCB, it is better to use
one of the appropriate OOS functions to do this.

For example, to set the filename in the FCB you can use OOS function 29H. The
function number is passed in the AH register. The address of the FCB is passed in
the ES:DI register pair. The address of the fIlename is passed in the DS:SI register
pair. The fIlename is an ASCII string terminated by the end character (ASCII code
0). The AL register contains flags for converting the fIlename and are discussed in
more detail in Appendix C.

Open FCB

After the FCB is properly formatted the fIle can be opened or created using a OOS
function. When this happens DOS stores information about that fIle in the PCB

89
6. The Disk Operating System PC System Programming

such as the file size, date and time of file creation, etc. At this point the FCB is
considered opened.

By default, the record length is set to 128 bytes when the FCB is opened. To
override this record length, store the desired record length at offset OEH of the FCB
after it is opened. Otherwise the default length will be used.

DTA

For record lengths greater than 128 bytes, the record buffer also known as the
DTA, or Disk Transfer Area must be moved to accommodate the longer record
size. Normally, DOS builds the DTA in the PSP (Program Segment Prefix).
Accessing the file using the default DTA for a record length greater than 128 bytes
would overwrite some of the other fields in the PSP.

The most convenient way to select a new DT A is to reserve the space in the
program's data segment. To change the address of the DTA use DOS function
lAH. The address of the new DTA is passed in the DS:DX register pair. DOS
assumes that you have set aside an area large enough to accommodate your largest
record length so you don't have to specify the new length.

File access
For sequential file access, processing begins at the first record in the file. DOS
maintains a record pointer in the FCB to keep track of the current record within the
file. Each time the file is accessed, DOS advances the pointer so that the second,
third, fourth, etc record is processed in order.

For random file access, the records can be processed in any order. The position of
each record relative to the beginning of the file determines its record number. This
record number is then passed to DOS to access a specific record. The last field of
the FCB is used to specify the record number to DOS.

It's also possible to change from sequential access mode to random access mode
and vice versa since processing depends on a specific DOS function to access the
file. In effect. there are two sets of independent functions. one for sequential access
and one for random functions.

Following is a list of all of the FCB functions of DOS interrupt 21H. A more
detailed description of the functions is found in Appendix C.

Function No. Task


OFH Open file
lOH Close file
l3H Delete file
14H Sequential read
ISH Sequential write
I6H Create file
I7H Rename file

90
Abacus 6.6 File ManlJgemenl in DOS

Function No. Task


lAH Set DTA address
21H Random Read (of record)
22H Random Write (of record)
23H Determine file size
24H Set record number for random access
27H Random read (one or more records)
28H Random write (one or more records)
29H Enter filename into FeB

Some basic rules about these functions should be mentioned here:

Using the FCB functions, you can access several files, each with their own unique
FCB. To tell OOS which file is to be accessed, pass the address of the file's FCB
in the DS:DX register pair.

Most of the functions return an error code in the AL register or the value zero if
the function was successfully completed. For functions which open, close, create
or delete a file, a code of 255 is returned if an error occurs. The other functions
return specific error codes. More detailed information about these errors can be
determined by calling OOS function 59H but is available only in versions of DOS
V3.0 or later.

Handles vs. FCBs

After the two groups of functions made available by OOS have been presented, the
advantages and disadvantages of the individual functions should be discussed
briefly. For those who want to convert a program from the CP/M or UNIX
operating systems into DOS, the choice will be easy, but for those who want to
develop a new program under OOS, this discussion can help in your deciding on
which set of functions to use.

Handles

There are two main advantages to using handle functions. The first is the
capability to access a file in any subdirectory of the disk. The second is that the
handle functions are not limited to the number of FCBs which can be stored in a
program's memory space.

There are a number of additional considerations. You can access the name of a disk
drive only by using an FCB. When the FCB is opened, you can easily determine
its file size and the date of the last modification. The handle functions
automatically provide an area large enough to accommodate the records in the file.

As you can see there are arguments for and against using either the FCB functions
or the handle functions. For future versions of DOS, the handle functions will play
a more important role and the importance of the FCB functions will diminish.
This is reason enough to use the handle functions for your new program
development

91
6. The Disk Operating System PC System Programming

6.7 Accessing the DOS Directory

There are two groups of DOS functions for working with directories. The first
group is used to manipulate the subdirectories and the second to search for mes on
the mass storage devices.

With DOS Version 2.0 came the introduction of subdirectories. A mass storage
device could be logically divided into smaller subdirectories which could in turn be
further subdivided. In effect this organization created a directory tree.

Main directory

START.BAT

= Directory, subdirectory

= File
Directory tree

In this directory tree, the names and numbers of subdirectories are not static.
Therefore there must be a way to add, change and delete entries on the tree. Other
functions must be available to set the current directory so that a complete
pathname is not required for all me accesses.

At the user level the MD, RD and CD commands can be used to make a directory,
remove a directory and change a current directory. Internally, these commands are
performed with functions 39H, 3AH and 3BH of DOS interrupt 21H.

All three functions use identical calling conventions.

The function number is passed in the AH register. The address of the path is passed
in the DS:DX register pair. The path is a string and may be a complete path
designation including a preceding drive letter followed by a colon (a device name)
and terminated by ASCII code O. If the device name is omitted, the current device
is the default.

92
Abacus 6.7 Accessing the DOS Directory

Following execution, the carry flag indicates the return code. If the carry flag is
reset (0), then execution was successful. If the carry flag is set, then an error
occurred and the error code is passed back in the AX register.
Function 39H creates or makes a new directory (Make Directory). The name for the
new directory is specified as the last element in the path. An error will be returned
by the functions if one or more of the directories specified in the path do not exist,
if the new directory name already exists or if the maximum number of ftles in the
root directory is exceeded.

Function 3AH deletes or removes a directory (Remove Directory). An error will be


returned by the function if the target directory is not empty or the specified
directory does not exist in the current path.

Function 3BH changes the current directory (Change Directory). An error is


returned if the directories named in the path do not really exist.

Function OEH sets the default disk drive. Besides the function number in the AH
register, only the device code of the new current device must be passed in the DL
register. Code 0 stands for the device A, 1 for B, 2 for C, etc.
Directory specification
Before specifying the current directory using function 3BH, it is sometimes
necessary to find the current directory. DOS makes function 47H available to the
programmer for this purpose. Since it can return the path of the current directory
for any device, the device number must be passed to the function. If this is the
current device, the value 0 must be passed in the DL register. For all other devices,
the value 1 must be passed for drive A, 2 for B, 3 for C, etc.

Besides the device code, the function must also have the address of a 64-byte buffer
within the user program. The DS register contains the segment and the SI register
holds the offset address of this buffer. After the function call this buffer contains
the path designation of the current directory, terminated with the end character
(ASCII code 0). The path designation cannot be preceded by the device name or the
\ character. If the current directory is the root directory, the buffer contains only the
end character. If a device code unknown to DOS was passed during the function
call, the carry flag is set and the AX register contains the error code OFH.

Let's consider the functions for searching for one or more files in the current
directory on the current device. Again the parallel between handle and FCB
functions appears. Two function groups exist to search for files. The group of
FCB functions has the disadvantage that they limit the search to ftles in the current
directory of a certain device, while handle functions allow searching for files in any
directories of any devices. The term "handle" functions doesn't really fit these
functions since they are not addressed with a handle. This designation originated
with the introduction of subdirectories (and therefore the handle functions) in DOS
Version 2.0. Version 1.0 offered only the FCB functions.

93
6. The Disk Operating System PC System Programming

6.7.1 Searching for Flies using FeB Functions

This method of file search uses functions llH and 12H. Using them you can
search for files with a fixed name or flIes with a mename extension. Function IlH
finds the first flIe in the current directory. Function 12H finds all other additional
files. The FCBs play a significant role since they mediate between the calling
program and the two functions. Let's see how we can search for mes in a directory:

First the program must reserve space for two FCBs. This is done either by
reserving memory in the data area of the program, or by requesting memory from
DOS using function 48H. The programmer can use either normal or extended
FCBs. Extended FCBs offer the advantage of being able to search for mes with
special attributes (system or hidden), volume names and subdirectories. The
flIename for which the search will be made is specified in one of the FCBs. DOS
places the name of the flIe(s) that it finds in the other FCB. To differentiate
between the two FCBs, they are designated with the names Search FCB and Found
FCB.

The address of the Found FCB must be passed to DOS using function lAH. The
Found FCB becomes the new data transmission area (DTA) when this function call
occurs. This area is important for these two functions as well as all other functions
which transfer data between computer and disks. For this reason function 2FH
should determine the address of the current DTA before activating the new DTA.
When the file search ends, the DTA can be restored to its original state using
function lAH.

After the DTA is set to the Found FCB, the next step is to place the name of the
file you are looking for into the'Search FCB. For a more general search, the
wildcards * and ? may be used. You can transfer the flIename directly or transfer it
using function 29H. If you want to search through all mes, use the filename *.*.
If an extended FCB is used, you may insert an additional value into the attribute
field of the Search FCB to limit the search to mes with certain attributes only (see
Section 6.12 for more information on the various attributes).

This concludes the preliminary work. The file search can begin with the current
directory. For this purpose, function IlH is called with the function number in the
AH register, the segment address of the Search FCB in DS and the offset address in
the DX register. If the system finds a flIe with the indicated name, the AL register
contains the value 0 after the function call. If the flIename wasn't found, the AL
register contains a value of 255. The found flIename and its attributes (if extended
FCBs are used) can be read from the Found FCB. For additional searches, function
12H (not function I1H) is called. Function 12H's register contents during call and
return are similar to function IlH. If it returns the value 255 in the AL register
during one of the calls, the Search has ended.

94
Abacus 6.7 Accessing the DOS Directory

6.7.2 Searching for Flies using Handle Functions

Working with handle functions is easier than working with the FCB functions.
There are functions for searching for the first file (the 4EH function) and
subsequent files (the 4FH function). Both functions return the information to the
DTA. For this reason the DTA should be moved into an area accessible to the
current program before calling either of these functions. This area must have at
least 43 bytes available. As mentioned in connection with the FCB functions, the
DTA should be restored to its original address after the search ends.

During the call of the 4EH function, the function number is passed in the AH
register, the attribute in the CX register and the address of the file to be found in
the DS:DX register pair. The filename is a series of ASCII characters, terminated
with an end character (ASCII code 0). In addition to a device name, you may add a
complete path designation and the wildcard characters * and ? If a path is not
specified, DOS assumes that the search should be made in the current directory of
the indicated device. If a device is not specified, the search proceeds on the current
device. Mter the function call, the carry flag indicates whether a file was found. If
the file couldn't be found, the carry flag is set, and the AX register contains an
error code. An error code of 2H is returned if the indicated path does not exist If no
file could be found, an error code of 12H is returned. If the carry flag is reset, the
DTA contains the information about the file found. It has the following structure:
Address Contents Type
+OOH reserved for DOS 21 bytes
+15H Attribute of file found 1 byte
+16H Time of last modification 1 word
+18H Date of last modification 1 word
+1AH low word of file size 1 word
+lEH high word of file size 12 bytes

Function 4FH executes any further searches. The function number is passed in the
AH register, and no other parameters are required. The carry flag indicates if there
are additional fIles in the current directory to which the search may be applicable.

95
6. The Disk Operaling System PC System Programming

Demonstration programs
The three programs below read directory entries and display them on the screen
using one of the handle functions. You'll find the display more user friendly than
the DOS DIR command: the files appear in a window, and the filename display
stops as soon as the window is fIlled with filenames. This permits easy reading of
filenames. By pressing any key, the program displays any additional pages of
fIlenames.

All three programs are designed on the same basic principle: first the main
program determines the search path. It contains the names of the directories in
which the search should be made for the files, the names of the files and the device
where the directory is located. This name can contain wildcards (* and 7) to search
for several files at the same time. If the user does not indicate a search path, the
program defaults to the search path n*.*n. This displays all fIles in the current
directory of the current device, as well as the hidden attribute fIles.

After the program determines the search path, a routine coordinates the loading and
display of individual directory entries. First a routine creates the display window on
the screen for individual entry output. Then a search proceeds for the first entry
using DOS function 4EH. If an entry is found, the screen displays the entry.
Function 4FH searches for all subsequent entries and displays them in the window.

The bottom line of the display window moves up one line with each new line
displayed. Once the entire window fills with data. any further display of entries
stops until the user presses a key. After all entries in the selected directory have
been displayed, the number of fIles is displayed and the program ends.

BASIC listing: DlRB.BAS


100 ,***********************************.*****.*.*********.********.**,
110 DI R B *,
f* _______________________________________________________________ *'
120

130 Task : display all files in a directory

140 '* in a window on the display

160 '* Author : MICHAEL TISCHER

170 '* developed : 07/23/87

180 '* last Update : 04/08/89

190 ,********* ••• *.*.***.**********************************.**********,


200 '

210 CLS : KEY OFF

220 PRINT"WARNING: This program can be run only if GWBASIC was started"

225 PRINT" from the " '

230 PRINT"DOS level with the <GWBASIC /m:60000> command." : PRINT

240 PRINT"If this is not the case, please enter <s> for Stop."

250 PRINT: PRINT"otherwise press any key ••• ·;

260 AS = INKEYS : IF AS = "s" THEN END

270 IF AS - "" THEN 260

280 GOSUB 60000 'Install function for calling interrupt

290 CLS

300 PRINT "DIR (c) 1987 by Michael Tischer"

310 PRINT

320 PRINT·Please input the search path for the file."

330 PRINT"Example: If all files with the extension .BAT in the Root"

340 PRINT" directory of the disk in drive A should be displayed,"

350 PRINT" then please input A:\*.BAT."

360 PRINT·With a blank input, all files in the current directory •

96
Abacus 6.7 Accessing the DOS Directory

370 PRINT"are displayed." ; PRINT


380 INPUT "Search Path: ",DIR$ 'Input Search Path
390 IF DIR$ - "" THEN DIR$ - " •.•• 'search in current directory
400 ENTRY' - 14 '14 Display entries in window
410 GOSUB 50000 'Input Directory and output
420 END
430 '
50000 1***.****** •• ****.**.* •• **.**** •••• *.*.***** ••• *.*.** ••••••••••• _
50010 ,. Input one Directory and display
50020 ,*-------------------------------------------------------------*'
50030 '. Input: DIR$
50040 ,.
50050 '. Output:
= the search path
ENTRY\ = Number of entries in the window
none
.'.'
50050 .• ** •• *****•••••• *** •••••••••• **** •• ******** •• *** ••••• ********.*.
50070 '
50080 DIM MONTH$[ll] 'accepts names of months
50090 RESTORE 50600
50100 FOR 1\ - 0 TO 11 READ MONTH$[I\] NEXT
50110 INR\ = &H21 'Call DOS-Interrupt 21H
50120 FCT\ - &H2F 'Get function number for DTA
50130 CALL IA(INR\,FCT\,Z%,OFSHI%,OFSLO%,Z%,Z%,Z\,Z\,Z%,Z\,DTASEG\,Z\)
50140 DTAOFS% - OFSLO\ + OFSHI% • 256
50150 CLS
50160 OFFSET% INT«20 - ENTRY%) / 2) + 1 , Start line of window
50170 LOCATE OFFSET%,14
50180 PRINT TAB (14) 'f-----'1'----Tr-----''1
50190 PRINT TAB(14)", Filename ,Size , Date , Time
T ,"
,RHSVD,"
50200 PRINT TAB (14) " , - - - - - + - - - + - - - - - - + - - - - + - - -
50210 FOR 1% - 1 TO ENTRY%
50220 PRINT TAB (14) '" "
'output a line for every entry
, ""
'"
50230 NEXT 'output next line
50240 PRINT TAB (14) "L.-----cl---cl,-------.cl- ---cl--Joo
50250 NUMWIND% = -1 'Number of entries in window
50260 NUMFND% - 0 'Number of entries found up to now
50270 ATTRIBUTE% = 255 'search for files with any Attribute
50280 GOSUB 51000 'search for first entry
50290 IF NOT (FOUNDIT%) THEN 50500 'no entry found --> finished
50300 NUMFND% = NUMFND% + 1 'Increase number of entries found
50310 NUMWIND% = NUMWIND\ + 'Increase number of entries in window
50320 IF NUMWIND% <> ENTRY% THEN 50410 'window full?
50330 LOCATE OFFSET\+ENTRY%+4,14 'Set Cursor to line under window
50340 COLOR 0,7 'switch on inverse character display
50350 PRINT" Please press any key ":
50360 A$ = INKEY$ IF A$.= THEN 50360 'wait for a key
50370 LOCATE ,14 'Cursor in line under window
50380 COLOR 7,0 'switch on normal character color
50390 PRINT STRING$(51," "):
50400 NUMWIND% = -1 'the next entry is the first in the window
50410 NUMBER% - 1 : COLOUR% - 7
50420 ULR% = OFFSET% + 2 : LRR\ = OFFSET%+ENTRY% + 1
50430 ULe% = 14 : LRC% = 62
50440 GOSUB 54000 'scroll window up
50450 LOCATE OFFSET%+ENTRY%+2,15 'Set Cursor to last window line
50460 PRINT " , ,
50470 GOSUB 53000 'Output entry
50480 GOSUB 52000 'Get next entry
50490 IF FOUNDIT% THEN 50300 'continue if no entry is available
50500 LOCATE OFFSET%+ENTRY%+4,14 'Cursor in line under the window
50510 COLOR 0,7 'switch on inverse character display
50520 PRINT STRING$(51," H);
50530 LOCATE ,14 'Cursor in line under window
50540 IF NUMFND% - 0 THEN PRINT" no file found"; : GOTO 50570
50550 IF NUMFND% = 1 THEN PRINT· found one file": : GOTO 50570
50560 PRINT NUMFND%; "files found";
50570 COLOR 7,0 'switch on normal character color
50580 RETURN
50590 '
50600 DATA "JAN·,"FEB", "MAR", "APR", "MAY·, "JUN·, "JUL", IIAUG" , "SEp·
50610 DATA "OCT", "NOV", "DEC"

97
6. The Disk Operating System PC System Programming

50620 '
51000 ,****.*************************************** ••••• **************'
51010 ,* Search for first entry in a Directory *,
51020 ,*-------------------------------------------------------------*,
51030 '* Input: DIR$ - Search path

51040 ,* ATTRlBUTE\ - Attribute of file

51050 '* Output: FOUND IT\ - -1 if entry found, otherwise 0

51060 Info the Directory entry is entered into Variable DTA\ *,

51070 '* *'

51080 Z\ is a Dummy-Variable *,

51090 ,*******••• ***.***.**************.*********************** ••••• **,


51100 '
51110DIR$ DIR$ + CHR$(O) 'Put End character on search path
51120 FCT\ &H4E 'Search function number for first entry
51130 INR\ - &H21 'Call DOS-Interrupt 21H
51140 ATLO\ - ATTRlBUTE\ AND 255 'LD-Byte of Attribute
51150 ATHH - INT(ATTRIBUTE\ / 256) 'HI-Byte of Attribute
51160 OFSLo\ - PEEK(VARPTR(DIR$)+1) 'LO-Byte of Offset address
51170 OFSHI\ - PEEK(VARPTR(DIR$)+2) 'HI-Byte of Offset address
51180 CALL IA(INR\,FCT\,Zt,Z\,Z\,ATHlt,ATLO%,OFSHI%,OFSLO\,Z%,Zt,Z%,FLAGS%)
51190 FOUNDIT\ - «FLAGS\ AND 1) = 0) 'Test Carry-Flag
51200 RETURN 'return to calling program
51210 '
52000 1******.* •• ******.*********.*.***********************.****.*.***,
52010 '* find next entry in Directory *,
52020
52030
'*-------------------------------------------------------------*'
Input: DIR$ = Search path *,

52040 ATTRIBUTE\ = Attribute of file *,

52050 ,* Output: FOUNDIT\ = -1 if file found, otherwise 0 *,

52060 ,* Info the Directory entry is read into Variable DTA\

52070 ,*

52080 ,* Z% is a Dummy-Variable *'

52090 ,**********************************************.****************'
52100 '
52110 FCT\ = &H4F 'Find function number for next entry
52120 INR\ = &H21 'Call DOS-Interrupt 21H
52130 CALL IA(INR\,FCT\,Z\,Z\,Z\,Z\,Z\,Z\,Z\,Z\,Z\,Z\,FLAGS\)
52140 FOUNDIT' = «FLAGS\ AND 1) - 0) 'test Carry-Flag
52150 RETURN 'back to calling program
52160 '
53000 ,******* ••• *****************************************************1
53010 ,* Output a Directory entry from the DTA to the display
53020 '* ____________________________________________________ ---------*1
53030 Input: OFFSET% first line of the Directory window *,
53040 ENTRY \ Number of entries in the Directory window
53050 DTAOFS\ Offset address of the DTA
53060 MONTH$ = contains the names of months
53070 Output: none *'
53080 ,***************************************************************1
53090 '
53100 DEF FNDTA(X) = PEEK(DTAOFS% + X)
53110 DEF SEG = DTASEG\ 'Set Segment address of the DTA
53120 LOCATE OFFSET'+ENTRY%+2,15 'Output in the last line of the window
53130 H = 30 'Offset address in DTA for file names
53140 WHILE FNDTA(I\) <> 0 'the END character terminates the name
53150 PRINT CHR$(FNDTA(I\)); 'output a character of the file name
53160 I\ = 1\ + 1 'next character
53170 WEND 'End of Loop
53180 LOCATE OFFSET'+ENTRY\+2,28 'Set Cursor to column 28
53190 PRINT USING ".If .. U"; FNDTA(26) + FNDTA(27) * 256! + FNDTA(28) *
4096! + FNDTA(29) * 65536!;
53200 DATE = FNDTA(24) + FNDTA(25) * 256 'Get Date
53210 LOCATE OFFSET%+ENTRY\+2,36 'Set Cursor to Column 36
53220 PRINT MONTH$[(INT(DATE / 32) AND 15) - 1]; 'Output name of month
53230 PRINT"/";:PRINT USING • .. ·;DATE AND 31; 'Output day of month
53240 PRINT USING "/ffU";INT(DATE / 512) + 1980; 'OUtput year
53250 LOCATE OFFSET'+ENTRY%+2,49 'Set Cursor to column 49
53260 FTIME = FNDTA(22) + FNDTA(23) * 256 'Get time
53270 PRINT USING "ff";INT(FTIME / 2048); 'Output hour
53280 PRINT " ..
....
,

98
AbaclU 6.7 Accessing the DOS Directory

53290 PRINT USING • •• ·;INT(FTIME I 32) AND 63; 'Output Minute


53300 LOCATE OFFSETt+ENTRYt+2,59 'Set Cursor to column 59
53310 FOR It - 0 TO 4 'test Bits 0 to 4 of file attribute
53320 IF (FNDTA(21) AND (2 A lt» <> o THEN PRINT·X·; ELSE PRINT· -;
53330 NEXT n 'test next Bit
53340 DEF SEG : RETURN 'back to calling program
53350 '
54000 1***************************************************************'
54010 '* Scroll current display page up or erase
54020
54030
,*-------------------------------------------------------------*,
,* Input: NUMBER' - how many lines scrolled
54040 ,* ULe' - column upper left *,
54050 '. ULRt - line upper left *,
54060 '. LRC' - column lower right
54070 '. LRRt - line lower right *'
54080 COLOR' - color of erased line
54090 Output: none .'
54100 '* Info If 0 is given for NUMBERt, the screen area *'
54110 indicated is erased *,
54120 ,* the Variable zt is a Dummy .'
54130 '*_._._-_._._._------------_._------_._._-------_._._.**********'
54140 '

54150 FCTt=6 'Function number for scrolling up

54160 INR'·&H10 'Call BIOS-Video-Interrupt 16H

54170 CALL IA(INR\,FCT\,NUMBERt,COLOUR%,Z',ULRt,ULC%,LRR%,LRC%,Z%,Z%,Z',Z')

54180 RETURN 'back to calling program

54190 '

60000 '*****.*.*****.***~*.***.**.***********.***********.** **********'


60010 '* Initialize Routine for Interrupt call
60020 ,*-------------------------------------------------------------*'*'
60030 '* Input : none
60040 ,* Output: IA is the Start address of the Interrupt-Routine *,
60050 1* ______ •••• _._------_._._--------------_._._---------**********­
60060 '
60070 IA=60000! 'Start address of the Routine in the BASIC-Segment
60080 DEF SEG 'Set BASIC-Segment
60090 RESTORE 60130
60100 FOR I% = 0 TO 160 READ Xt POKE IA+lt,X% : NEXT 'Poke Routine
60110 RETURN 'back to calling program
60120 '
60130 DATA 85,139,236, 30, 6,139,118, 30,139, 4,232,140, 0,139,118
60140 DATA 12,139, 60,139,118, 8,139, 4, 61,255,255,117, 2,140,216
60150 DATA 142,192,139,118, 28,138, 36,139,118, 26,138, 4,139,118, 24
60160 DATA 138, 60,139,118, 22,138, 28,139,118, 20,138, 44,139,118, 18
60170 DATA 138, 12,139,118, 16,138, 52,139,118, 14,138, 20,139,118, 10
60180 DATA 139, 52, 85,205, 33, 93, 86,156,139,118, 12,137, 60,139,118
60190 DATA 28,136, 36,139,118, 26,136, 4,139,118, 24,136, 60,139,118
60200 DATA 22,136, 28,139,118, 20,136, 44,139,118, 18,136, 12,139,118
60210 DATA 16,136, 52,139,118, 14,136, 20,139,118, 8,140,192,137, 4
60220 DATA 88,139,118, 6,137, 4, 88,139,118, 10,137, 4, 7, 31, 93
60230 DATA 202, 26, 0, 91, 46,136, 71, 66,233,108,255

One problem in the BASIC version of the directory listing occurs during the
directory output. Functions 4EH and 4FH read the entry into the DTA. It would
make more sense to move the DTA to a variable within the program (an integer
array would be best) to make it easier for the routine which outputs the entry to
access the data. BASIC's garbage collection feature makes this difficult. The
integer array containing the DTA moves periodically in storage and the address of
the DTA, stored internally in DOS, no longer cOrresponds with the address of this
integer array.

For this reason, the DOS function 2FH determines the DTA address. As the entries
are displayed, this address accesses the DTA to determine the me information.

99
6. The Disk Operating System PC System Programming

Pascal listing: DIRP.PAS


{*********************************************************************}
{* DIRP *}
{*-------------------------------------------------------------------*/
{* Task Display all files of any Directory, *}
(* including Subdirectories and *)
(* Volume Names *)
(*-------------------------------------------------------------------*)
{* Author MICHAEL TISCHER *}
(* developed on : 7.8.87 *I
(* last Update : 9.21. 87 *)
{*********************************************************************}

program DIRP;

Uses (Turbo 4.0 Units)


Crt,
Dos;

const ENTRY 14; { Number of entries visible }

type RegTyp = record

ax, bx, ex, dx, bp,

dl, si, ds, eSt flags : integer;

(! Turbo 4.0 owners should use the Registers type from the DOS unit.)
end;

{** this is the format of a Directory entry *****J


(** as returned by the functions 4EH and 4FH )
DirBufTyp - record
Reservebuf array [1 •• 21) of char;

Attribut byte;

Ztime integer;

Zdate integer;

Datgrlo integer;

Datgrhi integer;

DatName array [1 •. 13) of char

end;

Path string [65) ;

var DirBuf DirBufTyp; accepts a Directory entry


DatName Path; { Files to be found

{*********************************************************************}
{* GETFIRST: read in the first Directory entry *1
{* Input none *J
{* Output true or false, depending if an entry was found */
(* *)
{* Info the entry is stored in Variable DIRBUF *1
{*********************************************************************}

function GetFirst{DateiName Path; { files to be found


Attribute integer) : boolean; {search Attribute

var Register regtyp; Register-Variable for call of Interrupt

begin
DateiName := DateiName + fO; { terminate filename with NUL
Register.ax := $4E shl 8; Function number for search of first
Register.cx := Attribute; ( Attribute, for which search is performed
Register.ds seg{DateiName); ( Segment address of filename
Register.dx := succ{ofs(DateiName»; (Offset address of filename
msdos (Dos. Registers (Register»; { Call DOS Interrupt 21H {Turbo 4.0/1
{NOTE:Turbo 3.0 users should change previous line to read msdos{Register);1
( defined in DOS unit.)
if (Register. flags and 11 = 0 { Test Carry-Flag 1

100
Abacus 6.7 Accessi"g the DOS Directory

then GetFirst '. true { Equal to 0 : file found


else Get First := false; { no file found
end;

{•• ****** •• ********************************************************.*}

{* GETNEXT read in the following Directory entry *}


{* Input none *}
(* Output true or false, depending if another entry was found *)
{* *}
{* Info this function can only be called after a successful *}
{* call of the function GETFIRST *}
(* the entry is stored in the Variable DIRBUF *)
{********************************************************************)

function Get Next : boolean;

var Register : regtyp; { Register-Variable for interrupt call }

begin
Register.ax :- S4F shl B; Function number for next search }
msdos{Dos.Registers{Register}}; { Call DOS Interrupt 21H V 4.0}
{NOTE: Turbo 3.0 users should change the previous}
{line to read msdos(Register};}
if (Register. flags and 1) - 0 ( Test Carry-Flag )
then Get Next '. true Equal to 0 : File found }
else GetNext :- false; otherwise no file found }
end;

(*** •• ***************.*.*******.************************************)
(* PRINTDATA: Output information on an entry *)
{* Input none *}
(* Output none *)
(* Info the information about the entry are taken by this *)
{* procedures from Variable DIRBUF *}
{*************** •• ******************************.*.***.******* ••• ***)

procedure PrintData;

var Counter : byte;


DataLenght1, { both Variables are used
DataLenght2 : real; { to calculate file length
begin
writeln; { the window is scrolled up by one line
Counter := 1; ( begins with the first character of the name
while (DirBuf.DatName[Counter)<>tO) do ( repeat up to NUL
begin
write(DirBuf.DatName[Counter); output characters of name
Counter := succ(Counter) { process next character
end;
gotoxy(13, ENTRY};

DataLenght1 := DirBuf.Datgrhi; { determine file length

if DataLenghtl < 0 then DataLenght1 65536.0 + DataLenght1;

DataLenght2 := DirBuf.Datgrlo;

if DataLenght2 < 0 then DataLenght2 '. 65536.0 + DataLenght2;

write(' }', DataLenght1 * 65536.0 + DataLenght2:7:0);

gotoxy(21, ENTRY};

write (' ) , );

case (DirBuf.Zdate shr 5 and 15) of ( determine month )

1 write ('Jan');
2 write ('Feb');
3 write ('Marl) ;
4 write ('Apr') ;
5 write ('May') ;
6 write ('Jun') ;
7 write ('Jul' );
8 write ('Aug') ;
9 write ('Sep') ;
10 write ('Oct') ;
11 write ('Nov') ;
12 write ('Dec')

101
6. The Disk Operating System PC System Programming

end:
write('/',DirBuf.Zdate and 31:2, 'j'); { determine day
write(DirBuf.Zdate shr 9 + 1980:4); ( determine year
gotoxy(34, ENTRY);
write('I', DirBuf.Ztime shr 11:2, ':'); determine hour
write(DirBuf.Ztime shr 5 and 63:3); determine minutes
gotoxy(44, ENTRY); evaluate file attribute
write (' I ') ; separator to preceding field
if (DirBuf.Attribut and 1)<>0 then write('X') ( Read-only?
else write(' ');
i f (DirBuf .Attribut and 2)<>0 then write('X') hidden?
else write(' ');
i f (DirBuf.Attribut and 4)<>0 then write('X') system?
else write (' ');
i f (DirBuf.Attribut and 8) <>0 then write('X') Volume-Label?
else write (' ');
i f (DirBuf.Attribut and 16)<>0 then write ('X') { Directory?
else write(' 'I;
write (' I'); ( right border of window frame )
end;

{****************************************************.****************}
1* SETDTA *'
1* Input
{* Output
set Address of DTA
: see above
: none *}
*'
(***********************•• ******************************** •• **********)

procedure SetDTA(Segment, ( new Segment address of the DTA


Offset integer); (new Offset address of the DTA

var Register regtyp; 1 Register-Variable for call of the Interrupt

begin
Register.ax := $lA shl 8; { Set Function number for DTA
Register.ds := Segment; { Segment address into DS register
Register.dx := Offset; ( Offset address into DX register
msdos(Dos.Registers(Register»; ( Call DOS-Interrupt 21H )
INOTE: Turbo 3.0 users should change the previous}
Iline to read msdosIRegister);)
end:

{*********************************************************************}
{* BUILDSCREENDISPLAY: prepares the display for output of the *}
(* Directory *}
{* Input : none *}
(* Output : none *}
{**** •• ***************************************************************}

procedure BuildScreenDisplay;

var Counter : integer;

begin
clrscr: 1 clear display
window (14, (20-ENTRY) shr 1+1,64, (20-ENTRY) shr 1 +5+ENTRY);
gotoxy(l,l); Cursor to left upper corner of window
write ('r T T TTl');
writel'l Filename I Size I Date Time IRHSVDI');
write('I------------lf------~-------------~-------+-----I');
for Counter .= 1 to ENTRY do

write (' I I I I I I ') ;

wri te ('L.------l.----l.------l.- ---.l--Jo ) ;

window (15, (20-ENTRY) shr 1+4,66, (20-ENTRY) shr 1 +3+ENTRY);

gotoxy(l, ENTRY); ( Cursor to upper left corner of window )

end:

{*********************************************************************}
{* DIR: controls the input and output of Directories *1
{* Input : none *)

102
Abacus 6.7 Accessing the DOS Directory

(* output : none *)
{*********.******* ••••• ***.*** ••• ****.********** •• ***.****************}

procedure Dir;

var NumEntries, Total number of entries found


Numwind integer; { Number of entries in window
KeyPress char; ( wait for key activation
begin
SetDTA{Seg(DirBuf), Ofs(DirBuf»; { DirBuf is the new DTA
clrscr: ( clear display
writeln('DIR (c) 1987 by Michael Tischer'113110);
writeln('Please indicate search path for files ');
writeln('Example: if all files with the extension .BAT in the root ');
writeln('directory of the disk drive should be displayed please input ');
writeln(' A:*.BAT.');
writeln(' If no search path is indicated, all files in the current');
writeln(' directory are displayed. '113110);

write('Which files are to be displayed: , ) ;

readln(DatName); { read in filenames

if DatName = ,. then DatName := 1*.-': search for all files

BuildScreenDisplay; Construct display for output

Numwind := -1; { no entry in window yet

NumEntries : = 0; { no entry found

if GetFirst{DatName, 255) then search for first entry

Attribute does not matter


repeat

NurnEntries := succ(NumEntries); { found another entry

Nurnwind :­ succ(Numwind); { one more entry into window

i f Numwind = ENTRY then { window full ?

begin ( Yes

window (14, (20-ENTRY) shr 1 +5+ENTRY,66, (20-ENTRY) shr 1 +6+ENTRY);

gotoxy(l, 1); { Cursor to last line of window }

textbackground(7); ( white background )

text color (0) ; { black characters}

write (' Please press a key .) ;

repeat until keypressed; wait for key press

(read (kbd, KeyPress);} { read key code

{ otherwise it remains in the buffer

gotoxy(l, 1); Cursor to the upper left corner of the window

textbackground(O); black background

textcolor(15) ; { white characters

write (' I):

window(15, (20-ENTRY) shr 1+4,65, (20-ENTRY) shr 1 +3+ENTRY);

gotoxy(l, ENTRY); ( return Cursor to old position

Numwind := 0; start count with 0 again

end:
PrintData; ( output data of entry

until not(GetNext); { does another entry exist ?

window(14, (20-ENTRY) shr 1 +5+ENTRY,65, (20-ENTRY) shr 1 +6+ENTRY);

gotoxy(1, 1); { Cursor to the upper left corner of window

textbackground(7); { white background

textcolor (0); ( black characters

write (' I) ;

gotoxy (2, 1);

case NurnEntries of

o : write('no file found ');


1 : write('found a file ');

else write(NurnEntries,' files found ')

end:
window (1, 1, 80, 25); set whole display as window I
end;

{**************************************** ••• ***********.**************}


(** MAIN PROGRAMM **)
(*********.***********************************************************.

begin
Dir; { Load Directory and display }

103
6. The Disk Operating System PC System Programming

end.

In the above Pascal program and in the following C program, accessing the DTA
is much easier than in the BASIC version of the same program. RECORD or
STRUCT defines the structure of the directory entry into the DTA, and the
programs implement a variable of this type. OOS function lAH then transfers the
DTA to this variable. All the information in a directory entry can be easily
accessed. With Turbo Pascal, the display design is particularly easy. Turbo Pascal
also has a procedure to define any display area as a window. However, the C
language program uses the scroll function of the BIOS interrupt lOH to scroll the
directory window one line upward.
C listing: DIRC.C
/***************************************************** ****************j
1* D I R C *1
1*-------------------------------------------------------------------*1
1* Task : Displays all files in any Directory, *1
1* including Sub-Directories and volume names *1
1* on the screen. *1
1*-------------------------------------------------------------------*1
1* Author MICHAEL TISCHER *1
1* developed on : 08/15/87 *1
1* last Update : 04/08/89 *I
1*----------------_·_--------------------------------- ----------------* I
1* (MICROSOFT C) *1
1* Creation : MSC DIRC; *1
1* LINK DIRC; *1
1* Call : DIRC *I
1*-------------------------------------------------------------------*1
1* (BORLAND TURBO C) *1
1* Creation With the RUN command in the command line *1
1* Info Arguments can be passed to the program with *1
1* the OPTION/ARGS command in the command line *1
1* of TURBO C *1
1* or *1
1* *1
1* Creation : TCC DIRC *1
1* Call : DIRC *1
/*********************************************************************/

.include <dos.h> 1* include Header files *1

finclude <io.h>

'include <string.h>

'define FALSE 0 1* Constants make reading of *1

'define TRUE 1 1* Program text easier *1

tdefine byte unsigned char

tdefine ENTRY 14 1* this many directory entries fit on the screen *1

'define EZ (20-ENTRY » 1) 1* first line of Directory window *1

'define NRM Ox07 1* white characters on black background *1

'define INV Ox70 1* black characters on white background (inverted) *1

1*-- this is the format of a Directory entry returned by --------*1


1*-- the functions 4EH and 4FH *1
struct DirStruct {
byte Reservebuf [21] ;
byte Attribute;
unsigned int Ftime;
unsigned int Fdate;
unsigned long Fsize;
char Fname[13];
};

104
Abacus 6.7 Accessing the DOS Directory

/*** ••••• ****** ••• *************************.**********.***************/


1* GETPAGE gets the current display page *1
1* Input : none *I
1* Output : see above *I
/**** •• ************************************* •• ********.***************/

byte GETPAGEO

union REGS Register; 1* Register-Variable for Interrupt call *1

Register.h.ah - 15; 1* Function number *1


intB6(OxlO, &Register, &Register); 1* Call Interrupt lOH *1
return(Register.h.bh); 1* Number of current display *1
)

/****************** •• *************************************************/
1* SCROLLUP: moves a displa¥ area one or more lines *1
1* upward or erases it *1
1* Input see above *1
1* Output none *1
1* Info if 0 is passed as number, the display area *1
1* is filled with blanks *1
/*****************************************************.***************/

void ScrollUp(Number, Color, ColurnnUL, LineUL, ColumnLR, LineLR)


int Number; 1* Number of lines to be scrolled *1
int Color; 1* Color or attribute for blanks *1
int ColumnUL; 1* Column in the upper left corner of display area *1
int LineUL; 1* Line in the upper left corner of the display *1
int ColumnLR;I* Column in the lower right corner of the display area *1
int LineLR; 1* Line in the lower right corner of the display area *1

union REGS Register; 1* Register-Variable for Interrupt call *1


Register.h.ah - 6; 1* Function number *1
Register.h.al "" Number: 1* Number of lines *1
Register. h.bh "" Color; 1* Color of blank line (s) *1
Register.h.ch LineUL; 1* Coordinates of the scroll *1
Register.h.cl ColumnUL; 1* end or erase *1
Register.h.dh LineLR; 1* Set display window *1
Register.h.dl .. ColumnLRi
int86 (OxlO, &Register, &Register) ; 1* Call Interrupt lOH *1
I
/****.*********.*.***** •• ********* ••• ******* ••• ******.********* •• *****/
1* SETPOS sets the position of the cursor in current display page *1
1* Input see above *I
1* Output none *I
1* Info the position of the blinking display cursor is changed *1
1* by the call of this function only when the *1
1* display page indicated is the current display page *1
1* *1
/***** ••• *.************** •• **************** ••• ****************.*.*****/

void SetPos(Colurnn, Line)


1nt Column; 1* new Cursor column ·1
int Line; 1* new Cursor line *1

union REGS Register; 1* Register-Variable for Interrupt call *1


Register.h.ah 2; 1* Function number *1
Register.h.bh GETPAGE(); 1* Display page *1
Register.h.dh Line; 1* Display line *1
Register.h.dl - Column; 1* Display column *1
intB6(OxlO, &Register, &Register); 1* Call Interrupt lOH *1
I

105
6. The Disk Operating System PC System Programming

/********** •• ****************************************************.****/
1* GETPOS Get the position of the Cursor in current display page *1
1* Input : none */
1* Output : see below *I
/*******************+.**************************** •• ** ****************1

void GetPos(Column, Line)

int *Column; /* Column where the Cursor is located */

int *Line; 1* Line where the Cursor is located *1

union REGS Register; /* Register-Variable for Interrupt call */

Register.h.ah - 3; /* Function number */


Register.h.bh * GETPAGE(); /* Display page */
int86(OxlO, &Register, &Register); /* Call Interrupt 10H */
*Column = Register.h.dl; /* Read result of the Function */
*Line = Register.h.dh; /* from the Registers */
)

/***********************************.*********************************/
/* WRITECHAR: writes a character with an attribute to the current */
/* cursor position on the current display page */
/* Input see below */
/* Output none */
j******************************************** •• ******* ****************/

void WriteChar(Character, Color)

char Character; /* Character for output */

int Color; /* its Attribute or color */

union REGS Register; 1* Register-Variable for Interrupt call */

Register.h.ah 9; /* Function number */


Register.h.al Character; /* character for output */
Register.h.bh - GETPAGE(); /* Display page *1
Register.h.bl Color; /* Color of character for output */
Register.x.cx 1; /* output character only once *1
int86 (Oxl0, &Register, &Register); 1* Call Interrupt lOH *1
)

/*********************************************************************/
/* WT writes a character string with constant color starting */
1* at a specified position on the current display page. */
/* Input see below */
/* Output none */
/* Info Text is a Pointer to a character Vector, which contains */
/* the text to be output and is terminated with a '\0' *1
/* character. *1
/*********************************************************************/

void WT(Column, Line, Text, Color)

int Column; 1* Display column for output */

int Line; 1* Display line for output */

char *Text; 1* Text for output *1

int Color; 1* Color/Attribute of the Text *1

union REGS Register; 1* Register-Variable for Interrupt call *1


SetPos(Column, Line); 1* Set Cursor */
while (*Text) 1* Output Text up to '\0' character */
{
WriteChar (' ,Color) ; /* Indicate color *1
Register.h.ah 14; 1* Function number */
Register.h.bh ~ GETPAGE(); 1* Display page */
Register.h.al ~ *Text++; 1* of character to be output */
int86(OxlO, &Register, &Register); 1* Call Interrupt */
)

106
Abacus 6.7 Accessing the DOS Directory

/*************************** •••• ****** •• ******* ••• ******* •• *** ••• *****/


1* CLS Clear current display and set Cursor into upper left *1
1* corner *1
1* Input none *1
1* Output none *1
/ •••• *** •••••• **** •••••••••••• ***.*** •••••••• ** ••••••• ** •••••• ** •••••• /

void CIs ()

ScrollUp(O, NRM, 0, 0, 79, 24); 1* Clear Screen *1

SetPos(O, 0); 1* Set Cursor *1

/** ••• ****** •••••• ******.*.*.* •• ****.* •••••• ** ••• ********* •• **** •• **.*,
1* BUILDSCREENDISPLAY: prepares the display for the output of the *1
1* Directory. *1
1* Input none *I
1* Output none *1
/ •• **.*** •••• ***** •••• ****** ••••••••••••• *** •• **** •••• *** ••• *** ••••• *./

void BuildScreenDisplay()

{
byte i; 1* Loop Counter *1
Cls(); 1* Clear Screen *1
WT (14, EZ, 'f-------r r ---"'lTr------"1Tr- ----4'"--10, NOF) ;
WT(14,EZ+l,"1 Filename I Size I Date I Time IRHSVD I ",NOF);

WT(14,EZ+2,"I I I +---1 ",NOF);

for (i - EZ+3; i < EZ+3+ENTRY; i++)

WT(14,i,"1 I I I I",NOF)·
WT (14, EZ+ENTRY+3, "L.-----.L---.L-----.. L-----.L--J·,
NOF);

/.*** ••• *****.* •••• ** •• ******** •• ***.* ••• **************** •• ***********/


1* PRINTDATA: Output information about an entry *1
1* Input : see below *I
1* Output : none *I
,*.******************************.*********.**************************/

void PrintData(DirEntry, Line)


struct DirStruct *DirEntIY; 1* a Directory entry * I
byte Line; 1* Display line of entry *1
{
byte i; 1* Loop Counter *1

static char *Month[J 1* Vector with Pointer to name of month *1

"JAN II , "FEB'~, "MAR", "APR", "MAY", IlJUN",


"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
l;

SetPos(15, Line); 1* Set Cursor position for file name *1

for (i=O; (*DirEntry).Fname[il && i<15; printf("\c", (*DirEntry}.Fname[i++J»

SetPos(28, Line); 1* Set Cursor position for file size *1

printf("\71u", (*DirEntry) .Fsize); 1* Output file size *1

SetPos (36, Line); 1* Set Cursor position for Date *1

printf("\s-\2d-\4d", Month [ «*DirEntry) . Fdate » 5 & 15} - IJ,

(*DirEntry) • Fdate & 31, «*DirEntry) .Fdate » 9) + 1980);

SetPos(49, Line); 1* Set Cursor position for Time *1

printf ("'2d:\2d", (*DirEntry) • Ftirne » 11, (*DirEntry) .Ftime » 5 & 63);

SetPos(59, Line); 1* Set Cursor position for Attribute *1

for (i - 1; i <- 16; i «- 1)

i f «*DirEntry) .Attribute & i) printf ("X");

else printf(" H);

107
6. The Disk Operating System PC System Programming

/**************** •• ********************************** •• _-_._ •• _-------/


f* GETNEXT read the following Directory entry *f
1* Input none *f
1* Output TRUE, when an entry was found, otherwise FALSE *1
j-_.-. __ ._--_.__ ._.__ ._-----------_.._----_.. __
._-_._---_._------_._._/

1* Info the entry is read into DTA rem *1

byt e Get Next ()

union REGS Register; 1* Register-Variable for Interrupt call *1


Register.h.ah = Ox4F; /* Function number for Search of next entry *1
intdos(&Register, &Register); 1* Call DOS-Intr. 21H *f
return (!Register.x.cflag); /* Carry-Flag = 0: file found */
)

1-··_·---_··_··------------------_·_--_·_·_·_·_-_·_---.-.-._.-.---_._./
1* GETFIRST read the first Directory entry */
/* Input none */
f* Output TRUE, if entry was found, otherwise FALSE */
/* Info Entry is read into the DTA */
1-*------_·_·····_---------------_·_------*----------- -*_. __ .-._._._--/
byte GetFirst (Sname, Attribute)

char * Sname; /* file to be found */

unsigned int Attribute; /* the Search Attribute */

union REGS Register; /* Register-Variable for Interrupt call */


struct SREGS Segmente; f* accepts Segment register */
segread(&Segmente); f* Read in content of Segment register */
Register.h.ah = Ox4E; /* Function number for search of first */
Register.x.ex = Attribute; f* Attribute, for which search is made */
Register.x.dx = (unsigned int) Sname; /* Offset address search path*/
intdosx(&Register, &Register, &Segmente); f* Call DOS-Intr. 21H */
return(!Register.x.cflag); /* Carry-Flag = 0: file found */
)

j._--------*-----------------------_._------_._-_._._-----------_._._-/
/* SETDTA sets the DTA to a Variable in the Data Segment *f
/* Input : see below *f
/* Output : none */
1---·--·_--_·_-_·_------·_-_·_·_·· __ ·_·_---_····-·-·_· w.www.wwWWWWW_K_/
void SetDTA(Offset)

unsigned int Offset; f* new Offset address of the DTA *f

union REGS Register; f* Register-Variable for Interrupt call *f


struct SREGS Segment; /* accepts the Segment register *f

segread(&Segment); 1* Read in content of Segment register *f


Register.h.ah = OxlA; f* Set Function number for DTA *f
Register.x.dx = Offset; /* Offset address into DX-Register *f
intdosx(&Register, &Register, &Segment); f* Call DOS-Intr. 21H *f
)

/*********************************************************************/
f* DIR controls the input and output of Directories *f
f* Input : see below *f
/* Output : none *f
/** •• **************************************************.*.************/

void Dir(Sname, Attribute)

char *Sname; f* Pointer to Character Vector, containing search path */

int Attribute; f* Attribute of file to be found *f

108
Abacus 6.7 Accessing the DOS Directory

(
int NumEntries, /* Total number of entries found */
Numwind; /* Number of entries 1n display */
struct DirStruct DirEntry; /* a Directory entry */

SetDTA(&DirEntry); /* DIRENTRY is the new DTA */


BuildScreenDisplay{); /* Construct display for new Directory output */

Nurnwind = NumEntries - 0; /* no entry displayed in the window */

/* no entry found */

if (GetFirst(Sname, Attribute» /* search for first entry */

do

(
PrintData(&DirEntry, EZ+ENTRY+2); /* output entry */
if (++Numwind == ENTRY ) /* Window full 2 */
(
Nurnwind - 0; /* fill a window */
WT(14, EZ+4+ENTRY,
" Please press a key H, INV);
qetch (); /* wait for key */
WT(14, EZ+4+ENTRY,
",NRM) ;

ScrollUp(l, NRM, IS, EZ+3, 63, EZ+2+ENTRY);


WT(lS, EZ+2+ENTRY,
I ",NOF) ;
++NumEntries;
)
while (GetNext(»;
)
SetPos (14, EZ+4+ENTRY);,

switch (NurnEntries)

(
case 0 printf("no files found tt, ;
break:

case printf("one file found If) ;

break;

default printf ("ltd files found " , NurnEntries) ;


)

/ •• **.********* ••• ****.** •••••• ***.***.** ••• ************************.*,


/** MAIN PROGRAM **/
/****************************************.**********.****** •• ** ••••• **/

void main (Number, Arqurnent) I/'


int Number; /* Number of Arqurnents + 1 passed */
char *Arqurnent[]; /* Vector with pointer to Arqurnents */
{
switch (Number) "'.
/* react accordinq to */
( /* Arquments passed */
case Dir("*.*tI, ....0); /* Display all files in current */
break: /* Directory */
case 2 Dir (Arqurnent [1] , -0); /* Display all files in indicated */
break; /* Directory */
default : printf ("Invalid number of Parameters\nn);
)

109
6. Tlu! Disk Operating System PC System Programming

6.8 The EXEC Function


The EXEC function has been mentioned briefly several times before in relation to
the command processor. We'll examine the EXEC function more fully here and
describe its operation.

Parent/child

The EXEC function is one of the many DOS functions which can be called with
interrupt 21H (function 4BH). Generally speaking, this function lets a parent
program (main program) call a child program (secondary program). The child
program is loaded from a mass storage device into memory and then executes. If
this child program doesn't become resident, the memory occupied by the child is
released following program execution. The child program can also call another
program which works with the parent program. This creates a type of program
chaining limited only by the amount of available RAM.

One example of the EXEC function is the command processor. Using the EXEC
function, the command processor executes user-specified programs and becomes the
parent program. Some programs (such as Microsoft Word®) permit the user to
execute DOS commands from the main program using this function.

The parent program can pass parameters to the child program in the command line
and can also pass parameters using the environment block. It can also transfer
information to the child program within the PSP. Since the child program, like all
executable programs, has a PSP preceding it, information can be entered into the
two FCBs within this PSP and made accessible to the child program.

Child program

After transferring control to the child program, it can access all files and devices
previously opened by the parent program (or one of the parent programs) with a
handle function. This allows the child program to read information from a file or
write information to a file whose handle is known (the child program doesn't need
to know the filename). This is only possible if the handle was passed by the parent
program in one of the three methods described, or if the child program refers to one
of the five handles which are always open. These file accesses affect the file
pointer. Since values are not reset, these file accesses become "visible" to the
parent program when control returns to the parent program.

After execution of the child program, control returns to the parent program and
execution continues. To pass information (e.g., an error that occurred during the
execution of the child program), the child program can pass a numeric value at the
end of its execution. This can be done using DOS function 4CH, which terminates
a program and returns a code to the parent program.

The communication between parent and child program functions only if both
programs agree on this return value. After control returns to the parent program, it

110
Abacus 6.8 Th£ EXEC Function

can detemline the code using function 4DH of interrupt 21H. To use function 4DH
only the function number is passed in the AH register. The code passed by the
child program is returned to the calling (parent) program in the AL register.

Ending the child program


In addition, the contents of the AH register indicate how the child program
temlinated. The value 0 indicates a nOmlal temlination, while 1 shows that the
child program terminated when the user pressed <Control><C> or
<Control><Break>. If an error during access to a mass storage device forced the
child program to temlinate, a code of 2 is passed in the AH register. Finally the
value 3 indicates that the child program temlinated from a call to function 31H, or
interrupt 27H; the child program then becomes resident in memory.

As mentioned previously, the EXEC function can only load the child program if
enough memory is available. While DOS can estimate the memory needed for
EXE programs fairly accurately, it cannot do the same for COM programs. For
COM programs DOS reserves all unused memory. Because of this, a COM
program cannot call another program with the EXEC function, since DOS reserves
no extra memory. The same is true for many EXE programs. If a call to a child
program is necessary, the required memory space must be released from the calling
program before calling the EXEC function (see Sections 6.4.1 and 6.4.2 for
explanations on how this is done).

EXEC

If the EXEC function is called, the various parameters are loaded into the registers
before calling interrupt 21H. Function number 4BH is passed in the AH register.
A value of 0 or 3 is passed in the AI.. register. A value of 0 indicates that the
EXEC function is to load and execute the program while a value of 3 indicates that
the program is loaded as an overlay (without executing it). The address of the name
of the program to be loaded or executed is passed in the DS:DX register pair. And
the address of the parameter block is passed in the ES:BX register pair.

The program name is specified as an ASCII string and ended with a null character
(ASCII code 0). The program name can include the device name and a complete
path description. Its last element is the program name which, besides the name
itself, must have the extension .COM or .EXE. If the device name or path
designation are omitted, the system searches for the program in the current
directory of the current device. Since the EXEC function cannot execute a batch
fIle directly, the program name passed cannot contain the extension .BAT.

Batch child

If a batch fIle is to be executed, the COMMAND.COM (command processor) file


must be invoked first. To indicate that a batch file should be executed, the
parameter Ic followed by the name of the batch fIle to be executed is included on
the command line. Besides the ability to execute a batch fIle, calling the command

111
6. The Disk Operating System PC System Programming

processor with the Ic parameter offers the option of calling any other program, and
even internal DOS commands such as DIR.

Besides calling a program directly, it's possible to specify program names without
file extensions during a command processor call. The command processor searches
for an EXE file; then a COM file; and finally a BAT file. If none of these files
exist in the current directory, it searches all directories specified in the PATH
command. This chain of events is not followed during a direct program call
without the addition of the command processor.

The directory which contains the command processor should be specified. If not
specified, it will be loaded from the path indicated by the COMSPEC environment
string of the SET command.

Parameter blocks
Parameters can be passed to the command processor in the parameter block
following the program name. These parameters are identical to the parameters
entered from the keyboard when the program is called. How these parameters affect
the EXEC function will be seen shortly, but fmt take a look at the parameter
block's structure when the AL register contains the value O. This block's address is
passed to the EXEC function in the register pair ES:BX.

1 0-1 Seqment address of the environment block


2 2-3 Offset address of the command parameter
3 4-5 Segment address of the command parameter
4 6-7 Offset address of the first FCB
5 8-9 Segment address of the first FeB
6 10-11 Offset address of the second FeB
7 12-13 Seqrnent address of the second FeB

Field 1 indicates the segment address of the child program's environment block.
This block doesn't require an offset address since it always starts at a location
divisible by 16, and therefore its offset address is always to O.

Environment block
The command processor and other programs obtain information from the
environment block. The environment block is a series of ASCII character strings.
This infonnation can include paths for file searches. Each string has the following
syntax, tenninated by a null character (ASCII code 0):

Name - Parameter

The individual strings follow each other sequentially (i.e., the null character of one
string is immediately followed by the beginning character of the next string). The
environment block ends with a null character. Any environment block has a
maximum length of 32K.

112
Abacus 6.8 The EXEC FlU'lCtion

The environment block can be changed or modified by the user using the DOS
SET and PATH commands. Programs which remain resident after execution are
unaffected by any changes made to the environment block through these two DOS
commands once made resident
If the parent program wants to pass infonnation to the child program using the
environment block, it can either construct a new environment block or supplement
its own environment block with this infonnation. In the first case, the segment
address of the new environment block is specified in the fllSt field of the parameter
block. If the child program should have access to the environment block of the
parent program, specify a value of 0 in this field. Before blming over control to
the child program, the EXEC function stores the segment address of the
environment block in the memory location at address 2CH of the child program's
PSP.

If the child program is to use a new environment block, it should contain at least 3
strings which are normally part of the environment block of the parent program,
and are important to the command processor:
COMSPEC = Parameter
PATH - Parameter
PROMPT = Parameter

If a child program modifies its environment block, the parent program's


environment block remains unchanged after the child program completes its
execution.

Fields 2 and 3 indicate the command parameters' address which is passed to the
PSP of the program starting at address 80H. They must have the same strucwre in
memory as expected by DOS in the PSP. The first byte indicates the number of
command characters minus 1, then follows the command characters as nonnal
ASCII codes. The command parameters terminate with a carriage return (ASCII
code 13) which is not included in the character count. The fllSt character in the
string should be a space for compatibility with COMMAND.COM.

To call a batch program (called DO.BAT) using the command processor, the
following command parameters must be specified as a string in memory:
DB 10," Ie OO.BAT",13
The EXEC function copies the command parameters in a controlled fashion into
the PSP of the program to be executed. It removes all parameters which would
redirect the input or output, since a redirection of the standard input/output can
only be performed by the parent program. The child program can still use
input/output redirection if the standard input/output handles have been redirected by
the parent program (see Section 6.10 for more detailed information and an example
of this process).

113
6. The Disk Operating System PC System Programming

Fields 6, 7, 10 and 11 indicate two FCBs installed in the PSP at address 5CH or
6CH. If this is not required, specify -1 (FFFFH) in these two fields. If program
execution requires it, enter the first two command parameters in the two FCBs
with DOS function 29H. Before passing control to the child program, the EXEC
function copies these two FCBs into the PSP of the child program.

Even though all registers and the parameter block now have the required values, the
EXEC function cannot be called yet. Since it destroys the contents of all registers
up to the CS and IP registers during execution,the contents of all registers must
be placed on the stack before it is invoked. Then the contents of the SS and SP
registers must be stored within the code segment. Only then can interrupt 21H
function 4BH be called to activate the EXEC function. After the EXEC function
ends, the carry flag signals if the function executed normally. Before program
execution can continue, the value of the SS and SP registers must be restored,
from the code segment. Then the contents of the other register can be restored
again from the stack.

The EXEC function serves a different purpose when a value of 3 appears in the AL
register. In this case, it loads a COM program or an EXE program into memory
without executing. After the target program is loaded, control immediately returns
to the calling program. In contrast with sub-function 0, the program loads to a
memory address indicated by the calling program instead of loading to any non­
specific location. Since no parameters are passed to the loaded program, the
parameter block has a different structure during the call of sub-function 3 than
during the call of sub-function 0:
Field Byte Purpose
1 0-1 Segment address where overlay is loaded
2 2-3 Relocation factor

Before the function is called, the segment address to which the program should be
loaded is specified in the frrst field of the parameter block. If the calling program
doesn't have enough memory available for loading the external program, it should
request additional memory with one of the DOS memory management functions.
The loaded program loads directly to the segment address indicated with the offset
address 0 since no PSP precedes the program.

Relocation
The relocation factor adjusts the segment address of the called program. Since this
factor applies only to EXE programs (COM programs cannot have specific
segment assignments), the relocation factor for COM programs should always be
equal to O. The relocation factor for EXE programs should indicate the segment
address where the program will be loaded to confmn to the program's segment
assignments.

After the program is loaded, its routines are ready to be accessed. The routines of
the loaded program should always be treated as subroutines; and therefore, called

114
Abacus 6.8 TM EXEC FlUlCtion

with the machine language CALL instruction. It must always be a FAR type
instruction even though the loaded program may be located immediately following
the calling program, but can never have the same segment address. The offset
address for CALL is always l00H for a COM program, since execution always
starts immediately following the PSP at address l00H. This creates a problem.
Sub-function 3 prevents the PSP from loading. Therefore the code segment of the
COM program starts at address 0, not at the offset address l00H (relative to the
load segment). Since all jump instructions and accesses to data within the COM
program are relative to address l00H and not address 0, you cannot execute a FAR
CALL instruction with the address of the load segment as the segment address, and
address 0 as the offset address. The segment address for the FAR CALL must
indicate the address of the load segment minus 10H and the address l00H as the
offset address.

If the COM program specifically acts as an overlay for another program, entry
addresses other than address l00H are possible. In such a case, only the offset
address for the FAR CALL instruction changes. The segment address must remain
10H smaller than the address of the load segment.

EXEC and memory

The problem is different for EXE programs. If they are loaded for execution using
sub-function 0, the EXEC function sets the code segment and the instruction
pointer to the instruction which was declared as the first instruction in the
assembler source. This address, however, is unknown to the program which loaded
the EXE program as an overlay. This can easily be remedied by placing the frrst
executable instruction in the EXE program at the beginning of the EXE program.
This makes its offset address O. The EXE program source must not be in the
normal sequence with the stack frrst. In this case, the code segment must be the
frrst segment in the source to ensure that it begins the EXE program.

The FAR CALL uses the address of the load segment as the segment address, and
address 0 as the offset ad,dress.

While BASIC, Pascal and C have commands or procedures to call a program from
another program, assembly language routines must use DOS function 4BH. To
help you further understand this function, here is an example program.

The framework of the EXE program listed in Section 6.4.2 acts as the basis for
this program. The EXEPRG procedure performs the actual dirty work in this
program. It calls the new program using function 4BH. Two strings which contain
the name of the program to' be called and the necessary parameters are passed to it.
Both strings end with the null character (ASCII code 0). All variables required by
EXEPRG for execution can be found in the code segment. This offers the
advantage that the lines from the code segment only must be copied into one of the
application programs to use this routine. After calling EXEPRG, the carry flag
signals if an error occurred If true (carry flag=I), the AX register contains the error

115
6. The Disk Operating System PC System Programming

code as returned by the EXEC function of DOS. If the called program executed
correctly, the carry flag is reset (0) and the tennination code of the called progmm,
as returned by DOS function 4DH, is returned by the AX register.

Within this program, EXEPRG displays the current directory using the command
processor. The command processor defaults to the current directory of the current
device.
; •• ** ••• **.** •••••• ******.*** •••• ** ••••• ** •••••••••••• ··········**····i
;* EXEC *;
i*-------------------------------------------------------------------*;
;* Task Calls a program with the help of the *;
;* EXEC function of DOS. In this example *;
;* program the content of the current *;
;* Directory of the current device is displayed. *;
;*-------------------------------------------------------------------*;

;* Author MICHAEL TISCHER *;


;* developed on 08/01/87 *;
;* last Update 04/08/89 *;
;*-------------------------------------------------------------------*:
;* assembly : MASH EXEC; *;
;* IJNK EXEC; *;
:*-------------------------------------------------------------------*;
,Call : EXEC *;
i*···············_············_-_·····················................;

;== data ===----============--=====---=============--~===-=======--====

data segment para 'DATA' ;Definition of the data-segment

prgname db "\command.com",O ;Name of the program to be called


prgpara db "/c dir",O ;Parameters passed to program

data ends ;end of data-segment

;== code ---====--==========-=------==---=======-====----==-=========-­

code segment para 'CODE' ;Definition of the CODE-segment

assume cs:code, ds:data, ss:stack

exec proc far

mov ax, data ;Load segment address of the data segment


mov ds,ax ;into the OS-register

call set free irelease unused memory

mov dX,offset prgname ;offset address of program name


mov si,offset prgpara ;offset address of command line
call exeprg ; Call program

mov aX,4COOh ;end program with call of a DOS function


int 21h ;on return of error-code 0
exec endp

;-- SETFREE: Release memory not used ---------------­


i-- Input ES ~ address of PSP
i-- OUtput none
;-- Register AX, BX, CL and FLAGS are changed
;-- Info Since the stack-segment is always the last segment in an
EXE-file, ES:OOOO points to the beginning and SS:SP
to the end of the program in memory. Through this the
length of the program can be calculated

set free proc near

116
Abacus 6.8 The EXEC Fu.nction

mav bx,ss ;first subtract the two segment addresses


mav ax,es ;from each other. The result is
sub bx,ax ;number of paragraphs from PSP
Ito the beginning of the stack
mov ax,sp ;since the stackpointer is at the end of
mov cl,4 ;the stack segment, its content indicates
shr aX,cl ;the length of the stack
add bx,ax ;add to current length
inc bx las precaution add another paragraph

mav ah,4ah ;pass new length to DOS


int 21h

ret ;back to caller

set free endp

;-- EXEPRG: call another program -----------------------------­


;-- Input DS:DX = address of the Program Name
;-- DS:SI = address of the Command Line
OUtput carry flag - 1 : Error (AX = Error-code)
Register only AX and FLAGS are changed
;-- Info Program name and Command Line must be ASCII-String
;-- and terminated with ASCII-code 0

exeprg proc near

;Transmit Command Line into own buffer

land count characters

push bx ;Store all registers which are


push cx ;destroyed by the call to the
push dx ;005 EXEC function
push di
push s1
push bp
push ds
push es

mov di,offset comline+l ;address of chars in Command Line.


push cs ;CS to stack
pop es ;back as ES
xor bl,bl ;Set character count to 0
copypara: lodsb ;read a character
or al,al lis it a NUL-code (end)
je copyend ;Yes --> copied enough
stosb ;store in new buffer
inc bl ;increment character count
cmp bl,126 ;Maximum reached?
jne copypara ;No --> continue

copyend: mav cs:comline,bl ;store number of characters


rnov byte ptr es:[di],13 ;finish command line

mov cs:merkss,ss ISS and SP must be stored in


mov cs:merksp,sp ;variables 1n code segment

mov bX,offset parblock ;ES:BX points to parameter block


mav aX,4BOOh ;function number for EXEC function
int 21h ;Call DOS-function

cli ;Set interrupts for a moment from


mov ss,cs:merkss ;stack segment and stackpointer to
mov sp,cs:merksp ;their old values
sti ;Switch interrupt on again

pop es ;Get all Registers from stack again


pop ds
pop bp

117
6. The Disk Operating System PC System Programming

pop si

pop di

pop dx

pop cx

pop bx

jc exeend ;Errors? YES --> end


mov ah,4dh ;no errors, sense end-code of the
int 21h ;program which was executed

exeend: ret ;back to caller

;-- Variables of this routine only addressable through CS

merkss dw (?) ;accepts SS during program call


merksp dw (?) ;accepts SP during program call
parblock equ this word ;Parameter block for EXEC function
dw 0 ;environrnent block
dw offset comline ;offset and segment address of
dw seg code ;modified Command Line
dd 0 ;no data in PSP t1
dd 0 ;no data in PSP t2

comline db 128 dup (?) ;accepts modified Command Line

exeprg endp

;-- stack ------=---=============-==-===---------==-=---==-==-======

stack segment para stack ;Definition of the stack-segment

dw 256 dup (?) ;the stack has 256 Words

stack ends ;End of the stack-segment

;-= End ======-----==~===-===========================================

code ends ;End of the CODE-segment


end exec ;for execution start with EXEC

118
Abacus 6.9 Memory Allocation from DOS

6.9 Memory Allocation from DOS


DOS subdivides the maximum 640K of memory into roughly two areas. The ftrst
area is designated as the operating system area. It begins at memory location
0000:0000 and contains the interrupt vector table, some internal tables, buffers,
variable memory and the operating system code. This code retains the resident
portion of the command processor and the resident and installable device drivers.
The size of this area varies with the version of DOS in use, the sizes of the device
drivers installed, and other factors such as the number of disk buffers available.

The second area is designated as the TPA (Transient Program Area). It contains
programs and their environment blocks for execution. The TPA starts after the end
of the operating system area. Depending on the memory requirements of the
individual programs, DOS assigns them different amounts of memory administered
through a special data block preceding every memory range and connected with the
data block of the next memory range. This also applies to memory not assigned to
a program.

This data block, called a memory control block (or MCB) is a 16-byte block
containing a variety of information. An MCB begins at one of the addresses
divisible by 16, and controls memory allocation. DOS looks for the segment
address of the allocated memory range, and is helped in this task through the MCB.
The following table shows the structure of an MCB in memory:
Address Contents Type
+OOH ID ("Z"-last MCB, "M"-another MCB follows) 1 byte
+OlH Segment address of correspondinq PSP 1 word
+03H Number of paragraphs in allocated range 1 word
+05H unused 11 bytes
+lOH Allocated memory ranqe x paragraphs

As the table above illustrates, the MCB contains three ftelds. The ftrst fteld
indicates whether any MCBs follow the current MCB under analysis. The letters
"M" (more MCBs to follow) and "Z" (last MCB) are the initials of one of the
creators of MS-DOS, Mark Zbikowski.

The second fteld speciftes the segment address of the corresponding program's PSP.
This only applies when memory allocation becomes a part of the environment of
the program being handled. in which case the PSP is indicated by the contents of
this field. In most cases, this fteld simply points to the memory range needed by
the program.

The third fteld of the MCB speciftes the size of the corresponding memory range in
paragraphs. Next follows the memory range itself. then any further MCBs after
that (provided that the frrst fteld contains an "M" ID letter). MCBs can be linked
together to create a group, as shown in the ftgure below:

119
6. The Dis/c Operating System PC System Programming

Stan of memory :"


:"
(0000:0000)
Stan ofTPA Memo~ Control Block 1
Controlled by Memory Control Block
,Memory Control Block 2
Controlled by Memory Control Block
Memory Control Block 3
Controlled by Memory Control Block 3
:8
Memory Control Block 4 (last MCB) ......
Controlled by Memory Control Block 4
End of TPA
:~
F
End of memory

Memory allocation
If the DOS EXEC loader loads and executes a program, this function immediately
requests two data areas through another DOS function. The fU'St of these two areas
stores the environment block, while the second accepts the current program and the
program's PSP. The size of the area made available to a program is difficult to
estimate from the EXEC loader. This is even more difficult for COM programs
than for EXE programs since COM programs are copies of memory contents and
have no information preceding them. DOS therefore defaults to the maximum and
reserves the total available memory for a COM program.

This method worked well in the early days of DOS, but has created other
problems. While only one program could exist in memory at a time in the early
days of DOS, now it's common for one program to load and run a second program,
or even make one of the programs pennanently resident in memory. This can't be
done if no memory exists, as would be the case after loading a COM program.
This is why a COM program should always release the memory it no longer needs
after it starts (see Section 6.4.1 for details on how this happens).

A COM program can only load when a memory range large enough to
accommodate the COM program exists (Plus 256 bytes for the PSP and at least 2
bytes for the stack). The COM program ensures that enough memory is available.
Under the minimum conditions presented above, the program probably won't run
without errors, since few programs can operate with only a 2-byte stack.

EXE program fil~s have a set of information created by the linker. The EXEC
loader can determine the amount of memory required for code segment, data and
stack from this infonnation. The start of the EXE program itself contains
additional information about the amount of memory needed for the program. This
amount defines an upper and lower limit of the additional memory, rather than a
specific number of bytes. The EXEC loader tries to reserve the upper limit of

l20
Abacus 6.9 Memory Allocation from DOS

memory if it can. If this is not possible, the EXEC loader uses the lower limit or
reserves the remainder of memory. If the lower limit of memory cannot be
allocated, the loading process aborts and control returns to the program which
called the EXEC loader (in most cases, the command processor).

The same occurs after program execution when the EXEC loader releases the
reserved memory space for further use, unless prevented by function 31H of
interrupt 21H, called from the program.

Now that you know some of the theoretical aspects of DOS memory management,
here are descriptions of the most important of these DOS functions. Functions
48H, 49H and 4AH are all called through interrupt 21H. The function number is
passed in the AH register.

Function 48H allocates memory. The function number is passed in the AH register
and the number of paragraphs to be reserved (16 bytes per paragraph) is passed in
the BX register. If the requested number of paragraphs can be reserved, the function
returns with the carry flag clear. The AX register indicates the segment address of
the reserved memory. Therefore, it starts at address AX:OOOO. If the program
required more memory than was available, the carry flag is set following the call to
the function and the AX register contains an error code. The BX register contains
the maximum memory available in paragraphs.

Function 49H performs the reverse of function 48H. This function releases
memory previously reserved through function 48H. The segment address of the
memory area to be released is passed in the ES register. This segment address was
originally passed in the AX register when function 48H was called. Normally
function 49H should execute without errors and the carry flag should be reset after
the function call. If this is not the case, it could be caused by either a destroyed
data block (placed ahead of a memory area by DOS), or a segment address passed in
the ES register which doesn't match a reserved memory area.

A third function changes the size of memory area which had been previously
reserved. The memory area can be either enlarged or reduced by using function
4AH. The segment address of the area to be modified is passed in the ES register.
The BX register reserves the number of paragraphs (16-byte units) which the
memory area should contain. The register contents following the call to the
function are identical to those of function 48H.

Since calling DOS functions is relatively easy as far as memory management is


concerned and no special tricks are required, the following program is dedicated to a
different topic, which also relates to DOS memory management. We're talking
about a program that pokes around the system and checks all allocated memory as
well as its contents. The program is intelligent enough to differentiate between
storage areas that contain the environment of a program, a PSP, or other
information.

121
6. TM Disk Operaling System PC System Programming

The assignment of this program is to go through the memory from MCB to MCB
and examine the allocated storage areas. In order to move to the next MCB each
time, it uses the third field within an MCB, which helps it point to the next
MCB. This sets up a loop which will run until the last MCB is discovered, which
will have the letter "z.. in its ID field.

But in order to move through the chain of MCBs, the address of the first link, that
is the first MCB, must be known. DOS lists this within an internal structure
called DIB (DOS Information Block), which is not normally accessible to
application programs, i.e. this represents an undocumented DOS feature (see also
Section 6.15). However, we can find out the address of this structure with the help
of function 52H, which will output the address to the ES:BX register pair when
called.

Curiously, this address points to the second field in the MCB rather than the fU'St
But since it's the first field that contains the address of the first MCB, the
information we're looking for is behind the pointer. Since the pointer on the first
MCB consists of an offset address and a segment address, it is four bytes long and
can therefore be found at the address ES:(BX-4). But be careful with the address
statement, because it makes it seem as though all you have to do is subtract 4
from the contents of the BX register in order to get the effective address of the
desired information in the ES:BX register pair. This will only be successful if the
offset address in the BX register is greater than or equal to 4. But if it is less than
4, the consequences are disastrous, because this leaves a negative number. There is
no such thing as a negative memory address. Let's use an example to make this
clear:

If the value 0 is returned to the BX register as the offset address of the DIB, the
subtraction would give the value OFFFCH. With arithmetic operations, this is
interpreted quite correctly as -4. However, during memory access, this will not
point to the address -4, but rather right to OFFFCH, and therefore to the end rather
than the beginning of the accompanying segment Of course, you won't fmd what
you're looking for there.

The program will help you here, first of all by decrementing the delivered segment
address by 1. This reduces the effective address, which you get by appending the
segment address and the offset address, by 16. Finally, by adding 12 to the offset
address, the effective address is reduced by only 4 and points to the desired memory
location. The address of the first MCB can then be taken from this memory
location without any problems.

The loop which runs through all MeBs and analyzes them begins with this
address. First, some status information on the MCB and the memory it controls is
given. This includes:

• the MCB number


its address in memory

122
Abacus 6.9 Memory AllocaJ.ionfrom DOS

the address of the memory it controls


the contents of the ID field ("M" or HZ")
the address of the accompanying PSP (independent of whether it
even exists)
the size of the accompanying storage area in paragraphs and bytes

Next, the contents of the storage area that belongs to it are examined. We get its
address by incrementing the segment address of the MCB by 1. The first thing
we11 determine is whether we're df'.aling with an environment block in this storage
area. We'll know for sure if we find the string COMSPEC= at the beginning of the
area. This string starts every environment block. If this string is found, the
program proceeds as though this were indeed an environment block, and it lists the
individual environment strings. In front of these, it lists the name of the program
the environment block belongs to, which is located at the end of the environment
block for DOS version 3.0 and higher.

If the storage area cannot be identified as an environment block, we could possibly


be dealing with a PSP, and therefore a transient or resident program. The program
will start from here if it finds the machine language command !NT 20H (code
OCDH, 020H) in the first two positions of the memory range. This command
starts every PSP.

If the program also does not run into this, it can't tell if the memory range
contains program code, data, or whatever. In this case, the program will send the
first 80 bytes of the storage area to the screen as a hex and ASCII dump, in order
to give you a chance to figure it out for yourself.

After this, the user is prompted to strike any key. When the keystroke is received,
the next MCB is examined, and the program will finally end when the last MCB
has been handled.

The following programs in Pascal and C produce the MCB dump. A BASIC
version could not be implemented here because this program works its way
through the entire memory, and BASIC offers only the DEF, SEG and PEEK
commands for this purpose. The use of these commands is too awkward in this
case and would detract from the real task of the program.

Since both programs are very similar in terms of the logic, function calls, and
variables used, they are described together in the following section.

Both access memory with FAR pointers, since the storage areas to be addressed are
outside of their data segments. While Turbo Pascal automatically uses FAR
pointers, C requires selection of the appropriate memory configuration through
Compact, Huge, Large or with the help of Cast operations, each of which
explicitly defines the task with a FAR pointer. This program goes the latter way,
so that it may also be compiled in a memory configuration that works with NEAR
pointers by default (Tiny, Small, Medium).

123
6. TIu! Disk Operating System PC System Programming

Converting separately retrieved offset and segment addresses to one FAR pointer
presents a problem in both languages. This can be done in C with a macro, which
is already defined in the Include file DOS.H in Turbo C, but is missing in
Microsoft C. For this reason, the macro is defined within the C program, in case it
hasn't been previously defined. In Pascal, the address conversions happen with the
help of a small inline procedure, that enters both addresses directly into the
memory locations that form the pointer.

Beyond these brief remarks, the listings should be able to speak for themselves,
since they are fully documented.
Pascal listing: MEMP.PAS
1*********************************·******·**********··** •• *************}
{* MEMP *}
{*--------------------------------------------------------------------*}
{* Description : displays the memory blocks allocated by DOS. *}
{*--------------------------------------------------------------------*}
{* Author MICHAEL TISCHER *}
{* developed on : 08/22/1988 *}
{* last update : 08/22/1988 *}
1************·****_·*****************************···*·**********••• *.*.}

program MEMP;

uses DOS, CRT; { bind in the DOS and CRT units

type BytePtr "'" .... byte; pointer to a byte }


Range = array[0 •. 1000] of byte; an area, anywhere in RAM }
RngPtr "'" .... Rangei { pointer to an area }
MCB - record a memory control block }
IdCode char; { I'M" = a block follows, "Z" "" end }
PSP word; segment address of the PSP }
Distance word; { number of paragraphs - 1 }
end;
MCBPtr = AMeB; pointer to an MCB
MCBPtr2 = AMCBPtr; { pointer to an MCBPtr
HexStr string [41'; { stores a four-digit hex string

var CvHStr HexStr; { stores the converted hex string

1******************************·******·****··********·*****************}
{* GetDosVer: determines the DOS version *}
{* Input : none *}
{* Output: the DOS version number (30 for DOS 3.0, 33 for 3.3 etc.) *}
{*.**.*** ••• **•••••••••••••••••••• *•• *******••• *.**** •••• ********* •• ***}

function GetDosVer : byte;

var Regs : Registers; ( stores the processor registers )

begin
Regs.ah :~ $30; ( function no. for "Get Dos Version"
MsDos( Regs ); { call DOS interrupt $21
GetDosVer := Regs.al * 10 + Regs.ah; { get version number
end;

{*••• *** •••• *•• *••••• *••• *•••*.*.**•• ** •••••• *.*****.*** •••• *.***.** ••• }
(* MK_FP: creates a byte pointer out of the segment and offset *)
{* addresses passed. *}
{* Input - Seq - segment to which the point should point *}
{* - Ofs = offset address to which the pointer should point *}
{* Output the pointer *}

124
Abacus 6.9 Memory Allocationfrom DOS

{* Info : The pointer returned can be cast to any type pointer *)


{*** ••••••••••••••••••••••••• ** ••••••••••••••••••••• ** ••••••• * •••••••• *}

{$F+) This routine is intended for the FAR model and is


also suited for binding into a unit.

function MK_FP( Seg, Ofs : word) : BytePtr;

begin
inline $8B / $46 / $08 / mov ax, [bp+8) (get segment address)

$89 / $46 / $FE / mov [bp-2),ax (and put in pointer)

$8B / $46 / $06 / mov ax, [bp+6) (get offset address)

$89/ $46/ SFC ); mov [bp-4),ax (and put in pointer)

end;

(SF-) { NEAR routines possible again )


••• ** •••••••••••••••••••••••••••••••• ** •••••• ** ••• ** •••• ** ••• ****.*•••• }
{* HexString: creates a 4-digit hex string out of the number passed *)
{* Input : - HexVal • the number to be converted *)
(* Output : the hex str"ing *)
{* ••• *** •• ** •• *** ••••••••••••• **.*****••••••••••••• ** •••• ***** •••• *****}

function HexString( HexVal : word) : HexStr;

var counter, ( loop counter


Nibble byte; { the lowest nibble of the word

begin
CvHStr :- 'xxxx'; { initialize the string
for Counter:-4 downto 1 do ( run through the 4 digits of the string
begin

Nibble := HexVal and SOOOf; ( leave just the lower 4 bits

if ( Nibble > 9 ) then ( convert to a letter?

CvHStr[ Counter) •• chr(Nibble - 10 + ord('A·)) ( yes

else ( convert to a number

CvHStr[ Counter •• chr(Nibble + ord('O'));

HexVal := HexVal shr 4; shift HexVal 4 bits to the right

end;
HexString := CvHStr; ( return the created string
end;

•••• ** ••••••• *** •••••••••••••••••••••• ** •••••••••••••••••••••••••••••• *}


(* FirstMCB: Returns a pointer to the first MCB. *)
(. Input : none *)
(* Output : pointer to the firs MCB *)
(* •••••• ** ••••••••••••• ** ••••• *** ••••••••••••••••• *** ••••••••••••••• **.}

function FirstMCB : MCBPtr;

var Regs : Registers; ( stores the processor registers )

begin
Regs.ah := S52; { ftn. no.: get address of the DOS info block
MsDos ( Regs ); ( call DOS interrupt $21

(*-- ES: (BX-4) points to the first MCB, create pointer -------------*)
FirstMCB :- MCBPtr2( MK_FP( Regs.ES-1, Regs.BX+12 ) )A;
end;

(* ••••••• **** ••••••• *********************** ••••••••••• **************.**)


(* Dump: outputs hex and ASCII dump of a memory block. *)
(* Input - DPtr - pointer to the memory block to be dumped *)
(* - Num - number of lines to dump (16 bYtes each) *1
{* Output none *)
{.********************** ••• * •••• ** •• * ••••• * ••• **.*.***.****************}

procedure Dump( DPtr : RngPtr; Num(Numl : byte);

125
6. The Disk Operating System PC System Programming

type HBStr - string[2]; stores 2-digit hex numbers

var Offset, offset in the memory block


Z integer; ( loop Counter
HexStr HBStr; stores a hex number for hex dump

procedure HexByte( HByte byte) ;

begin
HexStr[l] := chr( (HByte shr 4) + ord('O') ); first digit
if HexStr[l] > '9' then convert to letters?
HexStr[l] := chr( ord(HexStr[l]) + 7); ( yes
HexStr[2] :- chr( (HByte and 15) + ord('O') ); ( second digit
if HexStr[2] > 'g' then convert to letters?
HexStr[2] .- chr( ord(HexStr[2]) + 7); ( yes
end;

begin
HexStr :== IZ Z '; ( initialize the hex string
writeln;
write('DUMP I 01234567B9ABCDEF 00 01 02 03 04 05 06 07 OB');
writeln(' Og OA DB OC OD DE OF');
write ('-----+--------------------------------------------------------');
writeln('--------------------------');
Offset :- 0; ( start with the first byte in the block
while Num>O do ( run through the loop ANZ times
begin

write (HexString(Offset) , ' I ');

for z:-o to 15 do ( process 15 bytes


if (DptrA[Offset+Z] >- 32) then valid ASCII character?
write( chr(Dpt~A[Offset+Z]) ) ( yes, output character
else ( no
write(' '); output space instead of character
write (' '); { set cursor to the hex portion
for Z:=O to 15 do ( process 15 bytes
begin
HexByte( DPtrA[Offset+Z] ); convert byte to hex
write (HexStr, , '); ( output hex string
end;
writeln;
Offset := Offset + 16; ( set offset in the next line
Dec( Num ); decrement number of remaining lines
end;
writeln;
end;

(***********.******************************* ••••• **********************)


(* TraceMCB: runs through the list of MCB's. *)
{* Input : none *}
(* Output : none *)
{***********.******** ••• *.*******************.*.****•• *******.*********}

procedure TraceMCB;

const ComSpec : array[0 •• 7} of char 'COMSPEC=';

var CurMCB{CurMCB} : MCBPtr;


Done boolean;
Key char;
NrMCB, { number of current MCB
Z integer; { loop counter
MemPtr Rngptr;
DosVer byte; ( DOS version number

begin
DosVer := GetDosVer; get DOS version
Done :- false;
NrMCB :- 1; { the first MCB is number 1
CurMCB '- FirstMCB; get pointer to the first MCB
repeat { follow the MCB chain

126
Abacus 6.9 Memory Allocation from DOS

if CurMCBA.IdCode = 'Z' then { last MCB reached?


Done : = true; { yes
writeln('MCB number NrMCB) ;
writeln('MCB address HexStri ng (seg (CurMCBA) ), ':',
HexString(ofs(CurMCBA» );
writeln('Memory addr. HexString (succ (seg (CurMCB A) ) ), ':',
HexString(ofs(CurMCBA» );
writeln('ID CurMCBA.IdCode);
writeln('PSP address " HexString(CurMCBA.PSP), ':0000');
writeln('Size = " CurMCBA.Distance, ' paragraphs "
'( " longint(CurMCBA.Distance) shl 4, , bytes I');
write ('Contents - ');

{*-- is it an environment? ---------------------------------------*)


Z := 0; { start the comparison at the first byte
MemPtr := RngPtr(MK FP(succ(Seg(CurMCB A», 0»; {pointer in RAM
while ( (Z<-7) and (ord(ComSpec[Z]) - MemPtrA[Z]) ) do
Inc(Z); { set Z to the nest character
if Z>7 then { was the string found?
begin yes, this is an environment
writeln('environment');
MemPtr :- RngPtr(MK FP(succ(Seg(CurMCBA», 0»;
if DosVer>- 30 then- { DOS Version 3.0 or higher?
begin { yes, get program name
write('Program name = ');
Z :- 0; { start with the first byte
while not ( (MemPtr A[Z] =0) and (MemPtrA[Z+l]~O) ) do
Inc ( Z ); { search for empty string
z : = Z + 4; set Z to the start of the prog name
if MemPtrA[Z]<>O then { is there a prog. name here?
begin
repeat { run through the program name
write( chr(MemPtrA[Z]) ); { output characters
Inc ( Z ); { process the next character
until MemPtrA[Z]=O; { to the end of the string
writeln;
end
else { program name not found )
writeln (' unknown') ;
end;

{*-- output the environment strings --------------------------*)


writeln(f13,'10, 'Environment strings');
Z := 0; { start with the first byte in the allocated block
while MemPtrA[Z]<>O do { repeat until empty string
begin
write ('
repeat { output a string
write ( chr {MemPtr A[Z]) ); print a character
Inc ( Z ); process the next character
until MemPtrA[Z]=O; { to the end of the string
Inc ( Z ); { set to the start of the next string
writeln; { end line
end
end
else { no envrionment )
begin

{*-- is it a PSP? --------------------------------------------*)


{*-- (starts with command INT 20 (code-$CD $20» -------------*)
MemPtr :- RngPtr(MK FP(succ(seg(CurMCBA», 0»; {set pcinter
if ( (MemPtrA[O]=$CD) and (MemPtr A[1]=$20) then
begin { it's a PSP
writeln('PSP (with program following)');
end
else { the command INT 20 was not found )

127
6. The Disk Operating System PC System Programming

begin
writeln('unidentifiable (program or data) ');
Dump ( MemPtr, 5); { dump the first 5x16 bytes)
end;

end;

write('----------==----==-~==--_= _______ ==~ __ = __ ' ) ;


writeln('------==--===== Press a key ---'I;
if ( not Done ) then
begin { set pointer to the next MCB )
CurMCB :- MCBPtr(MK FP(seg{CurMCB') + CurMCB'.Distance + 1, 0));
Inc(NrMeB); - { increment the number of the MCB ,
Key := ReadKey;
end;
until { Done ) { repeat until the last MCB is processed ,
end;

{************************************************************.*****.***}
(** MAIN PROGRAM
{*******************••• **** ••• *.*********************** •••• ********•• **}
**'
begin
ClrScr; clear the screen
TraceMCB; run through the MeBs
end.

C listing: MEMC.C
/************* •••***************.***************** ••**********.********/
1* M E M C *1
1*--------------------------------------------------------------------*1
1* Description : Displays the memory blocks allocated by DOS *1
1*--------------------------------------------------------------------*1
1* Author MICHAEL TISCHER *1
1* developed on : 08/23/1988 *I
1* last update : 05/1211989 *1
1*--------------------------------------------------------------------*1
1* (MICROSOFT C, *I
1* creation : CL lAS IZp memc.c *1
1* call : MEMC *I
1*--------------------------------------------------------------------*1
1* (BORLAND TURBO CJ *1
1* creation : via the Compile-Make command *1
1* (no project file) *I
,********* •• ****************** ••• ********************* *****************1

finclude <dos.h>

'include <stdlib.h>

typedef unsigned char byte; 1* build ourselves a byte *1


typedef unsigned segadr; 1* a segment address *1
typedef byte boolean;
typedef byte far *FB; 1* FAR pointer to a byte *1

/*=- Constants =========-===========================-======-==-==----=*'


'define TRUE 1* needed for working with boolean *1
'define FALSE 0

1*=- Structures and unions ====~~~~=~-============---~===~==-======-=-*I

struct MCB {
byte
segadr psp;
'* 'M'
1* describes an MCB in memory *1
=a block follows, 'Z'
1* segment address of the PSP *1
= end */

unsigned distance; 1* number of paragraphs reserved *1

128
Abacus 6.9 Memory Allocation from DOS

};

typedef struct MCB far *MCBPtr; '* FAR pointer to an MCB *'

'*-- Macros ----=-==---=======-=======--====---===---------=====----==*'

.ifndef MK_FP '*


was MK FP defined already? *'
.define MK_FP(seg, ofs} «void far *) «unsigned long) (seg)«161 (ofs»)
.endif

/.*** •••••••••• ***.** ••• ***.****.***** ••••••••••••••••••••• **••• ***


•••••
Function :FIRST M C B *
**------------------------------=-------------------------------------*.
Description Returns a pointer to the first MCB.

Input parameters : none

Return value : Pointer to the first MCB

******************* ••• ***********.*.*.*************************.***


****/

MCBPtr first_mcb()
{
union REGS regs; '* stores the processor registers *'
struct SREGS sreqs; '* stores the seqrnent registers *'

regs.h.ah = Ox52; '* ftn. no.: get address of the DOS info block *'
intdosx( 'regs, &regs, &sregs ); '* call DOS interrupt Ox21 *'

'*-- ES:(BX-4) points to the firs MCB, create pointer ---------------*'

return( *«MCBPtr far *) MK_FP( sregs.es-l, regs.x.bx+12 » );


}

j************.********* ••• ***** •••• ******************* *.******* ••• ******


Function : DUM P *
**--------------------------------------------------------------------**
Description Outputs hex and ASCII dump of a memory range.
Input parameters - bptr pointer to a memory area
- num : number of dump lines (each 16 bytes)
Return value none
*********************.***.********.************************************/

void dump ( FB bptr, byte num)


(
FB lptr; '* running pointer for printing a dump line *'
unsigned offset; '* offset address relative to BPTR *'
byte i; '* loop counter *'

printf ("\nDUMP I 01234567S9ABCDEF 00 01 02 03 04 as 06 07 OS");


printf(" 09 OA OB OC aD OE OF\n");
printf("-----+--------------------------------------------------------"};
printf ("-----------------\n");

for (offset-a; num-- ; offset +- 16, bptr +- 16)


{ '* run through the loop NOM times *'
printf ("\04x I ", offset);
for (lptr~bptr, i=16; i-- ; ++lptr) j* print character as ASCII *'
printf ("\c (*lptr<32) ? ••
M , *lptr);
printf(" ");
for (lptr~bptr, i-16; i-- ; '* output character as hex *'

}
printf ("\02X ., *lptr++);
printf ("\n"); '* move to the next line *'
/***********************************************************************
* Function : T R ACE MC B
**------------------------------=-------------------------------------**

129
6. The Disk Operating System PC System Programming

Description Traces the chain of MCB's. *


Input parameters none *
Ret urn va 1ue none
****************.* ••**.*************.*****.******** ••• **************.**,

void trace mcb ()


{ ­
static char fenv[] - ( 1* first environment string *1
'C', '0', 'M', '5', 'P', IE', 'C', '_I
I;
MCBPtr cur mcb; 1* pointer to the current MeB *1
boolean done; 1* TRUE i f the last MCB was found*1
byte nr_mcb, 1* number of the current MeB *1
i; 1* loop variable*1
FB lptr; 1* running pointer in the environment *1
done = FALSE; 1* now we get going *1
nr mcb = 1; 1* the first MeB is number 1 *1
cur mcb - first_mcb{); 1* get pointer to the first MeB *1
do - 1* process.the individual MCB's *1
{
if ( cur mcb->id code -= 'Z' ) 1* last MeB reached? *1
done - TRUE; - 1* yes *1
printf("MCB number = td\n", nr mcb++);
printf("MCB address 'Fp\n", cur_mcb);
printf("Mernoryaddr. = tNp:OOOO\n", FP SEG(cur mcb)+l);
printf("ID tc\n", cur mcb->id code);
printf("PSP address tFp\n°O, (FB) MK_FP(cur_mcb->psp, 0) );
printf("Size \u paragraphs ( tlu bytes )\n",
cur mcb->distance, (unsigned long) cur mcb->distance « 4);
printf("Contents = H);

1*-- is it an environment? -----------------------------------------*1


for (i-O, lptr=(FB) cur mcb+16;1* compare first ENV string with FENV *1
( i<sizeof fenv )- " ( * (lptr++) -- fenv[i++J ) ; )
,
if -- sizeof fenv ) 1* was a string found? *1
( 1* yes, it's an environment *1
printf (OOenvironment\n U);
if ( osrnajor >= 3 ) 1* DOS version 3.0 or higher? *1
( ­ 1* yes, get program name *1
print! ("Program name == ");
for ( ; ! (* (lptr++) --0 " *lptr~~O)
1* find last ENV string *1
if ( * (lptr +- 3) 1* is there a program name here? *f
( 1* yes *f
for ( ; *lptr ; /* run through the program name *f
printf ( "tc·, * (lptr++) ); 1* output a character *1
else 1* no program name was found *1
printf(Uunknown");
printf ("\n"); f* move to the next line *1
I
1*-- output the environment strings ------------------------------*1

printf("Environment strings\n U);

for (lptr=(FB) cur mcb +16; *lptr ++lptr)

( ­ 1* output a string *f
printf (" 00);
for ( ; *lptr ; ) 1* run through the string to a NUL character *1
printf ( "tc", * (lptr++) ); 1* output a character *1
printf ("\n") ; f* move to the next line *f
I
else 1* no envrionrnent *1
{

130
Abacus 6.9 Memory Allocation from DOS

/*- is it a PSP? -------------------------------------------------*/


/*-- (introduced with the command INT 20 (Code-oxCO Ox20» -------*/

if (*«unsiqned far *) MK FP( cur mcb->psp, 0 » -­ Ox20cd)


printf("PSP (with subsequent proqram)\n"); /* yes */
else /* the command INT Ox20 was not found */
{
printf("unidentifiable (proqram or data)\n");
dump ( (FB) cur_mcb + 16, 5); /* dump the first 5x16 bytes */
)

printf("==-=-------===--=-----=======---==--==--");

printf("==-=====----- Please press a key ~=\n");

i f ( !done ) /* another MeB? */

( /* yes, set pointer to the next MCB */


cur_mcb - (MCBPtr)
MK_FP( FP_SEG(cur_mcb) + cur mcb->distance + 1, 0 );
qetch(); - /* wait for a key */
)

while ( ! done ); /* repeat until the last MeB has been processed *j
)

/********************.***********************************.*.*.***** •• **/
/** MAIN PROGRAM **/
/*************************************************.******************.*/

void main ()

(
printf ("\nMEMC (c) 1988 by Michael Tischer\n\n");

trace_mcb () ; /* trace the chain of MCB's */

131
6. The Disk Operating System PC System Programming

6.10 DOS Filters


Filters are programs, routines or utilities which accept input and modify the data
for output. Filters do the same on the opemting system level: characters are passed
to these filters as input, the filters modify the characters then send the modified
characters as output. This manipulation takes many forms. Filters can sort data,
replace certain data with other data, encode data or decode data.

DOS has three basic filters available:

FIND searches input for a specified set of characters

SORT armnges text or data in order

MORE formats text display

These filters perform simple redirection of standard input/output. They read


characters from the standard input device, manipUlate the characters as needed, then
display them on the standard output device. The standard input device under OOS is
the keyboard, and the standard output device is the monitor. DOS versions of 2.0
and higher allow the user to redirect the standard input/output to files. Therefore, a
filter can read characters from the keyboard or from a file, depending on the standard
input device selected. This is possible by using a filter in conjunction with one of
the OOS handle functions for reading and writing. DOS offers five handles:

0 Standard input CON (Keyboard)


1 Standard output CON (Screen)
2 Standard error output CON (Screen)
3 Standard serial interface AUX
4 Standard printer PRN

If the user calls a program from the OOS level, the < character redirects input and
the> character redirects output. In the following example, the input comes from
the file IN.TXT instead of the keyboard. The output is written to the file
OUT.TXT instead of the screen:

sort <in.txt >out.txt

SORT

After the user enters the above command, DOS recognizes that a program named
SORT should be called. Then it encounters the expression <IN.TXT which
redirects the standard input. This occurs by assigning the handle 0 (standard input,
which formerly pointed to the keyboard) to the file IN.TXT. The expression
>OUT.TXT resets handle 1 to the OUT.TXT file instead of the screen. The affected
handle is first closed, and then the redirected file is opened.

132
Abacus 6.10 DOS Filters

Once the command processor finishes with the command line it calls the SORT
program using the EXEC function (DOS function 4BH). Since the program called
with the EXEC function has all the handles of the calling program available, the
SORT program can input/output characters to handles 0 and 1. Where the
characters originate is unimportant to the program.

After the SORT program completes its work, it returns control to the command
processor. The command processor resets the redirection and waits for further input
from the user.

Pipes
The filter principle as supported by DOS becomes especially powerful through
pipes. This expression comes from the idea of a pipeline used for transporting oil
or gas. DOS pipes have a similar function: they carry characters from one program
to another and permit the connection of various programs with each other.

When this happens, characters output from one program to the standard output
device can be read by another program from the standard input device. As in the
redirection of the standard input/output, the two programs do not notice the
pipelines. The difference between the two procedures is that under redirection of the
standard input/output devices, data can be redirected only to one device or file,
while the use of pipes allows data transfer to another program.

Combined filters

Pipes allow the user to connect multiple filters. The pipe character I is inserted
between the programs to be connected. An example should make this more
understandable: A text file named DEMO.TXT is sorted and then displayed on the
screen in page format. Even though this task appears to be very complicated at
first, it can be performed easily using two DOS filters: SORT and MORE. SORT
sorts the file and MORE displays the file on the screen in page format.

The question is, how can you tell the command processor to do these things? First
SORT is used. This filter is told to sort the file DEMO.TXT. The redirection of
standard input can be used as illustrated at the beginning of the chapter:

SORT <DEMO. TXT

After the user enters this command, SORT sorts the file DEMO.TXT then
displays the file on the screen. This display would be much easier to read in page
format. Formatted output can be implemented by redirecting the output from
SORT to a file (for example TEMP.TXT) and displaying this fIle using the
MORE command. The following sequence of commands do this:

SORT <DEMO. TXT >TEMP.TXT

MORE <TEMP. TXT

133
6. TIu! Disk Operating System PC System Programming

You can use a pipe to connect the SORT filter and the MORE filter, saving the
user typing time. The following command line sends the output from SORT
directly to MORE and immediately displays the sorted file in page format:
SORT <DEMO. TXT I MORE

Any number of filters can be connected using pipes. DOS always executes these
pipelined filters from left to right. It sends the output from the first program as
input to the second program, the second program's output as input to the third
program, etc. The last program can again force the redirection of the output with
the > character so that the final result of the whole program or filter chain travels
to a file or other device instead of the screen.

Note: DOS cannot send data from one filter directly to another because it
would have to execute both filters simultaneously, and the current
version of DOS doesn't have multiprocessing capabilities. Instead,
the following method is used. The input calls the first filter and
redirects its output to a pipe file. After the first filter ends its
processing, it calls the second filter but redirects its input to the pipe
file to read in the output from the first filter. This principle applies
to all filters. The pipe file is stored in the current working directory.

The word "dump" is a computer buzzword for a way to display the contents of a
file in ASCII characters and/or hexadecimal numbers. The DUMP programs below
performs this task as a filter. As the contents are displayed in ASCII format,
DUMP differentiates between normal ASCII characters (letters, numbers, etc.) and
control characters such as carriage return, linefeed, etc. These control characters are
displayed in mnemonic form (e.g., <CR> for carriage return and <LF> for
linefeed). This DUMP filter is fairly simple in structure, yet it can be very useful
to quickly examine a file's contents.

The structure of the DUMP program is typical for a filter. Since DUMP displays a
maximum of nine ASCII characters and/or hexadecimal codes per line, it asks for
nine characters by using the read function from the standard input device. If not
enough characters are available, it reads what characters are available. DUMP
places these characters in a buffer, then converts the characters into ASCII
characters and hex codes. This buffer will accept a comple.te line of 78 characters.
When the buffer processing finishes, the filter uses the handle to write to the
standard output device. This process is repeated until no more characters can be read
from the standard input device.

The following programs are written in Pascal, C and assembly language. Note that
there isn't a BASIC version. The reason is that BASIC, as an interpreted language,
is unsuitable for developing a filter which can be called from the DOS level. A
BASIC compiler would be required for this task.

134
Abacus 6.10 DOS Filters

Pascal listing: DUMPP.PAS


{******** •• ************ •• ******************************.*************}
(* DUM P P *J
(*------------------------------------------------------------------*J
{* Task a Filter, which reads in characters from the *}
(* Standard input device and outputs them *J
(* as Hex and ASCII dump on *J
(* the Standard output device *J
{*------------------------------------------------------------------*}
{* Author MICHAEL TISCHER *}
{* developed on : 08/08/87 *}
(* last Update : 05/04/89 *)
(*------------------------------------------------------------------*)
{* Info This program can only be called from the *}
(* DOS level after compiling to an EXE file *)
(* with TURBO *)
{*.-.-._.__....... _----_._._-----_._._-----_._._-_._.-***************}

program DUMP;

Uses Dos; ( Add DOS unit

($V-) suppress length test on strings

const NUL 0; ASCII-Code NUL-character

BEL = 7; ASCII-Code Bell character

BS 8; ASCII-Code Backspace

TAB - 9; ASCII -Code Tab

LF = 10; ASCII-Code Linefeed

CR = 13; ASCII-Code Carriage Return

EOF 26; ASCII-Code End of File

ESC - 27; ASCII-Code Escape

type SZText - string[3]; { passes the name of a special character


DumpBf array[1 .• 80] of char; ( accepts the output Dump

(*-*--------------._._-_._.__.... ------_._.-----------****.**********)
(* SZ writes the name of a control character into a Buffer *)
(* Input see below *)
(* OUtput none *J
(* Info after the call of this procedure the pointer *)
(* which was passed, points behind the last character of *)
{* the control character name in the Dump-Buffer *}
{********************************************************************}

procedure SZ(var Buffer DumpBf; { Text entered here


Text SZText; { Text to be entered
var Pointer integer) ; addr. of text in buffer

var Counter : integer;

begin
Buffer [Pointer] :- '<'; ( leads control character
for Counter := 1 to length(Text) do ( transfer Text to Buffer
Buffer[Pointer + counter] := Text[Counter];
Buffer[Pointer + Counter + 1] := '>'; ( terminates control char
Pointer := Pointer + Counter + 2; { Pointer to next character
end;

{*-_._._--*---_.... _----_..._-----_._.--_._._.... _-*--***** •• ********}


(* DODUMP reads characters in and outputs them as Dump *)
(* Input : none *)
(* Output : none *)
{* ••••• _._--------*--*.**********************************************}

procedure DoDump;

135
6. The Disk Operating System PC System Programming

Endc :- false; ( not the End


repeat

Regs. ah :- $3F; (Function number for reading handle

Regs.bx .= 0; the Standard input device is handle 0

Regs.ex .= 9; ( read in 9 characters

Regs.ds '= seg(NewByte); ( Segment address of the buffer

Regs.dx '= ofs(NewByte); ( Offset address of the buffer

MsDos ( Regs ); ( Call DOS-Interrupt 21H

if (Regs.ax - 0) then Endc '= true; ( no character read?

if not(Endc) then

begin ( NO
for Counter :- 1 to 30 ( Fill buffer with blanks
do DumpBuf [Counter) := , ';
DumpBuf[31) '= '219; Place Separator between Hex and ASCII
NextA :- 32; ASCII-characters follow separator
for Counter 1 to Regs.ax do ( start processing characters
begin ( read in
HexChr :- ord(NewByte[Counter) shr 4 + 48; ( Hex top 4 bits
if (HexChr > 57) then HexChr :- HexChr + 7; ( convert char
DumpBuf[Counter * 3 - 2) := chr(HexChr); ( store in buffer
HexChr :- ord(NewByte[Counter) and 15 + 48; ( Hex bot. 4 bits
if (HexChr > 57) then HexChr := HexChr + 7; ( convert number
DumpBuf[Counter * 3 - 1J := chr(HexChr); ( store in buffer
case ord(NewByte[Counter) of ( test ASCII-Code
NUL SZ(DumpBuf, 'NUL', NextA); ( NUL-character
BEL SZ(DumpBuf, 'BEL', NextA); ( Bell character
BS SZ(DurnpBuf, 'BS' , NextA); ( Backspace
TAB SZ(DumpBuf, 'TAB', NextA); ( Tab
LF SZ(DurnpBuf, 'LF' , NextA); ( Linefeed
CR SZ(DurnpBuf, 'CR' , NextA); Carriage Return
EOF SZ (DurnpBuf, ' EOF', NextA); ( End of File
ESC SZ(DurnpBuf, 'ESC', NextA); ( Escape
else
begin ( normal character
DumpBuf[NextA] := NewByte[Counter); ( Store ASCII-character
NextA '= succ(NextA) ( Set pointer to next character
end
end;
end;
DurnpBuf[NextA] :- '219; ( Set End character
DumpBuf[NextA+1) :- chr(CR); Carriage-Return followed by Line­
DumpBuf[NextA+2] :- chr(LF); ( feed to buffer end
Regs.ah '= $40; ( Function number for writing handle
Regs.bx :- 1; ( Standard output device is handle 1
Regs.cx '= NextA+2; ( Number of characters
Regs.ds := seg(DumpBuf); { Segment address of the buffer
Regs.dx '= ofs(DurnpBuf); ( Offset address of the buffer
MsDos ( Regs ); { Call DOS-Interrupt 21H
end;
until Endc; { repeat until no more characters are available
end;

t****************************************···*****··***.********** ••• *}
(* MAIN PROGRAM •)
{********** •• ***** •• ********* •• ***** ••• *.******** •• *********.********j

begin
DoDump; ( Output Dump )
end.

136
Abacus 6.10 DOS Filters

C listing: DUMPC.C
/********.* •• ******.*****.*************.****.***~***** ***************/
1* DUMPC *1
1*------------------------------------------------------------------*1
1* Task a Filter which reads in characters from the *1
1* Standard input and outputs them as *1
1* Hex and ASCII-Dump on *1
1* the Standard output device *1
1*------------------------------------------------------------------*1
1* Author MICHAEL TISCHER *1
1* developed on : 08/14/87 *1
1* last Update : 04/08/89 *1
1*------------------------------------------------------------------*1
1* (MICROSOFT C) *1
1* Creation MSC DUMPC; *1
1* LINK DUMPC; *1
1* Call DUMPC [<Input] [>Output] *1
1*------------------------------------------------------------------*1
1* (BORLAND TURBO C) *1
1* Creation : tcc dumpc *1
1* Call : DUMPC [<Input] [>Output] *1
/***************************************************** ***************j

tinclude <stdio.h> 1* include Header-files *1


tinclude <dos.h>

fdefine byte unsigned char

fdefine NUL a 1* Code of NUL-character *1


'define BEL 7 1* Code of Bell *1
'define BS 8 1* Code of Backspace-key *1
'define TAB 9 1* Code of Tab-key *1
fdefine LF 10 1* Code of Linefeed *1
'define CR 13 1* Code of Return-key *1
fdefine ESC 27 1* Code of Escape-key *1
fdefine tohex(c) ( «c)<10) ? «c) I 48) : «c) + 'A' - 10) )

j***************************************************** ***************/
1* GETSTDIN: reads a certain number of characters from the Standard *1
1* input device into a Buffer *1
1* Input see below *1
1* Output Number of characters read *1
1***************************************************** ***************/

unsigned int GetStdIn(Buffer, MaxChar)


char *Buffer; 1* Pointer in Character-Vector, which accepts data *1
unsigned int MaxChar; 1* maximum of characters to be read in *1

union REGS Register; 1* Register-Variable for Interrupt-Call *1


struct SREGS Segment; 1* accepts the Segment register *1
segread(&Segment); 1* read content of Segment register *1
Register.h.ah - Ox3F; 1* Function number for *1
Register.x.bx = 0; 1* the Standard input device is handle 0 *1
Register.x.cx = MaxChar; 1* Number of Bytes to be read *1
Register.x.dx = (unsigned int) Buffer; 1* Offset address of Buffer *1
intdosx(&Register, &Register, &Segment); 1* Call Interrupt 21H *1
return(Register.x.ax); 1* Number of Bytes read to caller *1
I

1***************************************************** ***************/
1* STRAP Attach character to string *1
1 * Input : see below *1
1* Output : Pointer behind the last added character *1
/****************************** ••• **.***~*.*****.***** ***************/

137
60 The Disk Operating System PC System Programming

char *Strap(Strinq, Textpointer)


char *String, 1* the source string *1
*Textpointer; /, Pointer to the text to be attached */

while (*Textpointer) /* repeat until '\0' detected */


*String++ = *Textpointer++; /* transmit character */
return (String) ; /* Pass Pointer to calling function */
)

j**.**.****.**** ••• **** •• *******.*** ••• *** •••• *** ••••• ******** ••• ****/
/* DODUMP reads the characters in and outputs them as Dump */
1* Input : none *1
1* Output : none *I
,.**** ••• ************.***** ••• ********* •••• *** •••• **** •• *********** •• /

void DoDurnp ()

char NewByte[9] , I*Accepts the characters read */


DurnpBuf[80] , 1* accepts a line of DUMP */
*NextAscii; /* points to next ASCII-character in the buffer */
byte i, 1* Loop counter */
Readbytes; /* Number of bytes read in */

DurnpBuf[30] = 219; /* Set separator between Hex and ASCII *1


while«Readbytes = GetStdIn(NewByte, 9» i= 0)
/-,It as long as characters are available *1

for (i = 0; i < 30; DurnpBuf[1++1 = ' 0);


/* Fill buffer with spaces */
NextAscii = &DurnpBuf[3ll; /* ASCII-characters start here */
for (i = 0; i < Readbytes; i++)
/* process all characters read in *1
{

DumpBuf [ i * 31 tohex ( (byte) NewByte [i 1 » 4);

1* convert Code in Hex */


DumpBuf[i*3+11 = tohex «byte) NewByte[i 1 & 15) ;
switch (NewByte [i 1) 1* evaluate ASCII-Code *1
{
case NUL NextAscii = Strap(NextAscii, "<NUL>") ;
break;
case BEL NextAscii Strap (NextAscii, u<BEL>") ;
break:
case BS NextAscii Strap (NextAscii, "<BS>");
break;
case TAB NextAscii = Strap(NextAscii, "<TAB>II);
break:
case LF NextAscii Strap (NextAscii, "<LF>") ;
break:
case CR NextAscii Strap (NextAscii, "<CR>"):
break:
case ESC NextAscii Strap (NextAscii, u<ESC>"):
break.;
case EOF NextAscii Strap (NextAscii, "<EOF>") ;
break.;
default *NextAscii++ = NewByte[il;
)

*NextAscii = 219; /' End character for ASCII representation */


, (NextAscii +1) = . \r'; 1* Carriage-Return to End of buffer */
* (NextAscii +2) ~
'\0' ; 1* NUL converted to LF on output */
puts (DurnpBuf) ; /' Write String on Standard output device */
I

/********.*.* ••••••• *.*.*******.**** •• ***** ••• ********* ••• *** ••• *****,
/** MAIN PROGRAM **1
,.****.********.*** •••• ** •• ***** •••• *** ••••• *****.**** ••• *** •• ***** •• j

138
Abacus 6.10 DOS Filters

void main ()

DoDump() ; /* Character input/output */


l

Assembler listing: DUMP.ASM


i*··*************···***************········*****···*** ******.****** •• ;
;* DUMP *;
:*-------------------------------------------------------------------*;
;* Task: A Filter which reads characters from the Standard input *;
;* and outputs them as Hex- and ASCII-Dump on *;
;* the Standard output device *;
:*-------------------------------------------------------------------*:
;* Author MICHAEL TISCHER *;
;* developed on : 08/01/87 *;
;* last Update : 04/08/89 *.
:*-------------------------------------------------------------------*;
;* assemble MASH DUMPA: *.
;* LINK DUMPAi *;
:* (important)... EXE2BIN DUMPA DUMP.COM *:
:*-------------------------------------------------------------------*:
;* Call : DUMP [<Input] [>Output] *;
i·····*****·****·····**····***····****···*****········ .*****.*********;
;-- Constants ====---=============---==-=~--===============-==========

NUL equ 0 ;ASCII-Code NUL-Character


BEL equ 7 ;ASCII-Code Bell
BS equ 8 ;ASCII-Code Backspace
TAB equ 9 ;ASCII-Code Tabulator
LF equ 10 ;ASCII-Code Linefeed
CR equ 13 ;ASCII-Code Carriage Return
EOF equ 26 ;ASCII-Code End of File
ESC equ 27 ;ASCII-Code Escape

;== Program starts here ============-================­

code segment para 'CODE' ;Definition of CODE-Segments

org 100h

assume cs:code, ds:code, es:code, ss:code

;-- Start routine ----------------------------------------------------­

dump label near

;-- Read in 9 Bytes from Standard input device -------------­

xor bX,bx ; Standard input has the handle 0


mov cx,9 ;read in 9 characters
mov dx,offset newbyte ;Address of the buffer
mov ah,3Fh ; Function code for handle reading
int 21h ;Call DOS-Function
or ax,ax ;characters read in?
jne dodump ;YES --> process line
jmp dumpend ;NO --> DUMPEND

dodump: mov dx,ax ;record number of characters read

;-- Fill output buffer with Spaces -------------------------­


mov ex,lS ;15 Words (30 Bytesl
mov aX,2020h ;ASCII-Code of .. " to AH and AL
mov di,offset dumpbuf ;the Address of the output buffer
cld ;increment on String commands
rep stosw ;Fill buffer with Spaces

139
6. The Disk Operating System PC System Programming

;-- Construct Output Buffer -------------------------------­

mov cx,dx ;Get number of characters read in


mov di,oifset dumpbuf+31 ;Position Ascii-Codes in the buffer
mov bX,offset newbyte ;Pointer to input buffer
mov si,offset dumpbuf ;Position for Hex-Codes in Buffer

bytein: mov ah, [bxj ;Read in Byte


push si ;store SI on the Stack
mov si, offset sotab ;Address of special character table
mov dx,offset sotext-6 ;Address of special character text
sotest: add dx,6 ;next entry in special text
lodsb ;Load code from special char table
cmp al,255 ;Reached end of table?
je noso ;YES --> no special character
cmp ah,al ida codes agree?
jne sotest ;NO --> test next table element

Code was a special character --------------------------­


push ex ; Store Counter
mov si,dx ;copy DX to SI
lodsb tread number of char control codes
mov el,al ;transfer number of characters to CL
rep movsb ;copy designation into buffer
pop cx ; get counter
pop si ; ret urn 51 from Stack
mov al,ah ;copy character to AL
jmp short hex ;calculate Hex-Code

noso: pop si ;return 51 from Stack


mov al,ah ;copy character to AL
stosb ; store in buffer

hex: mov al,ah ;Code of character to AL


and ah,11110 ;mask upper 4 Bit in AM
shr al,I ;shift AL right 4 Bits
shr al,l
shr al,l
shr al,l
or aX,3030h ;convert AH and AL into ASCII-Codes
cmp a1,"9" lis AL a letter?
jbe nobal ;NO --> no correction
add aI, "Au_ltlu_9 ;correct AL
nobal: crnp ah, "9" lis AM a letter?
jbe hexout ;NO --> no correction
add ah,"A"-"1"-9 ;correct AH
hexout: mov [sij,ax ;store Hex-Code in buffer
add si,3 ;point to next Position

inc bx ;set pointer to next Byte


loop bytein ; process next Byt e

mov al,219 ; set separator


stosb

mov aX,LF shl 8 + CR ;CR and LF terminate buffer


stosw ;write in buffer

Send Dump to the Standard output device

mov bx,l ;Standard output 1s handle 1


mov cx,di ;determine number of characters to be
sub cx,offset durnpbuf ; transmitted
mov dx,offset dumpbuf ;Address of buffer
mov ah,40h ; Function code for handle
int 21h ;call DOS-function
jmp dump ;read in next 9 Bytes

140
Abacus 6.10 DOS Filters

dumpend label near

rnov aX,4COOh ;Function number for ending program

int 21h ;end program with End code

;== Data =~~=======================--=---====-==============--=-=======

newbyte db 9 dup (?) ;the 9 Bytes read in


dumpbuf db 30 dup (?), 219 ;the output buffer
db 49 dup (?)

sotab db NUL,BEL,BS,TAB ;Table of control characters


db LF,CR,EOF,ESC
db 255

sotext equ this byte ; Text of special characters


db 5,"<NUL>" ; NUL
db 5, "<BEL>" ; Bell
db 4,'"<85> II ; Backspace
db 5, "<TAB>" ;Tabulator
db 4,"<LF> " ; Linefeed
db 4,"<CR> H ;Carriage-Return
db 5, "<EOF>1t ; End of File
db 5,"<ESC>" ; Escape

code ends ;End of CODE-Segment


end dump

141
6. The Disk Operating System PC System Programming

6.11 <Ctrl><Break> and Critical Error Interrupts


DOS offers two ways of stopping a program during execution. These situations
occur when the user hits <Ctrl><Break> (<Ctrl><C», or when a critical error
occurs during access to an external device (Le., printer, hard disk, disk drive, etc.).
Although the key combination varies with the PC configuration, we'll use
<Ctrl><Break> consistently in this section.

<Ctrl><Break>

Pressing <Ctrl><Break> to stop a program during execution can have some


serious consequences. After the user presses this key combination, DOS abruptly
takes control from the program without allowing the program to perform any
"housekeeping" that may be needed. Files are not closed properly, diverted interrupt
vectors are not reset, and allocated memory is not released. The final result can
range from a loss of data to a system crash.

In order to prevent this, DOS calls interrupt 23H. This interrupt is also known as
the <Ctrl><Break> interrupt. When a program is started, this interrupt points to a
routine which brings about the end of the program. But a program is free to select
a routine of its own, thus maintaining control of what occurs when the user
presses <Ctrl><Break>.

However, the interrupt routine doesn't execute immediately. The break flag
controls when the interrupt routine occurs. This flag can be set at the DOS prompt
using the BREAK (ON/OFF) command from DOS, or with the help of DOS
function 33H, sub-function 1. If the break flag is on, every time a function of
DOS interrupt 21H is called, the keyboard buffer will be checked to see if either
<Ctrl><Break> or <Ctrl><C> has been pressed. If the break flag is off, this check
will be made only when calling the DOS functions that access the standard input
and output devices.

If this test fmds the appropriate key combination, the processor registers are loaded
with the values contained in the DOS function to be executed. Only after this is
interrupt 23H called.

If a program directs this interrupt to a routine of its own, there are several ways to
react. For example, the program could open a window on the screen which asks if
the user would like to end the program. It can also decide for itself whether or not
the program should end.

Maintenance

If the program chooses to halt execution, some form of clean-up routine should
follow. A routine of this type closes all open files, resets any changed interrupt
pointers, and releases any allocated memory. After this, function 4CH can end the
program without returning control to the interrupt 23H caller.

142
Abacus 6.11 <Ctrl><.Break> and Critical EmJr Interrupts

H <Ctrl><Break> is to be ignored, then the IRET assembly language instruction


must return control to DOS. The program must then ensure that all processor
registers contain the same values they had when interrupt 23H was invoked.
Otherwise, the DOS function that was originally called cannot be completed
without error.

Both ways of handling this situation will be demonstrated in an example at the end
of this section.
Critical error interrupt
Unlike the <Ctrl><Break> interrupt, the critical error interrupt call is rarely a
reaction to something the user does intentionally. It is usually a reaction to an
error that occurs when accessing an external device, such as a printer, disk drive, or
hard disk. While the user can correct the error in many cases (e.g., printer not
turned on), other errors can be caused by hardware failures that require repairs (e.g.,
read error while accessing hard disk).

To make allowances for the various kinds of errors, the critical error interrupt
(interrupt 24H) normally points to a DOS routine that displays the following or a
similar message on the screen and waits for input from the user:
(A)bort (R)etry (I)gnore (F)ail

This clears the screen of the program currently under execution. In addition, this
interrupt doesn't provide a "clean" program end. Like <Ctrl><Break>, the program
is in a situation where files are not properly closed, allocated memory is not
released, etc.

Installing an interrupt handler in a program to replace the DOS handler can help
here, too. DOS enlists the help of a processor register to pass this handler various
information when it is called. This helps the interrupt handler locate the source of
the error. Bit 7 in the AH register indicates either a floppy or hard disk access error
(bit 7 ofO, or some other error (bit 7 on). In addition, the BP:SI register pair
points to the head of the device driver that was being called when the error
appeared. A detailed error code is contained in the lower 8 bits of the DI register,
and the contents of the upper 8 bits are undefined. This returns the following error
codes:

143
6. The Disk Operating System PC System Programming

Error Codes Passed to the Critical Error Handler


Code Meaning
OOh Disk is write protected
Olh Access to an unknown device
02h Drive not ready
03h Unknown command
04h CRC error
OSh Wrong data length
OGh Seek error
OTh Unknown device type
OSh Sector not found
09h Printer out of paper
OAb Write error
OBh Read error
OCh General error

When called, the; critical error handler can respond by opening a window on the
screen that asks the user to decide to ignore the error, retry the access, or abort the
program. The latter option can only instruct the interrupt to call DOS functions
OlH to OCH. This means that the program ends abruptly, similar to pressing
<Ctrl><Break>. While it is true that calling other DOS functions within the
handler causes no errors in itself, the return to DOS causes a system crash. Such
handlers are also not allowed to end a program through the use of DOS function
4CH. Instead the handler must return to its caller with the help of the lRET
command. With that, DOS expects a code in the AL register that will show it how
to react to the error. It interprets the contents of the AL register as follows:

Output Codes of a Critical Error Handler


Code Meaning
OOh Ignore the error
Olh Retry the operation
02h End program with Interrupt 23h
03h End function called with an
error (DOS 3.0 up only)

The last output code in the above list represents the most sensible reaction to an
error that can't be fixed by repeating the operation (as in the example where the
printer needs to be turned on). The receipt of this code invokes the normal ending
of the function call in which the error occurred. The function then sets the carry
flag to signal the error. While this makes a "critical" error and a "normal" error
indistinguishable to the program, it's possible to tell them apart by setting a flag
within the critical error handler.
*******.**.********************•••• **********.************************
CE HAN D
*--------------------------------=-----------------------------------*
Description : Forms the basic structure of an assembler
program, in which the DOS Ctrl-Break and *
Critical Error Interrupt are captured
*--------------------------------------------------------------------*
Author : MICHAEL TISCHER
developed on 9/5/1988
last update : 4/8/1989

144
Abacus 6.11 <Crrl> <Break> and Critical E"or Interrupts

;* call CE HAND *;
;* (please leave the disk drive open so that a *;
;* Critical Error occurs.) *;
;.**.***•••••• **••••••• ********** •••••• *****•••••**••• ····**···········i
i-- constants ---===~=-===================----==--==-=--=--=====--=====-

i== stack ===--=-===========-==~-===--====-==---------=---=========-=-==


stack segment para stack ;definition of the stack segment
dw 256 dup (?) ;the stack is 256 words
stack ends lend of the stack segment
i-- data
data segment para 'DATA' ;definition of the data segment
cr err db 0 ;goes to 1, if a critical error occurs
;during access to a peripheral device
;(floppy, hard disk, or printer)
db o ;error number of the critical error
cr mes db ·Critical error! (A) bort or (R) etry: $"
next line db 13,10,"$"
end mes db ·Program ended normally.$"
brk mes db "Program aborted.$"
dat nam db "A:TEST.DAT",O ;name of the test file
data ends ;end of the data segment
; == code
code segment para 'CODE' ;definition of the CODE segment
assume cs:code, ds:data, ss:stack
start proc far
;-- install both Interrupt Handlers -------------------------­
push cs ;put CS on the stack
pop ds land return as DS
mav ax, 2523h ;fct.no.: set Ctrl-Break Handler
mav dX,offset cbreak ;DS:DX now contains the address of H.
int 21h ;call DOS Interrupt
mov al,24h ;now set Interrupt 24h
mov dX,offset cerror ;DS:DX contains the address of the new H.
int 21h ;call DOS Interrupt
mov ax, data ;load segment address of the data segment in
mov ds,ax ;in the DS register
you can add your program here

for a demonstration, try to open a file ----------------­


,
on the opened disk drive ----------------­
dat_open: mav ah,3dh ;function number: open file
mav al,O ;file mode: read only
mav dx,offset dat nam ;DS:DX - addresse of the filename
int 21h ;call DOS Interrupt 21h
jnc exit ;no error? NO --> END
cup cr_err,O ;critical error?
je exit ;NO --> END
call crit err ;a critical error occured
jmp dat_open ;CRIT ERR returns only if the operation
;should be retried
; (IGNORE is not possible)
, the handler must not be re-installed before the end
, of the program, since this is done by DOS
exit: mov ah,9 ;function number: pass string
mov dX,offset end_mes ;DS:DX - address of the message
int 21h ;call DOS Interrupt
mov aX,4COOh ;function no.: end program (ERRCODE=O)
int 2lh ;call DOS Interrupt and end the program
;with it
start endp
;-- CRIT_ERR: called within the program after discovery of a ---------­
i-- critical error
crit_err proc near
i-- output message and ask for user input ------------------­
ask: mav ah,9 ;function number: output string
mov dx,offset cr mes ;DS:DX = address of the message
int 21h ;call DOS Interrupt

145
6. TIu! Disk Operating System PC System Programming

mov ah,l ;function number: input character


int 21h ;call DOS Interrupt
push ax ; note the input
mov ah,9 ;function number: output string
mov dx,offset next line;DS:DX - address of the message
int 21h - ;call DOS Interrupt
;-- interpret the user's input ------------------------------­
pop ax ;retrieve the input
crnp al,"A" ;abort?
je end_up ;90 to "clean-up" procedure
cmp aI, ·'a" ;abort?
je end_up ;go to "clean-up" procedure
crnp aI,"r" iretry?
je crend ;go to end of procedure
crnp aI, "R" ; retry?
jne ask :no, ask again
crend: ret ;return to caller
crit_err endp
;-- END_UP: executes a "clean" ending
end_up proc near
;-- all opened files can be closed and the system memory
;-- allocated by the program can be freed here

mov ah,9 ;function number: output string


mov dX,offset brk mes ;DS:DX = address of the message
int 21h ;call DOS Interrupt
mov aX,4COOh ;end the program normally with the
int 21h ; 4Ch function
end up endp
;-- CBREAK: the new Ctrl-Break Handler --------------------------------­
cbreak proc far
;-- all registers altered within this routine (excluding
;-- the Flag Register) have to be secured on the stack
push ds
rnov ax, data ;load the segment address of the
mav ds,ax ;data segment in the DS-Register
;-- for example, you can open a window here in which the
;-- user is asked if he really wants to end the program

jmp go on ;don't end program


;-- if the user decides to end the program, a routine with --­
;-- which the program can be ended can be started here
jmp end up ;prepare termination of the program
;-- the program should not be aborted, continue normal
execution
pop ds ;restore saved register
iret ;back to DOS, where the interrupted
;function is continued normally
,-,oreak endp
;-- CERROR: the new Critical Error Handler ----------------------------­
cerror proc far
;-- each of the registers (55, 5P, OX, ES, OX, CX und BX)
that was altered within this routine must be saved
;-- on the stack
sti ;allow interrupts again
push ds

146
Abacus 6.11 <Ctrl> <Break> and Critical Error 1nterrupts

rnov aX,data ;load segment address of the data segment


rnov ds,ax lin the OS-Register
rnov cr err,l ;point to critical error
rnov aX-;di ; error nmnber to AX
rnov cr_typ,al ;note error number
rnov al,3 lend function call with error
pop ds ; fetch OS again
iret
cerror endp
;----------------------------------------------------------------------­
code ends lend of the code segment
end start ;start program execution with
;the START procedure

147
6. The Disk Operating System PC System Programming

6. 1 2 DOS Device Drivers


A device driver is the pan of the operating system responsible for the control of,
and the communication with, the hardware. It represents the lowest level of an
operating system, and permits all other levels to work independent of hardware.
When adapting an operating system to various computers, this is an advantage.
The entire operating system doesn't have to be changed, only the various device
drivers.

In earlier operating systems, device drivers resided in the operating system code.
This meant that changes or upgrades of these routines to match new hardware were
very difficult, if not impossible. DOS Version 2.0 introduced a flexible concept of
device drivers. This makes it possible for the user to adapt even the most exotic
PC clone to DOS.

Custom drivers
Since communication between DOS and a device driver is based on relatively
simple function calls and data structures, the assembly language programmer can
develop a device driver to adapt DOS to any device. Unfortunately, device drivers
cannot be programmed in a higher level language.

When developing the code for a driver, the same rules are observed as for
developing a COM program (no direct segment access). The difference is that a
device driver starts at offset address OH, and not at lOOH. The end of this section
explains the assembly language implementation in detail.

Drivers
During the DOS boot process, the drivers NUL, CON, AUX, PRN and the drivers
for the disk drives and hard drive (if needed) are loaded and installed. They are
arranged sequentially in memory and connected to each other. If the user wants to
install his own driver, he has to inform DOS using the CONFIG.SYS file. This
text file contains the information which DOS requires for configuring the system.
Contents of the CONFIG.SYS file are read and evaluated during the boot process
after linking the standard drivers. If DOS finds the DEVICE= command, it knows
that a new driver should be included. The name of the driver and perhaps a device
and path designation are indicated after the equal sign.

ANSI.SYS

The following command sequence includes the ANSI.SYS driver, which is


supplied with DOS. This driver makes enhanced character output and keyboard
functions available:

DEVICE=ANSI.SYS

148
Abacus 6.12 DOS Device Drivers

The new driver is added to the chain immediately following the NUL device driver
(the first driver in the chain). The ANSI.SYS driver replaces the default CON
driver. To ensure that all function calls for monitor or keyboard communication
operate through ANSI.SYS, the ANSI.SYS driver is placed first in the device
group, and the CON driver is moved farther down the chain of devices. Since the
operating system moves from link to link during the search, it finds the new CON
driver (ANSI.SYS) first and uses it. Therefore, the system ignores the old CON
driver as seen in the illustration below:

Before adding

new CON
5'
driver Q
CD

en
:r
cc
3
CD
3
o
-<
!.
...CD
Q.

Z
en

The driver chain


ASSIGN

Not all drivers can be replaced with new ones. The NUL driver is always the first
driver in the chain. If you add a new NUL driver, the system ignores the new driver
and continues accessing the original NUL driver. This also applies to the drivers
for floppy disk drives and hard drives. The reason for this is that disk drives have
drive specifiers instead of names such as CON (e.g., A:). A new disk drive can be
added to the system, but since DOS may assign it the name D:, it may not be
addressed by all programs which want to access device A:. This problem can be
avoided by redirecting all device accesses using ooS's ASSIGN command. You
can make the ASSIGN command part of the AUTOEXEC.BAT file. It executes
after adding drivers and executing the CONFIG.SYS file. To redirect all accesses
from drive A: (the first disk drive) to device D: (in this case, a new driver for a new
disk drive), the AUTOEXEC.BAT file must contain the following command
sequence:

149
6. The Disk Operating System PC System Programming

ASSIGN A=D

The drivers for mass storage devices and the drivers such as PRN are handled
differently. ooS has two kinds of device drivers:

Characta' device drivers


Block device drivers

Character device drivers communicate with the keyboard, screen, printer and other
hardware on a character by character (byte by byte) basis. Block device drivers can
transmit an entire series of characters during each function call (disks, hard disks,
etc.). The two driver groups differ somewhat through the ways each supports
different functions.

6.12.1 Character Device Drivers

Let's start with character device drivers because their structure is simpler than block
device drivers. Character device drivers transmit one byte for every function call.
They communicate with devices such as the keyboard, display, printer and modem.
A device driver can service only one device. Therefore, individual drivers for
keyboard, display, printer, etc., exist in ooS after booting.

Character devices can operate in either cooked mode or raw mode.


Cooked mode

In cooked mode, the device driver reads characters from the device and performs a
test for certain control characters. OOS then passes the character to an internal
buffer. DOS also checks to determine whether any <Enter>, <Ctrl><P>,
<Ctrl><S> or <Ctrl><C> characters exist. If the system detects the <Enter>
character, it ignores any further input from the device driver, even if the specified
number of characters has not yet been read. Then the characters read are copied from
the internal buffer to the buffer of the calling program. If characters are output in
cooked mode, DOS tests for <Ctrl><C> or <Ctrl><Break>. If one of these
combinations is detected, the currently running program stops. Pressing
<Ctrl><S> temporarily stops the program until the user presses any other key.
<Ctrl><P> redirects the output from the screen to the printer (PRN). Pressing
<Ctrl><P> a second time redirects the output from the printer back to the screen.
Raw mode
In raw mode, the device driver reads all characters without testing. If a program
wants to read in 10 characters, it reads exactly 10 characters, even if the user
presses the <Enter> key as the second character of the string. Raw mode transmits
the characters direct to the calling program's buffer, instead of using an internal
DOS buffer. During character output, raw mode doesn't test for <Ctrl><C> or
<Ctrl><Break>.

150
Abacus 6.12 DOS Device Drivers

DOS function 44H of interrupt 21H defmes the mode of the character device driver
(see the end of this section for a detailed description of this interrupt).

6.12.2 Block Device Drivers

A block device driver normally communicates with mass storage devices such as
floppy or hard disks, or high speed cassette tapes. For this reason, they
simultaneously transmit a number of characters which are designated as a block. In
some cases, a single call to a function transmits several blocks of data. The sizes
of these blocks can differ from one mass storage device to another, as well as
within one particular mass storage device.

How block device drivers work


Unlike character device drivers, a block device driver can control several devices at
the same time. You can even divide one device into several logical units. For
example, a 40 megabyte hard disk can be divided into two 20 megabyte hard disks
with the names C and D. These logical devices have single-letter specifiers instead
of device names or fllenames. The device designation depends on its position in the
chain of device drivers. If a device driver supports several logical devices, single
letters can be used as specifiers in sequential order. This is why the example above
lists two logical drives named C and D instead of C and F.

Everyone of these devices must have a flle allocation table (FAn and a root
directory. Block device drivers make no distinction between cooked and raw modes.
They always read and write the exact number of blocks unless an error is detected.

Access
There are several ways to access a device driver. Character device drivers are
accessed using the normal FCB or handle functions by simply indicating the name
of a driver (e.g., CON: instead of a fllename). A block device driver is accessed
using the normal DOS functions (flle, directory, etc.) by using the drive designator
assigned by DOS during the boot process.

Functions IH through CH of interrupt 21H invoke read and write operations in a


device driver. Two other options exist for accessing device drivers. These will be
discussed shortly.

6.12.3 Structure of a Device Driver

Even though the two types of device drivers differ in some important details, they
do have similar structures. Each has a device header, a strategy routine and an
interrupt routine (a different kind of interrupt from the ones you've read about up
until now).

151
6. The Disk Operating System PC System Programming

Device beader
The device header appears at the beginning of each device driver and contains
infonnation needed by OOS for implementing the driver.

The fIrst two fIelds are the link to the next driver (offset and segment address) in
the chain of device drivers. The memory locations required for these link fIelds
must be reserved by the programmer, but OOS fIlls in when the driver is installed
in the system. The next fIeld of the device header is the attribute word. The
attribute word describes the device driver and tells OOS, among other things, if it
is a block or character device driver.

\7000
RAM
OOH nff.... t- "nnr...... "f n .. "j- nriv.. r 11 worn)
+ 02H Seament address of next driver 11 word)
+ 04H Device attribute 11 word)
+ 06H
+ 08H
+OAH
Offset address of strateqv routine (1 word)
Offset address of interrupt routine (1 word)
Driver name from character driver (8 bytes)
or number of devices used by block driver
/F= "

Device driver header

152
Abacus 6.12 DOS Device Drivers

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 bit
1=Cl.IT9rlt sIa"ldard
I I I IXI IXIXIXIXIXIXIXI I I I I-+­ output delAce

I 1=a.rrent sta1dErd
il>Ut deIAce
... 1=a.rrent
dockdeW:e
. 1=OJrrent
NULdeW:e
. 1=Medum
cha1ge
mcognized
1=OOn-1BM
~ bmat
(I:.b:k diver)

1~lJl1i
i1s1n.Ja3d
L...-. (cf1crac.U <:tiwr)

1=1OCTL
support

. 0=I:lI0ck driI.er
1=Ghaa::U ctiwr

Structure ofthe device attribute

Only bits 11 through 15 are used by a block driver. The IOCTL bit tells DOS if
this driver supports the IOCTL function of DOS. The end of this chapter and the
descriptions of functions 3 and 12 describe this function in greater detail. Bit 11
fIrst appears in DOS Version 3 and should be 0 in earlier versions. A block driver
indicates whether a medium change is recognized on the device supported (e.g., a
floppy disk drive). If the bit is set, the driver must support a few additional
functions.

The next two fields contain the offset address of the strategy routine and interrupt
routine. The last field contains the name of the device driver if it is a character
device driver. If the name is less than eight characters in length, blank spaces
(ASCII code 32) pad the remaining characters. If it is a block device driver, the fIrst
byte of this field contains the number of logical devices supported by the driver.
The remaining seven bytes of this fIeld remain unused and contain the value O.

Strategy routine
DOS calls the strategy routine first to initialize the driver, then repeatedly before
each subsequent I/O request from the driver's interrupt routine. The address of a data

153
6. The Disk Operating System PC System Programming

structure which contains infonnation about the operation to be performed (the


request header) is passed by DOS to the strategy routine in register pair ES:BX.
The double word pointer to the data block is stored, and control immediately
returns to DOS. DOS then calls the interrupt routine of the driver to perfonn the
actual operation.

The request header, whose address is passed to the strategy routine, always contains
at least 13 bytes and contains information which tells the driver how to perfonn
the upcoming operation. Depending on the operations performed, further
information can be added to the end of the request header which differs depending on
the operation.

RAM
+ OOH Data block lenqth in bytes
+ 01H Device number in communication (1 mdl
(1 word) \ 0000:0000
+ 02H Command code (1 word)
+ OSH Reserved (8 bytes)
+ DDH Media descriptor (1 byte)
+ OEH Buffer offset address (1 word)
+
+
+
1DH
12H
14H
Buffer segment address
Number
Starting sector
(1
(1
(8
word)
word)
bytes) / "

Structure ofthe request header

DOS calls the interrupt routine immediately after calling the strategy routine. Its
first task is to save the processor registers that will have their contents changed by
the various functions of the driver to the stack. Then it obtains the command code
from field 3 of the request header and calls the appropriate command code routine.
After executing the routine, it fills in the status field of the request header and
restores the processor registers from the stack. As a last step it returns control to
the calling DOS function.

Status field

The value of the status field specifies whether the function executed without error,
or if an error occurred during execution. For this reason, every driver function must
set the DONE bit (bit 8) in the status field. This DONE bit must be set even if the
function is a dummy (non-perfonning) function.

154
Abacus 6.12 DOS Device Drivers

1=error
O=medium write
1=ready 1=unknown device
2=drive not ready
1=busy 3=unknown command
4=read (CRC-) error
5=parameter data block
has a false length
6=search error
7=unknown medium
8=sector not found
9=printer out of paper
10=write error
11 =read error
12=common error

Statusfield error codes

6.12.4 Device Driver Functions

Under DOS Version 2, any installable device driver must support 13 functions,
numbered from 0 to 12, even if their only action consists of setting the DONE
flag in the status word. DOS Versions 3 and 4 include four additional functions
which can be supported, but are not required. Some of these functions concern one
of the two driver types, while others apply to both driver types (e.g.,
initialization). Unused functions must at least set the DONE flag of the status
word. Let's look at the various functions in detail according to their function
numbers.

Request header

Every function described here receives its arguments from the request header (whose
address is passed by DOS to the strategy routine) and stores its "results" in the
request header. For this reason, the offset address to the arguments, relative to the
beginning of the request header, is passed to the specified function. These
arguments are later transferred to variables. Besides this offset address, a flag
indicates whether this information consists of a byte, word or PTR. The PfR data
type represents a pointer to a buffer and consists of two adjacent words. The fIrst
word is the offset address of the buffer. The second word is the segment address of
the buffer.

155
6. The Disk Operating System PC System Programming

Function 0: Driver Initialization

OOS calls this function during the system boot procedure to initialize the device
driver. This function can involve hardware initialization, setting various internal
variables to their default values, or the redirection of interrupts. Since the entire
operating system has not been completely initialized at this point, the
initialization routine can only call functions 1 through OCH and 30H of OOS
interrupt 21H. These functions can be used to determine the OOS version number
and to display a driver identification message on the screen. Even if the newly
linked driver is a CON driver, the output to the display occurs through the old
CON driver, because there are no new drivers linked into the system after
completion of the initialization routine.

Initialization and the request header


The initialization routine can obtain two pieces of information from the request
header. The first item is the memory address containing the text following the
equal sign on the line in the CONFIG.SYS file that loaded the driver into the
system.

A typical line in a CONFIG.syS file can look like this:


DEVICE=ANSI.SYS

In this case, the device name is ANSI.SYS, which assigns the standard ANSI
escape sequences for screen control to the PC. The memory address passed to the
initialization routine points to the character following the equal sign (in this case,
the A of ANSI.SYS). This makes it possible to store additional information
following the name of the device driver. This information is ignored by OOS, but
can be read by other routines.

Logical device designation


The second item is only available under OOS Version 3.0 and higher, and only if
the driver is a block device driver. This is the letter designation of the frrst logical
device of the driver. The value 0 stands for A, 1 for B, 2 for C, and so on.

The initialization routine must retlD1l four parameters to the calling OOS function.
The first parameter is the status of the function, i.e., the indication of whether the
function has executed correctly. For a block device driver, the number of logical
devices supported must also be passed. This information could also be obtained
from the device driver's header, but is ignored by OOS.

156
Abacus 6.12 DOS Device Drivers

The next parameter that the device driver must pass to OOS is the highest memory
address which it occupies or uses. This lets DOS know where the next device
driver can be installed.

BPB

If the driver is a block device driver, the last argument passed must be the address
of an array which contains an entry for every logical device. This array contains the
addresses of BIOS parameter blocks (BPBs). The address is passed as two words,
the fIrst word contains the offset, and the second word contains the segment address
of the array. The fIrst two words within this table are the address for the fIrst
logical device supported. The next two words indicate the address for the second
logical device, etc. The BPB, described in detail in Section 6.12, is a data block
containing information which describes a logical device. If all or some of the
logical devices have the same format, all entries in the BPB address table can point
to a single BPB.

+ OOR Bytes per sector (1 word)


+ 02R Sectors per cluster (1 byte)
+ 03R Reserved sectors (including boot sectors) (1 word)
+ 05R Number of FATs (1 byte)
+ 06R Maximum number of entries in root directory (1 word)
+ OaR Total number of sectors (1 word)
+ OAR Media descriptor (1 byte)
+ OBH Number of sectors per FAT (1 word)

BIOS Parameter Block design

FaR = hard disk

F9R = 5.25" diskette, double-sided, 15 sectors per track

FeR = 5.25" diskette, single-sided, 9 sectors per track


FDR = 5.25" diskette, double-sided, 9 sectors per track
FER = 5.25" diskette, single-sided, a sectors per track

FFR = 5.25" diskette, double-sided, 8 sectors per track

Media descriptor byte

157
6. The Disk Operating System PC System Programming

Calling parameters of function 0:


Offset 2 (byte) Function number (0)
Offset 18 (ptr) Address of character that follows the equal sign after the
DEVICE command in the CONFIG.SYS file
Offset 22 (byte) Device number of the first device supported by the driver
(O=A, 1=B...) (applies to block device drivers from DOS
Version 3.0 UP only)

Returned parameters of function 0:


Offset 3 (word) Status word
Offset 13 (byte) Number of devices supported (block devices only)
Offset 14 (ptr) Address of first available memory location following the
driver
Offset 18 (ptr) Address of array containing the addresses of BPB (block
devices only)

Function 1: Media Check

This function is used only with a block device driver. A character device driver
should merely set the DONE flag of the status word and exit. This function is used
by DOS to determine whether the media (diskette) has changed. It is often used
when examining a disk directory. If the disk medium was not changed since the
last access, DOS still has this information in memory, otherwise DOS must reread
the information from the media which delays the execution of the current task.

In some cases, as with floppy disks, the answer to the question is fairly
complicated. For this reason DOS permits function 1 to answer not only with
"yes" and "no", but also with "don't know." In any case, the answer affects further
DOS activity.

If the media is unchanged, access to the media can take place immediately. If the
media was changed, however, DOS closes all internal buffers related to the current
logical device. This causes the loss of all data which should have been transmitted
to the media. Then it calls function 2 of the current device driver, loads the FAT
and the root directory. If the media check function answers with "don't know," the
additional steps taken by DOS depend on the status of the internal buffers related to
the current logical device. If these internal buffers are empty, DOS assumes that
the media was changed and acts as if function 1 answered "yes." If the buffers
contain data which should have been transmitted to the media, DOS assumes that
the media is intact and writes the data. If the media was indeed changed, the data
written to a changed media may damage the new diskette's file structure.

Since subsequent processing depends on the response from the media check
function, the driver should handle the response carefully. Before enabling the
mechanism used by the function to respond, the function examines the parameters
passed to it. If the driver supports several logical devices, the first parameter is the

158
Abacus 6.12 DOS Device Drivers

number of devices. Next is a media descriptor code. This code contains information
about the type of media last used in the current logical device. Only devices which
can handle several different formats can use this task. For example, AT disk drives
which can use both 360K and 1.2 megabyte diskette formats.

If the media check function determines that the medium in a device is non­
removable (e.g., a fixed disk), it can always respond "not changed". If, on the other
hand the device media can be changed (e.g., a disk), the correct response can only
be determined by fairly complex procedures. If these procedures are not used, the
response should be "don't know".

For the sake of completeness, here are the three procedures which provide fairly
accurate results.

Since a device with changeable media has an opening and closing mechanism, the
function should check to determine whether the media was removed. However, it
cannot determine if the removed media is identical to the newly inserted medium.

If the media has a name, the function should read this name to determine whether
the media was changed. This procedure only makes sense if every media has a
unique name.

The disk drive procedure used by DOS hinges on the fact that changing medium
takes some time. DOS assumes that even a user that can move fast needs about
two seconds to remove a diskette from a drive and insert a new diskette in the same
drive. If two consecutive diskette accesses occur less than two seconds apart, DOS
assumes that no diskette change occurred.

A byte in the data block is used to indicate changes. The value -1 (FFH) means
"changed", 0 means "don't know" and 1 means "not changed".

If the media was changed, the device driver signals a media change (bit 11 in the
device attribute = 1), the address of a buffer must be passed to DOS Version 3 and
newer, which contains the volume name of the previous media. This name must
be stored there as an ASCII string and terminated with an end character (ASCII
code 0).

Calling tarameters of function 1:


Offset 1 [byte) Device number
Offset 2 [byte) Function number (1)
Offset 13 (byte) Media descriptor byte

159
6. The Disk Operating System PC System Programming

Returned parameters of function 1:

Offset 3 (word) Status word

Offset 14 (byte) Was media changed ?

FFH =yes OOH =don't know 01H =no


Offset 15 (ptr) Address of buffer containing the previous volume name
J onlY if device indicates a media chan~e)

Function 2: Build BIOS Parameter Block (BPB)

This function is used only by block device drivers. A character device driver should
just set the DONE flag of the status word and exit. DOS calls this function when
the media check function determines that the media was changed. This function
returns a pointer to a new BPB for the media

As you can see by the layout of the calling parameters. the device number media
descriptor and a pointer to a buffer are passed to this function by DOS. If the
device is a standard format (bit 13 of the device attribute =0). then the buffer
contains the first sector of the FAT.

Calling parameters of function 2:


Offset 1 byte Device number
Offset 2 byte Function numberJ.~
Offset 3 ~yte Media descrit!tor ~
Offset 14 (ptr) Address of a buffer containiJlKthe FATJsee abov~

Returned~eters of function 2:
Offset 3Jwor<D J Status word
Offset 18 (pte) I Address of the BPB of addressed device

Function 3: I/O Control Read

This function passes control information from the character or block device driver
to the application program. It can only be called through function 44H of interrupt
21H if the IOCTL bit in the device attribute word in the device driver header is set
Different parameters are passed to the function. depending on whether the driver is
a c.JaraCter or a block device driver.

A character device driver is passed the number of characters to be transferred and the
address of a buffer for the transfer of the data

A block device driver is passed the device number, the media descriptor byte, the
address of the buffer to be used for the data transfer, the pointer to the flTSt sector to
be read and the number of sectors to be read.

160
Abacus 6.12 DOS Device Drivers

Calling llarameters of function 3:

Offset 1 [byte) Device number (block devices onlYL

Offset 2 [byte) Function number (3)

Offset 13 (byte) Media descriptor byte (block devices only)

Offset 14 (ptr) Address of buffer into which data should be transmitted

Offset 18 (word) Number of sectors to be read (block device) or

Number of characters to be read (character device)


Offset 20 (word) First sector to be read (block devices only)

Returned parameters of function 3:

Offset 3 (word) Status word

Offset 18 (word) Number of sectors read (block device)

Number of chara:ters read (character device)

Function 4: Read

This function reads data from the device to a buffer specified in the calling
parameter. Should an error occur reading the data. the error status must be set.
Additionally the function must report the number of sectors or bytes read
successfully. Simply reporting an error is not good enough.

Calling parameters of function 4:

Offset 1 (byte) Device number (block device only)

Offset 2 (byte) Function number (4)

Offset 13 (byte) Media descriptor byte (block device only)

Offset 14 (ptr) Address of buffer to which data should be read

Offset 18 (word) Number of sectors to be read (block device) or

Number of characters to be read (character device)


Offset 20 (word) First sector to be read (block device only)

Returned parameters of function 4:


Offset 3 (word) Status word
Offset 18 (word) Number of sectors read (block device) or
Number of characters read (character deVice)
Offset 22 (ptr) Pointer to volume ID on return of error OFH (Version 3.0
and higher)

Function 5: Non-destructive Read

This function is used by a character device driver to test for unread characters in the
input buffer. A block device should set the DONE flag of the status word and exit.

DOS tests for additional characters using this function. If more characters exist, the
busy bit must be cleared (set to 0) and the next character passed to DOS. The
character that is passed remains in the buffer so that a subsequent call to a read

161
6. The Disk Operating System PC System Programming

function will return this same character. If no additional characters exist, the busy
bit must be set (set to 1).

Function number 5

Returned...Jllll1!!!!eters of function 5:

Offset 3-.iword) I Status word

Offset 13 !byte) I Thecharocterread

Function 6: Input Status

This function is used to determine if a character is waiting to be read from the


input buffer of a character device. A block device driver should set the DONE flag
of the status word and exit.

If a character is waiting to be read from the input buffer, the busy bit is cleared (set
to 0). If a character is not in the input buffer, the busy bit is set (set to 1).

When a character is waiting to be read, the Input Status function (06H) resets the
status word busy bit to 0 and returns the character to DOS. The character is not
removed from the buffer and is therefore non-destructive. This function is
equivalent to a one-character look ahead.

Function number 6

Returned~ameters of function 6:
J
Offset 3 (word) Status word: Characters already in buffer =0; Read request to
l.J?l.!Ysical device = 1

Function 7: Flush Input Buffers

This function clears the internal input buffers of a character device driver. Any
characters read but not yet passed to DOS are lost when this function is used. A
block device driver should set the DONE flag of the status word and exit

Function number 7

162
Abacus 6.12 DOS Device Drivers

Function 8: Write

This function transfers characters from a buffer to the current device. If an error
occurs during transmission, the status word is used to indicate this error. Both
block and character devices use this function.

The parameters used for this function depend on whether the driver is for a character
or block device. Both pass a buffer address from which a certain number of
characters should be transferred. A character device driver is passed the number of
bytes to be transferred in addition to this information.

A block driver is passed the number of sectors to transfer (not the number of
characters), the number of the device to be addressed, its media descriptor and the
address of the first sector on the medium.

Should an error occur writing the data, the error status must be set. Additionally
the function must report the number of sectors or bytes written successfully.
Simply reporting an error is not good enough.

Calling parameters of function 8:

Offset I (byte) Device number (block drivers only)

Offset 2 (byte) Function number (8)

Offset 13 (byte) Media descriptor of device addressed (block device onlyl

Offset 14 {J>tt:) Address of the buffer containing data

Offset 18 (word) Number of sectors to be written (block device)

Number of characters to be written (character device)


Offset 20 (word) first sector to be written (block device only)

Returned parameters of function 8:

Offset 3 (word) status word

Offset 18 (word) Number of sectors written (block device)

Number of characters written (character device)


Offset 22 (ptr) Pointer to volume ID on return of error OFH (Version 3.0
up)

Function 9: Write with Verify

This function is similar to function 8, but with the difference that the characters
written are reread and verified.

Some devices, especially character devices such as a monitor or a printer, do not


require verification since either no errors occur during transmission (monitor) or
the data cannot be verified (printer).

163
6. The Disk Operating System PC System Programming

Calling parameters of function 9:

Offset 1 (byte) Device numberJQlock drivers only)

Offset 2 (byte) Function number (9)

Offset 13 (byte) Media descriptor of device addressed (block device only)

Offset 14 (ptr) Address of the buffer containing data

Offset 18 (word) Number of sectors to be written (block device)

Number of characters to be written (character device)


Offset 20 (word) First sector to be written (block device only)

Returned parameters of function 9:

Offset 3 (word) Status word

Offset 18 (word) Number of sectors written (block device)

Number of characters written (character device)


Offset 22 (ptr) Pointer to volume ID on return of error OFH (Version 3.0
up)

Function 10: Output Status

This function indicates whether the last write operation to a character device is
completed or not A block device should set the DONE flag in the status word and
exit.

If the last write operation is complete then the busy bit of the status word is
cleared; otherwise the busy bit is set to 1.

Function number 10

I
Returned ~eter of function 10:
Offset 3 (word) Status word: The busy bit is 1 if the last character output
has not been completed

Function 11: Flush Output Buffers

This function completely clears the output buffer even if it contains characters
waiting for output. A block device should set the DONE flag on the status word
and exit.

Function number 11

164
Abacus 6.12 DOS Device Drivers

Function 12: I/O Control Write

This function passes control infonnation from the application program to the
character or block device driver. It can only be called through function 44H of
interrupt 21H provided the IOC1L bit in the device attribute word in the device
driver header is set. Different parameters are passed to the function, depending on
whether the driver is a character or a block device driver.

A character device driver is passed the number of characters to be written and the
address of the buffer from which these characters are transferred.

A block device driver is passed the device number (in case the driver services
logical devices), the media descriptor byte, the address of the buffer from which the
data is to be written, the number of the ftrst sector to be written and the number of
sectors to be written.

A character device driver returns the number of bytes written. A block device driver
returns the number of sectors written.

Calling parameters of function 12:

Offset 1 (byte) Device number @ock device only)

Offset 2 (byte) Function number (121

Offset 13 (byte) Media descriptor of addressed device (l)lock device only)

Offset 14 (ptr) Address of buffer from which data should be read

Offset 18 (word) Number of sectors to be written (block device)

Number of characters to be written (character device)


Offset 20 (word) First sector to be written (block device only)

Returned parameters of function 12:


Offset 3 (word) Status word
Offset 18 (word) Number of sectors written (block device)
Number of characm written (character device)

The following four functions are supported by OOS version 3.0 and higher.

Function 13: Open

This function can be used only if the OCR (Open/Close/RM) bit in the device
attribute word in the device driver header is set Its task differs, depending whether
it is a character or block driver.

A block driver uses this function every time a ftle is opened. This function
detennines how many open fIles exist on this device. Use this command carefully,
since programs which access FCB function calls tend not to close open ftles. This
problem can be avoided by assuming during every media change that no ftles

us
6. The Disk Operating System PC System Programming

remain open. For devices with non-changeable media (e.g., a hard disk) even this
procedure may not help.

Within a character driver, this function can send an initialization string to the
device before transmitting the data. This is an advantage when used for
communication with the printer. The initialization string should not be included in
the driver, but can be called, for example, with the IOCTL function of interrupt
21H, which calls function 12 of a driver to transmit it from an application
program to the driver. The function can also be useful because it can prevent two
processes (in a network or in multiprocessing) from both accessing the same
device.

For the devices CON, PRN and AUX, this function is not called since they are
always open.

IReturned :r I
Offset 3 (word
eter of function 13:
Status word

Function 14: Device Close

This function is the opposite of function 13. This function can only be addressed if
the OCR bit in the device attribute word of the device driver header is set Its task
differs, depending whether it is a character or block driver.

A block driver calls it after closing a file. This can be used to decrement a count of
open files. Once all files on a device are closed the driver should flush the buffers
on removable media devices, because it is likely that the user is about to remove
the media.

A character driver can use this function to send some closing control information
to a device after completing output. For a printer this could be a formfeed. As in
function 13, the string could be transmitted from an application program using the
IOC1L function.

CallillKparameters of function 14:

Offset 1 (bvte) ] Device number (block device only)

Offset 2 (byte) I Function number (14)

IReturned :r eter of function 14:


Offset 3 (word I Status word

166
Abacus 6.12 DOS Device Drivers

Function 15: Removable Media

This function indicates if the media in a block device can be changed or not. This
function is used only if the OCR bit in the device attribute word of the device
driver is set. A character device driver should set the DONE flag in the status word
and exit.

If the media can be removed, the busy bit is cleared; otherwise it is set to 1.

Calling parameters of function 15:

Offset 1 (byte) I Device number O>Iock device only)

Offset 2 (byte) I Function number (15)

I
Returned parameter of function 15:
Offset 3 (word) Status word: If the media can be removed, the busy bit must
contain the value 0

Function 16: Output until Busy

This function transfers data from a buffer to an output device until the device is
busy (i.e., can no longer accept more characters). As this function is supported by
character devices, a block device driver should set the DONE flag on the status
word and exit.

This function works particularly well with print spoolers, through which files can
be sent to a printer as a background activity while a program executes in the
foreground. It is possible that not all of the characters in the transfer request will
be sent to a device during this function call. This is usually not an error, it could
be the result of the device becoming busy. The function is passed the number of
characters to be transmitted as well as the buffer address. If the output device
indicates during transmission that it can no longer accept additional characters, it
indicates the number of characters successfully transferred and returns control to the
device driver.

Calling parameters of function 16:

Offset 2 (bvte) Function number (16)

Offset 14 (ptr) Address of buffer from which data should be read

Offset 18 (word) Number of characters to be read

167
6. The Disk Operating System PC System Programming

Returned parameters of function 16:

Offset 3 (word) I Status word

Offset 18 (word) I Number of characters written

6.12.5 Clock Driver

The clock driver is a character device driver whose only function is to pass the date
and time from DOS to an application. The clock driver can also have a different
name, since DOS identifies it by the fact that bit 2 in the device attribute word of
the device driver header is set to I, instead of by name. Bit 15 must also be set
since the clock driver is a character device driver. Functions 2AR to 2DH of DOS
interrupt 21H read the date and time and call the driver. A clock driver must
support only functions 4, 8 and 0 (initialization). During the call of function 4
(reading), the date and time pass from the driver to DOS. DOS can set a new date
and time with function 8. Both functions have the time and date passed in a buffer
of 6 bytes in length.

+ OOH Number of days since Jan.1,1980 (1 word)


\ 00::'000
+ 02H Minutes (1 byte)
+ 03H Hour (1 byte)
+ 04H

+ OSH
Hundredths of seconds
Seconds
(1 byte)
(1 byte) V r

Passing date and time to a clock driver

The date format is unusual. Instead of passing the month, day and year separately,
DOS passes the number of days elapsed since January I, 1980 as a 16-bit number.
A fairly complex formula converts this number into normal date format, taking
leap years into account. The clock driver normally uses function 0 and 1 of the
BIOS interrupt IAH to read and set the time.

Clocks on AT models
AT and AT-compatible computers have a battery powered realtime clock.
Functions 0 and 1 of interrupt lAR use a software controlled time counter and not
the battery powered realtime clock. When the computer is rebooted, the date and
time previously set with driver function 8 is cleared. You can use the clock driver
to access the realtime clock using functions 2 and 5 of interrupt lAH instead of
function 0 and 1.

168
Abacus 6.12 DOS Device Drivers

6.12.6 Device Driver Calls from DOS

Now that you have some familiarity with the functions of the different device
drivers, you can look toward developing your own personal device driver. Here are
the steps which take place before and after calling a device driver function.

A chain of events begins when a DOS function which handles input and output is
called using interrupt 21H. Calling one of these functions can in turn call a series
of other functions and corresponding read and write operations.

Open
One example of this is when the Open function 3DH is called to open a file in a
subdirectory. First of all, before it can be opened, DOS must find the file. This
may require the searching of a set of directories instead of just reading in the FAT.
During each access of interrupt 21H, DOS determines which of the available device
drivers should be used to read or write characters. When this happens, DOS sets
aside an area in memory to store the information required by the device driver.

For files, DOS must convert the number of records to be processed into logical
sector numbers. DOS then calls the strategy routine of the device driver, to which
it passes the address of the newly created data block (request header). Then the
interrupt routine of the driver is called, which stores all registers. It isolates the
function code of the requested function from the data block and starts to process the
function.

If the addressed driver is a character device driver, the function only has to send the
characters to the hardware or request the characters to be read.

Block devices
For a block device (e.g., a mass storage device such as a floppy or hard disk) the
logical sector number must be converted into a physical address before a read or
write access. The logical sector number is broken down into a head, track and
physical sector number.

Mter the read or write operation ends, the driver function must place a result code
in the status field of the request header to be returned to the calling DOS function.
Next the contents of all registers are restored and control is returned to the calling
DOS function, which, depending on the result of the driver function, sets or resets
the carry flag and places any error code into the AX register. The interrupt function
then returns control to the routine which called interrupt 21H.

169
6. The Disk Operating System PC System Programming

6.12.7 Direct Device Driver Access: IOCTL

Here we discuss IOCTL in detail, since it offers an alternate method of


communicating with the device driver. You can only use these functions if the
IOCTL bit of the device attribute is set

The IOCTL function itself is one of many functions addressable from DOS
interrupt 21H. Its function number is 44H. Three groups of sub-functions are
accessible:

Device configwation

Data transmission

Driver status

The number of the desired sub-function is passed to the IOCTL function in the AI..
register. After the function call, the carry flag indicates whether the function
executed correctly. A set carry flag indicates the occurrence of an error and the error
code can be found in the AX register.

Character device driver status


The number of the desired sub-function is passed to the IOCTL function in the AI..
register. After the function call, the carry flag indicates whether the function
executed correctly. A set carry flag indicates the occurrence of an error and the error
code can be found in the AX register.

Sub-functions 6 and 7 can determine the status of a character device driver. Sub­
function 6 can determine if the device is able to receive data. Sub-function 7 can
determine if the device can send data. The handle of this device is passed in the BX
register.

If the device is ready, both functions 6 and 7 return the value FFH in the AI..
register.

Sub-function 2 reads control data from the character device driver. The handle is
passed in the BX register and the number of bytes to be read is passed in the CX
register. In addition, the DS:DX register pair contain the address of the buffer into
which the data will be read. If the carry flag is clear, then the function was
successful and the AX register contains the number of characters read. If the carry
flag is set, then there was an error and the AX register contains the error code.

Sub-function 3 writes control information from a buffer to the character device


driver. Again, the handle is passed in the BX register, the number of bytes to be
written in the CX register and the address of the buffer in the DS:DX register pair.

170
Abacus 6.12 DOS Device Drivers

The return codes are the same as for sub-function 2. These two sub-functions are
used to pass information between the application program and the device driver.
Block device driver status
Sub-functions 4 and 5 have the same task as sub-functions 2 and 3. However, they
are used for block devices and not character devices. Instead of passing the handle in
register BX, you pass the drive code (O=A, I=B, etc.) in the BL register.

Sub-function 0 is used to get device information for a specified handle. The sub­
function nwnber is passed in the AL register and the handle in the BX register. The
function returns the device information word in the DX register.
For block devices:

bits 8-15 = reserved

bit 7 = oif a block device

bit 6 = oif file has been written

1 if file has not been written

bits 0-5 = drive code (O=A, B=I, etc.)

For character devices:

bit 15 = reserved

bit 14 = 1 if device supports IOCTL sub-functions

oif device does not support IOCTL sub-


functions

bits 8-13 = reserved

bit 7 = if a cllarocter device

bit 6 oif end of file for input device

bit 5 = oif cooked mode

1 if raw mode

bit 4 reserved

bit 3 = I if clock device

bit 2 = 1 if NUL device

bit 1 = 1 if standard output device

bit 0 = I if standard input device

Cooked and raw modes


Sub-function 1 is used to set device information for a specified handle. This sub­
function is often used to set the standard input device from cooked mode to raw
mode or back.

Two final interrupts are sometimes used by block device drivers. These two
interrupts, 25H and 26H are used to read from and write to the disk drive. You can
use these interrupts, for example, to process disks that were formatted using a
"foreign" operating system.

171
6. The Disk Operating System PC System Programming

The device number is passed in the AL register, the number of sectors to be


transferred is passed in the ex register, the starting sector number to be transferred
is passed in the DX register and the buffer is passed in the DS:BX register. The
carry flag is clear if there was no errors. If the carry flag is set, then the error code
is returned in the AX register.

6.12.8 Tips on Developing Device Drivers

Major headaches in developing a device driver occur because of problems that arise
during the testing phases of a new driver. First, a device driver must load into a
memory location assigned to it by DOS, at an address unknown to the
programmer. Second, a newly developed eON driver can't be tested using the
DEBUG program, since DEBUG uses this driver for character input and output.

We recommend that after you write the actual driver, you write a short test
program that calls the individual functions in the same manner as DOS, but
without having the driver installed as part of DOS. The advantages to this are that
everything executes under user control, and the whole process can be corrected with
a debugger. In any case, a new device driver (especially a block device driver)
should only be linked into the system after it has been tested completely and has
been proven to be error-free.

Note: When working with a hard disk, prepare a floppy system diskette
before test booting the system from the hard disk with the new driver
installed for the first time. If a small bug should exist in the new
driver, and the initialization routine hangs up, the booting process
will not end and DOS will be out of control. In such a case, the only
remedy is to reset the system and boot with a DOS diskette in the
floppy drive. Once DOS loads, you can then access the hard disk and
remove the new driver.

6.12.9 Driver Examples

This section contains a sample device driver for each of the three different types of
device drivers, to demonstrate the information you've read about so far.

The first program is a character driver which corresponds exactly to the format of a
normal console driver. The second program is a block device driver which creates a
160K RAM disk. The final program is a DOS clock driver to support an AT
computer realtime clock.
***********************************************.****** ****··*********i
CONDRV *;
*-------------------------------------------------------------------*;
Task : This program represents a nonnal Console *;
Driver (Keyboard and Display Monitor). It should *;
serve as a framework for a driver in the form of *i
an ANSI.SYS driver. *;

172
Abacus 6.12 DOS Device Drivers

i*-------------------------------------------------------------------*i
;* Author MICHAEL TISCHER *;
;* developed on 8.4.87 *;
,. * last Update 9.2l.B7 *.
:*-------------------------------------------------------------------*;
;* assembly MASM CONDRV; *;
,. * LINK CONDRV; *.,
;* EXE2BIN CONDRV CONDRV.SYS *;
;*-------------------------------------------------------------------*:
;* c..ll Copy into Root Directory, copy the comnand *;
;* DEVICE-CONDRV.SYS into the file CONFIG.SYS *;
, and then boot t he System. *;
i···**********************************************···· ****************:

code segment

assume cs:code,ds:code,es:code,ss:cocte

org 0 ;Program has no PSP therefore start


;at Offset address 0

;== Constants ====_=========~_===================_======== __e._=_===_=­


cmd_fld equ 2 ; Offset command field in data block
status equ 3 ; Offset status field in data block
end_adr equ 14 ; Offset driver end-adr. in data block
num db equ 18 ; Offset number in data block
b adr equ 14 ; Offset buffer address in data block

KEY SZ equ 20 ;Size of key board buffer


num-cmd equ 16 ;Subfunctions 0-16 are supported

;-- Header of Device Driver ------------------------------------------­


dw -1,-1 ; Connect ion to next driver
dw l010100000000011b ;Driver attribute
dw offset strat ;Pointer to strategy routine
dw offset intr :Pointer to interrupt routine
db "CONDRV ;new Console driver

Jump Table for functions ------------------------­

fkt tab dw offset init ;Function 0: Initialization


dw offset dummy ;Function 1: Media Check
dw offset dummy ; Function 2: Create BPB
dw offset no_sup ; Function 3: I/O control read
dw offset read ;Function 4: Read
dw offset read b ; Function 5: Non-dest. Read
dw offset dummy ;Function 6: Input-Status
dw offset del in b :Function 7: Erase Input-Buffer
dw offset write :Function B: Write
dw offset write iFunction 9: Write , Verify
dw offset dummy ; Function 10: Output-Stat us
dw offset dummy ; Function 11: Erase Output-Buffer
dw offset no sup ; Function 12: I/O control write
dw offset duIDmY :Function 13: Open (starting at 3.0)
dw offset dwmry ; Funct ion 14: Close
dw offset dummy ; Function 15: changeable Medium
dw offset write ; Function 16: Out put unt 11 Busy

db_ptr dw (?) , (?) ;Address of data block passed

key_a dw 0 iPointer to next character in KEY SZ


key_e dw 0 ;Pointer to last character in KEY-SZ
key _bu db KEY SZ dup (1) ;internal Keyboard Buffer

;~= Routines and functions of driver ==_s==~=================_m=====

173
6. The Disk Operating System PC System Programming

strat proc far :Strategy routine

mov cS:dbytr,bx :Store address of data block in the


mov cs:db_ptr+2,es ;Variable DB_PTR

ret ;back to caller

strat endp

;---------------------------------------------------------------------­
intr proc far ;Interrupt routine

push ax ;Store registers on the stack


push bx
push ex
push dx
push di
push si
push bp
push ds
push es
pushf ;store also the flag register

push cs ;Set data segment register


pop ds ;Code is identical here with data

les di,dword ptr db-ptr;Address of data block to ES:DI


mov bl,es:[di+cmd fld! ;Get command-code
cmp bl,num cmd - ;is command-code permitted?
jle bc ok - ;YES --> bc_ok

mov aX,8003h iCode for "unknown Corranand"


jmp short intr end ;back to caller

;-- Command-Code was o.k. --> Execute command ---------------­

shl bl,l :Calculate pointer in jump table


xor bh,bh ierase BH
call [fkt_tab+bxl ;Call function
les di,dword ptr db_ptr;Address of the data block to ES:DI

:-- Execution of the function completed -------------------­

intr end label near


or aX,OlOOh ; Set finished-bit
·mov es:[di+statusj,ax ;store everything in the status field

popf ;Restore flag register


pop es ;Restore other registers
pop ds
pop bp
pop si
pop di
pop dx
pop cx
pop bx
pop ax

ret ;back to caller

intr endp

i---------------------------------------------------------------------­
dummy proc near ;This routine does nothing

xor ax,ax :Erase busy-bit

ret :back to caller

174
Abacus 6.12 DOS Device Drivers

durmny endp

;---------------------------------------------------------------------­
no_sup proc near ;This routine called for all functions
;which should really not be called
mov aX,B003h ;Error: Command not recognized
ret ;back to caller

;---------------------------------------------------------------------­

store_c proe near ;stores a character in the internal


;keyboard buffer
;Input: AL = character
BX = Position of the character

mov [bx+key_buJ,al ;store character in internal buffer


inc bl ;increment pointer to End
cmp bl,KEY_SZ ;End of buffer reached
jne store e ;NO --> STORE_E

xor bl,bl ;new end is the beginning of buffer

store e: ret ; back to caller

store c endp

;--------_._-----------------------------------------------------------­
read proe near :read a certain number of characters
;from the keyboard to a buffer

mov cx,es:[di+num_db] ;read number of characters


jcxz read e ;test if equal to 0
les di,es: [di+b_adrJ ;Address of character buffer to ES:DI
cld Ion STOSB count up
mov si, key a ;Pointer to next character in KEY S2
mov bx,kei::e ;Pointer to last character in KEY S2

read_I: cmp si,bx ;other characters in keyboard buffer?


jne read 3 ;YES --> READ_3

read_2: xor ah, ah ;Function number for reading is 0


int 16h ;Call BrOS Keyboard-interrupt
call store c ;Store characters in internal buffer
cmp al,O
- ;test if extended code
jne read 3 ; no --> READ_3

mov al,ah ;Extended Code is in AH


call store c ;store
read_3: mov al, [si+key_buJ ;read character from keyboard buffer
stosb ;transmit to buffer of calling funct.
inc si ;Incrernent pointer to next character
cmp si,KEY - SZ ;End of buffer reached?
jne read_4 ;NO --> READ_ 4

xor 8i,si ;next character is the first character


;in the keyboard buffer

read_4: loop read_l irepeat until all characters read


mov key_a, si ;Store position of tr.e next character
;in the key board buffer
mov byte ptr key_e,bl ;Store position of the last character
:in the key board buffer

read e: xor ax,ax ;everything o.k.


ret ;back to caller

175
6. The Disk Operating System PC System Programming

read endp

; ----------'-----------------------------------------------------------­

read b proc near ;read the next character from the


;key board but leave in the buffer

mov ah,l ;Function number for BIOS-interrupt


int 16h ;call BIOS Keyboard-interrupt
je ready1 ;no character present --> READ_PI

mov es: [di+13] ,al ;store character in data block


xor ax,ax ;everything o.k.
ret ;back to caller

ready1 label near

mov ax,OlOOh ; Set busy-bit (no character)


ret ;back to caller

read b endp

;---------------------------------------------------------------------­
del in b proc near ierase input buffer
-
mov ah,1
;still characters in the buffer?
int 16h
;Call BIOS key board interrupt
je del e
;no character in the buffer --> END

xor ah,ah :Remove character from buffer


int l6h ;Call BIOS key board interrupt
jmp short del in_b ;Test for additional characters
-
del_e: xor ax,ax ;everything o.k.
ret ;back to caller

del in_b endp


-
;---------------------------------------------------------------------­

write proc near ;write a specified number of


;characters on the display screen

mov cx,es:[di+num db] ;Number of characters read


jcxz write e ­ ;test if equal to 0
Id£ si,es:[di+b_adr] ;Address of character-buffer to OS:SI
cld ion LOOSB increment count

mov ah,3 ;read current display page


int l6h ;Call BIOS Video-interrupt

mov ah,14 ;Function number for BIOS interrupt

write_1: lodsb ;read character to be output to AL


int IOh ;call BIOS Video-interrupt
loop write ; repeat until all characters output

write e: xor ax,ax ; everything o.k~


ret ; ba ck to ca ller

write endp

;---------------------------------------------------------------------­
init proc near ;Initialization routine

mov word ptr es:[di+end adr] ,offset init ;Set End-Address of


mov es:[di+end_adr+2J,cs ;the driver

176
Abacus 6.12 DOS Device Drivers

xor ax,ax ; everything o.k.


ret ;back to caller
init endp

;=====---===-==-======--=~=============--==---=-----===-==============

code ends
end

The header of this driver describes a character device driver which handles both the
standard input device (keyboard) and the standard output device (monitor). Mter
linking it into the system, setting the two bits in the device attribute calls this
driver on all function calls previously handled by the CON driver. Like any other
driver, this driver has a strategy routine and an interrupt routine. The former stores
the address of the datablock in the variable DB_PI'R.

The interrupt routine saves the contents of all registers which will be changed by it
on the stack and gets the routine number to be called from the data block. It then
checks whether CONDRY supports this function. If not, it jumps directly to the
end of the interrupt routine and sets the proper error code in the status field of the
request header which was passed to the routine. Then it restores the registers which
were saved on the stack and returns control to the calling DOS function.

For any of the functions that are supported by the device driver, the offset address
of a routine to handle a particular function is determined from the table labeled
FK.T_TAB. Notice that the routines named DUMMY and NO_SUP appear several
times. DUMMY is for all functions which apply only to block device drives and
therefore are not used in this driver. The DUMMY routine clears the AX register
and sets the BUSY bit in the status word. The NO_SUP routine handles any
functions which cannot be used since the drive attribute for CONDRY does not
support these functions.

The STORE_C routine can be accessed from the lower level routines in this driver.
Its purpose is to store a character in the internal keyboard buffer of the driver. The
driver really shouldn't have this buffer available since BIOS (whose functions are
used by the driver to read characters from the keyboard) also has such a buffer. The
problem is that the BIOS always returns two characters when pressing a key with
extended codes (cursor keys, function keys etc.). If the higher level functions of
DOS only ask for one character at a time from CONDRY, the second character
must not be lost. It should be stored in a buffer and delivered to DOS by the read
function on the next call. This is STORE_C's task.

Reading characters

The next routine is the READ function. It obtains the number of characters to be
read from the request header passed by DOS. If it is 0, the routine is terminated
immediately. If not, then a loop starts which executes once for every character read
It rrrst tests for characters still stored in the internal keyboard buffer. If so, a
character is passed to the buffer of the calling function. If no additional character

177
6. The Disk Operating System PC System Programming

exists in the keyboard buffer, function 0 of the BIOS keyboard interrupt 16H
inputs a character from the keyboard. This character is also passed to the internal
keyboard buffer. If it's an extended keycode, it is divided into two characters. The
next step removes a character from the internal keyboard buffer and passes the
character to the buffer of the calling function. The process repeats until all
characters requested have been passed to DOS. Then the routine ends.

The higher level DOS functions also call the function named READ_P. It tests
whether a character was entered from the keyboard. If not, it sets the BUSY bit in
the status field of the request header passed by DOS, and returns to the calling
function. If a character was entered without having been read, the driver reads this
character and passes it to the calling DOS function in the request header, and resets
the busy bit. The character remains in the keyboard buffer, and on a subsequent call
of the read function, it is again passed to DOS. To test the availability of a
character, the READ] function uses function 1 of the BIOS keyboard interrupt
16H.

The function DEL_IN_B also gets called by the higher level DOS functions.
DEL_IN_B deletes the contents of the keyboard buffer. It removes characters from
the buffer using function 0 of the BIOS keyboard interrupt until function I
indicates that no more characters are available. This ends the function and it returns
to the calling function after the busy bit is reset

Writing characters
WRITE takes the number of characters from a buffer passed by DOS and displays
the characters on the screen. This routine uses function OEH of the BIOS video
interrupt. Once all characters have been displayed, it sets the BUSY bit in the
status field and ends the function. This function also executes when the higher
level DOS functions call the Write and Verify functions.

Initialization
The last function, the initialization routine, is called first by DOS. Since
CONDRV does not initialize variables and hardware, the routine simply enters the
driver's ending address into the passed request header. The routine returns its own
starting address since it will never be called again, and is the end of the chain of
drivers.

In its current form the driver has little use, since it uses only those functions
already available to the CON driver of DOS. It would be more practical if an
enhanced driver like ANSI.SYS were developed, through which screen design could
be more tightly controlled. For example, it's possible that such a driver would
have complete windowing capability which could be accessed from any program,
in any programming language.

The following block device driver creates a lOOK RAM disk:

178
Abacus 6.12 DOS Device Drivers

,. ••••• **.** •••••••• ** •••••••••••••••••••••••••••••••••••••••••• ***.** •.,


;* RAMDISK *;
i*-------------------------------------------------------------------*;
,. * Task : This Program is a Driver for a 160KB *;
;* RAM-Disk. *;
i*-------------------------------------------------------------------*i
;* Author MICHAEL TISCHER *;
;* developed orun 8.4.87 *;
;* last Update 9.21.87 *;
i*---------------------------------------------------- ---------------*;
;* assembly MASH RAMDISK; *;
;* LINK RAMDISK; *;
;* EXE2BIN RAMDISK RAMDISK.SYS *;
;*-------------------------------------------------------------------*;
;* Call Copy into Root Directory, enter the command *;
;* DEVICE=RAMDISK.SYS into the CONFIG.SYS file *;
, and then boot the System. *;
i············***···*********·····****·*************·····**············i
code segment

assume cs:code,ds:code,es:code,ss:code

org 0 ;Program has no PSP therefore begin


;at the offset address 0

i== Constants ================:=:==================~==========-=======

cmd fld equ ; Offset command field in data block


status equ 3 ; Offset status field in data block
num dev equ 13 ; Offset number of supported devices
changed equ 14 ;Offset medium changed?
end adr equ 14 ; Offset driver end-aAdr. in data block
b adr equ 14 ;Offset buffer address in data block
numcmd equ 16 ;the functions 0-16 are supported
num db equ 18 ;Offset number in data block
bpb_adr equ 18 ;Offset Address of BPB of the media
sector equ 20 ;Offset first sector number
dev des equ 22 ;Offset device-description of RAM-Disk

;== Data =====================================~=======-===============

erst b equ this byte ;this is the first byte of the driver

Header of the Device-Driver --------------------------------------­

dw -1,-1 ;Connection to next driver

dw 0100100000000000b ;Driver attribute

dw offset strat ;Pointer to strategy routine

dw offset intr ;Pointer to interrupt routine

db 1 ;a device is supported

db 7 dup (0) ;these bytes give the name

i-- Jump Table for the individual functions ------------------------­

fkt tab dw of fset init ; Funct ion 0: Ini t iali zat ion
dw offset med test ;Funct ion 1 : Media Test
dw offset get=bpb ; [unction 2: created BPS
dw offset read ; function 3: direct reading
dw offset read ;Function 4: Read
dw offset dummy ; Function 5: Read, remain in Buffer
dw offset dummy ;Function 6: Input-Status
dw offset dummy ; Funct ion 7: Erase Input-Buffer
dw offset write ; Funct ion 8: Write
dw offset write ; Function 9: Write & Verifi cation
dw offset dummy ;Function 10: Output -Stat us
dw offset dummy ;Function 11: Erase Output-Buffer
dw offset write ;Function 12: direct Write
dw offset dummy iFunctlon 13: Open (after DOS 3.0)
dw offset dummy ; Funct ion 14 : Close

179
6. The Disk Operating System PC System Programming

dw offset no rem ;Function IS: changeable Medium:


dw offset write ;Function 16: Output until Busy

db_ptr dw (:) , (?) ;Address of the data block passed


rd_seg dw (1) ;RD_SEG:OOOO beginning of the RAM-Disk

bpb_ptr dw offset bpb, (?) ;Accepts the address of the BPS

boot sek db 3 dup (0) ;normally a jump command to the boot


;Routine is stored here
db "MITI 1. 0" :Name of creator , version number
bpb dw 512 :512 bYtes per sector
db1 ;1 Sector per cluster
dw 1 ;1 reserved sector (boot-sector)
db1 ;1 File-Allocation-Table (FAT)
dw 64 ;maximum 64 entries in root directory
dw 320 ;total of 320 sectors D 160 K8
db OFEh ;Media descriptor (1 Side with 40
;Tracxs of 8 sectors each)
dw 1 ;every FAT occupies one sector

:-- the Boot routine not included since a System can not----­
be booted from a RAM-Disk

vol_name db "RAMDISK ;the actual volume-name


db 8 ;Attribute, defines volume-name

;=- Routines and functions of the Driver =c_£===_&======_~ ___=====___==


strat proc far ;Strategy routine

mov cs:db ptr,bx ;Store address of the data block


mov cs:db=ptr+2,es ;in the Variable DB PTR

ret ; back to caller

strat endp

:---------------------------------------------------------------------­
intr proc far ;Interrupt routine

push ax ;Store registers on the stack


push bx
push ex
push dx
push di
push si
push bp
push ds
push es
pushf ;also store flag register

push cs ;Set data segment register


pop ds ;Code identical with data here

les di,dword ptr db-ptr;Address of data block to ES:DI


rnov bl,es:[di+crnd_fldl ;Get command-code
crop bl,num cmd ;is command-code permitted?

jle bc ok ­ :YES --> bc_ok

rnov aX,8003h ; Code for lIunknown Corrmand ll

jmp short intr end ;back to caller

;-- Command-Code was o.k. --> Execute Command ---------------­


bc_ok: shl bl,1 ;Calculate pointer in jump table
xor bh,bh ;erase BH
call [fkt tab+bx] ;Call function
-

180
Abacus 6.12 DOS Device Drivers

;-- Execution of the function completed --------------------­

intr end label near


push cs ;Set data segment register
pop ds ;Code is identical with data here

les di,dword ptr db~tr;Address of the data block to ES:DI


or aX,0100h ;Set finished-bit
mov es:[di+statusJ,ax ;store everything in the status field

popf ;Restore flag register


pop es ;restore other registers
pop ds
pop bp
pop si
pop di
pop dx
pop cx
pop bx
pop ax

ret ;back to caller

intr endp

i---------------------------------------------------------------------­
init proc near ;Initialization routine

the following code is overwritten after the installation ­


by the RAM-Disk

;-- determine Device designation of the RAM-Disk -----------­

mov ah,30h ;Sense DOS Version with function 30 (h)

int 21h ;of DOS-interrupt 21 (h)

cmp a1,3 ;is it Version 3 or higher?

jb prinm ;YES --> PRINM

mov al,es: [di+dev_desl ;Get device designation

add aI, "AU ;convert to letters

mov im_ger,al istore in installation message

prinm: mov dx,offset initm ;Address of installation message


mov ah,9 ;output function number for string
int 2lh ;Call DOS-interrupt

Calculate Address of the first byte after the RAM-Disk -­


and set as End Address of the Driver

mov word ptr es:[di+end adrl,offset ramdisk+8000h


mov ax,CS - ;Size of RAM-Disk is 32KB plus
add aX,2000h ;2 • 64KB
moves: [di+end adr+2l,ax
mov byte ptr es:[di+num devl,1 ;1 device supported
mov word ptr es: [di+bpb-adrJ,offset bpb_ptr ;Address of the
moves: [di+bpb_adr+2 J. ds ; BPB-Pointer

mov ax,es ; Segment address of RAM-Disk beginning


mov bpbytr+2,ds ;Segment address of BPB in BPB-Pointer
mov dx,offset rarndisk ;calculate to offset address 0
mov cl,4 ;Divide offset address by 16 and thus
shr dx,cl ; convert into segment address
add ax,dx ;add the two segment addresses
mov rd_seg,ax ;and store

Create Boot-Sector --------------------------------------­

mov es,ax ;transfer segment address to ES


xor di,di ;Boots. begins with the 1. byte of RD

181
6. The Disk Operating System PC System Programming

mov si,offset boot sek ;Address of the boot-sector in memory


mov cX,1S ;on1y the first 15 words are used
rep movsw ; copy boot-sector into RAM-DisK

;-- Create FAT ------------------------------------------­


mov di,S12 ;FAT begins with the byte 512 of RD
mov a1,OFEh ;Write media-descriptor into the first
stosb ;byte of the FAT
mov aX,OFFFFH ;Store code for bytes 2 and 3 of FAT
stosw lin FAT
mov cx,236 ;remaining 236 words occupied by FAT
inc ax
rep stosw
;Set AX to °
;Set all FAT-entries to unoccupied

;-- Create Root Directory with Volume-Name ------------­

mov di,1024 ;Root Directory starts in 3rd Sector


mov si,offset vol_name ;Address of volume-name in memory
mov cx,6 ;the volume-name is 6 words long
rep movsw :Copy volume-name into RD

mov cx,1017 ;Fill the rest of the directories in


xor ax,ax ;Sectors 2, 3, 4 and 5 with zeros
rep stosw

xor ax,ax ; everything o.k.

ret ;back to caller

init endp

:---------------------------------------------------------------------­
dummy proc near ;This Routine does nothing

xor ax,ax ;Erase busy-bit

ret ;back to caller

dummy endp

;---------------------------------------------------------------------­
med_test proc near ;Media of RAM-Disk
;cannot be changed

mov byte ptr es:[di+changed),l

Kar ax,ax ; Erase busy-bit

ret ;back to caller

med test endp

:---------------------------------------------------------------------­
get_bpb proc near ;Pass address of BPB to DOS

mov word ptr es: [di+bpb_adr) ,offset bpb

mov word ptr es:[di+bpb_adr+2),ds

xor ax,ax ; Erase busy-bit

ret ;back to caller

get_bpb endp

i---------------------------------------------------------------------­
no_rem proc near ;Media of RAM-Disk cannot be changed
mov ax,20 ; Set busy-bit
ret ;back to caller

182
Abacus 6.12 DOS Device Drivers

no rem endp

;---------------------------------------------------------------------­

write proc near

xor bp,bp ;Transmission DOS --> RAM-Disk


jmp short move ;Copy data

write endp

j---------------------------------------------------------------------­
read proc near

mav bp,l ;Transmission RAM-Disk --> DOS

read endp

j-- MOVE: Move a certain number of sectors between RD and DOS


Input : BP - 0 : transmit from DOS to RD (Write)
j-- 1 : transmit from RD to DOS (Read)
Output : none
Registers : AX, BX, CX, OX, SI, 01, ES, OS and FLAGS are changed
j-- Info Information required (number, first sector)
;-- is taken from the data block passed by DOS

move proc near

mov bX,es: [di+num db] :Number of sectors read


mov dX,es: [di+Sectorj ;Number of first sector
les di,es: [di+b_adrj ;Address of buffer to ES:D1

move 1: or bX,bx iMore sectors to read


je move e iNa more sectors --> END
mov ax,dx ;Sector number to AX
mov el,S ;Calculate number of paragraphs
shl ax,el ; (Segment units) by Multiplication
add ax, cs: rd _seg ;with 32, add to Segment start of RD
mov ds,ax ; transmi t to DS
xor si,si ;Offset address is 0
mov ax,bx ;Number of sectors to be read to AX
cmp ax,128 ;more than 128 sectors to read
jbe move 2 ;NO --> read all sectors

mov aX,128 ;YES --> read 128 sectors (64 KB)

move 2: sub bx,ax ;subtract number of sectors read


add dx,ax ;add to sectorsto be read next
mov ch,al ; Number sect. to be read * 256 words
xor cI,el ;Set La-byte of word-counter to 0
or bp,bp ; Should be read
jne move 3 ;NO --> MOVE 3
mov ax,es ;Store ES in-AX
push ds ;Store DS on the stack
pop es ; read ES
mov ds,ax ;ES and DS are reversed now
xchg si,di ;exchange SI and DI
move 3: rep movsw ;copy data into DOS-buffer
or bp,bp i read ?
jne move 1 ;NO --> maybe other sectors to copy
mov ax,es ;Store ES in AX
push ds ;Store DS on the stack
pop es ;read ES
mov as,ax ;ES and DS have been exchanged
xchg si/di ;exchange SI and 01 again
jmp short move 1 iadditional sectors to copy

move e: xer ax,ax ; everything o.k.


ret ;back to caller

move endp

183
6. The Disk ()perating System PC System Programming

;-- RAM-Disk starts here ---------------------------­

if IS-erst b) mod 16 ;must start on a memory address


org ($-erst b) + 16 - «$-erst_b) mod 16) ; divisible by 16
endif ­

ramdisk equ this byte

initm db M*H* 160 KB RAMDISK as Device"

db U: installed (c) 1987 by MICHAEL TISCHERS",13,lO,10

;---------------------------------------------------------------------­
code ends

end

This driver is similar to the CONDRY driver. The biggest difference between the
two lies in the functions which each supports.

Note: The initialization routine INIT here is more comprehensive than the
CONDRY initialization routine, and remains in memory after the end
of execution even though it is no longer needed. You'll see why this
is so in the paragraph below entitled "The INIT routine" .

First, this routine fmds the DOS version number using function 30H. If the
version number equals or is greater than 3, the request header passed by DOS
contains the device designation of the RAM disk. The system reads the
designation, changes it to a character and places the character into the installation
message. DOS function 09H is used to display this message on the screen.

Next, the program computes the ending address of the RAM disk. Since the actual
data area of the RAM disk starts immediately after the last routine of this driver,
160K is added to the program's ending address. Further, the address of a variable
(BPB_P1R) containing the address of the BIOS parameter block is passed to DOS.
This variable describes the RAM disk's format. In this case, it tells DOS that the
RAM disk uses 512 bytes per sector. Each cluster is made up of one sector and
only one reserved sector (the boot sector) exists. In addition, only one FAT exists.
Additional information indicates that a maximum of 64 entries can be made in the
root directory and that the RAM disk has 320 sectors available (160K of memory).
The FAT occupies a single sector, and the media descriptor byte FEH designates a
diskette with one side and 40 tracks of 8 sectors each.

These parameters are then placed into the request header of DOS and the segment
address of the data area of the RAM disk is calculated (which the driver itself
requires, DOS does not need this information).

The INIT routine


The RAM disk must now be formatted, to create a boot sector, FAT and a root
directory. Since these data structures are in the flrst sectors of the RAM disk, a
normal INIT routine (which releases its memory to DOS), would overwrite itself

184
Abacus 6.12 DOS Device Drivers

with these data structures and would crash the system. This is why the
initialization routine is not at the end of the last routine of the driver, which would
place it at the beginning of the RAM disk's data area.

The boot sector occupies the complete first sector of the RAM disk, but only the
fIrst 15 words are copied into it since DOS only needs these. The name "boot
sector" is actually a misnomer here, since it's impossible to boot a system from a
RAM disk.

The second sector of the RAM disk contains the FAT. The fIrst two entries are the
media descriptor byte and 0 in the entries that follow. These zeros indicate
unoccupied clusters (an empty RAM disk).

The last data structure is the root directory. It contains no entries other than the
volume name.

Remaining routines

This concludes the work of the initialization routine and returns the system to the
calling function. The remaining driver routines are examined in order.

The DUMMY routine performs the same task as the routine of the same name in
the CONDRV driver.

The MED_TEST routine is found only in block device drivers. This routine
informs DOS whether or not the medium was changed.

The next routine, GET_BPB, simply passes the addresses of the variables which
contain the address of the BPB of the RAM disk to DOS, as the initialization
routine had already done.

NO_REM allows DOS to sense whether the medium (the RAM disk) can be
changed. You cannot change a RAM disk, so the program sets the BUSY bit in
the status fIeld.

The two most important functions of the driver perform read and write operations.
As in CONDRV, the program calls Write and Verify instead of the normal Write
function, since no data error can occur during RAM access. The routine itself does
very little; it loads the value 0 into the BP register and jumps to the MOVE
routine. The READ routine performs in a similar manner, except that it loads a 1
into the BP register.

MOVE itself is an elementary routine for moving data. The BP register signals
whether data is to move from the RAM disk to DOS or in the opposite direction.
The routine receives all other data (the DOS buffer's address, the number of the
sectors to be transferred and the fIrst sector to be transferred) from the data block
passed by DOS. See the comments in the MOVE routine for details of the
procedure.

185
6. The Disk Operating System PC System Programming

Changes

This RAM disk: can of course be enhanced. If you have enough unused memory,
you can extend the size of the RAM disk: to 360K. AT owners could make the
RAM disk: resident beyond the I megabyte boundary. In this case, the data transfer
between DOS and the RAM disk: would use function 87H of interrupt ISH.
The clock driver
This final sample driver directly accesses the battery powered clock of an AT
computer. It offers the advantage that when the two DOS commands DAlE and
TIME are used, the date and time are passed directly to the battery powered realtime
clock. Reading the date and time reads the information directly from the memory
locations of the realtime clock.
i**************************·*****···*********·***********************·i
;* ATCLK *;
i*------------