Input/Output

Input Output

Programs in previous chapters have used the input macro to input data from the PC console keyboard and the output macro to output data to the console display. Input and output from an assembly language program have been limited to the keyboard and the monitor. This chapter examines the underlying operating system calls that are used by the input and output macros. It then examines similar operating system calls that make it possible to read and write sequential files to secondary storage. Next it looks at the 80x86 instructions that actually do input and output and discusses alternative I/O schemes, including memory-mapped and interrupt-driven I/O.

Console I O Using the Kernel32 Library

Figure 12.1 shows a simple example illustrating how kernel32 functions can write a simple message. This example is similar to many of those seen previously in the book in its overall structure. However, it is missing the "standard" directive INCLUDE io.h. In addition to the familiar prototype for the ExitProcess function, it contains two new function prototypes. These functions are needed to write to the console.

; Program to display a simple message ; Author: R. Detmer ; Date: 6/98 .386 .MODEL FLAT ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD GetStdHandle PROTO NEAR32 stdcall, nStdHandle:DWORD WriteFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToWrite:DWORD, lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32 STD_OUTPUT EQU -11 cr EQU 0dh ; carriage return character Lf EQU 0ah ; line feed .STACK .DATA OldProg BYTE ''Old programmers never die.'', cr, lf BYTE ''They just lose their byte.'', cr, lf msgLng DWORD 56 ; number of characters in above message written DWORD ? hStdOut DWORD ? .CODE _start: INVOKE GetStdHandle, ; get handle for console output STD_OUTPUT mov hStdOut, eax INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR OldProg, ; address of string msgLng, ; length of string NEAR32 PTR written, ; bytes written 0 ; overlapped mode INVOKE ExitProcess, 0 ; exit with return code 0 PUBLIC _start END

Figure 12.1: Console output using kernel32 functions

The Windows 95/98/NT operating systems are similar to many others in that they treat input/output devices and disk files in a uniform manner. Note that in Fig. 12.1, a WriteFile call is used to display a message on the console. This same function can be used to write to a disk file. The device or file used for I/O is identified by its handle, a doubleword value in an assembly language program. The handle value must be obtained before the WriteFile call is made. There is more than one way to do this for a console file; GetStandardHandle provides an easy method.

Any GetStdHandle call has a single parameter; a numeric value, distinct from the handle, indicates the particular device. There are three standard devices: one for input, one for output, and one to report errors (normally the same as the standard output device). Each device number is usually equated to a symbol, and these symbols are used in code. We will only use the input and output devices; their numbers and names appear in Fig. 12.2. GetStdHandle is a function, returning in EAX a handle for the standard I/O device. The handle value is usually stored in memory to be available later. In the sample program, the returned value is immediately copied to the doubleword referenced by hStdOut.

Mnemonic

Equated Value

STD_INPUT

–10

STD_OUTPUT

–11

Figure 12.2: Standard device numbers

With five parameters, a WriteFile call is more complicated. The first is the handle that identifies the file—this handle is returned by GetStdHandle, not the device number. The second parameter is the address of the string—note the use of the NEAR32 PTR operator in the example to tell the assembler to use the address of OldProg rather than the value stored there. The third parameter is a doubleword containing the number of bytes to be displayed. The next parameter is used to return a value to the calling program. This value indicates how many bytes were actually written. In the case of output to the console, this will be the length of the message unless an error occurs. The fifth and final parameter will always be 0 in this book's examples. It can be used to indicate non-sequential access to some files, but we are going to deal only with sequential access.

Console input is almost as easy as output. Figure 12.3 shows a program that inputs a string of characters, converts each uppercase letter to lowercase, and displays the resulting string.

