share knowledge of OS

Sunday, August 22, 2010

Makefile Tutorial Introduction

Make is one of the original Unix tools for Software Engineering. By S.I. Feldman of AT&T Bell Labs circa 1975. But there are public domain versions (eg. GNU) and versions for other systems (eg. Vax/VMS).
Related tools are the language compilers (cc, f77, lex, yacc, etc.) and shell programming tools (eg. awk, sed, cp, rm, etc.). You need to know how to use these.

Important adjuncts are lint (source code checking for obvious errors) ctags (locate functions, etc. in source code) and mkdepend. These are nice, and good programmers use them.

Important, and related tools, are the software revision systems SCCS (Source Code Control System) and RCS (Revision Control System -- the recommended choice)

The idea is to automate and optimize the construction of programs/files -- ie. to leave enough foot prints so that others can follow.

Makefile Naming
make is going to look for a file called Makefile, if not found then a file called makefile. Use the first (so the name stands out in listings).
You can get away without any Makefile (but shouldn't)! Make has default rules it knows about.

Makefile Components
Comments
Comments are any text beginning with the pound (#) sign. A comment can start anywhere on a line and continue until the end of the line. For example:

# $Id: slides,v 1.2 1992/02/14 21:00:58 reggers Exp $
Macros
Make has a simple macro definition and substitution mechanism. Macros are defined in a Makefile as = pairs. For example:

MACROS= -me
PSROFF= groff -Tps
DITROFF= groff -Tdvi
CFLAGS= -O -systype bsd43
There are lots of default macros -- you should honor the existing naming conventions. To find out what rules/macros make is using type:
% make -p

NOTE: That your environment variables are exported into the make as macros. They will override the defaults.
You can set macros on the make command line:

% make "CFLAGS= -O" "LDFLAGS=-s" printenv
cc -O printenv.c -s -o printenv
Targets
You make a particular target (eg. make all), in none specified then the first target found:

paper.dvi: $(SRCS)
$(DITROFF) $(MACROS) $(SRCS) >paper.dvi
NOTE: The the line beginning with $(DITROFF) begins with TAB not spaces.
The target is made if any of the dependent files have changed. The dependent files in this case are represented by the $(SRCS) statement.
Continuation of Lines
Use a back slash (\). This is important for long macros and/or rules.

Conventional Macros
There are lots of default macros (type "make -p" to print out the defaults). Most are pretty obvious from the rules in which they are used:

AR = ar
GFLAGS =
GET = get
ASFLAGS =
MAS = mas
AS = as
FC = f77
CFLAGS =
CC = cc
LDFLAGS =
LD = ld
LFLAGS =
LEX = lex
YFLAGS =
YACC = yacc
LOADLIBS =
MAKE = make
MAKEARGS = 'SHELL=/bin/sh'
SHELL = /bin/sh
MAKEFLAGS = b
Special Macros
Before issuing any command in a target rule set there are certain special macros predefined.

$@ is the name of the file to be made.
$? is the names of the changed dependents.
So, for example, we could use a rule


printenv: printenv.c
$(CC) $(CFLAGS) $? $(LDFLAGS) -o $@
alternatively:
printenv: printenv.c
$(CC) $(CFLAGS) $@.c $(LDFLAGS) -o $@
There are two more special macros used in implicit rules. They are:
$< the name of the related file that caused the action.
$* the prefix shared by target and dependent files.
Makefile Target Rules
The general syntax of a Makefile Target Rule is

target [target...] : [dependent ....]
[ command ...]
Items in brackets are optional, ellipsis means one or more. Note the tab to preface each command is required.
The semantics is pretty simple. When you say "make target" make finds the target rule that applies and, if any of the dependents are newer than the target, make executes the com- mands one at a time (after macro substitution). If any dependents have to be made, that happens first (so you have a recursion).

A make will terminate if any command returns a failure sta- tus. That's why you see rules like:

clean:
-rm *.o *~ core paper
Make ignores the returned status on command lines that begin with a dash. eg. who cares if there is no core file?
Make will echo the commands, after macro substition to show you what's happening as it happens. Sometimes you might want to turn that off. For example:

install:
@echo You must be root to install
Example Target Rules
For example, to manage sources stored within RCS (sometimes you'll need to "check out" a source file):

SRCS=x.c y.c z.c

$(SRCS):
co $@
To manage sources stored within SCCS (sometimes you'll need to "get" a source file):
$(SRCS):
sccs get $@
Alternativley, to manage sources stored within SCCS or RCS let's generalize with a macro that we can set as required.
SRCS=x.c y.c z.c
# GET= sccs get
GET= co

$(SRCS):
$(GET) $@
For example, to construct a library of object files
lib.a: x.o y.o z.o
ar rvu lib.a x.o y.o z.o
ranlib lib.a
Alternatively, to be a bit more fancy you could use:
OBJ=x.o y.o z.o
AR=ar

lib.a: $(OBJ)
$(AR) rvu $@ $(OBJ)
ranlib $@
Since AR is a default macro already assigned to "ar" you can get away without defining it (but shouldn't).
If you get used to using macros you'll be able to make a few rules that you can use over and over again.
For example, to construct a library in some other directory


INC=../misc
OTHERS=../misc/lib.a

$(OTHERS):
cd $(INC); make lib.a
Beware:, the following will not work (but you'd think it should)
INC=../misc
OTHERS=../misc/lib.a

$(OTHERS):
cd $(INC)
make lib.a
Each command in the target rule is executed in a separate shell. This makes for some interesting constructs and long continuation lines.
To generate a tags file

SRCS=x.c y.c z.c
CTAGS=ctags -x >tags

tags: $(SRCS)
${CTAGS} $(SRCS)
On large projects a tags file, that lists all functions and their invocations is a handy tool.
To generate a listing of likely bugs in your problems
lint:
lint $(CFLAGS) $(SRCS)

Lint is a really good tool for finding those obvious bugs that slip into programs -- eg. type classes, bad argu- ment list, etc.
Some Basic Make Rule
People have come to expect certain targets in Makefiles. You should always browse first, but it's reasonable to expect that the targets all (or just make), install, and clean will be found.

make all -- should compile everything so that you can do local testing before installing things.
make install -- should install things in the right places. But watch out that things are installed in the right place for your system.
make clean -- should clean things up. Get rid of the executables, any temporary files, object files, etc.
You may encounter other common targets, some have been already mentioned (tags and lint).

An Example Makefile for printenv
# make the printenv command
#
OWNER=bin
GROUP=bin
CTAGS= ctags -x >tags
CFLAGS= -O
LDFLAGS= -s
CC=cc
GET=co
SRCS=printenv.c
OBJS=printenv.o
SHAR=shar
MANDIR=/usr/man/manl/printenv.l
BINDIR=/usr/local/bin
DEPEND= makedepend $(CFLAGS)
all: printenv

# To get things out of the revision control system
$(SRCS):
$(GET) $@
# To make an object from source
$(CC) $(CFLAGS) -c $*.c

# To make an executable

printenv: $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS)

# To install things in the right place
install: printenv printenv.man
$(INSTALL) -c -o $(OWNER) -g $(GROUP) -m 755 printenv $(BINDIR)
$(INSTALL) -c -o $(OWNER) -g $(GROUP) -m 644 printenv.man $(MANDIR)

# where are functions/procedures?
tags: $(SRCS)
$(CTAGS) $(SRCS)

# what have I done wrong?
lint: $(SRCS)
lint $(CFLAGS) $(SRCS)

# what are the source dependencies
depend: $(SRCS)
$(DEPEND) $(SRCS)

# to make a shar distribution
shar: clean
$(SHAR) README Makefile printenv.man $(SRCS) >shar

# clean out the dross
clean:
-rm printenv *~ *.o *.bak core tags shar

# DO NOT DELETE THIS LINE -- make depend depends on it.
printenv.o: /usr/include/stdio.h
Makefile Implicit Rules
Consider the rule we used for printenv


printenv: printenv.c
$(CC) $(CFLAGS) printenv.c $(LDFLAGS) -o printenv
We generalized a bit to get
printenv: printenv.c
$(CC) $(CFLAGS) $@.c $(LDFLAGS) -o $@
The command is one that ought to work in all cases where we build an executable x out of the source code x.c This can be stated as an implicit rule:
.c:
$(CC) $(CFLAGS) $@.c $(LDFLAGS) -o $@
This Implicit rule says how to make x out of x.c -- run cc on x.c and call the output x. The rule is implicit because no particular target is mentioned. It can be used in all cases.
Another common implicit rule is for the construction of .o (object) files out of .c (source files).

.o.c:
$(CC) $(CFLAGS) -c $<
alternatively
.o.c:
$(CC) $(CFLAGS) -c $*.c
Make Dependencies
It's pretty common to have source code that uses include files. For example:

% cat program.c

#include
#include "defs.h"
#include "glob.h"
etc....
main(argc,argv)
etc...
The implicit rule only covers part of the source code depen- dency (it only knows that program.o depends on program.c). The usual method for handling this is to list the dependen- cies separately;
etc...
$(CC) $(CFLAGS) -c $*.c
etc...
program.o: program.c defs.h glob.h
Usually an implicit rule and a separate list of dependencies is all you need. And it ought to be easy enough to figure out what the dependencies are.
However, there are a number of nice tools around that will automatically generate dependency lists for you. For example (trivial):

DEPEND= makedepend $(CFLAGS)
etc...
# what are the source dependencies

depend: $(SRCS)
$(DEPEND) $(SRCS)

etc....
# DO NOT DELETE THIS LINE -- ....

printenv.o: /usr/include/stdio.h
These tools (mkdepend, mkmkf, etc.) are very common these days and aren't too difficult to use or understand. They're just shell scripts that run cpp (or cc -M, or etc.) to find out what all the include dependencies are. They then just tack the dependency list onto the end of the Makefile.

Operating System Glossary

A list of operating system related terminology. Add a glossary word if it is not already there.

API Application Programming Interface
BDA BIOS Data Area
BIOS Basic Input Output System
BPB BIOS Parameter Block
CCP Configuration Control Register
CHS Cylinder Head Sector
DIR Digital Input Register
DMA Direct Memory Access
DSO Downloadable Sub Oliteration
EOF End Of File
FAT File Allocation Table
FCB File Control Block
FDC Floppy Drive Controller
FDD Floppy Disk Drive
FLIH First Level Interrupt Handler
GDT Global Descriptor Table
HAL Hardware Abstraction Table
HDC Hard Disk Controller
HDD Hard Disk Drives
HTTP Hyper Text Transfer Protocol
IDT Interrupt Descriptor Table
IF Interrupt Flag
IP Internet Protocol
IPC Inter Process Communication
ISA Industry Standard Architecture
ISR Internet Service Routine
IVT Interrupt Vector Table
LBA Logical Block Addressing
LDT Local Descriptor Table
LPC Low Pin Count
LUT Look up Table
MBR Master Boot Record
MBR memory buffer register
MMU Memory Manage Unit
MSR Main Status Register
NTFS New Table File System
OCW Operation Command WOrd
OTP One Time Programmable
PAE Physical Address Expansion
PAS Physical Address Space
PC Program Counter
PCB Process Control Block
PDBR Page Directory Base Register
PDT Page Directory Table
PIC Programmable Interrupt Controller (IC 8259A)
PIT Programmable Interrupt Timer (IC 8254)
PMM Physical Memory Manager
POST power on self test
PPI Programmable Peripheral Interface
PSE Page Size Extension
PTE Page Table Entries
SC Stack Counter
SDL Shielded Data Link
SJF shortest job first
SLIH Second Level Interrupt Handler
SP Stack Pointer
STBR segment table base register
TCP Transmission control protocol
TDR Tape Drive Register
TLB Translation Lookaside Buffer
TSR Terminate and Stay Resident
USB Universal Serial Bus
VAS Virtual Address Space
VFS Virtual File System
VMM Virtual Memory Manager

Principles of good GUI Design

Graphical user interfaces (GUIs) have become the user interface of choice. Yet despite the GUI's popularity, surprisingly few programs exhibit good interface design. Moreover, finding information explaining what constitutes a good and intuitive interface is exceedingly difficult. In this article, I describe the basic rules for all good interfaces -the cardinal dos and don'ts.

However, before starting in on what constitutes good design, I need to explain the causes of bad design. This way, if you are tempted to deviate from the tried and true, you'll know where the wrong path leads -and, I hope, get back to good design.



Forgetting the User
Developers often design for what they know, not what the users know. This age-old problem occurs in many other areas of software development, such as testing, documentation, and the like. It is even more pernicious in the interface because it immediately makes the user feel incapable of using the product. Avoid this error diligently.



Give Users Control
GUI designers' predilection for control is evident in applications that continually attempt to control user navigation by graying and blackening menu items or controls within an application. Controlling the user is completely contradictory to event-driven design in which the user rather than the software dictates what events will occur. As a developer, if you are spending a lot of time dynamically graying and blackening controls, you need to re-examine your design approach and realize that you may be controlling the user, who may not want to be controlled. As business changes at a faster pace, flexibility in user interfaces will become a key enabler for change. Allowing the user to access the application in ways you never dreamed can be scary, but satisfying for you as a developer and empowering for the user.



Too Many Features at the Top Level
Examine a VCR built in 1985 and then examine one built in 1995. You will see a startling difference in the interface of the two models. The model built in 1985 will have an abundance of buttons readily available on the faceplate of the unit, many of which will remain a mystery since the manual was lost years ago. The 1995 model will have only a few buttons for the key features people use: play, fast-forward, reverse, stop, and eject. This model will probably have even more features than the model built a decade before, yet the features will be cleverly tucked away behind a drop-down panel or sliding door, accessible when needed but not staring you in the face.

Likewise, you should ensure that features used frequently are readily available. Avoid the temptation to put everything on the first screen or load the toolbar with rarely used buttons. Do the extra analysis to find out which features can go behind the panel instead of on the faceplate.



GUI Successes
Now, let's discuss some GUI successes. Successful GUIs share many common characteristics. Most importantly, good GUIs are more intuitive than their character-based counterparts. One way to achieve this is to use real-world metaphors whenever possible. For example, an application I recently examined used bitmaps of Visa and MasterCard logos on buttons that identified how a customer was going to pay. This graphical representation was immediately intuitive to users and helped them learn the application faster.

Another important characteristic of good GUIs is speed, or more specifically, responsiveness. Many speed issues are handled via the design of the GUI, not the hardware. Depending on the type of application, speed can be the make-or-break factor in determining an application's acceptability in the user community. For example, if your application is oriented toward online transaction processing (OLTP), slow performance will quickly result in users wanting to abandon the system.

You can give a GUI the appearance of speed in several ways. Avoid repainting the screen unless it is absolutely necessary. Another method is to have all field validations occur on a whole-screen basis instead of on a field-by-field basis. Also, depending upon the skills of the user, it may be possible to design features into a GUI that give the power user the capability to enter each field of each data record rapidly. Such features include mnemonics, accelerator keys, and toolbar buttons with meaningful icons, all of which would allow the speed user to control the GUI and rate of data entry.



Dos And Don'ts
Every good developer should have the goal of designing the best GUIs possible. But how does a developer make this goal a reality? By following sound, proven GUI design principles such as those listed in the following sections.

Like any good professional, YOU need some rules for repeatable successful designs. We have used the principles offered here for work with our own customers and have taught more than 20,000 GUI-design students nationally and internationally. These principles should help you as well.



Understand People
Applications must reflect the perspectives and behaviors of their users. To understand users fully, developers must first understand people because we all share common characteristics. People learn more easily by recognition than by recall. Always attempt to provide a list of data values to select from rather than have the users key in values from memory. The average person can recall about 2,000 to 3,000 words, yet can recognize more than 50,000 words.



Be Careful Of Different Perspectives
Many designers unwittingly fall into the perspective trap when it comes to icon design or the overall behavior of the application. I recently saw an icon designed to signify "Rolled Up" totals for an accounting system. To signify this function, the designer put much artistic effort into creating an icon resembling a cinnamon roll. Unfortunately, the users of the system had no idea what metaphor the icon was supposed to represent even though it was perfectly intuitive from the designer's perspective. A reserved-icons table containing standard approved icons, such as the one shown in Figure 1, will help eliminate these problems.



Reserved Icons
Figure 1
Picture Meaning and Behaviour Use to Identify an Application Used to Identify a Function Reserved Word Text Label

Information Message No Yes
(identifies Information message box) None

Warning Message No Yes
(identifies Warning message box) None

Question Message No Yes
(identifies question message box) None

Error Message No Yes
(identifies error message box) None


Design for Clarity
GUI applications often are not clear to end users. One effective way to increase the clarity of applications is to develop and use a list of reserved words. A common complaint among users is that certain terms are not clear or consistent. I often see developers engaging in spirited debates over the appropriate term for a button or menu item, only to see this same debate occurring in an adjacent building with a different set of developers. When the application is released, one screen may say "Item," while the next screen says "Product," and a third says "Merchandise" when all three terms denote the same thing. This lack of consistency ultimately leads to confusion and frustration for users.

Figure 2 gives an example of a list of reserved words. An application-development group might complete and expand the table with additional reserved words.



List of Reserved Words
Figure 2
Text Meaning And Behavior Appears
On Button Appears
On Menu Mnemonic
Keystrokes Shortcut
Keystrokes
OK Accept data entered or acknowledge information presented and remove the window Yes No None or
Cancel Do not accept data entered and remove the window Yes No None Esc
Close Close the current task and continue working with the application; close view of data Yes Yes Alt+C None
Exit Quit the application No Yes Alt+X Alt+F4
Help Invoke the application's Help facility Yes Yes Alt+H Fl
Save Save data entered and stay in current window Yes Yes Alt+S Shift+Fl2
Save As Save the data with a new name No Yes Alt+A F12
Undo Undo the latest action No Yes Alt+U Ctrl+Z
Cut Cut the highlighted characters No Yes Alt+T Ctrl+X
Copy Copy highlighted text No Yes Alt+C Ctrl+C
Paste Paste the copied or cut text at the insertion point No Yes Alt+P Ctrl+V


Design for Consistency
Good GUIs apply consistent behavior throughout the application and build upon a user's prior knowledge of other successful applications. When writing software for business applications, provide the user with as many consistent behaviors as possible. For example, both the Embassy Suites and Courtyard Marriot hotel chains are growing rapidly due to their popularity among business travelers who know they will be provided with a familiar room and a consistent set of amenities. The bottom line is that business travelers are not looking for a new and exciting experience at each new city. Business users of your software have similar needs. Each new and exciting experience you provide in the software can become an anxiety-inducing experience or an expensive call to your help desk.



Provide Visual Feedback
If you've ever found yourself mindlessly staring at the hourglass on your terminal while waiting for an operation to finish, you know the frustration of poor visual feedback. Your users will greatly appreciate knowing how much longer a given operation will take before they can enjoy the fruits of their patience. As a general rule, most users like to have a message dialog box with a progress indicator displayed when operations are going to take longer than seven to ten seconds. This number is highly variable based on the type of user and overall characteristics of the application.



Provide Audible Feedback
Last week, I had the opportunity to ride in elevators in which a pleasant voice informed riders which floor they were on. The building was fairly new, and at first, employees thought the voice was cute. After six months of traveling floor to floor, employees ignore the voice and see it as more of an annoyance than a help. The same thing can happen with your GUls, except the annoying sounds are not contained within the walls of an elevator, but instead are available to everyone within earshot of the worker's cubicle. Put sound on a few hundred workstations, and a real cacophony emerges in the open-air cubicle environment. However, audible feedback can be useful in cases where you need to warn the user of an impending serious problem, such as one in which proceeding further could cause loss of data or software. Allow users to disable audio feedback, except in cases when an error must be addressed.



Keep Text Clear
Developers often try to make textual feedback clear by adding a lot of words. However, they ultimately make the message less clear. Concise wording of text labels, user error messages, and one-line help messages is challenging. Textual feedback can be handled most effectively by assigning these tasks to experienced technical writers.



Provide Traceable Paths
If your users ever say something akin to, "I don't know how I got to this window, and now that I'm here, I don't know how to get out," then you have not provided a traceable (or, in this case, retraceable) path. Providing a traceable path is harder than it sounds. It starts with an intuitive menu structure from which to launch your specific features.

You must also identify areas where you can flatten the menu structure and avoid more than two levels of cascading menus. Providing a descriptive title bar within each dialog box will help greatly to remind the user what menu items or buttons were pressed to bring them to the window now in focus.



Provide Keyboard Support
Keyboards are a common fixture on users' desktops and provide an efficient means to enter text and data. With the introduction of GUI applications, we often assume users will embrace a mouse as the primary interactive device. This can become time-consuming and inefficient for the touch typist or frequent users of your application.

Keyboard accelerators can provide an efficient way for users to access specific menu items or controls within a window. The accelerators used should be easy to access and limited to one or two keys (such as F3 or Ctrl-P). Keyboards have limitations in the GUI world, such as when trying to implement direct-manipulation tasks like drag and drop, pointing, and resizing.

In contrast, you will always find a smaller set of users who are not touch typists and hence embrace the mouse as a point-and-click nirvana. The result is that you need to provide complete and equal keyboard and mouse support for all menu and window operations.



Watch the Presentation Model
A critical aspect that ties all these facets of the interface together is the interface's look and feel. The look and feel must be consistent. On the basis of users' experiences with one screen or one dialog box, they should have some sense of how to interact with the next screen or control.

Searching the interface model for good design and continuity is most important. The model should involve careful decisions, such as whether the application will have a single or multiple document interface. The model also will validate how users perform their main tasks within the application.

Identifying the appropriate presentation for the application will greatly facilitate the subsequent windows being developed since they will have a common framework to reside in. On the other hand, if you do not define the presentation model early in the design of your GUI, late changes to the look and feel of the application will be much more costly and time-consuming because nearly every window may be affected.



Modal vs. Modeless Dialogs
When we need input from the user, we often use a modal dialog box. Using modal dialogs has long been shunned by many developers as too constraining on the user. However, modal dialogs do have many uses in complex applications since most people only work on one window at a time. Try to use modal dialogs when a finite task exists. For tasks with no fixed duration, modeless dialogs will normally be the preferable choice with a major caveat: Try to keep the user working in no more than three modeless windows at any one time. Go beyond this magical number and the support-desk phones will start ringing as users spend their time managing the various open windows rather than concentrating on their tasks. Use the table in Figure 3 to determine the appropriate use of dialog boxes and windows.



When to Use Dialog Boxes Or Windows
Figure 3
Type Description Use Example
Modal Dialog box Presentation of a finite task File Open dialog box
Save As dialog box
Modeless Dialog box Presentation of an ongoing task Search dialog box
History List dialog box
Task List dialog box
Applicaton Window Window frame with document
(child) windows contained within Presentation of multiple instances of an object
Comparison of data within two or more windows Word Processor
Spreadsheet
Document Window Modeless dialog box or document window contained within and managed by Application window Presentation of multiple parts of an application Multiple views of data (sheets)
Secondary Window Primary window of a secondary application Presentation of another application called from parent Invoke Help within an application


Control Design
Controls are the visual elements that let the user interact with the application. GUI designers are faced with an unending array of controls to choose from. Each new control brings with it expected behaviors and characteristics. Choosing the appropriate control for each user task will result in higher prodtictivity, lower error rates, and higher overall user satisfaction. Use the table in Figure 4 as a guideline for control usage in your screens.



Guidelines For Using Controls
Figure 4
Control Number Of Choices In Domain Shown Type Of Controls
Menu Bar Maximum 10 items Static action
Pull-Down Menu Maximum 12 items Static action
Cascading Menu Maximum 5 items, 1 cascade deep Static action
Pop-up Menu Maximum 10 items Static action
Push-button 1 for each button, maximum of 6 per dialog box Static action
Check Box 1 for each box, maximum of 10 to 12 per group Static set/select value
Radio Button 1 for each button, maximum of 6 per group box Static set/select value
List Box 50 in list, display 8 to 10 rows Dynamic set/select value
Drop-down List Box Display 1 selection in control at a time, up to 20 in a drop-down box Dynamic set/select single value
Combination List Box Display 1 selection in control at a time in standard format up to 20 in a drop-down box Dynamic set/select single value; add value to list
Spin Button Maximum 10 values Static set/select value
Slider Dependent on data displayed Static set/select value in range
Finally, try to keep the basic behavior and placement of these controls consistent throughout your application. As soon as you change the behavior of these basic controls, your user will feel lost. Make changes thoughtfully and apply the changes consistently.



Applying Design Principles
Understanding the principles behind good GUI design and applying them to your applications can be a challenge. Let's examine an application to see how these principles can result in an improved interface.



Exploring a GUI Design in Need of Redesign
The interface in Figure 5 is used by an ambulance-dispatching company to maintain customer data, provide billing information, and dispatch ambulances. The application was a port from a character-based system and contains several design errors that affect the user's performance with this mission-critical application. Keep in mind that GUI ease of use and clarity is especially important in a critical application such as this where rapid handling of a request can make the difference between life and death. Here is what is wrong with this screen:

Figure 5



Too many functions at the top level. The users requested that the new application provide all information at their fingertips. This results in the screen being used for both customer maintenance and ambulance dispatching. If you enter extensive customer information and then press the Update button, the record is updated. However, if you enter minimal customer information, such as Social-security number, diagnosis, from-location, and to-location, then press the Trans button, an ambulance will be dispatched. Updating and dispatching functions need to be on separate dialog boxes.
Too many buttons. The buttons along the right should be on the application's parent window, possibly in a toolbar, but not on this child window.
Poor navigational assistance. GUI controls should be positioned according to frequency of use. The most important field should be in the upper left; the least important field should be in the lower right. It's hard to imagine how the company and invoice number could be the most important fields when dispatching an ambulance.
Inappropriate use of controls. The designer chose to use text labels rather than group boxes to identify which groups of data would be placed in the boxes. This many group boxes with text labels in these positions makes the screen appear convoluted and makes it difficult to distinguish the data from the labels. Also, the editable fields should be identified with a box around them so that it is intuitively obvious which fields can be changed.
Lack of symmetry. Just lining up fields, group boxes, and buttons will make this GUI much easier to use. Our brains like order, not chaos.


An Improved Interface
Figures 6 and 7 show a much improved interface for this same application:

Figure 6

Figure 7



Order out of chaos. This application should contain several child windows for the different tasks a user might perform. These tasks can be accessed easily through the Tasks menu or by pushing one button on the vertical toolbar. The Dispatch button invokes a modal dialog box instead of a modeless child window. That way, you can require the user to acknowledge completion of the dispatching task. If it were a modeless window, the user might overlay it without ever dispatching the ambulance.
Reordering input fields. The confusing order of fields has been more logically structured based on importance and frequency of use.
Improved controls. The revised interface features consistent use of data-entry fields. Any field in which a user can enter data is surrounded by a border. Group boxes are used to group related fields together or to illustrate a domain.
These changes, suggested by the principles we have previously discussed, make for a clean and more intuitive interface.



Implementing Effective Standards
Once you implement some good design practices into your GUI applications, how do you ensure others in your organization will do the same? The most cost-effective way to ensure consistency among your GUI applications is to implement GUI standards that are easy to use, clear, and concise. We've all experienced the "standards" manual that is energetically distributed to coworkers only to be placed immediately on the developer's shelf next to other unread manuals. To ensure your standards do not meet this same fate, provide them in an online hypertext format. Divide your standards into rules -which must be followed or the developer will have some explaining to do- and recommendations. Developers like to know what is mandatory and where they have discretion.



Conclusion
Designing good GUis is a critical skill for application developers in the 1990s, regardless of the GUI platform for which they are designing. Good GUI designs don't happen naturally. They require that the developer learn and apply some basic principles, including making the design something the user will enjoy working with every day. They also require that the developer get as much experience as possible in working on and being exposed to good GUl designs.

Remember, if you apply the principles and get some experience in good GUI design, your users will have an easier time getting their jobs accomplished using the GUIs you produce for them.

James Hobart is a senior consultant with Corporate Computing International (CCI), a provider of client-server and GUI services, consulting, and products. He specializes in the design and development of large-scale, high-volume client-server applications. He has extensive experience in GUI design for transaction-processing systems and strategies for migration from character-based systems. He can be reached at 70334.3064@compuserve.com.

Partition Tables Intoduction

Sometime we think how our harddisk(hdd) is partitioned. We may have certain partitions on our hdd which logically divides it into pieces. Well to simplify the things I will discuss only primary partitions here.

The first sector of the drive is called the Master Boot Record (MBR). You can read it with LBA = 0. For any new projects, you should not even worry about accessing a drive in CHS mode, as LBA just numbers the sectors sequentially starting at zero, which is much simpler. All IDE drives support accessing sectors using LBA. Also, all IDE drives use sectors that are 512 bytes. Recent Microsoft operating systems refer to using larger sectors, but the drives still use 512 bytes per sector and MS is just treating multiple sectors as if they were one sector. The remainder of this page will only refer to LBA address of 512 byte sectors.

The first 446 bytes of the MBR are code that boots the computer. This is followed by a 64 byte partition table, and the last two bytes are always 0x55 and 0xAA. You should always check these last two bytes, as a simple "sanity check" that the MBR is ok.


The MBR can only represent four partitions. A technique called "extended" partitioning is used to allow more than four, and often times it is used when there are more than two partitions. All we're going to say about extended partitions is that they appear in this table just like a normal partition, and their first sector has another partition table that describes the partitions within its space. But for the sake of simply getting some code to work, we're going to not worry about extended partitions (and repartition and reformat any drive that has them....) The most common scenario is only one partition using the whole drive, with partitions 2, 3 and 4 blank.

Each partition description is just 16 bytes, and the good news is that you can usually just ignore most of them. The fifth byte is a Type Code that tells what type of filesystem is supposed to be contained within the partition, and the ninth through twelth bytes indicate the LBA Begin address where that partition begins on the disk.


Normally you only need to check the Type Code of each entry, looking for either 0x0B or 0x0C (the two that are used for FAT32), and then read the LBA Begin to learn where the FAT32 filesystem is located on the disk.

Type Codes which represent some Filesystems
01 DOS 12-bit fat
02 XENIX root
03 XENIX /usr
04 DOS 3.0+ 16-bit FAT (up to 32M)
05 DOS 3.3+ Extended Partition
06 DOS 3.31+ 16-bit FAT (over 32M)
07 OS/2 IFS (e.g., HPFS)
07 Advanced Unix
07 Windows NT NTFS
07 QNX2.x (pre-1988)
08 OS/2 (v1.0-1.3 only)
08 AIX boot partition
08 SplitDrive
08 DELL partition spanning multiple drives
08 Commodore DOS
08 QNX 1.x and 2.x ("qny")
09 AIX data partition
09 Coherent filesystem
09 QNX 1.x and 2.x ("qnz")
0a OS/2 Boot Manager
0a Coherent swap partition
0a OPUS
0b WIN95 OSR2 32-bit FAT
0c WIN95 OSR2 32-bit FAT, LBA-mapped
0e WIN95: DOS 16-bit FAT, LBA-mapped
0f WIN95: Extended partition, LBA-mapped
10 OPUS (?)
11 Hidden DOS 12-bit FAT
12 Compaq config partition
14 Hidden DOS 16-bit FAT <32M
16 Hidden DOS 16-bit FAT >=32M
17 Hidden IFS (e.g., HPFS)
18 AST SmartSleep Partition
19 Unused (Claimed for Willowtech Photon COS)
1b Hidden WIN95 OSR2 32-bit FAT
1c Hidden WIN95 OSR2 32-bit FAT, LBA-mapped
1e Hidden WIN95 16-bit FAT, LBA-mapped
20 Unused
21 Reserved
21 Unused
22 Unused
23 Reserved
24 NEC DOS 3.x
26 Reserved
31 Reserved
32 NOS
33 Reserved
34 Reserved
35 JFS on OS/2 or eCS
36 Reserved
38 THEOS ver 3.2 2gb partition
39 Plan 9 partition
39 THEOS ver 4 spanned partition
3a THEOS ver 4 4gb partition
3b THEOS ver 4 extended partition
3c PartitionMagic recovery partition
3d Hidden NetWare
40 Venix 80286
41 Linux/MINIX (sharing disk with DRDOS)
41 Personal RISC Boot
41 PPC PReP (Power PC Reference Platform) Boot
42 Linux swap (sharing disk with DRDOS)
42 SFS (Secure Filesystem)
42 Windows 2000 marker
43 Linux native (sharing disk with DRDOS)
44 GoBack partition
45 Boot-US boot manager
45 Priam
45 EUMEL/Elan
46 EUMEL/Elan
47 EUMEL/Elan
48 EUMEL/Elan
4a AdaOS Aquila (Default)
4a ALFS/THIN lightweight filesystem for DOS
4c Oberon partition
4d QNX4.x
4e QNX4.x 2nd part
4f QNX4.x 3rd part
4f Oberon partition
50 OnTrack Disk Manager (older versions) RO
50 Lynx RTOS
50 Native Oberon (alt)
51 OnTrack Disk Manager RW (DM6 Aux1)
51 Novell
52 CP/M
52 Microport SysV/AT
53 Disk Manager 6.0 Aux3
54 Disk Manager 6.0 Dynamic Drive Overlay
55 EZ-Drive
56 Golden Bow VFeature Partitioned Volume.
56 DM converted to EZ-BIOS
57 DrivePro
57 VNDI Partition
5c Priam EDisk
61 SpeedStor
63 Unix System V (SCO, ISC Unix, UnixWare, ...), Mach, GNU Hurd
64 PC-ARMOUR protected partition
64 Novell Netware 286, 2.xx
65 Novell Netware 386, 3.xx or 4.xx
66 Novell Netware SMS Partition
67 Novell
68 Novell
69 Novell Netware 5+, Novell Netware NSS Partition
70 DiskSecure Multi-Boot
71 Reserved
73 Reserved
74 Reserved
74 Scramdisk partition
75 IBM PC/IX
76 Reserved
77 M2FS/M2CS partition
77 VNDI Partition
78 XOSL FS
7E
80 MINIX until 1.4a
81 MINIX since 1.4b, early Linux
81 Mitac disk manager
82 Prime
82 Solaris x86
82 Linux swap
83 Linux native (usually ext2fs)
84 OS/2 hidden C: drive
84 Hibernation partition
85 Linux extended partition
86 Old Linux RAID partition superblock
86 NTFS volume set
87 NTFS volume set
8a Linux Kernel Partition (used by AiR-BOOT)
8b Legacy Fault Tolerant FAT32 volume
8c Legacy Fault Tolerant FAT32 volume using BIOS extd INT 13h
8d Free FDISK hidden Primary DOS FAT12 partitition
8e Linux Logical Volume Manager partition
90 Free FDISK hidden Primary DOS FAT16 partitition
91 Free FDISK hidden DOS extended partitition
92 Free FDISK hidden Primary DOS large FAT16 partitition
93 Hidden Linux native partition
93 Amoeba
94 Amoeba bad block table
95 MIT EXOPC native partitions
97 Free FDISK hidden Primary DOS FAT32 partitition
98 Free FDISK hidden Primary DOS FAT32 partitition (LBA)
99 DCE376 logical drive
9a Free FDISK hidden Primary DOS FAT16 partitition (LBA)
9b Free FDISK hidden DOS extended partitition (LBA)
9f BSD/OS
a0 Laptop hibernation partition
a1 Laptop hibernation partition
a1 HP Volume Expansion (SpeedStor variant)
a3 Reserved
a4 Reserved
a5 BSD/386, 386BSD, NetBSD, FreeBSD
a6 OpenBSD
a7 NEXTSTEP
a8 Mac OS-X
a9 NetBSD
aa Olivetti Fat 12 1.44Mb Service Partition
ab Mac OS-X Boot partition
ab GO! partition
ae ShagOS filesystem
af ShagOS swap partition
b0 BootStar Dummy
b1 Reserved
b3 Reserved
b4 Reserved
b6 Reserved
b7 BSDI BSD/386 filesystem
b8 BSDI BSD/386 swap partition
bb Boot Wizard hidden
be Solaris 8 boot partition
c0 CTOS
c0 REAL/32 secure small partition
c0 NTFT Partition
c1 DRDOS/secured (FAT-12)
c2 Reserved for DR-DOS 7+
c2 Hidden Linux
c3 Hidden Linux swap
c4 DRDOS/secured (FAT-16, < 32M)
c5 DRDOS/secured (extended)
c6 DRDOS/secured (FAT-16, >= 32M)
c6 Windows NT corrupted FAT16 volume/stripe set
c7 Windows NT corrupted NTFS volume/stripe set
c7 Syrinx boot
c8 (See also ID c2.)
c9 (See also ID c2.)
ca (See also ID c2.)
cb reserved for DRDOS/secured (FAT32)
cc reserved for DRDOS/secured (FAT32, LBA)
cd CTOS Memdump?
ce reserved for DRDOS/secured (FAT16, LBA)
d0 REAL/32 secure big partition
d1 Old Multiuser DOS secured FAT12
d4 Old Multiuser DOS secured FAT16 <32M
d5 Old Multiuser DOS secured extended partition
d6 Old Multiuser DOS secured FAT16 >=32M
d8 CP/M-86
da Non-FS Data
db Digital Research CP/M, Concurrent CP/M, Concurrent DOS
db CTOS (Convergent Technologies OS -Unisys)
db KDG Telemetry SCPU boot
dd Hidden CTOS Memdump?
de Dell PowerEdge Server utilities (FAT fs)
df DG/UX virtual disk manager partition
df BootIt EMBRM
e0 Reserved by STMicroelectronics for a filesystem called ST AVFS.
e1 DOS access or SpeedStor 12-bit FAT extended partition
e3 DOS R/O or SpeedStor
e4 SpeedStor 16-bit FAT extended partition < 1024 cyl.
e5 Tandy DOS with logical sectored FAT (According to Powerquest.)
e5 Reserved
e6 Reserved
eb BFS (aka BeFS)
ed Reserved for Matthias Paul's Sprytix
ee Indication that this legacy MBR is followed by an EFI header
ef Partition that contains an EFI file system
f0 Linux/PA-RISC boot loader
f1 SpeedStor
f2 DOS 3.3+ secondary partition (Powerquest writes: Unisys DOS with logical sectored FAT.)
f3 Reserved (Powerquest writes: Storage Dimensions SpeedStor.)
f4 SpeedStor large partition
f4 Prologue single-volume partition
f5 Prologue multi-volume partition
f6 Reserved (Powerquest writes: Storage Dimensions SpeedStor. )
fa Bochs
fb VMware File System partition
fc VMware Swap partition
fd Linux raid partition with autodetect using persistent superblock (Powerquest writes: Reserved for FreeDOS. )
fe SpeedStor > 1024 cyl.
fe LANstep
fe IBM PS/2 IML (Initial Microcode Load) partition, located at the end of the disk.
fe Windows NT Disk Administrator hidden partition
fe Linux Logical Volume Manager partition (old)
ff Xenix Bad Block Table
The Number of Sectors field can be checked to make sure you do not access (particularly write) beyond the end of the space that is allocated for the parition. However, the FAT32 filesystem itself contains information about its size, so this Number of Sectors field is redundant. Several of Microsoft's operating systems ignore it and instead rely on the size information embedded within the first sector of the filesystem. (yes, I have experimentally verified this, though unintentionally :) Linux checks the Number of Sectors field and properly prevents access beyond the allocated space. Most firmware will probably ignore it. The Boot Flag, CHS Begin, and CHS End fields should be ignored.

Paging Introduction

Paging is another memory management technique which widely uses virtual memory concept. When paging is used, the processor divides the linear address space into fixed-size pages (of 4KBytes, 2 MBytes, or 4 MBytes in length) that can be mapped into physical memory and/or disk storage. When a program (or task) references a logical address in memory, the processor translates the address into a linear address and then uses its paging mechanism to translate the linear address into a corresponding physical address.

Linear Page Translation during Paging

If the page containing the linear address is not currently in physical memory, the processor generates a page-fault exception (#14). The exception handler for the page-fault exception typically directs the operating system to load the page from disk storage into physical memory. When the page has been loaded in physical memory, a return from the exception handler causes the instruction that generated the exception to be restarted. The information that the processor uses to map linear addresses into the physical address space and to generate page-fault exceptions (when necessary) is contained in page directories and page tables stored in memory.


Page-Directory & Page-Table Entries



Advantages of paging
Address translation: each task has the same virtual address
Address translation: turns fragmented physical addresses into contiguous virtual addresses
Memory protection (buggy or malicious tasks can't harm each other or the kernel)
Shared memory between tasks (a fast type of IPC, also conserves memory when used for DLLs)
Demand loading (prevents big load on CPU when a task first starts running, conserves memory)
Memory mapped files
Virtual memory swapping (lets system degrade gracefully when memory required exceeds RAM size)

Segmentation Intoduction

Segmentation is a Memory Management technique in which memory is divided into variable sized chunks which can be allocated to processes. Each chunk is called a segment. A table stores the information about all such segments and is called Global Descriptor Table (GDT). A GDT entry is called Global Descriptor. It comprise of :



Logical Address to Linear Address
To translate a logical address into a linear address, the processor does the following:

Uses the offset in the segment selector to locate the segment descriptor for the segment in the GDT or LDT and reads it into the processor. (This step is needed only when a new segment selector is loaded into a segment register.)

Examines the segment descriptor to check the access rights and range of the segment to insure that the segment is accessible and that the offset is within the limits of the segment.

Adds the base address of the segment from the segment descriptor to the offset to form a linear address.


If paging is not used, the processor maps the linear address directly to a physical address (that is, the linear address goes out on the processor’s address bus). If the linear address space is paged, a second level of address translation is used to translate the linear address into a physical address.


Segment Selector
A segment selector is a 16-bit identifier for a segment. It does not point directly to the segment, but instead points to the segment descriptor that defines the segment. A segment selector contains the following items:

Index
(Bits 3 through 15). Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base address of the GDT or LDT (from the GDTR or LDTR register, respectively).

TI (table indicator) flag
(Bit 2). Specifies the descriptor table to use: clearing this flag selects the GDT; setting this flag selects the current LDT.

Interrupt Descriptor Table

The Interrupt Descriptor Table, or IDT, is used in order to show the processor what Interrupt Service Routine (ISR) to call to handle either an exception or an 'int' opcode (in assembly). IDT entries are also called by Interrupt Requests whenever a device has completed a request and needs to be serviced.

Each IDT entry is similar to that of a GDT entry. Both have hold a base address, both hold an access flag, and both are 64-bits long. The major differences in these two types of descriptors is in the meanings of these fields. In an IDT, the base address specified in the descriptor is actually the address of the Interrupt Service Routine that the processor should call when this interrupt is 'raised' (called). An IDT entry doesn't have a limit, instead it has a segment that you need to specify. The segment must be the same segment that the given ISR is located in. This allows the processor to give control to the kernel through an interrupt that has occured when the processor is in a different ring (like when an application is running).

The access flags of an IDT entry are also similar to a GDT entry's. There is a field to say if the descriptor is actually present or not. There is a field for the Descriptor Privilege Level (DPL) to say which ring is the highest number that is allowed to use the given interrupt. The major difference is the rest of the access flag definition. The lower 5-bits of the access byte is always set to 01110 in binary. This is 14 in decimal. Here is a table to give you a better graphical representation of the access byte for an IDT entry.

7 6 5 4 0
P DPL Always 01110 (14)
P - Segment is present? (1 = Yes)
DPL - Which Ring (0 to 3)
Create a new file in your kernel directory called 'idt.c'. Edit your 'build.bat' file to add another line to make GCC also compile 'idt.c'. Finally, add 'idt.o' to the ever growing list of files that LD needs to link together to create your kernel. 'idt.c' will declare a packed structure that defines each IDT entry, the special IDT pointer structure needed to load the IDT (similar to loading a GDT, but alot less work!), and also declare an array of 256 IDT entries: This will become our IDT.

#include < system.h >

/* Defines an IDT entry */
struct idt_entry
{
unsigned short base_lo;
unsigned short sel; /* Our kernel segment goes here! */
unsigned char always0; /* This will ALWAYS be set to 0! */
unsigned char flags; /* Set using the above table! */
unsigned short base_hi;
} __attribute__((packed));

struct idt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));

/* Declare an IDT of 256 entries. Although we will only use the
* first 32 entries in this tutorial, the rest exists as a bit
* of a trap. If any undefined IDT entry is hit, it normally
* will cause an "Unhandled Interrupt" exception. Any descriptor
* for which the 'presence' bit is cleared (0) will generate an
* "Unhandled Interrupt" exception */
struct idt_entry idt[256];
struct idt_ptr idtp;

/* This exists in 'start.asm', and is used to load our IDT */
extern void idt_load();

This is the beginning half of 'idt.c'. Defines the vital data structures!
Again, like 'gdt.c', you will notice that there is a declaration of a function that physically exists in another file. 'idt_load' is written in assembly language just like 'gdt_flush'. All 'idt_load' is is calling the 'lidt' assembly opcode using our special IDT pointer which we create later in 'idt_install'. Open up 'start.asm', and add the following lines right after the 'ret' for '_gdt_flush':

; Loads the IDT defined in '_idtp' into the processor.
; This is declared in C as 'extern void idt_load();'
global _idt_load
extern _idtp
_idt_load:
lidt [_idtp]
ret

Add this to 'start.asm'
Setting up each IDT entry is alot easier than building a GDT entry. We have an 'idt_set_gate' function which accepts the IDT entry number, the base address of our Interrupt Service Routine, our Kernel Code Segment, and the access flags as outlined in the table introduced above. Again, we have an 'idt_install' function which sets up our special IDT pointer as well as clears out the IDT to a default known state of cleared. Finally, we would load the IDT by calling 'idt_load'. Please note that you can add ISRs to your IDT at any time after the IDT is loaded. More about ISRs later.

/* Use this function to set an entry in the IDT. Alot simpler
* than twiddling with the GDT ;) */
void idt_set_gate(unsigned char num, unsigned long base,
unsigned short sel, unsigned char flags)
{
/* We'll leave you to try and code this function: take the
* argument 'base' and split it up into a high and low 16-bits,
* storing them in idt[num].base_hi and base_lo. The rest of the
* fields that you must set in idt[num] are fairly self-
* explanatory when it comes to setup */
}

/* Installs the IDT */
void idt_install()
{
/* Sets the special IDT pointer up, just like in 'gdt.c' */
idtp.limit = (sizeof (struct idt_entry) * 256) - 1;
idtp.base = &idt;

/* Clear out the entire IDT, initializing it to zeros */
memset(&idt, 0, sizeof(struct idt_entry) * 256);

/* Add any new ISRs to the IDT here using idt_set_gate */

/* Points the processor's internal register to the new IDT */
idt_load();
}

The rest of 'idt.c'. Try to figure out 'idt_set_gate'. It's easy!
Finally, be sure to add 'idt_set_gate' and 'idt_install' as function prototypes in 'system.h'. Remember that we need to call these functions from other files, like 'main.c'. Call 'idt_install' from inside our 'main()' function, right after the call to 'gdt_install'. You should be able to compile your kernel without problems. Take some time to experiment a bit with your new kernel. If you try to do an illegal operation like dividing by zero, you will find that your machine will reset! We can catch these 'exceptions' by installing Interrupt Service Routines in our new IDT.

Global Descriptor Table

A vital part of the 386's various protection measures is the Global Descriptor Table, otherwise called a GDT. The GDT defines base access privileges for certain parts of memory. We can use an entry in the GDT to generate segment violation exceptions that give the kernel an opportunity to end a process that is doing something it shouldn't. Most modern operating systems use a mode of memory called "Paging" to do this: It is alot more versatile and allows for higher flexibility. The GDT can also define if a section in memory is executable or if it is infact, data. The GDT is also capable of defining what are called Task State Segments (TSSes). A TSS is used in hardware-based multitasking, and is not discussed here. Please note that a TSS is not the only way to enable multitasking.

Note that GRUB already installs a GDT for you, but if we overwrite the area of memory that GRUB was loaded to, we will trash the GDT and this will cause what is called a 'triple fault'. In short, it'll reset the machine. What we should do to prevent that problem is to set up our own GDT in a place in memory that we know and can access. This involves building our own GDT, telling the processor where it is, and finally loading the processor's CS, DS, ES, FS, and GS registers with our new entries. The CS register is also known as the Code Segment. The Code Segment tells the processor which offset into the GDT that it will find the access privileges in which to execute the current code. The DS register is the same idea, but it's not for code, it's the Data segment and defines the access privileges for the current data. ES, FS, and GS are simply alternate DS registers, and are not important to us.

The GDT itself is a list of 64-bit long entries. These entries define where in memory that the allowed region will start, as well as the limit of this region, and the access privileges associated with this entry. One common rule is that the first entry in your GDT, entry 0, is known as the NULL descriptor. No segment register should be set to 0, otherwise this will cause a General Protection fault, and is a protection feature of the processor.

Each GDT entry also defines whether or not the current segment that the processor is running in is for System use (Ring 0) or for Application use (Ring 3). There are other ring types, but they are not important. Major operating systems today only use Ring 0 and Ring 3. As a basic rule, any application causes an exception when it tries to access system or Ring 0 data. This protection exists to prevent an application from causing the kernel to crash. As far as the GDT is concerned, the ring levels here tell the processor if it's allowed to execute special privileged instructions. Certain instructions are privileged, meaning that they can only be run in higher ring levels. Examples of this are 'cli' and 'sti' which disable and enable interrupts, respectively. If an application were allowed to use the assembly instructions 'cli' or 'sti', it could effectively stop your kernel from running. You will learn more about interrupts in later sections of this tutorial.

Each GDT entry's Access and Granularity fields can be defined as follows:
7 6 5 4 3 0
P DPL DT Type
P - Segment is present? (1 = Yes)
DPL - Which Ring (0 to 3)
DT - Descriptor Type
Type - Which type?
7 6 5 4 3 0
G D 0 A Seg Len. 19:16
G - Granularity (0 = 1byte, 1 = 4kbyte)
D - Operand Size (0 = 16bit, 1 = 32-bit)
0 - Always 0
A - Available for System (Always set to 0)
In our tutorial kernel, we will create a GDT with only 3 entries. Why 3? We need one 'dummy' descriptor in the beginning to act as our NULL segment for the processor's memory protection features. We need one entry for the Code Segment, and finally, we need one entry for the Data Segment registers. To tell the processor where our new GDT table is, we use the assembly opcode 'lgdt'. 'lgdt' needs to be given a pointer to a special 48-bit structure. This special 48-bit structure is made up of 16-bits for the limit of the GDT (again, needed for protection so the processor can immediately create a General Protection Fault if we want a segment whose offset doesn't exist in the GDT), and 32-bits for the address of the GDT itself.

We can use a simple array of 3 entries to define our GDT. For our special GDT pointer, we only need one to be declared. We call it 'gp'. Create a new file, 'gdt.c'. Get gcc to compile your 'gdt.c' by adding a line to your 'build.bat' as outlined in previous sections of this tutorial. Once again, I remind you to add 'gdt.o' to the list of files that LD needs to link in order to create your kernel! Analyse the following code which makes up the first half of 'gdt.c':


#include < system.h >

/* Defines a GDT entry. We say packed, because it prevents the
* compiler from doing things that it thinks is best: Prevent
* compiler "optimization" by packing */
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));

/* Special pointer which includes the limit: The max bytes
* taken up by the GDT, minus 1. Again, this NEEDS to be packed */
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));