; Program to input a message and echo it in lowercase ; Author: R. Detmer ; Date: 6/98 .386 .MODEL FLAT ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD GetStdHandle PROTO NEAR32 stdcall, nStdHandle:DWORD ReadFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToRead:DWORD, lpNumberOfBytesRead:NEAR32, lpOverlapped:NEAR32 WriteFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToWrite:DWORD, lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32 STD_INPUT EQU -10 STD_OUTPUT EQU -11 .STACK .DATA prompt BYTE ''String to convert? '' CrLf BYTE 0ah, 0dh StrIn BYTE 80 DUP (?) read DWORD ? written DWORD ? hStdIn DWORD ? hStdOut DWORD ? .CODE _start: INVOKE GetStdHandle, ; get handle for console output STD_OUTPUT mov hStdOut, eax INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR prompt, ; address of prompt 19, ; length of prompt NEAR32 PTR written, ; bytes written 0 ; overlapped mode INVOKE GetStdHandle, ; get handle for console input STD_INPUT mov hStdIn, eax INVOKE ReadFile, hStdIn, ; file handle for keyboard NEAR32 PTR StrIn, ; address of string 80, ; maximum number to read NEAR32 PTR read, ; bytes read 0 ; overlapped mode mov ecx, read ; set up loop to convert lea ebx, StrIn ; starting address forCh: cmp BYTE PTR [ebx], 'A' ; char < 'A' ? jl endIfUpper ; skip if so cmp BYTE PTR [ebx], 'Z' ; char > 'Z' ? jg endIfUpper ; skip if so add BYTE PTR [ebx], 'a' - 'A' ; convert to lower endIfUpper: inc ebx ; point at next character loop forCh ; repeat mov ecx, read ; get length to write add ecx, 2 ; for leading CR and LF INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR crLf, ; start with ecx, ; length of output NEAR32 PTR written, ; bytes written 0 ; overlapped mode INVOKE ExitProcess, 0 ; exit with return code 0 PUBLIC _start END

Figure 12.3: Console I/O using kernel32 functions

The new function in this example is Readfile. It is very similar to WriteFile except that the second parameter has the address of an input buffer, the third parameter gives the maximum number of characters to read, and the fourth parameter returns the number of characters actually read.

The number of characters read will normally be smaller than the size of the buffer to receive the characters. If it is larger, values in memory following the input buffer may be destroyed. An additional consideration with console input is that carriage return and linefeed characters are added to the characters that you key in. That is, if you type six characters and then press Enter, eight characters will actually be stored in the input buffer—the six characters plus the carriage return and linefeed.

In the program from Fig. 12.3, there is a blank line of output before the line of lowercase characters, which is because of the CR/LF that is in memory before the input buffer. The starting address for output includes these two additional characters and the character count has been increased by two to include these characters. Because the original character count includes the CR/LF at the end of the characters read in, there will also be a skip to a new line after the characters are displayed.

The input and output macros that you have used in most of this book expand into procedure calls that use the kernel32 console input/output functions. The relevant portion of the file IO.ASM is shown in Fig. 12.4.