/* Our GDT, with 3 entries, and finally our special GDT pointer */
struct gdt_entry gdt[3];
struct gdt_ptr gp;

/* This will be a function in start.asm. We use this to properly
* reload the new segment registers */
extern void gdt_flush();

Managing your GDT with 'gdt.c'
You will notice that we added a declaration for a function that does not exist yet: gdt_flush(). gdt_flush() is the function that actually tells the processor where the new GDT exists, using our special pointer that includes a limit as seen above. We need to reload new segment registers, and finally do a far jump to reload our new code segment. Learn from this code, and add it to 'start.asm' right after the endless loop after 'stublet' in the blank space provided:

; This will set up our new segment registers. We need to do
; something special in order to set CS. We do what is called a
; far jump. A jump that includes a segment as well as an offset.
; This is declared in C as 'extern void gdt_flush();'
global _gdt_flush ; Allows the C code to link to this
extern _gp ; Says that '_gp' is in another file
_gdt_flush:
lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointer
mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump!
flush2:
ret ; Returns back to the C code!

Add these lines to 'start.asm'
It's not enough to actually reserve space in memory for a GDT. We need to write values into each GDT entry, set the 'gp' GDT pointer, and then we need to call gdt_flush() to perform the update. There is a special function which follows, called 'gdt_set_entry()', which does all the shifts to set each field in the given GDT entry to the appropriate value using easy to use function arguments. You must add the prototypes for these 2 functions (at very least we need 'gdt_install') into 'system.h' so that we can use them in 'main.c'. Analyse the following code - it makes up the rest of 'gdt.c':