STD_OUTPUT EQU -11 STD_INPUT EQU -10 GetStdHandle PROTO NEAR32 stdcall, nStdHandle:DWORD ReadFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToRead:DWORD, lpNumberOfBytesRead:NEAR32, lpOverlapped:NEAR32 WriteFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToWrite:DWORD, lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32 .DATA written DWORD ? read DWORD ? strAddr DWORD ? strLength DWORD ? hStdOut DWORD ? hStdIn DWORD ? .CODE ; outproc(source) ; Procedure to display null-terminated string ; No registers are changed; flags are not affected. outproc PROC NEAR32 push ebp ; save base pointer mov ebp, esp ; establish stack frame pushad pushfd ; save flags mov esi,[ebp+8] ; source address mov strAddr, esi ; find string length mov strLength, 0 ; initialize string length WhileChar: cmp BYTE PTR [esi], 0 ; character = null? jz EndWhileChar ; exit if so inc strLength ; increment character count inc esi ; point at next character jmp WhileChar EndWhileChar: INVOKE GetStdHandle, ; get handle for console output STD_OUTPUT mov hStdOut, eax INVOKE WriteFile, hStdOut, ; file handle for screen strAddr, ; address of string strLength, ; length of string NEAR32 PTR written, ; bytes written 0 ; overlapped mode popfd ; restore flags popad ; restore registers pop ebp ret 4 ;exit, discarding parameter outproc ENDP ; inproc(dest,length) ; Procedure to input a string from keyboard. ; The string will be stored at the address given by dest. ; The length parameter gives the size of the user's buffer. It is ; assumed that there will be room for the string and a null byte. ; The string will be terminated by a null character (00h). ; Flags are unchanged. inproc PROC NEAR32 push ebp ; save base pointer mov ebp, esp ; establish stack frame pushad ; save all registers pushfd ; save flags INVOKE GetStdHandle, ; get handle for console STD_INPUT mov hStdIn, eax mov ecx, [ebp+8] ; string length mov strLength, ecx mov esi, [ebp+12] ; source address mov strAddr, esi INVOKE ReadFile, hStdIn, ; file handle for keyboard strAddr, ; address of string strLength, ; length of string NEAR32 PTR read, ; bytes read 0 ; overlapped mode mov ecx, read ; number of bytes read mov BYTE PTR [esi+ecx-2],0 ; replace CR/LF by trailing null popfd ; restore flags popad ; restore registers pop ebp ret 8 ; exit, discarding parameters inproc ENDP

Figure 12.4: Input/output procedures in IO.ASM

At this point there is nothing surprising in the input/output code in IO.ASM. It starts with the same directives that appeared in the previous two examples. The data area does not include an input buffer since this will be in the user's calling program. It does have the variable strAddr to locally store the input or output buffer address that is passed as a parameter. The output procedure outproc expects this to be the address of a null-terminated string. After standard procedure entry code, it computes the length of that string. It then gets the handle for the console and writes to the console, exactly as in the earlier example in Fig. 12.1.

The input procedure inproc is also simple. After standard procedure entry code, it gets the handle for the console and copies the two parameters (length and string address) to local variables. A ReadFile call does the actual input. The only complication is that the inproc procedure promises a null-terminated string, and the string read by ReadFile is terminated by the CR/LF. The code

mov ecx, read mov BYTE PTR [esi+ecx-2],0

places a null byte at the end of the string, actually replacing the carriage return character by a null. It works because the starting address of the string is in ESI, so that when the character count is put in ECX, ESI+ECX–2 points to the address of the next-to-last character in the input buffer.

This is an appropriate time to repeat the warning from Section 6.1: some Microsoft operating system functions may require that the stack be doubleword-aligned. When these functions are used in procedures, you must only push doubleword values onto the stack. This is, for instance, why the code in Fig. 12.4 contains a pushfd instruction even though a pushf would save all the flag values that are meaningful to most programs.

Programming Exercises 12.1

  1. Using only functions from kernel32—and without using the book's I/O package—write a program that will prompt for and input a name from the console in the form last, first (that is, last name, comma, first name) and display it with an appropriate label in the format first last (that is, first name, space, last name).
  2. Using only functions from kernel32—and without using the book's I/O package—write a program that will prompt for and input a phrase from the console and will report whether or not it is a palindrome (that is, exactly the same string when reversed).

Sequential File I O Using the Kernel32 Library

File processing applications generally involve opening the file, reading from or writing to the file, and finally closing the file. At the level of the kernel32 library, opening the file means to obtain a handle for it. Closing the file that has been read may be important to free it up for access by another user. Closing a file that has been written may be necessary to force the operating systems to save the final characters. In this section we investigate how to do some of these operations for sequential disk files. File operations like these are usually more appropriately done using a high-level language, so the primary purpose of this section is to give you a sense of what is "under the hood" of a high-level language.

Figure 12.5 shows a program that prompts for inputs the name of a file and then displays the contents of the file on the console. It includes two new kernel32 function prototypes, CreateFileA and CloseHandle. In spite of its name, CreateFileA is used both to open an existing file or to create a new file. CloseHandle is used to close a file.

; Read sequential file and display on console ; Author: R. Detmer ; Date: 6/98 .386 .MODEL FLAT ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD STD_OUTPUT EQU -11 STD_INPUT EQU -10 GENERIC_READ EQU 80000000h OPEN_EXISTING EQU 3 GetStdHandle PROTO NEAR32 stdcall, nStdHandle:DWORD ReadFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToRead:DWORD, lpNumberOfBytesRead:NEAR32, lpOverlapped:NEAR32 WriteFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToWrite:DWORD, lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32 CreateFileA PROTO NEAR32 stdcall, lpFileName:NEAR32, access:DWORD, shareMode:DWORD, lpSecurity:NEAR32, creation:DWORD, attributes:DWORD, copyHandle:DWORD CloseHandle PROTO NEAR32 stdcall, fHandle:DWORD .DATA written DWORD ? read DWORD ? fileName BYTE 60 DUP (?) hStdOut DWORD ? hStdIn DWORD ? hFile DWORD ? buffer BYTE 64 DUP (?) prompt BYTE ''File name? '' .CODE _start: INVOKE GetStdHandle, ; handle for console output STD_OUTPUT mov hStdOut, eax INVOKE GetStdHandle, ; handle for console input STD_INPUT mov hStdIn, eax INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR prompt, ; address of prompt 12, ; length of prmpt NEAR32 PTR written, ; bytes written 0 ; overlapped mode INVOKE ReadFile, hStdIn, ; file handle for keyboard NEAR32 PTR fileName, ; address for name 60, ; maximum length NEAR32 PTR read, ; bytes read 0 ; overlapped mode mov ecx, read ; number of bytes read mov BYTE PTR fileName[ecx-2],0 ; add trailing null INVOKE CreateFileA, ; open file NEAR32 PTR fileName, ; file name GENERIC_READ, ; access 0, ; no sharing 0, ; no predefined security OPEN_EXISTING, ; open only if file exists 0, ; no special attributes 0 ; no copied handle mov hFile, eax ; handle for file readLoop: INVOKE ReadFile, hFile, ; file handle NEAR32 PTR buffer, ; address for input 64, ; buffer length NEAR32 PTR read, ; bytes read 0 ; overlapped mode INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR buffer, ; address for output read, ; write same number as read NEAR32 PTR written, ; bytes written 0 ; overlapped mode cmp read, 64 ; were 64 characters read? jnl readLoop ; continue if so INVOKE CloseHandle, ; close file handle hfile INVOKE ExitProcess, 0 ; exit with return code 0 PUBLIC _start ; make entry point public END ; end of source code

Figure 12.5: Sequential file input using kernel32 functions

CreateFileA returns the handle of a file that it opens or creates, or returns 1 (FFFFFFFF16) if the operation fails. It has seven parameters

  1. The address of a null-terminated string giving the name of the file
  2. A doubleword giving the desired access. We will only use GENERIC_READ (8000000016) and GENERIC_WRITE (4000000016).
  3. A doubleword indicating how the file can be shared. We will use 0 to indicate that it cannot be shared.
  4. This parameter is used to indicate whether this file can be used by child processes. We will use 0 to indicate that it cannot.
  5. A doubleword containing flags indicating what to do if the file does not exist. We will use OPEN_EXISTING (3) when opening an existing file; the CreateFileA function will fail if the file does not exist. We will use CREATE_NEW (1) when creating a new file; the CreateFileA function will fail if the file already exists. In other applications, CREATE_ALWAYS (2) may be appropriate; this creates a new file if one does not exist and overwrites an existing file if it does exist.
  6. This parameter is used to set various file attributes. We will use a value of 0 to indicate no special attributes.
  7. The final parameter can be used to indicate the handle of a template file whose attributes will be used for the newly created file. We will always use 0 to indicate no template.

As we will use CreateFileA, we will specify parameters 1, 2 and 5, and supply zeros for the other four.