/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(int num, unsigned long base, unsigned long limit,
unsigned char access, unsigned char gran)
{
/* Setup the descriptor base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;

/* Setup the descriptor limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);

/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}

/* Should be called by main. This will setup the special GDT
* pointer, set up the first 3 entries in our GDT, and then
* finally call gdt_flush() in our assembler file in order
* to tell the processor where the new GDT is and update the
* new segment registers */
void gdt_install()
{
/* Setup the GDT pointer and limit */
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;

/* Our NULL descriptor */
gdt_set_gate(0, 0, 0, 0, 0);

/* The second entry is our Code Segment. The base address
* is 0, the limit is 4GBytes, it uses 4KByte granularity,
* uses 32-bit opcodes, and is a Code Segment descriptor.
* Please check the table above in the tutorial in order
* to see exactly what each value means */
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

/* The third entry is our Data Segment. It's EXACTLY the
* same as our code segment, but the descriptor type in
* this entry's access byte says it's a Data Segment */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

/* Flush out the old GDT and install the new changes! */
gdt_flush();
}

Add this to 'gdt.c'. It does some of the dirty work relating to the GDT!
Don't forget the prototypes in 'system.h'!
Now that our GDT loading infrastructure is in place, and we compile and link it into our kernel, we need to call gdt_install() in order to actually do our work! Open 'main.c' and add 'gdt_install();' as the very first line in your main() function. The GDT needs to be one of the very first things that you initialize because as you learned from this section of the tutorial, it's very important. You can now compile, link, and send our kernel to our floppy disk to test it out. You won't see any visible changes on the screen: this is an internal change. Onto the Interrupt Descriptor Table (IDT)!

FAT File System

FAT. DOS File Allocation Table that is. The FAT is the first sectors on a disk that tell the operating system where to find a file, how much of it there is and where all the pieces are. It also marks the sectors as used, full or bad so the system can determine where to place a file. Ok...Sounds easy. This information can be found all over the internet. But have you ever tried to figure it out? Well I did, and after a lot of looking and reading and playing with disk, this is what I've learned...

The DOS disk is laid out in tracks, also called cylinders. Each track has sectors. And there are two sides, so 2 heads. That is where simple ends.

A disk can have any number of tracks, but most 1.44 floppies have 80. This is pretty easy but remember tracks are also called cylinders. Track 0 is on the outer edge of the disk with track 79 on the inter edge. All tracks have the same number of sectors even though the outer tracks have more room.

A track can have any number of sectors, but most 1.44 floppies have 18. This is set by a value in the Bios Parameter Block, BPB, which is in first of the boot sector.

A disk can also have clusters. The value can also be determined from the BPB. Clusters are a group of sectors. On most 1.44 floppies there are 1 sector per cluster.

Now...With all this, are you confused? Well, just hold on, it gets better.

Absolute Sector
Absolute sector is addressing the disk by head, track and sector. It is the way the controller and the BIOS access the disk. It is also the way to install you own bootstrap code or manually work on the FAT.So where is the FAT? Let's take a look.

HEAD 0 TRACK 0 SECTOR 1
This is the boot code sector. It is also where the BPB is located.

Program start.
Jump over data ;Three bytes off start.
NOP ;If short jump, must have NOP to make 3 bytes
OEM_ID db 8 bytes
BytesPerSector dw 0x0200 ;512 bytes per sector
SectorsPerCluster db 0x01 ;1 sector per cluster
ReservedSectors dw 0x0001 ;Reserved sectors.. ie boot sector
TotalFats db 0x02 ;2 copies of the FAT
MaxRootEntries dw 0x0E0 ;Number of entries in the root. 224
TotalSectors dw 0x0B40 ;Number of sectors in volume 2880
MediaDescriptor db 0xF0 ; 1.44 floppy
SectorsPerFat dw 0x0009 ;Number of sectors in a FAT 9
SectorsPerTrack dw 0x0012 ;Sectors per Track 18
NumHeads dw 0x0002 ;2 heads
HiddenSectors dd 0x00000000
TotalSectorsLarge dd 0x00000000
DriveNumber db 0x00
Flags db 0x00
Signature db 0x29
VolumeID dd 0xFFFFFFFF
VolumeLabel db "XXXXXXXXXXXX" ;12 bytes 8+"."+3
SystemID db "FAT12 " ;8 bytes

Code starts here.
At byte 510 is stored the word 0xAA55. This is what the BIOS looks for to see if the disk is a boot disk.

HEAD 0 TRACK 0 SECTOR 2
This is the start of the FAT. To defy logic, instead of going head 0 then head 1, DOS sticks with head 0 till sector 18. This can really confuse you at first. Anyway. From looking at the BPB we see that the FAT is 9 sectors long and there are two copies. So Starting here the FAT track table goes all the way to HEAD 0 TRACK 0 SECTOR 18

HEAD 1 TRACK 0 SECTOR 1
Because the boot sector took one sector, one sector of the second FAT wraps to head 1. So head 1, track 0 and sector 1 is the last sector of the FAT sector table.

HEAD 1 TRACK 0 SECTOR 2
This is the first sector of the FAT directory. This is where programs look to see what's on the disk. Each entry is 32 bytes long. The name of the file, date/time stamp, attribute, start sector and length are located here. 32 bytes times the number of entries, 224, gives 7168 bytes. Divide that by the the number of bytes per sector, 512, you get 14 sectors. So the directory table is 14 sectors long.

HEAD 1 TRACK 0 SECTOR 16
This is the first sector of the data area. It is here that the actual files are kept.

So. Now that we know where we are at, you might be asking how do you find the file. Well..Now for the really fun, fun stuff.

Logical Sector Addressing
The location of the file is marked in the FAT in logical sector addressing. Instead of saying the file is at head 0, track 0, sector 1, it is at sector 0. This does make it easier to store the location, but requires a couple of math formulas to get back to absolute sector.

If you take a look at a directory table you can see how this works.


The first 11 bytes are the file name: 0x44 0x4F 0x4E 0x54 0x44 0x41 0x4D 0x4E 0x4D 0x4F 0x44 Dontdamn mod DOS adds the period.

The next 1 byte is the attribute: 0x20 100000 Binary which stands for Archive Others are:

0x01 000001 Read only
0x02 000010 Hidden
0x04 000100 System file
0x08 001000 Volume ID
0x10 010000 Directory
The next 8 bytes are reserved:

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

The next 2 bytes are index in EA Data:

0x00 0x00

The next 4 bytes are the time/date stamp, encoded:

0x5A 0xA4 0x5D 0x18

The next 2 bytes are the entry cluster in the file chain:

0x02 0x00

The next 4 bytes are the file size in bytes:

0xFF 0x20 0x04 0x00

If you look at bytes 26 and 27,(remember to start counting from 0) 0x02 and 0x00 in this example. This is the starting cluster for this file. Because of the way DOS stores numbers, this is actually 0x00 0x02 or 2. So this file starts at cluster 2. More on how to find where this is in a minute.

First, let's look at the sector table at head 0, track 0 and sector 2.


Take your starting number, 2 and multiply it by 1.5. This gives you three. Now look at the sector table and get the third and fourth byte. 0x03 0x40 Reverse their order to get 0x4003. Because our starting number, 3, was a whole number we AND the hexadecimal number with 0x0FFF to get 3.

Multiply 3 by 1.5 to get 4.5. So go to the table and get the 4th and 5th bytes. 0x40 0x00 Reverse them 0x00 0x40 to get 0x40. Because 4.5 was not a whole number we shift 0x40 to the right to get 0x04. Or 4 decimal.

Multiply 4 by 1.5 to get 6. So go to the table and get the 6th and 7th bytes. 0x05 0x60 Reverse them 0x60 0x05 to get 0x6005. Because 6 is a whole number we AND 0x6005 and 0x0FFF to get 5.

Continue until you get the bytes 0xFF 0x0F. When they are reversed they form 0x0FFF which marks the end sector of the file.

To find the logical sectors, take the numbers and subtract 2: 3-2=1, 4-2=2, 5-2=3 and so on.

Multiply that number by the number of bytes in a sector. 1*0x200=0x200 2*0x200=0x400 3*0x200=600 and so on.

Now add that number to the offset of the FAT. On this disk:

1 reserved 0x0200
2 FATs at 9 sectors each. (9*2)*512 0x2400
1 Directory 224 entries of 32 byte 0x1C00
Total 0x4200
So add 0x200 to 0x4200 to get 0x4400. Now divide this by bytes per sector (512 or 0x200) to get logical sector 34
0x4200 + 0x400 = 0x4600 / 0x200 = 0x23 sector 35
0x4200 + 0x600 = 0x4800 / 0x200 = 0x24 sector 36
and so on till the end.

Now that we know the logical sector, you might be wondering how to get it to absolute sector so we can read it. Well we use a few formulas to figure it out. They are:

sector = ( logical sector MOD sector per track) + 1
head = (logical sector \ sector per track ) MOD number of heads
track = logical sector \ (sector per track * number of heads)
Note that this is integer division instead of floating point division. And also Modulo math. If you can't figure this out just turn on QBasic and enter a quick formula. So. Using the formulas above we get:

sector= (34 MOD 512)+1= 17
head= (34 \ 512) MOD 2= 1
track= 34 \ (512 *2)= 0
Our file starts at Head 1 Track 0 Sector 17
The next sector Head 1 Track 0 Sector 18
The next sector Head 0 Track 1 Sector 1
and so on till the end.
There you go. The skinny on the FAT. I hope that this helps someone. I know I did a lot of searching and playing with disk to figure this out. Most places tell you how it works but not where it is located on the disk.

A note for the OS programer. DOS does not read the second FAT. I have placed a 512 byte banner at the last sector of the second fat and called it on boot.

LBA to CHS Translation Introduction

Definations
Background and Assumptions
CHS Translation Algorithms


CHS translation is needed only for disk drives between 528MB and 8GB. Below 528MB CHS addresses do not require translation. Above 8GB CHS addressing is not supported by ATA hard disk drives or by system BIOS software. Most systems are finally making the switch to using LBA addressing on all capacity drives.

Introduction
This is very technical. Please read carefully. There is lots of information here that can sound confusing the first time you read it.

Why is an understanding of how a BIOS works so important? The basic reason is that the information returned by INT 13H AH=08H is used by FDISK, it is used in the partition table entries within a partition record (like the Master Boot Record) that are created by FDISK, and it is used by the small boot program that FDISK places into the Master Boot Record. The information returned by INT 13H AH=08H is in cylinder/head/sector (CHS) format, it is not in LBA format. The boot processing done by your computer's BIOS (INT 19H and INT 13H) is all CHS based.

Read this so that you are not confused by all the false information going around that says "LBA solves the >528MB problem".

Read this so that you understand the possible data integrity problem that a WD EIDE type BIOS creates. Any BIOS that has a "LBA mode" in the BIOS setup could be a WD EIDE BIOS. Be very careful and NEVER chage the "LBA mode" setting after you have partitioned and installed your software.


Definitions
528MB - The maximun drive capacity that is supported by 1024 cylinders, 16 heads and 63 sectors (1024x16x63x512). This is the limit for CHS addressing in the original IBM PC/XT and IBM PC/AT INT 13H BIOS.
8GB - The maximum drive capacity that can be supported by 1024 cylinders, 256 heads and 63 sectors (1024x256x63x512). This is the limit for the BIOS INT 13H AH=0xH calls.
ATA - AT Attachment. The real name of what is widely known as IDE.
CE Cylinder - Customer Engineering cylinder. This is the last cylinder in P-CHS mode. IBM has always reserved this cylinder for use of disk diagnostic programs. Many BIOS do not account for it correctly. It is of questionable value these days and probably should be considered obsolete. However, since there is no industry wide agreement, beware. There is no CE Cylinder reserved in the L-CHS address. Also beware of diagnostic programs that do not realize they are operating in L-CHS mode and think that the last L-CHS cylinder is the CE Cylinder.
CHS - Cylinder/Head/Sector. This is the traditional way to address sectors on a disk. There are at least two types of CHS addressing: the CHS that is used at the INT 13H interface and the CHS that is used at the ATA device interface. In the MFM/RLL/ESDI and early ATA days the CHS used at the INT 13H interface was the same as the CHS used at the device interface.
Today we have CHS translating BIOS types that can use one CHS at the INT 13H interface and a different CHS at the device interface. These two types of CHS will be called the logical CHS or L-CHS and the physical CHS or P-CHS in this document. L-CHS is the CHS used at the INT 13H interface and P-CHS is the CHS used at the device interface.

The L-CHS used at the INT 13 interface allows up to 256 heads, up to 1024 cylinders and up to 63 sectors. This allows support of up to 8GB drives. This scheme started with either ESDI or SCSI adapters many years ago.

The P-CHS used at the device interface allows up to 16 heads up to 65535 cylinders, and up to 63 sectors. This allows access to 2^28 sectors (136GB) on an ATA device. When a P-CHS is used at the INT 13H interface it is limited to 1024 cylinders, 16 heads and 63 sectors. This is where the old 528MB limit originated.

ATA devices may also support LBA at the device interface. LBA allows access to approximately 2^28 sectors (137GB) on an ATA device.

A SCSI host adapter can convert a L-CHS directly to an LBA used in the SCSI read/write commands. On a PC today, SCSI is also limited to 8GB when CHS addressing is used at the INT 13H interface.

EDPT - Enhanced fixed Disk Parameter Table. This table returns additional information for BIOS drive numbers 80H and 81H. The EDPT for BIOS drive 80H is pointed to by INT 41H. The EDPT for BIOS drive 81H is pointed to by INT 46H. The EDPT is a fixed disk parameter table with an AxH signature byte. This table format returns two sets of CHS information. One set is the L-CHS and is probably the same as returned by INT 13H AH=08H. The other set is the P-CHS used at the drive interface. This type of table allows drives with >1024 cylinders or drives >528MB to be supported. The translated CHS will have <=1024 cylinders and (probably) >16 heads. The CHS used at the drive interface will have >1024 cylinders and <=16 heads. It is unclear how the IBM defined CE cylinder is accounted for in such a table. Compaq probably gets the credit for the original definition of this type of table.
FDPT - Fixed Disk Parameter Table - This table returns additional information for BIOS drive numbers 80H and 81H. The FDPT for BIOS drive 80H is pointed to by INT 41H. The FDPT for BIOS drive 81H is pointed to by INT 46H. A FDPT does not have a AxH signature byte. This table format returns a single set of CHS information. The L-CHS information returned by this table is probably the same as the P-CHS and is also probably the same as the L-CHS returned by INT 13H AH=08H. However, not all BIOS properly account for the IBM defined CE cylinder and this can cause a one or two cylinder difference between the number of cylinders returned in the AH=08H data and the FDPT data. IBM gets the credit for the original definition of this type of table.
LBA - Logical Block Address. Another way of addressing sectors that uses a simple numbering scheme starting with zero as the address of the first sector on a device. The ATA standard requires that cylinder 0, head 0, sector 1 address the same sector as addressed by LBA 0. LBA addressing can be used at the ATA interface if the ATA device supports it. LBA addressing is also used at the INT 13H interface by the AH=4xH read/write calls.
L-CHS, Logical CHS. The CHS used at the INT 13H interface by the AH=0xH calls. See CHS above.
MBR - Master Boot Record (also known as a partition table) - The sector located at cylinder 0 head 0 sector 1 (or LBA 0). This sector is created by an "FDISK" utility program. The MBR may be the only partition table sector or the MBR can be the first of multiple partition table sectors that form a linked list. A partition table entry can describe the starting and ending sector addresses of a partition (also known as a logical volume or a logical drive) in both L-CHS and LBA form. Partition table entries use the L-CHS returned by INT 13H AH=08H. Older FDISK programs may not compute valid LBA values.
OS - Operating System.
P-CHS, Physical CHS. The CHS used at the ATA device interface. This CHS is also used at the INT 13H interface by older BIOS's that do not support >1024 cylinders or >528MB. See CHS above.


Background and Assumptions
First, please note that this is written with the OS implementor in mind and that I am talking about the possible BIOS types as seen by an OS during its hardware configuration search.

It is very important that you not be confused by all the misinformation going around these days. All OS's that want to be co-resident with another OS (and that is all of the PC based OS's that I know of) MUST use INT 13H to determine the capacity of a hard disk. And that capacity information MUST be determined in L-CHS mode. Why is this? Because: 1) FDISK and the partition tables are really L-CHS based, and 2) MS/PC DOS uses INT 13H AH=02H and AH=03H to read and write the disk and these BIOS calls are L-CHS based. The boot processing done by the BIOS is all L-CHS based. During the boot processing, all of the disk read accesses are done in L-CHS mode via INT 13H and this includes loading the first of the OS's kernel code or boot manager's code.