The CloseHandle function is very simple. It has a single parameter, the handle of the file to be closed.

The main read loop in Fig. 12.5 uses ReadFile to read 64 characters at a time from the source file. End of file is detected by comparing the number of characters actually read to 64. If it is smaller, then the end of the file has been reached. However, note that the characters read are displayed first, so that you don't lose the last partial buffer.

In this example, there is nothing special about the number 64 except that it is a power of two. Most operating systems maintain their own buffers for disk file access, and since the size of such a buffer is almost always a power of two, it makes sense to have the program's buffer a size that is comparable.

Figure 12.6 shows a program that will create a disk file from console input. It first prompts for and inputs the name of the file. It creates that file, fails if it already exists, and copies lines from the console keyboard to the file until the user begins a line with %%, a character combination chosen to be unlikely to appear at the beginning of a line in ordinary text.

; Create sequential file from console input ; Author: R. Detmer ; Date: 6/98 .386 .MODEL FLAT ExitProcess PROTO NEAR32 stdcall, dwExitCode:DWORD STD_OUTPUT EQU -11 STD_INPUT EQU -10 GENERIC_WRITE EQU 40000000h CREATE_NEW EQU 1 GetStdHandle PROTO NEAR32 stdcall, nStdHandle:DWORD ReadFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToRead:DWORD, lpNumberOfBytesRead:NEAR32, lpOverlapped:NEAR32 WriteFile PROTO NEAR32 stdcall, hFile:DWORD, lpBuffer:NEAR32, nNumberOfCharsToWrite:DWORD, lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32 CreateFileA PROTO NEAR32 stdcall, lpFileName:NEAR32, access:DWORD, shareMode:DWORD, lpSecurity:NEAR32, creation:DWORD, attributes:DWORD, copyHandle:DWORD CloseHandle PROTO NEAR32 stdcall, fHandle:DWORD .DATA written DWORD ? read DWORD ? fileName BYTE 60 DUP (?) hStdOut DWORD ? hStdIn DWORD ? hFile DWORD ? buffer BYTE 128 DUP (?) prompt1 BYTE ''File name? '' prompt2 BYTE ''Enter text. Start a line with %% to stop'', 0dh, 0ah .CODE _start: INVOKE GetStdHandle, ; handle for console output STD_OUTPUT mov hStdOut, eax INVOKE GetStdHandle, ; handle for console input STD_INPUT mov hStdIn, eax INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR prompt1, ; address of prompt 12, ; length of prompt NEAR32 PTR written, ; bytes written 0 ; overlapped mode INVOKE ReadFile, hStdIn, ; file handle for keyboard NEAR32 PTR fileName, ; address for name 60, ; maximum length NEAR32 PTR read, ; bytes read 0 ; overlapped mode mov ecx, read ; number of bytes read mov BYTE PTR fileName[ecx-2],0 ; add trailing null INVOKE CreateFileA, ; open file NEAR32 PTR fileName, ; file name GENERIC_WRITE, ; access 0, ; no sharing 0, ; no predefined security CREATE_NEW, ; open if file doesn't exist 0, ; no special attributes 0 ; no copied handle mov hFile, eax ; handle for file INVOKE WriteFile, hStdOut, ; file handle for screen NEAR32 PTR prompt2, ; address of prompt 43, ; length of prompt NEAR32 PTR written, ; bytes written 0 ; overlapped mode readLoop: INVOKE ReadFile, hStdIn, ; read from console NEAR32 PTR buffer, ; address for input 128, ; buffer length NEAR32 PTR read, ; bytes read 0 ; overlapped mode cmp buffer, ''%'' ; first character %? jne continue ; continue if not cmp buffer+1, ''%'' ; second character %? je endRead ; quit if so continue: INVOKE WriteFile, hfile, ; file handle NEAR32 PTR buffer, ; address for output read, ; write same number as read NEAR32 PTR written, ; bytes written 0 ; overlapped mode jmp readLoop ; continue if so endRead: INVOKE CloseHandle, ; close file handle hfile INVOKE ExitProcess, 0 ; exit with return code 0 PUBLIC _start ; make entry point public END ; end of source code