Second, because there can be multiple BIOS types in any one system, each drive may be under the control of a different type of BIOS. For example, drive 80H (the first hard drive) could be controlled by the original system BIOS, drive 81H (the second drive) could be controlled by a option ROM BIOS and drive 82H (the third drive) could be controlled by a software driver. Also, be aware that each drive could be a different type, for example, drive 80H could be an MFM drive, drive 81H could be an ATA drive, drive 82H could be a SCSI drive.

Third, not all OS's understand or use BIOS drive numbers greater than 81H. Even if there is INT 13H support for drives 82H or greater, the OS may not use that support.

Fourth, the BIOS INT 13H configuration calls are:

AH=08H, Get Drive Parameters. This call is restricted to drives up to 528MB without CHS translation and to drives up to 8GB with CHS translation. For older BIOS with no support for >1024 cylinders or >528MB, this call returns the same CHS as is used at the ATA interface (the P-CHS). For newer BIOS's that do support >1024 cylinders or >528MB, this call returns a translated CHS (the L-CHS). The CHS returned by this call is used by FDISK to build partition records.
AH=41H, Get BIOS Extensions Support. This call is used to determine if the IBM/Microsoft Extensions or if the Phoenix Enhanced INT 13H calls are supported for the BIOS drive number.
AH=48H, Extended Get Drive Parameters. This call is used to determine the CHS geometries, LBA information and other data about the BIOS drive number.
The FDPT or EDPT. While not actually a call, but instead a data area, the FDPT or EDPT can return additional information about a drive.
Other tables. The IBM/Microsoft extensions provide a pointer to a drive parameter table via INT 13H AH=48H. The Phoenix enhancement provides a pointer to a drive parameter table extension via INT 13H AH=48H. These tables are NOT the same as the FDPT or EDPT.
Note: The INT 13H AH=4xH calls duplicate the older AH=0xH calls but use a different parameter passing structure. This new structure allows support of drives with up to 2^64 sectors (really BIG drives). While at the INT 13H interface the AH=4xH calls are LBA based, these calls do NOT require that the drive support LBA addressing.


CHS Translation Algorithms
As you read this, do not forget that all of the boot processing done by the system BIOS via INT 19H and INT 13H use only the INT 13H AH=0xH calls and that all of this processing is done in CHS mode.

First, lets review all the different ways a BIOS can be called to perform read/write operations and the conversions that a BIOS must support.

An old BIOS (like BIOS type 1 below) does no CHS translation and does not use LBA. It only supports the AH=0xH calls:
INT 13H (L-CHS == P-CHS) ATA
AH=0xH --------------------------------> device
(L-CHS) (P-CHS)

A newer BIOS may support CHS translation and it may support LBA at the ATA interface:
INT 13H L-CHS ATA
AH=0xH --+--> to --+----------------> device
(L-CHS) | P-CHS | (P-CHS)
| |
| | P-CHS
| +--> to --+
| LBA |
| |
| L-CHS | ATA
+--> to -----------------+---> device
LBA (LBA)

A really new BIOS may also support the AH=4xH in addtion to the older AH\0xH calls. This BIOS must support all possible combinations of CHS and LBA at both the INT 13H and ATA interfaces:
INT 13H ATA
AH=4xH --+-----------------------------> device
(LBA) | (LBA)
|
| LBA
+--> to ---------------+
P-CHS |
|
INT 13H L-CHS | ATA
AH=0xH --+--> to --+------------+---> device
(L-CHS) | P-CHS | (P-CHS)
| |
| | P-CHS
| +--> to --+
| LBA |
| |
| L-CHS | ATA
+--> to -----------------+---> device
LBA (LBA)