Figure 12.6: Create a file from console input

There is very little new in this example. The call to CreateFileA uses GENERIC_WRITE and CREATE_NEW for creation of a new file. The main loop reads a string of up to 128 characters from the keyboard and writes the string to the file. Loop control is accomplished by checking the first two characters of the string before writing it to the file.

Exercises 12.2

  1. The examples in this section do not check to be sure that the file open is successful. Why does the code in Fig. 12.5 "work" even if the file is not successfully opened? How do you modify the code in Fig. 12.5 to display a warning message and exit if the file is not opened?
  2. The examples in this section do not check to be sure that the file open is successful. What happens when you run the program in Fig. 12.6, specifying an output file that already exists? How do you modify the code in Fig. 12.6 to display a warning message and exit if the file is not opened?

Programming Exercises 12.2

  1. A file dump program displays each byte of a file as a two-character hexadecimal code and the corresponding printable character, if any. Using only the kernel32 library (not IO.ASM), write a file dump program that will input the name of a file and then dump it to the console display using the following format:

    Show 16 characters per line, first in hex with a space after each hex pair so that this takes a total of 48 positions, then as ordinary characters, substituting a period for a nonprintable character, with no spaces between. A typical line will look like

    50 72 6F 67 72 61 6D 6D 69 6E 67 20 0D 0A 69 73 Programming ..is

    After 20 lines are displayed on the console, prompt the user with "m[ore] or q[uit]?" and either continue with the next 20 lines or exit the program based on the response.

  2. Write a program to copy a source file to a destination file. Specifically, the program must prompt for the source file name, attempt to open the source file and exit with an error message if it cannot do so. If the source file is opened successfully, then the user will be prompted for the destination file name. If the destination file exists, which can be determined by attempting to open it with CREATE_NEW, the user should be asked if the old file is to be destroyed with CREATE_ALWAYS, and the program should terminate if the answer is negative. If the destination file does not exist, no warning is needed before making the file copy. Use only input/output functions from the kernel32 library, not macros from IO.H.
  3. Write a program that will copy a source file to a destination file, changing all uppercase letters to lowercase letters, leaving other characters unchanged. The program must prompt for both file names. It is not necessary to warn the user if the destination file exists before wiping it out with the copy. Use only input/output functions from the kernel32 library, not macros from IO.H.
  4. Write a program that will process a collection of fixed format records from the file RECORDS.DAT. Each line of the file will consist of ASCII data with

    • a person's name in columns 1-20
    • an integer right-justified in columns 21-25

    Each line of the file will be terminated by a carriage return and a linefeed character so that the total line length is 27 characters. Such a file can be produced by a standard text editor. The program must echo the lines of data and then report

    • the number of records
    • the sum of the numbers
    • the person with the largest number

    Use only input/output functions from the kernel32 library, not macros from IO.H. The atod and dtoa macros from IO.H may be used.

Lower Level Input Output

Earlier in this book input and output have been done using macros in IO.H. In this chapter, input and output have been done using function calls from the kernel32 library, a somewhat lower-level approach. You have probably also done higher-level I/O using high-level programming languages. This section discusses I/O at a level lower than that offered by the kernel32 library, covering the Intel 80x86 and other architectures. Since low-level I/O is increasingly restricted to the operating system, this section does not show actual code.

As discussed in Chapter 2, the Intel 80x86 architecture has memory addresses from 0000000016 to FFFFFFFF16. It also has a separate I/O address space, with port addresses ranging from 000016 to FFFF16. Memory addresses have been used by many of the instructions covered in this book. However, port addresses are used by only a few instructions, the most common of which are the in and out instructions that move data from the addressed port to or from the accumulator (e.g., AL, AX, or EAX). In this sense, they are like limited mov instructions.