You would think there is only one L-CHS to P-CHS translation algorithm, only one L-CHS to LBA translation algorithm and only one P-CHS to LBA translation algorithm. But this is not so. Why? Because there is no document that standardizes such an algorithm. You can not rely on all BIOS's and OS's to do these translations the same way.

The following explains what is widely accepted as the "correct" algorithms.

An ATA disk must implement both CHS and LBA addressing and must at any given time support only one P-CHS at the device interface. And, the drive must maintain a strick relationship between the sector addressing in CHS mode and LBA mode. Quoting the ATA-2 document:

LBA = ( (cylinder * heads_per_cylinder + heads ) * sectors_per_track ) + sector - 1

This algorithm can be reversed such that an LBA can be converted to a CHS:


cylinder = LBA / (heads_per_cylinder * sectors_per_track)
temp = LBA % (heads_per_cylinder * sectors_per_track)
head = temp / sectors_per_track
sector = temp % sectors_per_track + 1

Protected Mode! What is Protected Mode ?

The 80386+ provides many new features to overcome the deficiencies of 8086 which has almost no support for memory protection, virtual memory, multitasking, or memory above 640K - and still remain compatible with the 8086 family. The 386 has all the features of the 8086 and 286, with many more enhancements. As in the earlier processors, there is the real mode. Like the 286, the 386 can operate in protected mode. However, the protected mode on 386 is vastly different internally. Protected mode on the 386 offers the programmer better protection and more memory than on the 286. The purpose of protected mode is not to protect your program. The purpose is to protect everyone else (including the operating system) from your program.

Protected Mode vs Real Mode
Superficially protected mode and real mode don't seem to be very different. Both use memeory segmentation, interrupts and device drivers to handle the hardware. But there are differences which justify the existence of two separate modes. In real mode, we can view memory as 64k segments atleast 16bytes apart. Segmentation is handled through the use of an internal mechanism in conjunction with segment registers. The contents of these segment registers (CS,DS,SS...) form part of the physical address that the CPU places on the addresss bus. The physical address is generated by multiplying the segment register by 16 and then adding a 16 bit offset. It is this 16 bit offset that limits us to 64k segments.

fig 1: Real Mode Addressing

In protected mode, segmentation is defined via a set of tables called descriptor tables. The segment registers contain pointers into these tables. There are two types of tables used to define memory segmentation : The Global Descriptor Table and The Local Descriptor Table. The GDT contains the basic descriptors that all applications can access. In real mode one segment is 64k big followed by the next in a 16 byte distance. In protected mode we can have a segment as big as 4Gb and we can put it wherever we want. The LDT contains segmentation information specific to a task or program. An OS for instance could set up a GDT with its system descriptors and for each task an LDT with appropriate descriptors. Each descriptor is 8 bytes long. The format is given below (fig 3). Each time a segment register is loaded, the base address is fetched from the appropriate table entry. The contents of the descriptor is stored in a programmer invisible register called shadow registers so that future references to the same segment can use this information instead of referencing the table each time. The physical address is formed by adding the 16 or 32 bit offset to the base address in the shadow register.These differences are made clear in figures 1 and 2.