In an IBM-compatible PC, common I/O devices normally have standard port assignments. For example, the parallel printer port known as LPT1 uses three port addresses: 0378, 0379, and 037A. The first of these ports is used to send characters to a printer, the second to determine its status, and the third to send control information to the printer. Serial ports are usually controlled by a serial input/output (SIO) chip, which will also require several port addresses.

One of the options in the 80x86 architecture is to use memory-mapped I/O. With memory-mapped I/O, some of the ordinary memory addresses are assigned for input/output purposes and regular data movement instructions are used to transfer data to or from external devices. The hardware designer chooses whether to use memory-mapped I/O or the separate I/O address space when building the system. Other architectures, for example the Motorola 680x0 designs, use only memory-mapped I/O.

Regardless of how I/O devices are addressed, there is the separate issue of how to know when the device has a character ready for the program, or conversely, how to ensure that the device is ready to receive a character from the program. We will look at the situation of sending a character to an old-fashioned, slow, mechanical printer. Obviously the computer can generate characters to be printed much more rapidly than the printer can print them. One technique is to use polling-that is, the program repeatedly checks a status port on the device until it gets a report that the device is able to accept a character, then it transmits the character. The design looks like

forever get status from status port; if clear to send character, then exit loop; end loop; transmit character to data port;

The loop in this design is called a busy-waiting loop for obvious reasons. Unless the computer is otherwise set up for multitasking, it can do no useful work while waiting for the device to accept the character.

Interrupt-driven I/O relies on hardware interrupts to inform the CPU of a device's change in status. An interrupt is a hardware signal generated by the device and received by the CPU. When the CPU receives such a signal, it normally finishes executing the current instruction, and then transfers control to an interrupt procedure. This is very similar to a regular procedure call.

An Intel 80x86 system provides for up to 256 different interrupts. The address for an interrupt procedure comes from a table of addresses in the very bottom of memory. Memory locations 0 to 102410 contain 256 addresses corresponding to interrupt levels 0 through 255. In general, for interrupt type t, the interrupt procedure's address is stored at address 4*t.

A computer system may be designed to generate an interrupt when a key on the keyboard is pressed. The associated interrupt procedure would capture the character and store it in a buffer for later processing before returning, allowing the computer to go back to whatever it was doing.

The 80x86 architecture includes an int instruction that enables a program to invoke an interrupt procedure. Not all interrupt types are used by hardware devices, and some operating systems, notably Microsoft DOS, use int instructions to call operating system functions.

80x86 interrupts 0 and 4 are always preassigned. Interrupt type 0 is automatically called by the 80x86 CPU when division by zero is attempted. A simple program containing the instruction int 0 also calls the divide by zero interrupt handler, showing how a particular 80x86 system is set up to handle division errors without actually doing a division.

The handler for interrupt type 4 also has an assigned purpose, namely to handle overflow conditions that result from instructions. This interrupt handler is not called automatically by the 80x86. It can be called using int 4 but is more commonly invoked by the into (interrupt on overflow) instruction. This is a conditional call: The overflow interrupt handler is called if the overflow flag OF is set, but otherwise execution continues with the next instruction. Typically an into instruction would follow an instruction that might cause overflow to occur.

Exercises 12.3

  1. What are the advantages of memory-mapped I/O? What are the advantages of using a separate address space for I/O?
  2. What address contains the interrupt procedure address for interrupt 1510 in an 80x86 system?

Summary

Input and output can be done at many levels, from high-level language procedures down to in and out instructions. The kernel32 library illustrates the operating-system level example of I/O. This library has functions for getting a file or device handle, reading from a file or device, writing to a file or device, and releasing the file or device.

At the hardware level, I/O may either use separate port addresses for external devices or it may use memory-mapped I/O, with a portion of the regular memory space assigned to external devices rather than memory.

Devices may be accessed by polling or-more efficiently-by using interrupt-driven I/O. The 80x86 architecture provides for up to 256 different interrupts, although these are often assigned other uses than servicing I/O requests.

Категории