fig 2: Protected Mode Addressing

fig 3: Segment Descriptor Format


We have yet another table called the interrupt descriptor table or the IDT. The IDT contains the interrupt descriptors. These are used to tell the processor where to find the interrupt handlers. It contains one entry per interrupt, just like in Real Mode, but the format of these entries is totally different.

Entering Protected Mode
The 386 has four 32 bit control registers named CR0, CR1, CR2 and CR3. CR1 is reserved for future processors, and is undefined for the 386. CR0 contains bits that enable and disable paging and protection and bits that control the operation of the floating point coprocessor. CR2 and CR3 are used by the paging mechanism. We are concerned with bit 0 of the CR0 register or the PE bit or the protection enable bit. When PE = 1, the processor is said to be operating in protected mode with the segmentation mechanism we described earlier. If PE = 0, the processor operates in real mode. The 386 also has the segmentation table base registers like GDTR, LDTR and IDTR.These registers address segments that contain the descriptor tables. The GDTR points to the GDT. The 48 bit GDTR defines the base and the limit of the GDT directly with a 32 bit linear address and a 16 bit limit.

Switching to protected mode essentially implies that we set the PE bit. But there are a few other things that we must do. The program must initialise the system segments and control registers. Immediately after setting the PE bit to 1 we have to execute a jump instruction to flush the execution pipeline of any instructions that may have been fetched in the real mode. This jump is typically to the next instruction. The steps to switch to protected mode then reduces to the following:

Build the GDT
Enable protected mode by setting the PE bit in CR0
Jump to clear the prefetch queue
So by doing this you will be in the world of protected mode.

Real mode Introduction

Real mode as its name suggest is real, that means everything is real in it. The memory which is addressed is real i.e. it really exists at the addressed location. The real mode was put into the processors after 80286 so that they run programs of their predecessor and also for loading the operating system and switching into the Protected mode.

In real addressing mode the memory is addressed by 16bits of addresses. That is you can address at most (2^16=) 1 MB of physical memory.

Memory Structure
Linear Address Range Memory Type Use
0 - 3FF Ram Real-mode Interrupt Vector Table (IVT)
400 - 4FF Ram Bios Data Ares (BDA)
500 - 9FBFF Ram Free Memory (Below 1 MB) 630K
9FC00 - 9FFFF Ram Extended Bios Data Area (EBDA)
A0000 - BFFFF Video Ram VGA Frame buffer
C0000 - C7FFF Rom Video Bios 32K
C8000 - EFFFF Nothing
F0000 - FFFFF Rom Motherboard Bios 64K

Programming Floppy Disk Controllers Overview

The PC usually uses the NEC µPD765 floppy disk controller. The AT can also incorporate an Intel 82072A controller, while the PS/2 uses an Intel 82077A. The µPD765 and the ROM code in the controller form a microcontroller, and this handles the majority of the work of the controller. All PC compatibles have FDCs which are compatible with the controllers described in this document.

This document describes the registers used to interface with the controller, and the commands which it recognises.

There are a lot of delays involved in communicating with the controller. These delays are for a variety of reasons, including the time needed to spin up the drive motor, and the time taken to move the head to a new position and wait for it to settle in place.

When the drive motor is started up or the a seek is requested, there will be a delay until the drive is ready for the next command. An interrupt is issued by the hardware when it is ready for the next command, and this will tell you that the drive is ready for your command.

In a single tasked environment (such as DOS), the only option is to have your driver constantly wait for an interrupt, and then respond to it. However, in a multi-tasked or multi-threaded environment, it is perfectly acceptable to write a driver which allows other tasks to be executed while waiting for the interrupt.

When performing a read or write operation, data may be transferred a byte at a time by reading from or writing to the appropriate port, or a sector/track at a time through the use of DMA channel 2. Programming the DMA controller is beyond the scope of this document, but will be described in a future document to be added to this site.

Configuration of an FDC on a PC
The Floppy Controller on a PC uses a standard configuration. On the XT there are 3 ports available for control and data access registers. On the AT, there are 4, and on the PS/2 there are 6.

The base port address used for the controller is dependant on whether the controller is configured as the primary or secondary controller. This base address controls the port addresses used for each of the registers on the controller. It can additionally be noted that all floppy controllers on a PC use DMA channel 2 for data transfer during a read or write, and they all issue a hardware interrupt via IRQ6 to be serviced by INT 0eh by default.


Primary Address Secondary Address Write (W) / Read (R)
base address 3f0h 370h
status register A (PS/2) 3f0h 370h R
status register B (PS/2) 3f1h 371h R
digital output register DOR 3f2h 372h W
main status register 3f4h 374h R
data rate select register (DSR)(PS/2) 3f4h 374h W
data register 3f5h 375h R/W
digital input register DIR (AT) 3f7h 377h R
configuration control register (AT) 3f7h 377h W
DMA channel 2 2
IRQ 6 6
INTR 0eh 0eh
Note that the controller can be configured differently from the defaults for handling interrupts.


FDC Registers
This section gives more detailed information on the use of the registers listed in the above table. Additionally, the first registers to be described are those common to each system described, and these will be followed by descriptions of AT specific registers and PS/2 specific registers.

Common Registers:

Digital Output Register (DOR)
Main Status Register (MSR)
Data Register (DR)
AT Specific Registers:

Digital Input Register (DIR)
Configuration Control Register (CCR)
PS/2 Specific Registers:

Status Register A
Status Register A
Data Rate Select Register
Digital Output Register
7 6 5 4 3 2 1 0
MOTD MOTC MOTB MOTA DMA REST DR1 DR2

MOTD, MOTC, MOTB, MOTA: Motor control for floppy drive D, C, B, A
1 = Start Motor 0 = Stop Motor
DMA: DMA and IRQ channel
1 = Enabled 0 = Disabled
REST: Controller reset
1 = Controller enabled 0 = Execute controller reset
DR1,DR0: Drive select
00 = drive 0 (A)
01 = drive 1 (B)
10 = drive 2 (C)
11 = drive 3 (D)
This register is write only, and controls the drive motors, as well as selecting a drive and the DMA/IRQ mode, and resetting the controller.

If the REST bit is set, the controller is enabled, in order to accept and execute commands. If it is equal to 0, the controller ignores all commands and carries out an internal reset of all internal registers (except the DOR).

Note that a drive cannot be selected unless its motor is on, although setting the bits at the same time is acceptable.

Note also that drives 2 and 3 (floppy drives C and D) are not supported in all systems.

Example: To start the motor

To start the motor on drive A and select it, ready for another operation, you would use the following:

MOTA = 1 (start motor for drive A)
DMA = 1 (assuming you want to use DMA and interrupts)
REST = 1 (Controller enabled, otherwise no other commands will be executed)
DR1,DR0 = 00 (Select drive A)
All other bits are set to 0
Thus 16 + 8 + 4 + 0 = 28, or 01Ch, is sent to port 3f2h (Drive A is usually on the primary controller).

In assembly language, this would be written as:

mov al,01ch
mov dx,03f2h
out dx,al

Example: To reset the controller
To reset the controller, send 0 to port 3f2h. This turns off all motors, selects no drives (because drive A's motor is not active, it cannot be selected), disables the DMA and IRQ line, and resets the controller.

The code for this is as follows:

mov al,0
mov dx,03f2h
out dx,al
Main Status Register
7 6 5 4 3 2 1 0
MRQ DIO NDMA BUSY ACTD ACTC ACTB ACTA


MRQ: Main Request
1 = Data Register ready 0 = Data Register not ready
DIO: Data input/output
1 = Controller ? CPU 0 = CPU ? Controller
NDMA: non-DMA mode
1 = Controller not in DMA mode 0 = Controller in DMA mode
BUSY: Instruction (device busy)
1 = active 0 = not active
ACTD, ACTC, ACTB, ACTA: Drive D, C, B, A in positioning mode
1 = active 0 = not active
The MSR is read-only, and contains the controller's status information. This register can be read whatever else the controller is doing.

It should be noted that this register is different from status registers ST0-ST3, which contain data concerning the last command executed. These registers are accessed via the data registers.

Bit 7 (MRQ) indicates whether the controller is ready to receive or send data or commands via the data register.

DIO is used to provide an indication of whether the controller is expecting to receive data from the CPU, or if it wants to output data to the CPU.

If the controller is set up to use DMA channel 2 to transfer data to or from main memory, the NDMA bit is not set. If this bit is set, data transfer is carried out exclusively by means of read or write commands to the data register. In this case, the controller issues a hardware interrupt every time that it either expects to receive or wants to supply a data byte.

Bit 4 indicates whether the controller is busy or not. If the bit is set, the controller is currently executing a command.

Bits 0-3 indicate which (if any) drive is currently in the process of positioning it's read/write heads, or being recalibrated.

Note that the delay waiting for the controller to be ready for a read or write can be as much as 175µs on an Intel controller, and longer on older controllers.

Example: To test whether the controller is ready to receive commands and data, it is necessary to test MRQ and DIO. This involves reading the port, masking the bits, and doing a comparison on the result (to test the values of the bits).

mrqloop:
mov dx,03f4h
in al,dx
and al,0c0h
cmp al,080h
jne mrqloop
This code will keep looping until the controller says that it is ready to receive data. Note that if the controller is expecting to output data to the CPU, this code will not spot that. You would need to add another couple of lines of code if you need to check for both possibilities (In general, you would know from previous commands whether the controller should be expecting or offering data, and so only need to check for one condition).



Data Register
The data register is an 8 bit register, like each of the other registers, which provides indirect access to a stack of registers. A command can be one to nine bytes in length, and the first byte tells the controller how many more bytes to expect. The controller sends the command bytes to the correct registers in it's stack, saving the programmer from the need to use a separate index register, as is the case in some other devices (e.g. some VGA registers).

Some controllers, such as the i82077A, have a buffer, with a programmable threshold, allowing the data to be transferred several bytes at a time. This helps to speed up the transfer of data and commands, as well as reducing the response time seen on the µPD765.


Status Register ST0
7 6 5 4 3 2 1 0
IC1 IC0 SE UC NR HD US1 US0


IC1, IC0: Interrupt Code
00 = normal termination;
command terminated without any errors
01 = abnormal termination;
the controller started execution of the command, but couldn't terminate it correctly
10 = invalid command;
the controller could not start command execution
11 = abnormal termination by polling;
drive became not ready
SE: Seek End
The controller has completed a seek or a calibration command,
or has correctly executed a read or write command which has an implicit seek
UC: Unit Check
Set if the drive faults or if a recalibrate cannot find track 0 after 79 pulses.
NR: drive not ready
HD: head currently active
1 = head 1 0 = head 0
US1 , US0: currently selected drive (unit select)
00 = drive 0 (A:)
01 = drive 1 (B:)
10 = drive 2 (C:)
11 = drive 3 (D:)